Logging in Go: Ein System auswählen und verwenden

Sie sind relativ neu in der Sprache Go. Wahrscheinlich verwenden Sie sie, um eine Webanwendung oder einen Server zu schreiben, und Sie müssen eine Protokolldatei erstellen. Sie führen eine kurze Websuche durch und stellen fest, dass es eine Vielzahl von Optionen für die Protokollierung in Go gibt. Woher wissen Sie, welche Sie wählen sollen? In diesem Artikel finden Sie die Antwort auf diese Frage.

Wir werden einen Blick auf das eingebaute logPaket werfen und herausfinden, für welche Projekte es geeignet ist, bevor wir andere Protokollierungslösungen erkunden, die im Go-Ökosystem weit verbreitet sind.

Was soll protokolliert werden

Ich muss Ihnen nicht sagen, wie wichtig die Protokollierung ist. Logs werden von jeder produktiven Webanwendung verwendet, um Entwicklern und Betreibern zu helfen:

  • Fehler im Code der Anwendung aufspüren
  • Leistungsprobleme aufdecken
  • Post-Mortem-Analysen von Ausfällen und Sicherheitsvorfällen durchführen

Die Daten, die Sie tatsächlich protokollieren, hängen von der Art der Anwendung ab, die Sie erstellen. In den meisten Fällen werden Sie einige Variationen der folgenden Daten haben:

  • Der Zeitstempel für das Auftreten eines Ereignisses oder die Erstellung eines Protokolls
  • Protokollebenen wie Debug, Error oder Info
  • Kontextdaten, um zu verstehen, was passiert ist, und um die Situation leicht reproduzieren zu können

Was nicht protokolliert werden sollte

Im Allgemeinen sollten Sie keine sensiblen Geschäftsdaten oder persönlich identifizierbare Informationen protokollieren. Dazu gehören unter anderem:

  • Namen
  • IP-Adressen
  • Kreditkartennummern

Durch diese Einschränkungen sind die Protokolle zwar aus technischer Sicht weniger nützlich, aber sie machen Ihre Anwendung sicherer. In vielen Fällen können Vorschriften wie GDPR und HIPAA die Protokollierung von personenbezogenen Daten verbieten.

Einführung in das Log-Paket

Die Go-Standardbibliothek hat ein eingebautes logPaket, das die meisten grundlegenden Logging-Funktionen bietet. Es hat zwar keine Log-Levels (wie Debug, Warning oder Error), aber es bietet alles, was man braucht, um eine grundlegende Logging-Strategie einzurichten.

Hier ist das einfachste Logging-Beispiel:

package mainimport "log"func main() { log.Println("Hello world!")}

Der obige Code gibt den Text „Hello world!“ in den Standardfehler aus, enthält aber auch das Datum und die Uhrzeit, was praktisch ist, um Protokollnachrichten nach dem Datum zu filtern.

2019/12/09 17:21:53 Hello world!

Standardmäßig druckt das log-Paket in den Standardfehler-Ausgabestrom (stderr), aber Sie können es dazu bringen, in lokale Dateien oder jedes Ziel zu schreiben, das die io.Writer-Schnittstelle unterstützt. Es fügt auch einen Zeitstempel zur Protokollmeldung hinzu, ohne dass eine zusätzliche Konfiguration erforderlich ist.

Protokollierung in eine Datei

Wenn Sie Protokollmeldungen in einer Datei speichern müssen, können Sie dies tun, indem Sie eine neue Datei erstellen oder eine vorhandene Datei öffnen und sie als Ausgabe des Protokolls festlegen. Hier ein Beispiel:

