Cum să colectezi, să standardizezi și să centralizezi jurnalele Golang

sfat / jurnale /golang /microservicii

Organizațiile care depind de sisteme distribuite își scriu adesea aplicațiile în Go pentru a profita de caracteristicile de simultaneitate, cum ar fi canalele și gorutinele (de exemplu, Heroku, Basecamp, Cockroach Labs și Datadog). Dacă sunteți responsabil pentru construirea sau susținerea aplicațiilor Go, o strategie de logare bine gândită vă poate ajuta să înțelegeți comportamentul utilizatorilor, să localizați erorile și să monitorizați performanța aplicațiilor dumneavoastră.

Această postare vă va arăta câteva instrumente și tehnici pentru gestionarea logărilor Golang. Vom începe cu întrebarea despre ce pachet de logare să folosim pentru diferite tipuri de cerințe. Apoi, vom explica câteva tehnici pentru a face ca jurnalele dvs. să fie mai ușor de căutat și mai fiabile, pentru a reduce amprenta de resurse a configurației dvs. de logare și pentru a standardiza mesajele de logare.

Cunoașteți pachetul de logare

Go vă oferă o multitudine de opțiuni atunci când alegeți un pachet de logare, iar noi vom explora câteva dintre acestea mai jos. În timp ce logrus este cea mai populară dintre bibliotecile pe care le acoperim și vă ajută să implementați un format coerent de logare, celelalte au cazuri de utilizare specializate care merită menționate. Această secțiune va examina bibliotecile log, logrus și glog.

Utilizați log pentru simplitate

Biblioteca de logare încorporată a lui Golang, numită log, vine cu un logger implicit care scrie pe eroarea standard și adaugă un timestamp fără a fi nevoie de configurare. Puteți utiliza aceste jurnale rudimentare pentru dezvoltarea locală, atunci când obținerea unui feedback rapid de la codul dumneavoastră poate fi mai importantă decât generarea unor jurnale bogate și structurate.

De exemplu, puteți defini o funcție de împărțire care returnează o eroare apelantului, în loc să iasă din program, atunci când încercați să împărțiți la zero.

package mainimport ( "log" "errors" "fmt" )func divide(a float32, b float32) (float32, error) { if b == 0 { return 0, errors.New("can't divide by zero") } return a / b, nil}func main() { var a float32 = 10 var b float32 ret, err := divide(a,b) if err != nil{ log.Print(err) } fmt.Println(ret)}

Pentru că exemplul nostru împarte cu zero, va emite următorul mesaj de jurnal:

2019/01/31 11:48:00 can't divide by zero

Utilizați logrus pentru jurnale formatate

Recomandăm scrierea jurnalelor Golang folosind logrus, un pachet de logare conceput pentru logare structurată care este bine adaptat pentru logarea în JSON. Formatul JSON face posibil ca mașinile să analizeze cu ușurință jurnalele dumneavoastră Golang. Și din moment ce JSON este un standard bine definit, este simplu să adăugați context prin includerea de noi câmpuri – un analizor ar trebui să fie capabil să le preia automat.

Utilizând logrus, puteți defini câmpuri standard pentru a le adăuga la jurnalele JSON utilizând funcția WithFields, așa cum se arată mai jos. Puteți efectua apoi apeluri către logger la diferite niveluri, cum ar fi Info(), Warn() și Error(). Biblioteca logrus va scrie automat jurnalul ca JSON și va insera câmpurile standard, împreună cu orice câmpuri pe care le-ați definit din mers.

package mainimport ( log "github.com/sirupsen/logrus")func main() { log.SetFormatter(&log.JSONFormatter{}) standardFields := log.Fields{ "hostname": "staging-1", "appname": "foo-app", "session": "1ce3f6v", } log.WithFields(standardFields).WithFields(log.Fields{"string": "foo", "int": 1, "float": 1.1}).Info("My first ssl event from Golang")}

Jurnalul rezultat va include mesajul, nivelul jurnalului, data și ora și câmpurile standard într-un obiect JSON:

{"appname":"foo-app","float":1.1,"hostname":"staging-1","int":1,"level":"info","msg":"My first ssl event from Golang","session":"1ce3f6v","string":"foo","time":"2019-03-06T13:37:12-05:00"}

Utilizați glog dacă sunteți preocupat de volum

