Informationen zum Protokollieren in Go

Abgeschlossen

Protokolle spielen eine bedeutende Rolle in einem Programm, da sie zu einer Informationsquelle werden, die Sie überprüfen können, wenn etwas schief geht. Wenn ein Fehler auftritt, sehen Endbenutzer in der Regel nur eine Meldung, die auf ein Problem mit dem Programm hinweist. Aus der Perspektive eines Entwicklers benötigen wir mehr Informationen als eine einfache Fehlermeldung. Dies liegt hauptsächlich daran, dass wir das Problem reproduzieren möchten, um eine korrekte Lösung zu schreiben. In diesem Modul erfahren Sie, wie die Protokollierung in Go funktioniert. Außerdem lernen Sie einige der Methoden kennen, die Sie immer implementieren sollten.

Das log-Paket

Zunächst einmal bietet Go ein einfaches Standardpaket zum Arbeiten mit Protokollen. Sie können es so verwenden, wie Sie das fmt-Paket verwenden. Das Standardpaket stellt keine Protokollebenen bereit und ermöglicht Ihnen nicht, separate Protokollierungen für jedes Paket zu konfigurieren. Wenn Sie komplexere Protokollierungskonfigurationen schreiben müssen, können Sie dazu ein Protokollierungsframework verwenden. Die Protokollierungsframeworks bearbeiten wir später.

Nachfolgend ist die einfachste Möglichkeit aufgeführt, Protokolle zu verwenden:

import (
    "log"
)

func main() {
    log.Print("Hey, I'm a log!")
}

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

2020/12/19 13:39:17 Hey, I'm a log!

Standardmäßig enthält die log.Print()-Funktion das Datum und die Uhrzeit als Präfix der Protokollmeldung. Sie können das gleiche Verhalten mit fmt.Print() erzielen, aber Sie können auch andere Aufgaben mit dem log-Paket ausführen, wie das Senden von Protokollen an eine Datei. Die log-Paketfunktionalität sehen wir uns später genauer an.

Sie können die log.Fatal()-Funktion verwenden, um einen Fehler zu protokollieren und das Programm so zu beenden, als würden Sie os.Exit(1) verwenden. Verwenden wir den folgenden Codeausschnitt, um es zu testen:

package main

import (
    "fmt"
    "log"
)

func main() {
    log.Fatal("Hey, I'm an error log!")
    fmt.Print("Can you see me?")
}

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

2020/12/19 13:53:19  Hey, I'm an error log!
exit status 1

Beachten Sie, dass die letzte Zeile, fmt.Print("Can you see me?"), nicht ausgeführt wird. Dies liegt daran, dass der log.Fatal()-Funktionsaufrufs das Programm beendet. Sie erhalten ein ähnliches Verhalten, wenn Sie die log.Panic()-Funktion verwenden, die auch die panic()-Funktion aufruft, wie im folgenden Beispiel:

package main

import (
    "fmt"
    "log"
)

func main() {
    log.Panic("Hey, I'm an error log!")
    fmt.Print("Can you see me?")
}

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

2020/12/19 13:53:19  Hey, I'm an error log!
panic: Hey, I'm an error log!

goroutine 1 [running]:
log.Panic(0xc000060f58, 0x1, 0x1)
        /usr/local/Cellar/go/1.15.5/libexec/src/log/log.go:351 +0xae
main.main()
        /Users/christian/go/src/helloworld/logs.go:9 +0x65
exit status 2

Sie erhalten weiterhin die Protokollmeldung, aber jetzt erhalten Sie auch die Fehlerstapelüberwachung.

Eine weitere wichtige Funktion ist log.SetPrefix(). Sie können sie verwenden, um den Protokollmeldungen des Programms ein Präfix hinzuzufügen. Beispielsweise können Sie diesen Codeausschnitt verwenden:

package main

import (
    "log"
)

func main() {
    log.SetPrefix("main(): ")
    log.Print("Hey, I'm a log!")
    log.Fatal("Hey, I'm an error log!")
}

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

main(): 2021/01/05 13:59:58 Hey, I'm a log!
main(): 2021/01/05 13:59:58 Hey, I'm an error log!
exit status 1

Sie legen das Präfix einmalig fest, und ihre Protokolle enthalten Informationen wie den Namen der Funktion, von der das Protokoll stammt.

