Registrazione in Go: Scegliere un sistema e usarlo

Sei relativamente nuovo al linguaggio Go. Probabilmente lo state usando per scrivere una web app o un server, e avete bisogno di creare un file di log. Quindi, fate una rapida ricerca sul web e scoprite che ci sono un sacco di opzioni per il log in Go. Come si fa a sapere quale scegliere? Questo articolo vi aiuterà a rispondere a questa domanda.

Daremo un’occhiata al pacchetto integrato log e determineremo per quali progetti è adatto prima di esplorare altre soluzioni di log che sono prevalenti nell’ecosistema Go.

Cosa registrare

Non ho bisogno di dirti quanto sia importante la registrazione. I log sono usati da ogni applicazione web di produzione per aiutare gli sviluppatori e le operazioni:

  • Osservare i bug nel codice dell’applicazione
  • Scoprire i problemi di performance
  • Fare l’analisi post-mortem delle interruzioni e degli incidenti di sicurezza

I dati che effettivamente registrerai dipenderanno dal tipo di applicazione che stai costruendo. La maggior parte delle volte, avrete qualche variazione di quanto segue:

  • Il timestamp di quando si è verificato un evento o è stato generato un log
  • Livelli di log come debug, error, o info
  • Dati contestuali per aiutare a capire cosa è successo e rendere possibile riprodurre facilmente la situazione

Cosa non registrare

In generale, non dovresti registrare nessuna forma di dati aziendali sensibili o informazioni di identificazione personale. Questo include, ma non è limitato a:

  • Nomi
  • Indirizzi IP
  • Numeri di carte di credito

Queste restrizioni possono rendere i log meno utili da una prospettiva ingegneristica, ma rendono la vostra applicazione più sicura. In molti casi, regolamenti come GDPR e HIPAA possono vietare la registrazione di dati personali.

Introduzione al pacchetto log

La libreria standard di Go ha un pacchetto integrato log che fornisce la maggior parte delle caratteristiche di base dei log. Anche se non ha livelli di log (come debug, warning o error), fornisce comunque tutto il necessario per impostare una strategia di log di base.

Ecco un esempio di log di base:

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

Il codice sopra stampa il testo “Hello world!” sullo standard error, ma include anche la data e l’ora, il che è comodo per filtrare i messaggi di log per data.

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

Di default, il pacchetto log stampa sul flusso di output standard error (stderr), ma è possibile farlo scrivere su file locali o su qualsiasi destinazione che supporti l’interfaccia io.Writer. Aggiunge anche un timestamp al messaggio di log senza alcuna configurazione aggiuntiva.

Log in un file

Se hai bisogno di memorizzare i messaggi di log in un file, puoi farlo creando un nuovo file o aprendo un file esistente e impostandolo come output del log. Ecco un esempio:

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

Quando eseguiamo il codice, quanto segue viene scritto su logs.txt.

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

Come detto prima, puoi fondamentalmente inviare i tuoi log in uscita a qualsiasi destinazione che implementa l’interfaccia io.Writer, quindi hai molta flessibilità nel decidere dove registrare i messaggi nella tua applicazione.

Creazione di logger personalizzati

Anche se il pacchetto log implementa un logger predefinito che scrive sull’errore standard, possiamo creare tipi di logger personalizzati usando il metodo log.New().

Quando si crea un nuovo logger, è necessario passare tre argomenti a log.New():

  • out: Qualsiasi tipo che implementa l’interfaccia io.Writer, che è dove verranno scritti i dati di log
  • prefix: Una stringa che viene aggiunta all’inizio di ogni linea di log
  • flag: Un insieme di costanti che ci permettono di definire quali proprietà di log includere in ogni voce di log generata dal logger (di più su questo nella prossima sezione)

Possiamo sfruttare questa caratteristica per creare logger personalizzati. Ecco un esempio che implementa i logger Info, Warning e Error:

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

Dopo aver creato o aperto il file logs.txt all’inizio della funzione init, inizializziamo i tre logger definiti fornendo la destinazione di uscita, la stringa di prefisso e i flag di log.

Nella funzione main, i logger vengono utilizzati chiamando la funzione Println, che scrive una nuova voce di log nel file di log. Quando esegui questo programma, quanto segue verrà scritto in 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