Câteva biblioteci de logare vă permit să activați sau să dezactivați logarea la niveluri specifice, ceea ce este util pentru a menține volumul jurnalului sub control atunci când treceți de la dezvoltare la producție. O astfel de bibliotecă este glog, care vă permite să folosiți stegulețe în linia de comandă (de exemplu, -v pentru verbozitate) pentru a seta nivelul de logare atunci când vă executați codul. Puteți folosi apoi o funcție V() în declarațiile if pentru a vă scrie jurnalele Golang numai la un anumit nivel de logare.

De exemplu, puteți folosi glog pentru a scrie aceeași eroare „Can’t divide by zero” de mai devreme, dar numai dacă faceți logarea la nivelul de verbositate 2. Puteți seta verbozitatea la orice număr întreg cu semn pe 32 de biți sau puteți utiliza funcțiile Info(), Warning(), Error() și Fatal() pentru a atribui nivelurile de verbozitate de la 0 la 3 (respectiv).

 if err != nil && glog.V(2){ glog.Warning(err) }

Puteți face ca aplicația dvs. să consume mai puține resurse prin înregistrarea doar la anumite niveluri în producție. În același timp, dacă nu există niciun impact asupra utilizatorilor, este adesea o idee bună să înregistrați cât mai multe interacțiuni cu aplicația dvs., apoi să folosiți un software de gestionare a jurnalelor, cum ar fi Datadog, pentru a găsi datele de care aveți nevoie pentru investigația dvs.

Bune practici pentru scrierea și stocarea jurnalelor Golang

După ce ați ales o bibliotecă de logare, veți dori, de asemenea, să planificați unde în codul dvs. să faceți apeluri către logger, cum să stocați jurnalele dvs. și cum să le dați sens. În această secțiune, vom recomanda o serie de cele mai bune practici pentru organizarea jurnalelor Golang:

  • Realizați apeluri către logger din cadrul procesului principal al aplicației dumneavoastră, nu în cadrul goroutine.
  • Scrieți jurnalele din aplicația dumneavoastră într-un fișier local, chiar dacă le veți trimite ulterior către o platformă centrală.
  • Standardizați jurnalele dumneavoastră cu un set de mesaje predefinite.
  • Transmiteți jurnalele dvs. către o platformă centrală, astfel încât să le puteți analiza și agrega.
  • Utilizați anteturi HTTP și ID-uri unice pentru a înregistra comportamentul utilizatorului în cadrul microserviciilor.

Evitați să declarați gorutine pentru logare

Există două motive pentru a evita crearea propriilor gorutine pentru a gestiona scrierea jurnalelor. În primul rând, poate duce la probleme de concurență, deoarece dubluri ale loggerului ar încerca să acceseze același io.Writer. În al doilea rând, bibliotecile de logare pornesc de obicei singure goroutine, gestionând intern orice probleme de concurență, iar pornirea propriilor goroutine nu va face decât să interfereze.

Scrieți-vă jurnalele într-un fișier

Inclusiv dacă vă trimiteți jurnalele către o platformă centrală, vă recomandăm să le scrieți mai întâi într-un fișier pe mașina locală. Veți dori să vă asigurați că jurnalele dvs. sunt întotdeauna disponibile la nivel local și că nu se pierd în rețea. În plus, scrierea într-un fișier înseamnă că puteți decupla sarcina de a vă scrie jurnalele de cea de a le trimite către o platformă centrală. Aplicațiile dvs. însele nu vor trebui să stabilească conexiuni sau să transmită jurnalele dvs. și puteți lăsa aceste sarcini pe seama unui software specializat, cum ar fi Datadog Agent. Dacă vă rulați aplicațiile Go în cadrul unei infrastructuri containerizate care nu include deja stocare persistentă – de exemplu, containere care rulează pe AWS Fargate – este posibil să doriți să configurați instrumentul dvs. de gestionare a jurnalelor pentru a colecta jurnalele direct din fluxurile STDOUT și STDERR ale containerelor dvs. (acest lucru este gestionat în mod diferit în Docker și Kubernetes).

Implementați o interfață standard de logare

Când scriu apeluri către loguri din interiorul codului lor, echipele folosesc adesea nume de atribute diferite pentru a descrie același lucru. Atributele incoerente pot deruta utilizatorii și pot face imposibilă corelarea jurnalelor care ar trebui să facă parte din aceeași imagine. De exemplu, doi dezvoltatori ar putea înregistra aceeași eroare, un nume de client lipsă atunci când gestionează o încărcare, în moduri diferite.

