Loggen in Go: Een systeem kiezen en het gebruiken

Je bent relatief nieuw met de taal Go. Je gebruikt het waarschijnlijk om een web-app of een server te schrijven, en je moet een logbestand maken. Dus, je doet een snelle zoektocht op het web en vindt dat er een ton van opties voor het loggen in Go zijn. Hoe weet je welke je moet kiezen? Dit artikel zal je helpen om die vraag te beantwoorden.

We zullen eens kijken naar het ingebouwde log pakket en bepalen voor welke projecten het geschikt is, voordat we andere log-oplossingen gaan onderzoeken die gangbaar zijn in het Go ecosysteem.

Wat te loggen

Ik hoef je niet te vertellen hoe belangrijk loggen is. Logs worden door elke productie webapplicatie gebruikt om ontwikkelaars en operations te helpen:

  • Opsporen van bugs in de code van de applicatie
  • Ontdekken van prestatieproblemen
  • Post-mortem analyse van uitval en beveiligingsincidenten

De gegevens die u daadwerkelijk logt, hangen af van het type applicatie dat u bouwt. Meestal zult u enige variatie in het volgende hebben:

  • De tijdstempel voor wanneer een gebeurtenis plaatsvond of een log werd gegenereerd
  • Log-niveaus zoals debug, error of info
  • Contextuele gegevens om te helpen begrijpen wat er is gebeurd en het mogelijk te maken om de situatie gemakkelijk te reproduceren

Wat niet te loggen

In het algemeen moet u geen enkele vorm van gevoelige bedrijfsgegevens of persoonlijk identificeerbare informatie loggen. Dit omvat, maar is niet beperkt tot:

  • Namen
  • IP-adressen
  • Creditcardnummers

Deze beperkingen kunnen logs minder nuttig maken vanuit een technisch perspectief, maar ze maken uw toepassing veiliger. In veel gevallen kan regelgeving zoals GDPR en HIPAA het loggen van persoonlijke gegevens verbieden.

Inleiding tot het log-pakket

De Go standaard bibliotheek heeft een ingebouwd log pakket dat de meeste basis log-functies biedt. Hoewel het geen log-niveaus heeft (zoals debug, warning, of error), biedt het toch alles wat je nodig hebt om een basis logging strategie op te zetten.

Hier volgt het meest basis logging voorbeeld:

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

De code hierboven drukt de tekst “Hello world!”naar de standaardfout, maar het bevat ook de datum en tijd, wat handig is voor het filteren van logberichten op datum.

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

Het log-pakket drukt standaard af naar de standaardfout (stderr) uitvoerstroom, maar u kunt het naar lokale bestanden laten schrijven of naar elke bestemming die de io.Writer-interface ondersteunt. Het voegt ook een tijdstempel toe aan het logbericht zonder extra configuratie.

Loggen naar een bestand

Als u logberichten in een bestand moet opslaan, kunt u dit doen door een nieuw bestand te maken of een bestaand bestand te openen en dit als de uitvoer van het logboek in te stellen. Hier volgt een voorbeeld:

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

Wanneer we de code uitvoeren, wordt het volgende geschreven naar logs.txt.

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

Zoals eerder vermeld, kunt u uw logboeken in principe uitvoeren naar elke bestemming die de io.Writer-interface implementeert, dus u hebt veel flexibiliteit bij het beslissen waar u berichten in uw toepassing wilt loggen.

Aangepaste loggers maken

Hoewel het log pakket een voorgedefinieerde logger implementeert die naar de standaard fout schrijft, kunnen we aangepaste logger types maken met behulp van de log.New() methode.

Wanneer u een nieuwe logger maakt, moet u drie argumenten doorgeven aan log.New():

  • out: Elk type dat de io.Writer interface implementeert, waar de loggegevens naar zullen worden geschreven
  • prefix: Een string die wordt toegevoegd aan het begin van elke logregel
  • flag: Een set constanten waarmee we kunnen definiëren welke logging-eigenschappen moeten worden opgenomen in elk logboekitem dat door de logger wordt gegenereerd (meer hierover in de volgende sectie)

We kunnen gebruik maken van deze functie om aangepaste loggers te maken. Hier is een voorbeeld dat Info, Warning en Error loggers implementeert:

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

Na het maken of openen van het logs.txt bestand bovenaan de init functie, initialiseren we de drie gedefinieerde loggers door de uitvoer bestemming, prefix string, en log vlaggen op te geven.

In de main functie worden de loggers gebruikt door de Println functie aan te roepen, die een nieuwe log entry naar het log bestand schrijft. Wanneer u dit programma uitvoert, wordt het volgende geschreven naar 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