Nota che in questo esempio, stiamo registrando in un singolo file, ma puoi usare un file separato per ogni logger passando un file diverso quando crei il logger.

Log flags

Puoi usare le costanti di log flag per arricchire un messaggio di log fornendo informazioni di contesto aggiuntive, come il file, il numero di linea, la data e l’ora. Per esempio, passare il messaggio “Qualcosa è andato storto” attraverso un logger con una combinazione di flag mostrata qui sotto:

log.Ldate|log.Ltime|log.Lshortfile

stamperà

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

Purtroppo, non c’è controllo sull’ordine in cui appaiono o sul formato in cui sono presentati.

Introduzione ai framework di log

L’uso del pacchetto log è ottimo per lo sviluppo locale quando ottenere un feedback veloce è più importante che generare log ricchi e strutturati. Al di là di questo, è molto probabile che sia meglio usare un framework di log.

Un grande vantaggio dell’uso di un framework di log è che aiuta a standardizzare i dati di log. Questo significa che:

  • È più facile leggere e capire i dati di log.
  • È più facile raccogliere i log da diverse fonti e inviarli a una piattaforma centrale per essere analizzati.

Inoltre, i log sono praticamente un problema risolto. Perché reinventare la ruota?

Scegliere un framework di log

Decidere quale framework usare può essere una sfida, poiché ci sono diverse opzioni tra cui scegliere.

I due framework di log più popolari per Go sembrano essere beglog e logrus. La popolarità di glog è sorprendente, dal momento che non è stato aggiornato in diversi anni. logrus è meglio mantenuto e utilizzato in progetti popolari come Docker, quindi ci concentreremo su di esso.

Iniziare con logrus

Installare logrus è semplice come eseguire il seguente comando nel terminale:

go get "github.com/Sirupsen/logrus"

Una grande cosa di logrus è che è completamente compatibile con il pacchetto log della libreria standard, quindi puoi sostituire le tue importazioni di log ovunque con log "github.com/sirupsen/logrus" e funzionerà!

Modifichiamo il nostro precedente esempio “hello world” che usava il pacchetto log e usiamo invece logrus:

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

Eseguire questo codice produce l’output:

INFO Hello world!

Non potrebbe essere più facile!

Il log in JSON

logrus è adatto per un log strutturato in JSON che – essendo JSON uno standard ben definito – rende facile per i servizi esterni analizzare i tuoi log e rende anche l’aggiunta di contesto a un messaggio di log relativamente semplice attraverso l’uso di campi, come mostrato di seguito:

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

L’output di log generato sarà un oggetto JSON che include il messaggio, il livello di log, il timestamp e i campi inclusi.

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

Se non sei interessato a produrre i tuoi log come JSON, sappi che esistono diversi formattatori di terze parti per logrus, che puoi vedere sulla sua pagina Github. Puoi anche scrivere il tuo formattatore personale, se preferisci.

Livelli di log

A differenza del pacchetto log standard, logrus supporta i livelli di log.

logrus ha sette livelli di log: Trace, Debug, Info, Warn, Error, Fatal e Panic. La gravità di ogni livello aumenta man mano che si scende nella lista.

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

Impostare un livello di log su un logger ti permette di registrare solo le voci necessarie a seconda del tuo ambiente. Per impostazione predefinita, logrus registrerà tutto ciò che è Info o superiore (Warn, Error, Fatal, o 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.")}

Eseguendo il codice di cui sopra si otterrà il seguente 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"}

Nota che il messaggio di livello Debug non è stato stampato. Per includerlo nei log, impostare log.Level a log.DebugLevel:

log.SetLevel(log.DebugLevel)

Wrap up

In questo post, abbiamo esplorato l’uso del pacchetto log integrato e stabilito che dovrebbe essere usato solo per applicazioni banali o quando si costruisce un prototipo veloce. Per tutto il resto, l’uso di un framework di log tradizionale è un must.

Abbiamo anche esaminato i modi per garantire che le informazioni contenute nei vostri log siano coerenti e facili da analizzare, specialmente quando vengono aggregate su una piattaforma centralizzata.

Grazie per aver letto!

Leave a Reply