Golang înregistrează pentru aceeași eroare cu mesaje diferite din locații diferite.
Golang înregistrează pentru aceeași eroare cu mesaje diferite din locații diferite.

O modalitate bună de a impune standardizarea este de a crea o interfață între codul aplicației dvs. și biblioteca de logare. Interfața conține mesaje de jurnal predefinite care implementează un anumit format, facilitând investigarea problemelor prin asigurarea faptului că mesajele de jurnal pot fi căutate, grupate și filtrate.

Golang înregistrează pentru o eroare folosind o interfață standard pentru a crea un mesaj coerent.
Golang înregistrează pentru o eroare folosind o interfață standard pentru a crea un mesaj coerent.

În acest exemplu, vom declara un tip Event cu un mesaj predefinit. Apoi vom folosi mesajele Event pentru a face apeluri către un logger. Colegii de echipă pot scrie jurnale Golang furnizând o cantitate minimă de informații personalizate, lăsând aplicația să facă munca de implementare a unui format standard.

În primul rând, vom scrie un pachet logwrapper pe care dezvoltatorii îl pot include în codul lor.

package logwrapperimport ( "github.com/sirupsen/logrus")// Event stores messages to log later, from our standard interfacetype Event struct { id int message string}// StandardLogger enforces specific log message formatstype StandardLogger struct { *logrus.Logger}// NewLogger initializes the standard loggerfunc NewLogger() *StandardLogger {var baseLogger = logrus.New()var standardLogger = &StandardLogger{baseLogger}standardLogger.Formatter = &logrus.JSONFormatter{}return standardLogger}// Declare variables to store log messages as new Eventsvar ( invalidArgMessage = Event{1, "Invalid arg: %s"} invalidArgValueMessage = Event{2, "Invalid value for argument: %s: %v"} missingArgMessage = Event{3, "Missing arg: %s"})// InvalidArg is a standard error messagefunc (l *StandardLogger) InvalidArg(argumentName string) { l.Errorf(invalidArgMessage.message, argumentName)}// InvalidArgValue is a standard error messagefunc (l *StandardLogger) InvalidArgValue(argumentName string, argumentValue string) { l.Errorf(invalidArgValueMessage.message, argumentName, argumentValue)}// MissingArg is a standard error messagefunc (l *StandardLogger) MissingArg(argumentName string) { l.Errorf(missingArgMessage.message, argumentName)}

Pentru a utiliza interfața noastră de logare, trebuie doar să o includem în codul nostru și să facem apeluri către o instanță de StandardLogger.

