• Logging Schema / Format
  • Collector, Parsen und Transformieren/Filtern
  • Logging Backend

Logging Schema / Format

Wenn man sich heutzutage mit Logging beschäftigt, möchte man oftmals gerne einheitliche Logs haben, um sich schnell zurecht zu finden und Korrelation, in Anwendungen die direkt oder indirekt zusammen arbeiten, zu finden.

Das Thema Logging existiert eigentlich seit es Anwendungen/Service im Unix-Bereich gibt. Durch die Historie, wurden immer wieder neue Formate definiert mit hinsicht auf der Funktion, der einzelnen Anwendungen. Apache und Nginx haben ihre eigenen Formate, die z.B. im Nginx Ingress Controller für Kubernetes wieder umdefiniert worden, was das einsammeln und parsen durch ein Collector oder spätere Suchen im Backend erschwärt. Ein weiteres super Beispiel für Unleserlichkeit von Logs ist Redis, hier werden die Loglevel wie Info, Warning oder Debug nicht mit Anfangbuchstaben, sondern mit Sonderzeichen kodiert: https://build47.com/redis-log-format-levels/

Der erste Versuch einer Standartisierung ist wohl syslog, dieser Standard wurde über die Jahre auch weiter entwickelt und beherscht neben loglevel / serenity und timestamps inzwischen auch TCP und signierung um verlorende Logs zu identifizieren. Leider fehlt nach wievor eine definition für die Log-Message selbst oder weitere Felder.

Inzwischen ist man auf die Idee von JSON als Logformat gekommen, zwar erkennt man hier nicht ohne weiteres ob alle Logs abgekommen sind. Doch es lassen sich weitere Felder definieren, wie z.B Benutzernamen, IP-Adressen, HTTP-Statuscode, Funktionsnamen in der Anwendung usw. die zusätzlich zur Log-Message übertragen wird. Durch eine Trennung der Log-Message von solchen Feldern, ergeben sich Vorteile im Backend, hier kann nun einfach nach IP-Adressen oder Nutzern einfach gesucht werden (oder beim Tracing nach eine Aufrufe-ID und diese dann durch die verschiedenen Anwendungen wie bei ein Stack-Trace nachvollzogen).

Genauso, lassen sich solche Felder in einigen Backend speziell behandeln. So ist es möglich, die Felder wie Benutzer aus Datenschutzgründen vorzeitig zu löschen und dabei den Rest eines Logs anonym noch gespeichert zu haben. Dabei ist eine Grundvorrausetzung, das keine extra-Information in der Log-Message zu finden ist. Hieraus könnte man printf als antipattern für die Anwendungsentwicklung ableiten.

Mit der Funktion von solchen Feldern, kommt man auf die Fragestellung, wie sollten diese alle genannt werden und unter welchen Objekt-Key zur Verfügung gestellt werden. Dieses Schema sollte über die verschieden Anwendungen hinweg gelten, damit man wie oben beim Tracing auch hier z.B. die Interaktion eines Benutzer in den verschiedenen Anwendungen nachvollziehen kann. Eine gute Grundlage zur Benennung bietet das Elastic Common Schema (ECS).

Zu letzten Krönung, kann man sich fragen, ob es JSON sein muss, mit hinblick auf die Administration vorort (ohne das einsameln und suchen in ein Backend) und den unklarheiten in JSON ( Why JSON is sometimes bad and should feel Bad). Für die Admininstration vor Ort, ist JSON beim Lesen wirklich unübersichtlich. Hier würde ich als Entwickler die Option auf ein anderes Format mit den Namen logfmt empfehlen, Zur Übertragung, Abspeichern usw. könnte man noch auf die Idee neuere, noch Bessere und komprimiertere Formate, wie msgpack, zu wählen. Die Idee ist löblich und findet meine Unterstützung (immerhin ist das Logging einer der größten energiefresser in der Informatik), doch damit verlieren wir noch mehr an Leserlichkeit und viele Tools (Collectoren usw.) aus dem Loggingbereich sind aktuelle noch nicht darauf vorbereitet. Daher solltet ihr das Logformat durch ein Option auf JSON oder zumindest logfmt (oder beides) einstellbar machen.

Fast alle modernen Logging-Library unterstützen das Einstellen von Logformaten (auch Anwendungen reichen oft Optionen neben den Loglevel raus, wie nginx und apache, auch wenn nicht mit eingeschränkte Optionen). Bitte nicht das Rad neuerfinden, führt nur zu Problemen beim Parsen und macht die Welt nur komplizierter (wie Redis).

Empfehlung:

  • Loglibrary mit Logformaten einstellbar machen:
    • JSON first
    • logfmt second (für Administratoren kleiner Infrastruktur ohne Logging Backend)
  • Sich an Elastic Common Schema (ECS) halten
  • printf als AntiPattern definieren

