LinkMesh

Doku, Blog und Changelog durchsuchen

Die Dokumentation ist nur auf Englisch verfügbar.

ENDE
Zerknittertes, unbedrucktes Endlospapier läuft durch eine Führung und tritt als exakt ausgerichteter Stapel aus
LinkMeshObservability Data Collection Management
OpenTelemetryLogs

Ihre Logs sind kein JSON. Das ist in Ordnung.

Falten Sie mehrzeilige Stack Traces zu einem Ereignis und parsen Sie gerade genug – ohne Ihre Anwendungen umzuschreiben.

linkmesh.io
5 Min. Lesezeit

Die meisten Leitfäden zum „strukturierten Logging mit OpenTelemetry” setzen stillschweigend voraus, dass man bereits sauberes JSON mit einem level-Feld und einer trace_id aussendet. Echte Systeme tun das nicht. Man hat ein Jahrzehnt an Diensten, die reinen Text loggen, ein Framework, das Stack Traces über 30 Zeilen druckt, und mindestens eine Komponente, deren Format sich niemand mehr erinnert gewählt zu haben. Man kann nicht alles umschreiben, und man sollte es auch nicht müssen, nur um seine Logs in eine Pipeline zu bekommen.

Die gute Nachricht: OpenTelemetry verlangt nichts von dieser Struktur. Eine Log-Zeile ist ein Log-Record, ob sie nun JSON ist oder nicht. Die Arbeit besteht nicht darin, seine Anwendungen umzuformatieren – sie besteht darin, das Durcheinander so wie es ist zu onboarden, das eine Ding zu zähmen, das wirklich kaputtgeht (mehrzeilige Einträge), und nur das zu parsen, was man tatsächlich abfragen muss.

Roh ist in Ordnung: nicht-JSON ist nicht nicht-OTel

Der filelog-Receiver des OpenTelemetry Collectors verfolgt eine Datei und verwandelt jede Zeile in einen Log-Record, wobei er die ganze Zeile in den body des Records legt. Kein Schema, kein Parsen, keine Mitarbeit der Anwendung erforderlich. Eine Zeile wie:

2026-07-17T09:14:22Z ERROR checkout-api order 8821 failed: NullPointerException

wird zu einem Log-Record, dessen body genau diese Zeichenkette ist. Das reicht bereits, um sie zu versenden, zu durchsuchen und aufzubewahren. Struktur ist etwas, das man dort hinzufügt, wo es sich lohnt – keine Vorbedingung, um loszulegen.