Merk op dat we in dit voorbeeld naar een enkel bestand loggen, maar u kunt een apart bestand voor elke logger gebruiken door een ander bestand door te geven wanneer u de logger maakt.

Logvlaggen

U kunt logvlagconstanten gebruiken om een logbericht te verrijken door extra contextinformatie te geven, zoals het bestand, regelnummer, datum en tijd. Als u bijvoorbeeld het bericht “Er ging iets mis” door een logger stuurt met een vlaggencombinatie zoals hieronder weergegeven:

log.Ldate|log.Ltime|log.Lshortfile

zal

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

Afdrukken

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

Helaas is er geen controle over de volgorde waarin ze verschijnen of het formaat waarin ze worden gepresenteerd.

Introduceren van logboekframeworks

Het gebruik van het log-pakket is prima voor lokale ontwikkeling wanneer het krijgen van snelle feedback belangrijker is dan het genereren van rijke, gestructureerde logboeken. Buiten dat, zul je waarschijnlijk beter af zijn met het gebruik van een logging framework.

Een groot voordeel van het gebruik van een logboek raamwerk is dat het helpt om de loggegevens te standaardiseren. Dit betekent dat:

  • Het gemakkelijker is om de loggegevens te lezen en te begrijpen.
  • Het gemakkelijker is om logs van verschillende bronnen te verzamelen en ze naar een centraal platform te voeden om ze te analyseren.

Daarnaast is logging zo’n beetje een opgelost probleem. Waarom het wiel opnieuw uitvinden?

Het kiezen van een logging framework

Het kiezen van een framework kan een uitdaging zijn, omdat er verschillende opties zijn om uit te kiezen.

De twee meest populaire logging frameworks voor Go lijken beglog en logrus te zijn. De populariteit van glog is verrassend, omdat het al enkele jaren niet meer is bijgewerkt. logrus is beter onderhouden en wordt gebruikt in populaire projecten zoals Docker, dus we zullen ons daarop richten.

Aan de slag met logrus

Het installeren van logrus is zo eenvoudig als het uitvoeren van het onderstaande commando in uw terminal:

go get "github.com/Sirupsen/logrus"

Een geweldig ding over logrus is dat het volledig compatibel is met het log-pakket van de standaardbibliotheek, dus u kunt uw logimport overal vervangen door log "github.com/sirupsen/logrus" en het zal gewoon werken!

Laten we ons eerdere “hello world” voorbeeld aanpassen dat het log pakket gebruikte en in plaats daarvan logrus gebruiken:

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

Het uitvoeren van deze code levert de volgende uitvoer op:

INFO Hello world!

Het kan niet eenvoudiger zijn!

Logging in JSON

logrus is zeer geschikt voor gestructureerde logging in JSON, waarmee – aangezien JSON een goed gedefinieerde standaard is – externe services uw logs gemakkelijk kunnen parseren en waarmee ook relatief eenvoudig context aan een logbericht kan worden toegevoegd door het gebruik van velden, zoals hieronder wordt weergegeven:

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

De gegenereerde logoutput is een JSON-object dat het bericht, het logniveau, het tijdstempel en de bijgevoegde velden bevat.

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

Als u niet geïnteresseerd bent in het uitvoeren van uw logs als JSON, moet u weten dat er verschillende formatters van derden bestaan voor logrus, die u kunt bekijken op de Github-pagina ervan. U kunt zelfs uw eigen formatter schrijven als u dat verkiest.

Log niveaus

In tegenstelling tot het standaard log pakket, ondersteunt logrus log niveaus.

logrus heeft zeven log niveaus: Trace, Debug, Info, Warn, Error, Fatal, en Panic. De ernst van elk niveau neemt toe naarmate u lager in de lijst komt.

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

Door een logging-niveau op een logger in te stellen, kunt u alleen de items loggen die u nodig hebt, afhankelijk van uw omgeving. Standaard logt logrus alles dat Info of hoger is (Warn, Error, Fatal, of 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.")}

Het uitvoeren van de bovenstaande code zal de volgende uitvoer produceren:

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

Merk op dat het Debug-niveau bericht niet is afgedrukt. Om het in de logs op te nemen, stelt u log.Level in op log.DebugLevel:

log.SetLevel(log.DebugLevel)

Wrap up

In dit bericht hebben we het gebruik van het ingebouwde log-pakket verkend en vastgesteld dat het alleen moet worden gebruikt voor triviale toepassingen of bij het bouwen van een snel prototype. Voor al het andere is het gebruik van een mainstream logging framework een must.

We hebben ook gekeken naar manieren om ervoor te zorgen dat de informatie in uw logs consistent en gemakkelijk te analyseren is, vooral bij het aggregeren op een gecentraliseerd platform.

Dank voor het lezen!

Leave a Reply