Logga in Go: Välj ett system och använd det

Du är relativt ny i Go-språket. Du använder det förmodligen för att skriva en webbapplikation eller en server, och du behöver skapa en loggfil. Så du gör en snabb webbsökning och upptäcker att det finns massor av alternativ för loggning i go. Hur vet du vilket du ska välja? Den här artikeln kommer att utrusta dig för att besvara den frågan.

Vi kommer att ta en titt på det inbyggda log-paketet och avgöra vilka projekt det lämpar sig för innan vi utforskar andra loggningslösningar som är vanliga i Go-ekosystemet.

Vad ska man logga

Jag behöver inte tala om för dig hur viktig loggning är. Loggar används av varje producerad webbapplikation för att hjälpa utvecklare och driftspersonal:

  • Spotta buggar i applikationens kod
  • Upptäcka prestandaproblem
  • Göra post-mortem-analyser av avbrott och säkerhetsincidenter

Den data som du faktiskt loggar kommer att bero på vilken typ av applikation du bygger. För det mesta kommer du att ha någon variation av följande:

  • Tidsstämpeln för när en händelse inträffade eller en logg genererades
  • Loggnivåer som debug, error eller info
  • Kontextuella data för att hjälpa till att förstå vad som hände och göra det möjligt att enkelt återskapa situationen

Vad man inte ska logga

I allmänhet bör man inte logga någon form av känsliga affärsdata eller personligt identifierbar information. Detta inkluderar, men är inte begränsat till:

  • Namn
  • IP-adresser
  • Kreditkortsnummer

De här restriktionerna kan göra loggarna mindre användbara ur ett tekniskt perspektiv, men de gör din applikation säkrare. I många fall kan bestämmelser som GDPR och HIPAA förbjuda loggning av personuppgifter.

Introduktion av loggpaketet

Standardbiblioteket i Go har ett inbyggt logpaket som tillhandahåller de flesta grundläggande loggningsfunktioner. Även om det inte har loggningsnivåer (t.ex. felsökning, varning eller fel), tillhandahåller det ändå allt du behöver för att få en grundläggande loggningsstrategi uppsatt.

Här är det mest grundläggande loggningsexemplet:

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

Koden ovan skriver ut texten ”Hello world!” till standardfelet, men den innehåller också datum och tid, vilket är praktiskt för att filtrera loggmeddelanden efter datum.

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

Som standard skriver log-paketet ut till utdataströmmen för standardfel (stderr), men du kan få det att skriva till lokala filer eller till en annan destination som har stöd för io.Writer-gränssnittet. Det lägger också till en tidsstämpel till loggmeddelandet utan någon ytterligare konfiguration.

Loggning till en fil

Om du behöver lagra loggmeddelanden i en fil kan du göra det genom att skapa en ny fil eller öppna en befintlig fil och ställa in den som utdata för loggen. Här är ett exempel:

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!")}

När vi kör koden skrivs följande till logs.txt.

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

Som tidigare nämnts kan du i princip skriva ut dina loggar till vilken destination som helst som implementerar io.Writer-gränssnittet, så du har stor flexibilitet när du bestämmer var du vill logga meddelanden i ditt program.

Skapa egna loggare

Trots att log-paketet implementerar en fördefinierad logger som skriver till standardfelet kan vi skapa egna loggartyper med hjälp av log.New()-metoden.

När du skapar en ny loggare måste du skicka in tre argument till log.New():

  • out: Det är där loggningsdata kommer att skrivas till
  • prefix: Alla typer som implementerar io.Writer-gränssnittet, vilket är den typ som loggningsdata kommer att skrivas till
  • prefix: En sträng som läggs till i början av varje logglinje
  • flag: En uppsättning konstanter som gör att vi kan definiera vilka loggningsegenskaper som ska inkluderas i varje loggpost som genereras av loggaren (mer om detta i nästa avsnitt)

Vi kan dra nytta av den här funktionen för att skapa anpassade loggare. Här är ett exempel som implementerar Info, Warning och Error loggers:

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")}

När vi har skapat eller öppnat logs.txt-filen högst upp i init-funktionen initialiserar vi sedan de tre definierade loggers genom att ange utdatadestinationen, prefixsträngen och loggflaggor.

