Zum Inhalt springen

Anvil Feed einbauen

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 IntegrationenKanä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:

VariableBedeutungVorgabe
ANVIL_INGEST_TOKENder Schlüssel des Kanalskeine — ohne ihn ist das SDK aus
ANVIL_SERVICEder Dienstname, unter dem Vorfälle laufenkeine — ohne ihn ist das SDK aus
ANVIL_ENVIRONMENTdie Umgebung, nach der die Regel gewählt wirdproduction
ANVIL_INGEST_ENDPOINTdie Adresse der Annahmehttps://app.anvil-coder.tech/api/feed
ANVIL_RELEASEdie Version deiner Anwendungaus 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.