In LinkMesh fügt man dies als File-Tail-Quelle hinzu: Richten Sie sie auf einen Pfad (oder einen Glob wie /var/log/app/*.log), und sie rendert den filelog-Receiver für Sie.

Die Mehrzeilen-Falle

Hier fällt naives Log-Onboarding auseinander. Ein Stack Trace ist für einen Menschen ein Ereignis und für einen zeilenorientierten Leser 30 Ereignisse:

2026-07-17T09:14:22Z ERROR checkout-api NullPointerException processing order 8821
    at com.acme.checkout.OrderService.total(OrderService.java:88)
    at com.acme.checkout.OrderService.submit(OrderService.java:41)
Caused by: java.lang.NullPointerException
    at com.acme.pricing.Rules.apply(Rules.java:203)
    ... 14 more

Verfolgt man das mit einer Standardkonfiguration, bekommt man einen Record für die Kopfzeile und einen Record pro at ...-Zeile – 30 zusammenhanglose Fragmente. Ihre Metrik „Fehlerrate” zählt einen Vorfall als dreissig. Die Meldung, die zählt (das Caused by:), wird von der Ausnahme abgeschnitten, die sie erklärt. Darauf zu alarmieren ist aussichtslos.

Behebe es: den Eintragsbeginn matchen, den Rest falten

Die Behebung ist eine einzige Idee: Sagen Sie dem Receiver, wo ein neuer Eintrag beginnt, und behandeln Sie alles andere als Fortsetzung des vorherigen. Jeder Eintrag im obigen Beispiel beginnt mit einem Zeitstempel, also erledigt es ein Start-of-Entry-Muster, das auf den Zeitstempel matcht:

multiline:
  line_start_pattern: '^\d{4}-\d{2}-\d{2}'

Nun falten sich die Kopfzeile und ihre 29 Fortsetzungszeilen zu einem Log-Record, dessen body den gesamten Stack Trace enthält. Ein Ereignis rein, ein Ereignis raus – Ihre Fehlerrate ist wieder ehrlich, und das Caused by: reist mit seiner Ausnahme.

In LinkMesh setzt man dies an der File-Tail-Quelle – ein einziges Feld –, und der Sample-Viewer bestätigt es: Der ganze Trace erscheint als ein Record, nicht als zerfetzter Haufen.

Konfiguration einer File-Tail-Quelle in LinkMesh: eine „Application Logs”-Quelle mit Include Paths gesetzt auf /var/log/app/*.log und, im Abschnitt Multiline Config, einem Line Start Pattern von ^\d{4}-\d{2}-\d{2} – der Zeitstempel, der den Beginn jedes Eintrags markiert, sodass alles danach in denselben Record gefaltet wird.

Parsen Sie nur, was Sie brauchen – und behalten Sie den rohen Body

Mit intaktem Ereignis will man meist nur gerade genug Struktur, um abzufragen und zu routen: eine Schweregrad-Ebene, vielleicht einen Dienst oder eine Fehlerklasse. Man muss einen Stack Trace nicht vollständig „strukturieren” – man braucht level=ERROR als Attribut, um darauf filtern und alarmieren zu können.

Ein transform-Prozessor (OTTL) erledigt das, ohne den rohen Body anzufassen. Leiten Sie die Ebene aus dem Inhalt ab:

set(attributes["level"], "ERROR") where IsMatch(body, "(?i)error|exception")
set(attributes["level"], "WARN")  where attributes["level"] == nil and IsMatch(body, "(?i)warn")
set(attributes["level"], "INFO")  where attributes["level"] == nil

Der Body bleibt genau so, wie er ankam – nichts geht verloren –, und man gewinnt ein sauberes level-Attribut zum Filtern, Routen und Alarmieren.

Die Falle bei jedem Transform ist, ihn blind zu schreiben: eine leicht falsche Bedingung, und man hat alles falsch gelabelt und findet es in der Produktion heraus. LinkMeshs Dry-Run-Vorschau schliesst diese Schleife. Öffnen Sie den Transform-Schritt, füttern Sie ihn mit einem echten mehrzeiligen Record und beobachten Sie Eingabe und Ausgabe nebeneinander – die Eingabe hat kein level, die Ausgabe hat level = ERROR, ergänzt und hervorgehoben. Man sieht die Extraktion tatsächlich geschehen, bevor man sie speichert.

LinkMeshs Prozessor-Dry-Run am Severity-Transform. Das INPUT-Panel enthält einen checkout-api-Log-Record, dessen Body ein NullPointerException-Stack-Trace ist, mit nur einem env: prod-Attribut – kein level. Das OUTPUT-Panel zeigt denselben Record mit einem neuen, hinzugefügten level: ERROR-Attribut, grün hervorgehoben in der Diff-Rinne.

Versende es: ein Eintrag pro Ereignis, abfragbar über das geparste Feld

Richten Sie die Pipeline auf ein Grafana Cloud Logs (Loki)-Ziel, und das ganze mehrzeilige Ereignis landet als ein einziger Loki-Eintrag – der vollständige Stack Trace intakt im Body, mit Ihrem geparsten level zum Filtern verfügbar. Eine LogQL-Abfrage nach Fehlern liefert ganze Vorfälle, keine Kopfzeilen-Fragmente:

{service_name="checkout-api"} | level = "ERROR"

otelcol oder Alloy – dasselbe Ergebnis

LinkMesh verwaltet Collectors auf zwei Laufzeiten – dem OpenTelemetry Collector (über OpAMP) und Grafana Alloy (über remotecfg) – und der Mehrzeilen-Log-Pfad funktioniert auf beiden gleich. Man konfiguriert die File-Tail-Quelle und den Transform einmal; LinkMesh generiert die richtige Konfiguration für welche Laufzeit auch immer jeder Collector betreibt. Der Stack Trace faltet sich zu einem Ereignis, und die level-Extraktion greift identisch, sodass man nicht an einen Agenten gebunden ist, um saubere Logs zu bekommen.

Der Punkt

Man strukturiert sich nicht in OpenTelemetry hinein – man onboardet die Logs, die man hat, behebt das eine Ding, das wirklich kaputtgeht (mehrzeilig), und parst das eine oder zwei Felder, die sie abfragbar machen. Roh bleibt roh, der Vorfall bleibt ganz, und man kann jede Transformation sehen, bevor sie die Produktion berührt.