I main-funktionen utnyttjas loggarna genom att anropa Println-funktionen, som skriver en ny loggpost till loggfilen. När du kör det här programmet kommer följande att skrivas till logs.txt.

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

Bemärk att vi i det här exemplet loggar till en enda fil, men du kan använda en separat fil för varje loggare genom att överlämna en annan fil när du skapar loggaren.

Log flags

Du kan använda loggflaggkonstanter för att berika ett loggningsmeddelande genom att ge ytterligare kontextinformation, till exempel fil, radnummer, datum och tid. Om du till exempel skickar meddelandet ”Något gick fel” genom en loggare med en flaggkombination som visas nedan:

log.Ldate|log.Ltime|log.Lshortfile

kommer meddelandet att skrivas ut

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

Det finns tyvärr ingen kontroll över i vilken ordning de visas eller i vilket format de presenteras.

Introduktion av loggningsramverk

Användning av paketet log är utmärkt för lokal utveckling när det är viktigare att få snabb återkoppling än att generera rika, strukturerade loggar. Utöver det är det oftast bättre att använda ett loggningsramverk.

En stor fördel med att använda ett loggningsramverk är att det hjälper till att standardisera loggdata. Detta innebär att:

  • Det är lättare att läsa och förstå loggdata.
  • Det är lättare att samla in loggar från flera källor och mata dem till en central plattform för att analyseras.

Det är dessutom i stort sett ett löst problem att logga. Varför uppfinna hjulet på nytt?

Välja ett loggningsramverk

Det kan vara en utmaning att bestämma vilket ramverk som ska användas, eftersom det finns flera alternativ att välja mellan.

De två mest populära loggningsramverken för Go verkar vara beglog och logrus. Glogs popularitet är överraskande, eftersom det inte har uppdaterats på flera år. logrus underhålls bättre och används i populära projekt som Docker, så vi kommer att fokusera på det.

Kom igång med logrus

Installera logrus är lika enkelt som att köra kommandot nedan i terminalen:

go get "github.com/Sirupsen/logrus"

En bra sak med logrus är att det är helt kompatibelt med log-paketet i standardbiblioteket, så du kan ersätta dina log-importar överallt med log "github.com/sirupsen/logrus" och det kommer bara att fungera!

Låt oss ändra vårt tidigare ”hello world”-exempel som använde log-paketet och använda logrus istället:

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

Körning av den här koden ger utdata:

INFO Hello world!

Det kan inte bli enklare!

Loggning i JSON

logrus är väl lämpad för strukturerad loggning i JSON som – eftersom JSON är en väldefinierad standard – gör det enkelt för externa tjänster att analysera dina loggar och gör det också relativt enkelt att lägga till kontext till ett loggmeddelande genom att använda fält, som visas nedan:

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

Den loggutgång som genereras kommer att vara ett JSON-objekt som innehåller meddelande, loggnivå, tidsstämpel och inkluderade fält.

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

Om du inte är intresserad av att ge ut dina loggar som JSON ska du vara medveten om att det finns flera tredjepartsformatörer för logrus, som du kan se på dess Github-sida. Du kan även skriva en egen formaterare om du föredrar det.

Lognivåer

Till skillnad från standardloggpaketet har logrus stöd för lognivåer.

logrus har sju loggnivåer: Trace, Debug, Info, Warn, Error, Fatal och Panic. Svårighetsgraden för varje nivå ökar när du går nedåt i listan.

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.")

Genom att ställa in en loggningsnivå på en logger kan du logga endast de poster du behöver beroende på din miljö. Som standard loggar logrus allt som är Info eller högre (Warn, Error, Fatal eller Panic).

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.")}

Körning av ovanstående kod ger följande utdata:

{"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"}

Märk väl att meddelandet på Debug-nivån inte skrevs ut. För att inkludera det i loggarna, sätt log.Level till log.DebugLevel:

log.SetLevel(log.DebugLevel)

Wrap up

I det här inlägget utforskade vi användningen av det inbyggda loggpaketet och fastställde att det endast bör användas för triviala tillämpningar eller när man bygger en snabb prototyp. För allt annat är användningen av ett vanligt loggningsramverk ett måste.

Vi tittade också på sätt att se till att informationen i dina loggar är konsekvent och lätt att analysera, särskilt när du aggregerar den på en centraliserad plattform.

Tack för att du läste!

Leave a Reply