---
titel: "Anvil Feed einbauen"
adresse: https://docs.anvil-coder.tech/docs/anvil-feed-sdk
beschreibung: "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."
sprache: de
---

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](https://docs.anvil-coder.tech/docs/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](https://docs.anvil-coder.tech/docs/anvil-feed-ingest).

**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](https://docs.anvil-coder.tech/docs/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:

```bash
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:

```bash
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:

```php
    ->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

```bash
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**:

```text
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`:

```kotlin
includeBuild("../anvil-feed/java")
```

`build.gradle.kts`:

```kotlin
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:

```bash
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:

```yaml
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:

```bash
./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:

```php
AnvilFeed\AnvilFeed::client()?->beforeSend(function (array $payload): ?array {
    $payload['tags']['tenant'] = CurrentTenant::opaqueId();
    return $payload;            // oder null: die Meldung wird verworfen
});
```

```java
@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.