Collector, Parsen und Transformieren

Es ist schön wenn wir nette Logs haben, doch am Ende müssen wir diese einsammeln und zentral zugänglich machen. Hierfür gibt es Collectoren, die diese einlesen, aus File, Journald und vielen weiteren Orten. Diese Parsen, aus Formaten wie logfmt, json usw. In ein Unternehmensweiten einheitlichen Format bringen (siehe Elastic Common Schema), mit Informationen anreichen (Hostname, KundenNummer, Stage, Rechenzentrum Zonen, GeoIP usw.) und möglicherweise Metriken ableiten (z.B. Error log counter).

Falls man sich mit den Collector auseinander setzen muss, ist es sinnvoll darauf zu achten, das man sich mit den Collector nicht in ein Vendor-Lock begibt. Daher sollte man darauf achten, das der Collector mehrere Backends unterstützt (nicht wie die Beats oder Beat-Agent von Elastic), sind hierfür z.B. Fluentbit,Fluentd, Logstash und vector.dev bekannt (wobei die Entwicklung von Fluentd und Logstash allmählich einschläft).

Wahrscheinlich gibt es weitere,von mir, noch nicht nähere betrachtete Collectoren, wie die folgenden:

  • promtail (aus der Loki- / Grafana-Welt, kA ob weitere Backends unterstützt werden)
  • syslog-ng (etwas legacy, ggf. kein gutes parsing und transformieren)

Empfehlungen:

  1. vector.dev:
  • In Rust geschrieben (security vorteile, one-binary)
  • cool GraphQL-API mit Top und Graph output zum Debugging von Flows und Errors
  • gutes Error-Handling in deren Remap language und in den Metriken
  1. fluentbit
  • ähnliche Umfangreiches Funktionen
  • In C geschrieben (abhänig von Libaries .so)
  • etwas schwerer zu debugging

Collector Strategien im Kubernetes

Hier ein kleiner Exkurse, wie es im Cloud / Kubernetes Kontext aussieht.

Kubernetes hat selbst keine Lösung für Logging, doch stellt die folgende Konzepte vor, um eine Lösung zu bauen, damit die Logs in ein Backend landen: https://kubernetes.io/docs/concepts/cluster-administration/logging/

  1. Die Logs werden von Pods auf stdout und stderr geschrieben und durch die Container-Engine ( e.g. docker, containerd/oci) weiter verarbeitet. Diese landen denn meist in Logdateien auf dem entsprechenden Kubernetes-Node, wo sie wieder eingelesen werden können. Ein Implementierung, wo der Collector gut konfiguriert werden kann (auf die Bedürfnisse der verschiedenen Containers) mittels "Flows", ist der Kube-Logging Operator (der auf fluentbit und fluentd aufsetzt - oder neuerdings auch syslog-ng): https://kube-logging.dev/docs/ , welcher bereits durch Banzai/Cisco/Axoflow und Rancher etwas Verbreitung gewonnen hat. (Diese CRD-Flows ermöglichen Hersteller, ein angepastes Parsing für ihrere Anwendung in ein Helm-Chart mit auszuliefern.) Hier gibt es die folgenden Ansätze, wie die Logs auf stdout und stderr kommen:

    • Entweder kann das die Anwendung im Container selbst
    • oder es wird ein Sidecar dazugestellt, welche z.B. tail -F auf die log-datei ausführt und so die Logs des eigentlichen Containers auf stdout bringt
  2. Die Logs werden direkt vom Pod an ein Backend versendet. Meine Erfahrung sagt mir, das die folgenden Implementierung extrem Aufwendig sind, dieses überall noch zusätzlich für all den Kram den man so deployen möchte, unzusetzen. Hierfür müssten man die ganzen HelmChart von Herstellern (sofern vorhanden), krass konfigurieren oder sogar umschreiben. Oder sogar im Container hineinbauen (was man aus mininalismus in ein Container eigentlich nicht möchte). Auf der anderen Seite steht, das mache Kubernetes-Cluster in Rechenzentren stehen, die nicht vertraut werden (hier wäre es wünschenswert, das Logs nicht auf dem Node/VM geschrieben werden, wo sie ggf. abgegriffen werden).

    • Entweder direkt von der Software oder dem Container ans Backend gesendet
    • oder es gibt hierfür ein Sidecar im Pod, wobei die original-software z.B in Dateien schreibt (z.B in emptyDir) und von ein anderen Container (sidecar) im selben Pod eingelesen und versendet

Logging Backend

Es gibt viele Logging Backends, sowohl OpenSource, als auch proritär. In den meisten Fällen, gibt es bereits etwas, das im Rechenzentrum existiert.

TODO:

  • elasticsearch
  • loki von grafana