Sie können sich auf der Go-Website weitere Funktionen ansehen.

Protokollierung in einer Datei

Neben dem Drucken von Protokollen in der Konsole möchten Sie möglicherweise Protokolle an eine Datei senden, damit Sie sie später oder in Echtzeit verarbeiten können.

Warum möchten Sie Protokolle an eine Datei senden? Zuerst möchten Sie möglicherweise bestimmte Informationen vor den Endbenutzern ausblenden. Die Benutzer sind daran unter Umständen nicht interessiert, oder Sie könnten vertrauliche Informationen sichtbar machen. Wenn Sie Protokolle in Dateien haben, können Sie alle Protokolle an einem einzigen Speicherort zentralisieren und sie mit anderen Ereignissen korrelieren. Dieses Muster ist typisch: das Vorhandensein von verteilten Anwendungen, die kurzlebig sein können, z. B. in Containern.

Verwenden Sie den folgenden Code, um das Senden von Protokollen an eine Datei zu testen:

package main

import (
    "log"
    "os"
)

func main() {
    file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        log.Fatal(err)
    }

    defer file.Close()

    log.SetOutput(file)
    log.Print("Hey, I'm a log!")
}

Wenn Sie den vorangehenden Code ausführen, wird in der Konsole nichts angezeigt. In Ihrem Verzeichnis sollte eine neue Datei namens „info.log“ angezeigt werden, die die Protokolle enthält, die Sie mit der log.Print()-Funktion gesendet haben. Beachten Sie, dass Sie zunächst eine Datei erstellen oder öffnen und dann das log-Paket konfigurieren müssen, um die gesamte Ausgabe an eine Datei zu senden. Anschließend können Sie die log.Print()-Funktion weiterhin wie gewohnt verwenden.

Protokollierungsframeworks

Es kann schlussendlich vorkommen, dass die Funktionen des log-Pakets nicht ausreichen. Es kann hilfreich sein, wenn Sie ein Protokollierungsframework verwenden anstatt die eigenen Bibliotheken zu schreiben. Protokollierungsframeworks für Go sind u. a. Logrus, zerolog, zap und Apex.

Sehen wir uns an, was Sie mit zerolog machen können.

Zunächst einmal müssen Sie das Paket installieren. Wenn Sie diese Reihe bearbeitet haben, verwenden Sie wahrscheinlich bereits Go-Module, sodass Sie nichts unternehmen müssen. In diesem Fall können Sie diesen Befehl auf der Arbeitsstation ausführen, um die zerolog-Bibliotheken zu installieren:

go get -u github.com/rs/zerolog/log

Verwenden Sie diesen Codeausschnitt nun, um ihn zu testen:

package main

import (
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
    log.Print("Hey! I'm a log message!")
}

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

{"level":"debug","time":1609855453,"message":"Hey! I'm a log message!"}

Beachten Sie, dass Sie nur die richtigen Importnamen hinzufügen müssen, und dann können Sie die log.Print()-Funktion weiterhin wie gewohnt verwenden. Berücksichtigen Sie außerdem, wie sich die Ausgabe im JSON-Format ändert. JSON ist ein nützliches Format für Protokolle beim Ausführen von Suchvorgängen an einem zentralen Speicherort.

Ein weiteres nützliches Feature ist, dass Sie Kontextdaten wie folgt schnell hinzufügen können:

package main

import (
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    zerolog.TimeFieldFormat = zerolog.TimeFormatUnix

    log.Debug().
        Int("EmployeeID", 1001).
        Msg("Getting employee information")

    log.Debug().
        Str("Name", "John").
        Send()
}

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

{"level":"debug","EmployeeID":1001,"time":1609855731,"message":"Getting employee information"}
{"level":"debug","Name":"John","time":1609855731}

Sehen Sie sich an, wie wir die Mitarbeiter-ID als Kontext hinzugefügt haben. Sie wird Teil der Protokollzeile als eine andere Eigenschaft. Außerdem ist es wichtig, hervorzuheben, dass die Felder, die Sie hinzufügen, stark typisiert sind.

Sie können andere Features mit zerolog implementieren, z. B. die Ebenenprotokollierung, die Verwendung von formatierten Stapelüberwachungen und die Verwendung von mehr als einer Protokollierungsinstanz zur Verwaltung verschiedener Ausgaben. Weitere Informationen finden Sie auf der GitHub-Website.