Dokumentation · Anbindung
Das Anvil-Feed-SDK in eine Laravel- oder Spring-Boot-Anwendung einbauen — ein Paket, drei Einstellungen, eine Zeile Code, und die Annahme sagt selbst, ob sie die Meldung gespeichert hat.
Der Posteingang für Betriebsfehler füllt sich, sobald deine Anwendung ihre Fehler meldet. Dieses Kapitel baut die Meldung ein: ein Paket, drei Einstellungen, eine Zeile Code — und am Ende sagt dir die Annahme selbst, ob sie deine Meldung gespeichert hat.
Es gibt zwei fertige SDKs: PHP mit Laravel 12 und Java 25 mit Spring Boot 4. Beide gibt es auch ohne Framework. Wer eine andere Sprache einsetzt, meldet direkt nach dem Server-Vertrag des Anvil Feed.
Vorabversion 0.1. Die Pakete sind noch nicht bei Packagist und Maven Central veröffentlicht. Bis dahin bindest du sie aus einem Checkout des SDK-Repositorys ein — die Schritte unten zeigen genau das. Den Zugang zum Repository bekommst du von uns auf Anfrage. Nach der Veröffentlichung schrumpft die Installation auf eine Zeile; der Rest dieses Kapitels bleibt, wie er ist.
Bevor du anfängst
Du brauchst einen Kanal und seinen Schlüssel. Beides legst du im Cockpit an, unter Integrationen → Kanäle für Betriebsfehler; der Weg steht Schritt für Schritt im Kapitel Betriebsfehler, Abschnitt „In 15 Minuten angebunden”.
Drei Dinge daraus zählen hier:
- Der Schlüssel beginnt mit
anv_srv_, ist 40 Zeichen lang und wird genau einmal angezeigt. - Er ist ein Zugangsdatum. Er gehört dorthin, wo dein Datenbank-Passwort liegt — in die Umgebung, in einen
Secret-Speicher, in die Variablen deines Deployments. Nie ins Repository, nie in eine eingecheckte
.env, nie in eine Image-Schicht. - Beim Anlegen des Kanals entsteht bereits eine Bindung für alle Umgebungen (
*) mit der Regel Planen, Freigabe abwarten. Deine erste Meldung kommt also an, ohne dass du vorher eine Umgebung binden musst.
Laravel
Installieren
Hole dir einen Checkout des SDK-Repositorys neben deine Anwendung und zeige Composer den Weg dorthin:
composer config repositories.anvil-feed-php --json '{"type":"path","url":"../anvil-feed/php/anvil-feed-php","options":{"versions":{"anvil-coder/anvil-feed-php":"0.1.0"}}}'
composer config repositories.anvil-feed-laravel --json '{"type":"path","url":"../anvil-feed/php/anvil-feed-laravel","options":{"versions":{"anvil-coder/anvil-feed-laravel":"0.1.0"}}}'
composer require anvil-coder/anvil-feed-laravel:^0.1
../anvil-feed ist der Pfad zu deinem Checkout — nur er muss stimmen. Den Service-Provider findet Laravel
selbst, du registrierst nichts.
Laravel 13 wird in dieser Version noch abgelehnt: das Paket verlangt Laravel 12.
Einstellen
In die .env deiner Anwendung — den Schlüssel aus dem Cockpit setzt du hinter das Gleichheitszeichen der
ersten Zeile:
ANVIL_INGEST_TOKEN=
ANVIL_SERVICE=invoicing-api
ANVIL_ENVIRONMENT=production
Danach einmal php artisan config:clear.
Setze Dienstname und Umgebung ausdrücklich. Ohne sie greift das SDK auf APP_NAME und APP_ENV zurück — das
eine ist meist ein Anzeigename mit Leerzeichen, das andere steht auf einem frischen Checkout auf local: ein
Rechner, von dem du nie melden wolltest, unter einem Namen, den im Posteingang niemand erkennt.
Eine Zeile Code
Das Laravel-12-Gerüst hat in bootstrap/app.php bereits einen leeren withExceptions-Block. Die Zeile kommt
dort hinein:
->withExceptions(function (Exceptions $exceptions): void {
\AnvilFeed\Laravel\Integration::handles($exceptions);
})->create();
Das ist die ganze Änderung. Keine Middleware, kein Wrapper um Controller, kein try/catch irgendwo. Laravel
protokolliert und rendert den Fehler weiter genau wie bisher.
Die Annahme fragen
php artisan anvil-feed:test
Der Befehl zeigt die Einstellungen, die er wirklich benutzt, schickt eine Meldung und gibt wieder, was die Annahme geantwortet hat:
endpoint: https://app.anvil-coder.tech/api/feed/events
service: invoicing-api
environment: production
event_id: 3e3c8f24-ae87-41ce-9087-7d8046735e96
verdict: accepted
The ingest stored the event.
accepted ist der Nachweis — das Wort der Annahme für „gespeichert”, aus ihrer Antwort gelesen. „Wurde
gesendet” wäre nur die halbe Wahrheit: eine abgelehnte Meldung wurde auch gesendet. Bei jedem anderen Urteil
endet der Befehl mit einem Fehlercode; er taugt damit als Prüfschritt in deinem Deployment.
Spring Boot
Einbinden
Mit Gradle bindest du den Checkout als Composite Build ein — ohne Veröffentlichung, ohne Versionsnummer.
settings.gradle.kts:
includeBuild("../anvil-feed/java")
build.gradle.kts:
dependencies {
implementation("tech.anvil-coder:anvil-feed-spring-boot-starter")
}
Gradle ersetzt die Koordinate durch den eingebundenen Build. Für Maven gibt es bis zur Veröffentlichung noch keinen Weg. Eine Anwendung ohne Web-Schicht — ein Batch-Job, ein Queue-Worker — nimmt dieselbe Abhängigkeit; ein Servlet-Container kommt dadurch nicht mit.
Der Starter braucht Java 25.
Einstellen
Der Schlüssel kommt aus der Umgebung deines Deployments. Lokal liest du ihn am besten so ein, dass er nicht in der Verlaufsdatei deiner Shell landet:
read -rs ANVIL_INGEST_TOKEN && export ANVIL_INGEST_TOKEN
In der application.yml steht dann nur der Verweis — Dienstname und Umgebung hat eine Spring-Anwendung meist
schon:
spring:
application:
name: shop-api # der Dienstname, unter dem die Vorfälle laufen
profiles:
active: production # die Umgebung — das ERSTE aktive Profil zählt
anvil:
feed:
token: ${ANVIL_INGEST_TOKEN}
Wer Dienstname oder Umgebung unabhängig von Spring setzen will, nimmt anvil.feed.service und
anvil.feed.environment; sie haben Vorrang.
Die Annahme fragen
Kein Code, ein Lauf:
./gradlew bootRun --args='--anvil.feed.send-test-event=true'
Die Ausgabe ist dieselbe wie bei Laravel, und es gilt dasselbe: accepted ist der Nachweis. Nimm den
Schalter danach wieder weg — er ist für einen Lauf gedacht, keine Dauereinstellung.
Im Cockpit nachsehen
Öffne den Posteingang des Projekts. Die Test-Meldung steht als neueste Zeile oben, mit dem Titel der
Test-Meldung und deinem Dienstnamen. Eine Suche nach der event_id gibt es im Posteingang nicht — das Urteil
des Befehls ist der Nachweis der Annahme, die Zeile im Posteingang zeigt dir, wie ein Vorfall für dein Team
aussieht.
Die fünf Einstellungen
Beide SDKs lesen dieselben fünf Variablen und sonst keine:
| Variable | Bedeutung | Vorgabe |
|---|---|---|
ANVIL_INGEST_TOKEN | der Schlüssel des Kanals | keine — ohne ihn ist das SDK aus |
ANVIL_SERVICE | der Dienstname, unter dem Vorfälle laufen | keine — ohne ihn ist das SDK aus |
ANVIL_ENVIRONMENT | die Umgebung, nach der die Regel gewählt wird | production |
ANVIL_INGEST_ENDPOINT | die Adresse der Annahme | https://app.anvil-coder.tech/api/feed |
ANVIL_RELEASE | die Version deiner Anwendung | aus dem .git-Verzeichnis gelesen, sonst nicht gesendet |
Fehlt der Schlüssel oder der Dienstname, schaltet sich das SDK ab und sagt das einmal im Log. Es wirft nie selbst einen Fehler in deine Anwendung.
Was gemeldet wird — und was nicht
Laravel: gemeldet wird, was Laravel selbst als Fehler behandelt — in Anfragen, Queue-Jobs und
artisan-Befehlen. Was Laravel von sich aus nicht meldet, erreicht auch das SDK nicht: abort(404),
Validierung, Anmeldung und Berechtigung, ModelNotFoundException, CSRF. Willst du eine dieser Klassen doch
sehen, nimmst du sie im selben withExceptions-Block mit $exceptions->stopIgnoring(...) aus — oder meldest
von Hand mit AnvilFeed\AnvilFeed::captureException($e).
Spring Boot: es zählt, wie die Anfrage endet. Endet sie mit einem Serverfehler (5xx), wird gemeldet —
gleich, ob ein eigener Handler die Fehlerseite gerendert hat. Endet sie mit 4xx, nicht: das ist eine Antwort an
deinen Nutzer, kein Betriebsfehler. Ohne dein Zutun erfasst werden ausserdem @Scheduled-Methoden,
@Async-Methoden ohne Rückgabewert, Aufgaben auf Spring-Executoren und Threads ohne eigenen Handler. Eine
reaktive Anwendung (WebFlux) bekommt in dieser Version alles davon, aber nichts je Anfrage.
Ein Fehler, der hundertmal in der Minute auftritt, wird im SDK gedrosselt: zehn Meldungen je Fehlerbild und Minute, der Rest wird verworfen. Bei einem solchen Sturm liegt der Zähler am Vorfall deshalb unter der wahren Zahl — der Vorfall selbst ist da, und darauf kommt es an.
Was deine Anwendung verlässt
Gesendet werden Fehlertyp und Meldung, bis zu 50 Stellen der Aufrufkette mit Datei und Zeile (PHP zusätzlich bis zu 20 Zeilen Quelltext um die Stelle), Methode und Adresse der Anfrage, Version und Branch — und die Tags, die du selbst setzt.
Dateipfade sind relativ zum Projekt. Im Cockpit steht app/Http/BookingController.php, nie der Pfad auf
deinem Server; wie dein Deployment aufgebaut ist, verlässt den Prozess nicht. Aus einem ausgelieferten
Java-Archiv wird nur der Dateiname gesendet.
Nach Namen gefiltert, bevor die Meldung den Prozess verlässt: jedes Feld, jeder Tag und jeder
Adress-Parameter mit dem Schlüssel password, token, secret, authorization, api_key, cookie,
email, credit_card oder iban — der ganze Wert wird durch [Filtered] ersetzt, in jeder Tiefe. Aus
Bearer <Wert> bleibt das Schema, der Wert fällt weg. Zugangsdaten in einer Adresse
(https://name:kennwort@host/) werden entfernt. Die IP-Adresse deiner Nutzer wird nie gesendet, und eine
Nutzerkennung nur, wenn du sie selbst mitgibst.
Was die Namensregel nicht sehen kann, ist ein Wert. Eine E-Mail-Adresse, Kartennummer oder IBAN mitten in
einer Fehlermeldung hat keinen Schlüssel, an dem sie sich erkennen liesse. Sie verlässt den Prozess und wird
von der Annahme gefiltert, bevor irgendetwas gespeichert wird — im Cockpit steht [Filtered]. Das
Versprechen des SDK selbst ist das engere. Halte Adressen und Nummern aus Fehlermeldungen heraus, die du
selbst schreibst, und trage eigene Schlüsselnamen in die denylist ein (Spring Boot: anvil.feed.denylist).
Gesendet wird nie innerhalb einer Anfrage. Meldungen sammeln sich im Speicher. Laravel schickt sie, nachdem die Antwort an deinen Nutzer unterwegs ist; Spring Boot schickt sie alle zwei Sekunden aus einem eigenen Hintergrund-Thread und ein letztes Mal beim Herunterfahren. Jeder Versand hat ein Zeitlimit — Vorgabe zwei Sekunden. Ist die Annahme nicht erreichbar, behält das SDK die Meldungen und versucht es später; deine Anwendung merkt davon nichts.
Eigenen Kontext mitgeben
Ein Tag am Vorfall hilft beim Einordnen — ein Mandant, ein Auftrag, eine Warteschlange. Du setzt ihn in einem Hook, der jede Meldung sieht, bevor sie hinausgeht:
AnvilFeed\AnvilFeed::client()?->beforeSend(function (array $payload): ?array {
$payload['tags']['tenant'] = CurrentTenant::opaqueId();
return $payload; // oder null: die Meldung wird verworfen
});
@Component
class AnvilFeedEnrichment {
AnvilFeedEnrichment(AnvilFeedClient client) {
client.beforeSend(payload -> {
payload.put("tags", Map.of("tenant", CurrentTenant.opaqueId()));
return payload; // oder null: die Meldung wird verworfen
});
}
}
Der Hook bekommt die bereits gefilterte Meldung, und was du hinzufügst, läuft auf dem Weg hinaus noch einmal durch den Filter. Nimm eine Kennung, keinen Namen und keine E-Mail-Adresse: die Annahme ist ein Fehlerspeicher, kein Nutzerverzeichnis.
Abschalten
ANVIL_ENABLED=false (Laravel) oder anvil.feed.enabled: false (Spring Boot) schaltet das SDK ab, ohne dass
du Code entfernst. Dasselbe geschieht von selbst, wenn kein Schlüssel gesetzt ist — mit einer Zeile im Log,
nie mit einem Fehler. Auf Entwickler-Rechnern ist das der sinnvolle Zustand.
Häufige Fragen
Der Befehl meldet rejected (unauthorized). Der Schlüssel ist unbekannt, widerrufen oder unvollständig kopiert — er
hat 40 Zeichen und beginnt mit anv_srv_. Die Annahme unterscheidet diese Fälle absichtlich nicht.
Der Befehl meldet rejected (no_binding). Für die gesendete Umgebung gibt es am Kanal keine Regel. Das passiert nur,
wenn die Bindung für * entfernt wurde: binde die Umgebung oder * im Cockpit neu.
Der Befehl meldet rejected (forbidden). Die Anfrage trug einen Origin-Kopf, kam also aus einem Browser. Ein
Server-Schlüssel gehört nicht in Browser-Code. Ist er dort gelandet, lege einen neuen Kanal an und widerrufe
den alten.
Der Befehl meldet failed transiently. Die Annahme wurde nicht erreicht; die Zeile darunter nennt den
Grund. Die Meldung bleibt in der Warteschlange und geht mit dem nächsten Versand hinaus.
Meine Anwendung steht hinter einem Proxy. PHP liest die Proxy-Variablen der Umgebung, Java die Proxy-Einstellungen der JVM samt ihrem Truststore. Verlangt der Proxy eine Anmeldung und weist ab, behalten beide SDKs die Meldungen, versuchen es mit wachsendem Abstand erneut und sagen einmal im Log, dass die Antwort vom Proxy kam und nicht von der Annahme.
Es kommt nichts an, und der Test-Befehl ist grün. Dann meldet deine Anwendung unter einer anderen Umgebung oder einem anderen Dienstnamen, als du erwartest. Der Test-Befehl zeigt beide in seinen ersten Zeilen.