Logge ind Go: Valg af system og brug af det

Du er relativt ny i Go-sproget. Du bruger det sandsynligvis til at skrive en webapp eller en server, og du har brug for at oprette en logfil. Så du laver en hurtig søgning på nettet og finder ud af, at der findes et væld af muligheder for logning i go. Hvordan ved du, hvilken en du skal vælge? Denne artikel vil ruste dig til at besvare det spørgsmål.

Vi vil tage et kig på den indbyggede log-pakke og afgøre, hvilke projekter den egner sig til, før vi udforsker andre logningsløsninger, der er fremherskende i Go-økosystemet.

Hvad skal logges

Jeg behøver ikke at fortælle dig, hvor vigtig logning er. Logs bruges af alle produktionswebapplikationer til at hjælpe udviklere og drift:

  • Spotte fejl i applikationens kode
  • Opdage problemer med ydeevnen
  • Gøre post mortem-analyser af udfald og sikkerhedshændelser

De data, du faktisk logger, afhænger af den type applikation, du bygger. Det meste af tiden vil du have en vis variation i følgende:

  • Tidsstemplet for, hvornår en hændelse indtraf eller en log blev genereret
  • Logniveauer som f.eks. debug, error eller info
  • Kontekstdata for at hjælpe med at forstå, hvad der skete, og gøre det muligt nemt at reproducere situationen

Hvad du ikke skal logge

Generelt set bør du ikke logge nogen form for følsomme forretningsdata eller personligt identificerbare oplysninger. Dette omfatter, men er ikke begrænset til:

  • Navne
  • IP-adresser
  • Kreditkortsnumre

Disse begrænsninger kan gøre logs mindre nyttige fra et teknisk perspektiv, men de gør din applikation mere sikker. I mange tilfælde kan regler som GDPR og HIPAA forbyde logning af personlige data.

Indførelse af log-pakken

Go standardbiblioteket har en indbygget log pakke, der giver de fleste grundlæggende logningsfunktioner. Selv om den ikke har logniveauerne (såsom debug, warning eller error), indeholder den stadig alt, hvad du har brug for for at få en grundlæggende logningsstrategi opsat.

Her er det mest grundlæggende logningseksempel:

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

Koden ovenfor udskriver teksten “Hello world!” til standardfejlen, men den indeholder også dato og klokkeslæt, hvilket er praktisk til at filtrere logmeddelelser efter dato.

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

Som standard udskriver log-pakken til standardfejl (stderr)-uddatastrømmen, men du kan få den til at skrive til lokale filer eller en hvilken som helst destination, der understøtter io.Writer-grænsefladen. Den tilføjer også et tidsstempel til logmeddelelsen uden yderligere konfiguration.

Logning til en fil

Hvis du har brug for at gemme logmeddelelser i en fil, kan du gøre det ved at oprette en ny fil eller åbne en eksisterende fil og indstille den som output af loggen. Her er et eksempel:

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ører koden, skrives følgende til logs.txt.

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

Som tidligere nævnt kan du i princippet udskrive dine logfiler til enhver destination, der implementerer io.Writer-grænsefladen, så du har stor fleksibilitet, når du skal beslutte, hvor meddelelser skal logges i dit program.

Skabelse af brugerdefinerede loggere

Selv om log-pakken implementerer en foruddefineret logger, der skriver til standardfejlen, kan vi oprette brugerdefinerede loggertyper ved hjælp af log.New()-metoden.

Når du opretter en ny logger, skal du sende tre argumenter til log.New():

  • out: En hvilken som helst type, der implementerer io.Writer-grænsefladen, hvor logdataene vil blive skrevet til
  • prefix: En streng, der tilføjes i begyndelsen af hver loglinje
  • flag: En streng, der tilføjes i begyndelsen af hver loglinje
  • flag: Et sæt konstanter, der giver os mulighed for at definere, hvilke logningsegenskaber der skal medtages i hver logningspost, der genereres af loggeren (mere om dette i næste afsnit)

Vi kan drage fordel af denne funktion til at oprette brugerdefinerede loggere. Her er et eksempel, der implementerer Info, Warning og Error loggere:

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 oprettet eller åbnet logs.txt-filen øverst i init-funktionen, initialiserer vi derefter de tre definerede loggere ved at angive outputdestinationen, prefixstrengen og logflagene.