package mainimport ( li "<PATH_TO_PACKAGE>/logwrapper")func main() { var standardLogger = := li.NewLogger() // You can then call a method of our standard logger in the context of an error // you would like to log. standardLogger.InvalidArgValue("client", "nil")}

Când rulăm codul nostru, vom obține următorul jurnal JSON:

{"level":"error","msg":"Invalid value for argument: client: nil","time":"2019-03-04T11:21:07-05:00"}

Centralizați jurnalele Golang

Dacă aplicația dvs. este implementată pe un cluster de gazde, nu este sustenabil să intrați prin SSH în fiecare dintre ele pentru a face tail, grep și a investiga jurnalele dvs. O alternativă mai scalabilă este de a transmite jurnalele din fișierele locale către o platformă centrală.

O soluție este de a utiliza pachetul Golang syslog pentru a transmite jurnalele din întreaga dvs. infrastructură către un singur server syslog.

Personalizați și eficientizați gestionarea jurnalelor Golang cu Datadog.

O altă soluție este de a utiliza o soluție de gestionare a jurnalelor. Datadog, de exemplu, poate să vă urmărească fișierele de jurnal și să transmită jurnalele către o platformă centrală pentru procesare și analiză.

Visualizarea Datadog Log Explorer poate afișa jurnalele Golang din diverse surse.

Puteți utiliza atributele pentru a reprezenta grafic valorile anumitor câmpuri de jurnal în timp, ordonate pe grupuri. De exemplu, ați putea urmări numărul de erori prin service pentru a vă anunța dacă există un incident în unul dintre serviciile dvs. Arătând jurnalele doar de la serviciul go-logging-demo, putem vedea câte jurnale de erori a produs acest serviciu într-un anumit interval de timp.

Gruparea jurnalelor Golang în funcție de stare.

De asemenea, puteți utiliza atributele pentru a detalia posibilele cauze, de exemplu pentru a vedea dacă un vârf în jurnalele de erori aparține unei anumite gazde. Puteți crea apoi o alertă automată bazată pe valorile jurnalelor dvs.

Să urmăriți jurnalele Golang în toate microserviciile

Când depanați o eroare, este adesea util să vedeți ce model de comportament a dus la aceasta, chiar dacă acel comportament implică un număr de microservicii. Puteți realiza acest lucru cu urmărirea distribuită, vizualizând ordinea în care aplicația dvs. execută funcții, interogări ale bazei de date și alte sarcini și urmărind acești pași de execuție pe măsură ce își croiesc drum printr-o rețea. O modalitate de a implementa urmărirea distribuită în cadrul jurnalelor dvs. este de a transmite informații contextuale sub formă de anteturi HTTP.

În acest exemplu, un microserviciu primește o cerere și verifică dacă există un ID de urmărire în antetul x-trace, generând unul dacă acesta nu există. Atunci când se face o cerere către un alt microserviciu, generăm apoi un nou spanID – pentru aceasta și pentru fiecare cerere – și îl adăugăm la antetul x-span.

func microService1(w http.ResponseWriter, r *http.Request) { client := &http.Client{} trace := r.Header.Get("x-trace") if ( trace == "") { trace = generateTraceId() } span := generateSpanId() // Hit the second microservice with the appropriate headers reqService2, _ := http.NewRequest("GET", "<ADDRESS>", nil) reqService2.Header.Add("x-trace", trace) reqService2.Header.Add("x-span", span) resService2, _ := client.Do(reqService2)}

Microserviciile din aval utilizează anteturile x-span ale cererilor primite pentru a specifica părinții intervalelor pe care le generează și trimit aceste informații ca antet x-parent către următorul microserviciu din lanț.

func microService2(w http.ResponseWriter, r *http.Request) { trace := r.Header.Get("x-trace") span := generateSpanId() parent := r.Header.Get("x-span") if (trace == "") { w.Header().Set("x-parent", parent) } w.Header().Set("x-trace", trace) w.Header().Set("x-span", span) if (parent == "") { w.Header().Set("x-parent", span) } w.WriteHeader(http.StatusOK) io.WriteString(w, fmt.Sprintf(aResponseMessage, 2, trace, span, parent))}

Dacă apare o eroare în unul dintre microserviciile noastre, putem folosi atributele trace, parent și span pentru a vedea traseul pe care l-a urmat o cerere, permițându-ne să știm ce gazde – și, eventual, ce părți din codul aplicației – trebuie să investigăm.

În primul microserviciu:

{"appname":"go-logging","level":"debug","msg":"Hello from Microservice One","trace":"eUBrVfdw","time":"2017-03-02T15:29:26+01:00","span":"UzWHRihF"}

În cel de-al doilea:

{"appname":"go-logging","level":"debug","msg":"Hello from Microservice Two","parent":"UzWHRihF","trace":"eUBrVfdw","time":"2017-03-02T15:29:26+01:00","span":"DPRHBMuE"}

Dacă doriți să aprofundați mai mult posibilitățile de urmărire Golang, puteți utiliza o bibliotecă de urmărire, cum ar fi OpenTracing sau o platformă de monitorizare care acceptă urmărirea distribuită pentru aplicațiile Go. De exemplu, Datadog poate construi automat o hartă a serviciilor folosind datele din biblioteca sa de urmărire Golang; poate vizualiza tendințele urmelor în timp și vă poate anunța despre serviciile cu rate neobișnuite de solicitări, rate de eroare sau latență.

Un exemplu de vizualizare care arată urmele de solicitări între microservicii.
Un exemplu de vizualizare care arată urmele solicitărilor între microservicii.

Registrări Golang curate și cuprinzătoare

În această postare, am evidențiat beneficiile și compromisurile mai multor biblioteci de logare Go. De asemenea, am recomandat modalități de a vă asigura că jurnalele dvs. sunt disponibile și accesibile atunci când aveți nevoie de ele și că informațiile pe care le conțin sunt coerente și ușor de analizat.

Pentru a începe să analizați toate jurnalele Go cu Datadog, înscrieți-vă pentru o versiune de încercare gratuită.

.

Lasă un răspuns

Adresa ta de email nu va fi publicată.