package mainimport ( "log" "os")func main() { // If the file doesn't exist, create it or append to the file file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) if err != nil { log.Fatal(err) } log.SetOutput(file) log.Println("Hello world!")}

Wenn wir den Code ausführen, wird Folgendes in logs.txt.

2019/12/09 17:22:47 Hello world!

Wie bereits erwähnt, können Sie Ihre Protokolle im Grunde an jedes beliebige Ziel ausgeben, das die io.Writer-Schnittstelle implementiert, so dass Sie bei der Entscheidung, wo die Meldungen in Ihrer Anwendung protokolliert werden sollen, sehr flexibel sind.

Erstellen von benutzerdefinierten Loggern

Obwohl das log-Paket einen vordefinierten logger implementiert, der in den Standardfehler schreibt, können wir benutzerdefinierte Logger-Typen mithilfe der log.New()-Methode erstellen.

Beim Erstellen eines neuen Loggers müssen Sie drei Argumente an log.New() übergeben:

  • out: Jeder Typ, der die io.Writer-Schnittstelle implementiert, in die die Protokolldaten geschrieben werden
  • prefix: Eine Zeichenkette, die an den Anfang jeder Protokollzeile angehängt wird
  • flag: Eine Reihe von Konstanten, mit denen wir definieren können, welche Protokollierungseigenschaften in jeden vom Logger erzeugten Protokolleintrag aufgenommen werden sollen (mehr dazu im nächsten Abschnitt)

Wir können diese Funktion nutzen, um eigene Logger zu erstellen. Hier ein Beispiel, das die Logger Info, Warning und Error implementiert:

package mainimport ( "log" "os")var ( WarningLogger *log.Logger InfoLogger *log.Logger ErrorLogger *log.Logger)func init() { file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) if err != nil { log.Fatal(err) } InfoLogger = log.New(file, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile) WarningLogger = log.New(file, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile) ErrorLogger = log.New(file, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)}func main() { InfoLogger.Println("Starting the application...") InfoLogger.Println("Something noteworthy happened") WarningLogger.Println("There is something you should know about") ErrorLogger.Println("Something went wrong")}

Nach dem Erstellen oder Öffnen der Datei logs.txt am Anfang der Funktion init werden die drei definierten Logger initialisiert, indem das Ausgabeziel, der Präfix-String und die Log-Flags angegeben werden.

In der Funktion main werden die Logger verwendet, indem die Funktion Println aufgerufen wird, die einen neuen Log-Eintrag in die Log-Datei schreibt. Wenn Sie dieses Programm ausführen, wird Folgendes in die Datei logs.txt geschrieben.

INFO: 2019/12/09 12:01:06 main.go:26: Starting the application...INFO: 2019/12/09 12:01:06 main.go:27: Something noteworthy happenedWARNING: 2019/12/09 12:01:06 main.go:28: There is something you should know aboutERROR: 2019/12/09 12:01:06 main.go:29: Something went wrong

Beachten Sie, dass wir in diesem Beispiel in eine einzige Datei protokollieren, aber Sie können für jeden Logger eine eigene Datei verwenden, indem Sie beim Erstellen des Loggers eine andere Datei übergeben.

Log-Flags

Sie können Log-Flag-Konstanten verwenden, um eine Log-Meldung durch zusätzliche Kontextinformationen wie Datei, Zeilennummer, Datum und Uhrzeit zu ergänzen. Wenn Sie beispielsweise die Meldung „Something went wrong“ (Etwas ist schief gelaufen) mit der folgenden Flag-Kombination an einen Logger weitergeben:

log.Ldate|log.Ltime|log.Lshortfile

wird

2019/12/09 12:01:06 main.go:29: Something went wrong

gedruckt. Leider gibt es keine Kontrolle über die Reihenfolge, in der sie erscheinen, oder das Format, in dem sie dargestellt werden.

Einführung von Logging-Frameworks

Die Verwendung des log-Pakets eignet sich hervorragend für die lokale Entwicklung, wenn es wichtiger ist, schnelles Feedback zu erhalten als umfangreiche, strukturierte Logs zu erstellen. Darüber hinaus ist es wahrscheinlich besser, ein Logging-Framework zu verwenden.

Ein großer Vorteil der Verwendung eines Logging Frameworks ist, dass es hilft, die Logdaten zu standardisieren. Das bedeutet:

  • Es ist einfacher, die Protokolldaten zu lesen und zu verstehen.
  • Es ist einfacher, Protokolle aus verschiedenen Quellen zu sammeln und sie zur Analyse an eine zentrale Plattform weiterzuleiten.

Außerdem ist das Problem der Protokollierung so gut wie gelöst. Warum das Rad neu erfinden?

Auswahl eines Logging-Frameworks

Die Entscheidung, welches Framework verwendet werden soll, kann eine Herausforderung sein, da es mehrere Optionen zur Auswahl gibt.

Die beiden beliebtesten Logging-Frameworks für Go scheinen beglog und logrus zu sein. Die Popularität von glog ist überraschend, da es seit mehreren Jahren nicht mehr aktualisiert wurde. logrus wird besser gepflegt und in beliebten Projekten wie Docker verwendet, daher werden wir uns darauf konzentrieren.

Einstieg in logrus

Die Installation von logrus ist so einfach wie das Ausführen des folgenden Befehls in Ihrem Terminal:

go get "github.com/Sirupsen/logrus"

Eine großartige Sache an logrus ist, dass es vollständig mit dem log-Paket der Standardbibliothek kompatibel ist, so dass Sie Ihre Log-Importe überall durch log "github.com/sirupsen/logrus" ersetzen können und es wird einfach funktionieren!

Lassen Sie uns unser früheres „Hallo Welt“-Beispiel, das das log-Paket verwendet hat, modifizieren und stattdessen logrus verwenden:

package mainimport ( log "github.com/sirupsen/logrus")func main() { log.Println("Hello world!")}

Wenn Sie diesen Code ausführen, erhalten Sie folgende Ausgabe:

INFO Hello world!

Es könnte nicht einfacher sein!

Protokollierung in JSON

logrus eignet sich gut für die strukturierte Protokollierung in JSON, die – da JSON ein gut definierter Standard ist – es externen Diensten leicht macht, Ihre Protokolle zu analysieren, und auch das Hinzufügen von Kontext zu einer Protokollnachricht durch die Verwendung von Feldern relativ einfach macht, wie unten gezeigt:

package mainimport ( log "github.com/sirupsen/logrus")func main() { log.SetFormatter(&log.JSONFormatter{}) log.WithFields( log.Fields{ "foo": "foo", "bar": "bar", }, ).Info("Something happened")}

Die erzeugte Protokollausgabe wird ein JSON-Objekt sein, das die Nachricht, die Protokollstufe, den Zeitstempel und die enthaltenen Felder enthält.

{"bar":"bar","foo":"foo","level":"info","msg":"Something happened","time":"2019-12-09T15:55:24+01:00"}

Wenn Sie nicht daran interessiert sind, Ihre Protokolle als JSON auszugeben, sollten Sie wissen, dass es mehrere Formatierer von Drittanbietern für logrus gibt, die Sie auf der Github-Seite einsehen können. Sie können sogar Ihren eigenen Formatierer schreiben, wenn Sie dies bevorzugen.

Protokollebenen

Im Gegensatz zum Standardprotokollpaket unterstützt logrus Protokollebenen.

logrus verfügt über sieben Log-Ebenen: Trace, Debug, Info, Warn, Error, Fatal und Panic. Der Schweregrad jedes Levels erhöht sich, wenn Sie in der Liste nach unten gehen.

log.Trace("Something very low level.")log.Debug("Useful debugging information.")log.Info("Something noteworthy happened!")log.Warn("You should probably take a look at this.")log.Error("Something failed but I'm not quitting.")// Calls os.Exit(1) after logginglog.Fatal("Bye.")// Calls panic() after logginglog.Panic("I'm bailing.")

Indem Sie einen Logging-Level für einen Logger festlegen, können Sie nur die Einträge protokollieren, die Sie je nach Umgebung benötigen. Standardmäßig protokolliert logrus alles, was den Status Info oder höher hat (Warnung, Fehler, Fatal oder Panik).

package mainimport ( log "github.com/sirupsen/logrus")func main() { log.SetFormatter(&log.JSONFormatter{}) log.Debug("Useful debugging information.") log.Info("Something noteworthy happened!") log.Warn("You should probably take a look at this.") log.Error("Something failed but I'm not quitting.")}

Wenn Sie den obigen Code ausführen, erhalten Sie die folgende Ausgabe:

{"level":"info","msg":"Something noteworthy happened!","time":"2019-12-09T16:18:21+01:00"}{"level":"warning","msg":"You should probably take a look at this.","time":"2019-12-09T16:18:21+01:00"}{"level":"error","msg":"Something failed but I'm not quitting.","time":"2019-12-09T16:18:21+01:00"}

Beachten Sie, dass die Meldung der Stufe Debug nicht gedruckt wurde. Um sie in die Logs einzuschließen, setzen Sie log.Level auf log.DebugLevel:

log.SetLevel(log.DebugLevel)

Wrap up

In diesem Beitrag haben wir die Verwendung des eingebauten Log-Pakets untersucht und festgestellt, dass es nur für triviale Anwendungen oder beim Bau eines schnellen Prototyps verwendet werden sollte. Für alles andere ist die Verwendung eines Mainstream-Logging-Frameworks ein Muss.

Wir haben uns auch angeschaut, wie man sicherstellen kann, dass die in den Protokollen enthaltenen Informationen konsistent und einfach zu analysieren sind, insbesondere wenn sie auf einer zentralen Plattform gesammelt werden.

Danke fürs Lesen!

Leave a Reply