I main-funktionen udnyttes loggerne ved at kalde Println-funktionen, som skriver en ny logpost til logfilen. Når du kører dette program, vil følgende blive skrevet til 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, at vi i dette eksempel logger til en enkelt fil, men du kan bruge en separat fil for hver logger ved at overgive en anden fil, når du opretter loggeren.

Logflag

Du kan bruge logflagkonstanter til at berige en logmeddelelse ved at give yderligere kontekstoplysninger, f.eks. fil, linjenummer, dato og klokkeslæt. Hvis du f.eks. sender meddelelsen “Noget gik galt” gennem en logger med en flagkombination, der er vist nedenfor:

log.Ldate|log.Ltime|log.Lshortfile

vil den udskrive

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

Der er desværre ingen kontrol over den rækkefølge, de vises i, eller det format, de præsenteres i.

Indførelse af logningsrammer

Brug af pakken log er fantastisk til lokal udvikling, når det er vigtigere at få hurtig feedback end at generere rige, strukturerede logs. Ud over det vil du højst sandsynligt være bedre stillet ved at bruge en logningsramme.

En stor fordel ved at bruge en logningsramme er, at den hjælper med at standardisere logdataene. Det betyder, at:

  • Det er nemmere at læse og forstå logdataene.
  • Det er nemmere at samle logs fra flere kilder og fodre dem til en central platform, hvor de kan analyseres.

Dertil kommer, at logning stort set er et løst problem. Hvorfor genopfinde hjulet?

Valg af logningsramme

Det kan være en udfordring at beslutte, hvilken ramme der skal bruges, da der er flere muligheder at vælge imellem.

De to mest populære logningsrammer til Go ser ud til at være beglog og logrus. Glog’s popularitet er overraskende, da det ikke er blevet opdateret i flere år. logrus er bedre vedligeholdt og bruges i populære projekter som Docker, så vi vil fokusere på det.

Kom i gang med logrus

Installeringen af logrus er så enkel som at køre nedenstående kommando i din terminal:

go get "github.com/Sirupsen/logrus"

En fantastisk ting ved logrus er, at den er fuldstændig kompatibel med log-pakken i standardbiblioteket, så du kan erstatte din log-import overalt med log "github.com/sirupsen/logrus", og det vil bare virke!

Lad os ændre vores tidligere “hello world”-eksempel, der brugte log-pakken, og bruge logrus i stedet:

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

Kørsel af denne kode giver output:

INFO Hello world!

Det kunne ikke være nemmere!

Logføring i JSON

logrus er velegnet til struktureret logføring i JSON, som – da JSON er en veldefineret standard – gør det nemt for eksterne tjenester at analysere dine logfiler og gør det også relativt enkelt at tilføje kontekst til en logmeddelelse ved hjælp af felter, som vist nedenfor:

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 genererede logudgang vil være et JSON-objekt, der indeholder meddelelsen, logniveauet, tidsstemplet og de inkluderede felter.

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

Hvis du ikke er interesseret i at udsende dine logfiler som JSON, skal du være opmærksom på, at der findes flere tredjepartsformatere til logrus, som du kan se på dens Github-side. Du kan endda skrive din egen formatter, hvis du foretrækker det.

Logniveauer

I modsætning til standardlogpakken understøtter logrus logniveauer.

logrus har syv logniveauer: Trace, Debug, Info, Warn, Error, Fatal og Panic. Sværhedsgraden for hvert niveau øges, efterhånden som du går nedad på listen.

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

Gennem at indstille et logningsniveau på en logger kan du kun logge de poster, du har brug for, afhængigt af dit miljø. Som standard vil logrus logge alt, der er Info eller derover (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ørsel af ovenstående kode vil give følgende output:

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

Bemærk, at meddelelsen på Debug-niveau ikke blev udskrevet. For at inkludere den i logfilerne skal du indstille log.Level til at være lig med log.DebugLevel:

log.SetLevel(log.DebugLevel)

Wrap up

I dette indlæg har vi udforsket brugen af den indbyggede logpakke og fastslået, at den kun bør bruges til trivielle programmer eller når der bygges en hurtig prototype. Til alt andet er brugen af en mainstream logningsramme et must.

Vi kiggede også på måder at sikre, at oplysningerne i dine logfiler er konsistente og lette at analysere, især når de aggregeres på en centraliseret platform.

Tak for læsning!

Leave a Reply