Az elosztott rendszerektől függő szervezetek gyakran Go nyelven írják alkalmazásaikat, hogy kihasználják az olyan párhuzamossági funkciókat, mint a csatornák és goroutines (pl., Heroku, Basecamp, Cockroach Labs és Datadog). Ha Go alkalmazások készítéséért vagy támogatásáért felel, egy jól átgondolt naplózási stratégia segíthet a felhasználói viselkedés megértésében, a hibák lokalizálásában és az alkalmazások teljesítményének nyomon követésében.
Ez a bejegyzés néhány eszközt és technikát mutat be a Golang naplók kezeléséhez. Azzal a kérdéssel kezdjük, hogy milyen naplózási csomagot használjunk a különböző típusú követelményekhez. Ezután ismertetünk néhány technikát, amelyekkel kereshetőbbé és megbízhatóbbá teheti a naplóit, csökkentheti a naplózási beállítás erőforrásigényét, és szabványosíthatja a naplóüzeneteket.
- Tudja meg a naplózási csomagját
- A log használata az egyszerűség kedvéért
- A logrus használata formázott naplókhoz
- Használja a glogot, ha aggódik a mennyiség miatt
- A Golang naplók írásának és tárolásának legjobb gyakorlatai
- Kerülje a goroutine-ok deklarálását a naplózáshoz
- Fájlba írja a naplóit
- Szabványos naplózási felület megvalósítása
- A Golang naplóinak központosítása
- A Golang-naplók nyomon követése mikroszolgáltatásokon keresztül
- Tiszta és átfogó Golang naplók
Tudja meg a naplózási csomagját
A Go rengeteg lehetőséget ad a naplózási csomag kiválasztásakor, és ezek közül az alábbiakban néhányat megvizsgálunk. Míg az általunk tárgyalt könyvtárak közül a logrus a legnépszerűbb, és segít egy konzisztens naplózási formátum megvalósításában, a többinek speciális felhasználási esetei vannak, amelyeket érdemes megemlíteni. Ez a rész a log, a logrus és a glog könyvtárakat fogja áttekinteni.
A log használata az egyszerűség kedvéért
Golang beépített naplózási könyvtára, a log
, egy alapértelmezett naplózót tartalmaz, amely a standard hibaüzenetbe ír és időbélyeget ad hozzá, konfiguráció nélkül. Ezeket a nyers és kész naplókat helyi fejlesztéshez használhatjuk, amikor a gyors visszajelzés a kódról fontosabb lehet, mint a gazdag, strukturált naplók generálása.
Meghatározhatunk például egy olyan osztásfüggvényt, amely a programból való kilépés helyett hibát küld vissza a hívónak, amikor nullával próbál osztani.
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)}
Mert példánk nullával oszt, a következő naplóüzenetet fogja kiadni:
2019/01/31 11:48:00 can't divide by zero
A logrus használata formázott naplókhoz
A Golang naplók írásához a logrus használatát javasoljuk, amely egy strukturált naplózásra tervezett naplócsomag, amely jól használható JSON-ban történő naplózásra. A JSON formátum lehetővé teszi, hogy a gépek könnyen elemezzék a Golang naplóit. És mivel a JSON egy jól definiált szabvány, egyszerűvé teszi a kontextus hozzáadását új mezők hozzáadásával – egy elemzőnek automatikusan fel kell tudnia venni őket.
A logrus használatával a WithFields
függvény használatával definiálhatja a JSON-naplókhoz hozzáadandó szabványos mezőket, ahogy az alább látható. Ezután különböző szinteken hívhatja a naplózót, például Info()
, Warn()
és Error()
. A logrus könyvtár automatikusan megírja a naplót JSON-ként, és beilleszti a szabványos mezőket, valamint az Ön által menet közben definiált mezőket.
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")}
A kapott napló tartalmazza az üzenetet, a naplószintet, az időbélyeget és a szabványos mezőket egy JSON objektumban:
{"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"}
Használja a glogot, ha aggódik a mennyiség miatt
Néhány naplókönyvtár lehetővé teszi, hogy bizonyos szinteken engedélyezze vagy letiltsa a naplózást, ami hasznos, ha a fejlesztés és a termelés közötti váltáskor kordában akarja tartani a napló mennyiségét. Az egyik ilyen könyvtár a glog
, amely lehetővé teszi, hogy a parancssorban zászlókkal (pl. -v
a szóbeliséghez) állítsuk be a naplózási szintet a kód futtatásakor. Ezután egy V()
függvényt használhatsz a if
utasításokban, hogy a Golang naplózását csak egy bizonyos naplózási szinten írd meg.
Például a glog segítségével megírhatod ugyanazt a “Can’t divide by zero” hibát, mint korábban, de csak akkor, ha a naplózás a 2
verbozitási szinten történik. A szóbeliséget bármilyen előjeles 32 bites egész számra beállíthatja, vagy a Info()
, Warning()
, Error()
és Fatal()
függvények segítségével a 0
-től 3
-ig (illetve)
if err != nil && glog.V(2){ glog.Warning(err) }
Azzal teheti kevésbé erőforrás-igényessé az alkalmazást, hogy termelésben csak bizonyos szinteket naplóz. Ugyanakkor, ha nincs hatása a felhasználókra, gyakran jó ötlet, ha minél több interakciót naplózol az alkalmazásoddal, majd a Datadoghoz hasonló naplókezelő szoftverekkel megtalálod a vizsgálathoz szükséges adatokat
A Golang naplók írásának és tárolásának legjobb gyakorlatai
Ha már kiválasztottál egy naplókönyvtárat, azt is meg kell tervezned, hogy a kódodban hol fogod meghívni a naplózót, hogyan fogod tárolni a naplókat, és hogyan fogod értelmezni őket. Ebben a szakaszban a Golang-naplók rendszerezéséhez egy sor legjobb gyakorlatot ajánlunk:
- A naplózót a fő alkalmazási folyamaton belül hívja meg, ne a goroutine-okon belül.
- Az alkalmazás naplóit helyi fájlba írja, még akkor is, ha később egy központi platformra küldi őket.
- Szabványosítsa a naplóit egy sor előre definiált üzenettel.
- Küldje a naplóit egy központi platformra, hogy elemezni és összesíteni tudja őket.
- Használjon HTTP-fejléceket és egyedi azonosítókat a felhasználói viselkedés naplózásához a mikroszolgáltatások között.
Kerülje a goroutine-ok deklarálását a naplózáshoz
Két oka van annak, hogy ne hozzon létre saját goroutine-okat a naplók írásához. Először is, ez párhuzamossági problémákhoz vezethet, mivel a naplózó duplikátumai megpróbálnának hozzáférni ugyanahhoz a io.Writer
hoz. Másodszor, a naplókönyvtárak általában maguk indítanak goroutine-okat, amelyek az esetleges párhuzamossági problémákat belsőleg kezelik, és a saját goroutine-ok indítása csak zavarja őket.
Fájlba írja a naplóit
Még ha a naplóit egy központi platformra küldi is, azt javasoljuk, hogy először a helyi gépén lévő fájlba írja őket. Biztosítani szeretné, hogy a naplói mindig elérhetőek legyenek helyben, és ne vesszenek el a hálózatban. Ráadásul a fájlba írás azt jelenti, hogy szétválaszthatja a naplók írásának feladatát a központi platformra való küldés feladatától. Maguknak az alkalmazásoknak nem kell kapcsolatot létrehozniuk vagy a naplóit streamelniük, és ezeket a feladatokat olyan speciális szoftverekre bízhatja, mint a Datadog Agent. Ha Go alkalmazásait olyan konténeres infrastruktúrában futtatja, amely még nem tartalmaz tartós tárolót – például az AWS Fargate-en futó konténereket -, akkor érdemes a naplókezelő eszközét úgy konfigurálni, hogy a naplókat közvetlenül a konténerek STDOUT és STDERR adatfolyamaiból gyűjtse (ezt a Docker és a Kubernetes másképp kezeli).
Szabványos naplózási felület megvalósítása
A kódjukból a naplókhoz való hívások írása során a csapatok gyakran különböző attribútumneveket használnak ugyanazon dolog leírására. Az ellentmondásos attribútumok összezavarhatják a felhasználókat, és lehetetlenné teszik az olyan naplók korrelációját, amelyeknek ugyanannak a képnek a részét kellene képezniük. Például két fejlesztő különböző módon naplózhatja ugyanazt a hibát, a hiányzó ügyfélnevet egy feltöltés kezelésénél.
A szabványosítás kikényszerítésének jó módja, ha létrehoz egy interfészt az alkalmazáskód és a naplókönyvtár között. Az interfész előre definiált naplóüzeneteket tartalmaz, amelyek egy bizonyos formátumot valósítanak meg, megkönnyítve a problémák kivizsgálását azáltal, hogy a naplóüzenetek kereshetők, csoportosíthatók és szűrhetők.
Ebben a példában egy Event
típust deklarálunk egy előre definiált üzenettel. Ezután Event
üzeneteket fogunk használni egy logger meghívására. A csapattagok úgy írhatnak Golang-naplókat, hogy minimális mennyiségű egyéni információt adnak meg, és az alkalmazásra bízzák a szabványos formátum megvalósításának munkáját.
Először is írunk egy logwrapper
csomagot, amelyet a fejlesztők beépíthetnek a kódjukba.
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)}
A naplózási felületünk használatához csak be kell építenünk a kódunkba, és meg kell hívnunk egy StandardLogger
példányt.
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")}
Amikor futtatjuk a kódunkat, a következő JSON naplót kapjuk:
{"level":"error","msg":"Invalid value for argument: client: nil","time":"2019-03-04T11:21:07-05:00"}
A Golang naplóinak központosítása
Ha az alkalmazásunkat több host fürtön keresztül telepítjük, nem fenntartható, hogy mindegyikbe SSH-n keresztül lépjünk be, hogy taileljük, grepeljük és vizsgáljuk a naplóinkat. Skalálhatóbb alternatíva, ha a naplófájlokat a helyi fájlokból egy központi platformra továbbítja.
Az egyik megoldás a Golang syslog csomag használata, amellyel az egész infrastruktúrából származó naplófájlokat egyetlen syslog-kiszolgálóra továbbítja.
A Golang naplókezelését a Datadoggal testre szabhatja és egyszerűsítheti.
A másik megoldás egy naplókezelő megoldás használata. A Datadog például képes követni a naplófájljait, és továbbítani a naplófájlokat egy központi platformra feldolgozás és elemzés céljából.
Attribútumok segítségével csoportosítással ábrázolhatja bizonyos naplómezők értékeit az időben. Például nyomon követheti a hibák számát service
szerint, hogy értesüljön arról, ha valamelyik szolgáltatásában incidens történt. Ha csak a go-logging-demo
szolgáltatás naplóit mutatjuk, láthatjuk, hogy ez a szolgáltatás hány hibanaplót produkált egy adott intervallumban.
Attribútumok segítségével a lehetséges okokat is feltárhatja, például megnézheti, hogy a hibanaplók kiugrása egy adott állomáshoz tartozik-e. Ezután a naplók értékei alapján automatikus riasztást hozhat létre.
A Golang-naplók nyomon követése mikroszolgáltatásokon keresztül
Hiba elhárításakor gyakran hasznos látni, hogy milyen viselkedési minta vezetett a hibához, még akkor is, ha ez a viselkedés több mikroszolgáltatást érint. Ezt az elosztott nyomkövetéssel érheti el, megjelenítve azt a sorrendet, amelyben az alkalmazás függvényeket, adatbázis-lekérdezéseket és egyéb feladatokat hajt végre, és követve ezeket a végrehajtási lépéseket, ahogyan azok egy hálózaton keresztül haladnak. Az elosztott nyomkövetés megvalósításának egyik módja a naplókon belül a kontextuális információk HTTP-fejlécek formájában történő átadása.
Ebben a példában az egyik mikroszolgáltatás fogad egy kérést, és ellenőrzi, hogy a x-trace
fejlécben van-e nyomkövetési azonosító, és generál egyet, ha nem létezik. Amikor egy másik mikroszolgáltatáshoz intézünk egy kérést, akkor egy új spanID-t generálunk – erre és minden kérésre -, és hozzáadjuk a x-span
fejléchez.
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)}
A bejövő kérések x-span
fejlécét a bejövő kérések x-span
fejlécében adjuk meg az általuk generált spanok szüleit, és ezt az információt a x-parent
fejléc formájában elküldjük a láncban következő mikroszolgáltatásnak.
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))}
Ha valamelyik mikroszolgáltatásunkban hiba lép fel, a trace
, parent
és span
attribútumok segítségével láthatjuk, hogy egy kérés milyen útvonalon haladt, így megtudhatjuk, hogy mely hosztokat – és esetleg az alkalmazás kódjának mely részeit – kell megvizsgálnunk.
Az első mikroszolgáltatásban:
{"appname":"go-logging","level":"debug","msg":"Hello from Microservice One","trace":"eUBrVfdw","time":"2017-03-02T15:29:26+01:00","span":"UzWHRihF"}
A másodikban:
{"appname":"go-logging","level":"debug","msg":"Hello from Microservice Two","parent":"UzWHRihF","trace":"eUBrVfdw","time":"2017-03-02T15:29:26+01:00","span":"DPRHBMuE"}
Ha mélyebben bele akarunk ásni a Golang nyomkövetési lehetőségeibe, használhatunk egy olyan nyomkövetési könyvtárat, mint az OpenTracing, vagy egy olyan felügyeleti platformot, amely támogatja a Go alkalmazások elosztott nyomkövetését. A Datadog például a Golang nyomkövetési könyvtár adataiból automatikusan képes a szolgáltatások térképét létrehozni; a nyomkövetések időbeli trendjeit vizualizálni; és tájékoztatni a szokatlan kérésszámú, hibaarányú vagy késleltetési idejű szolgáltatásokról.
Tiszta és átfogó Golang naplók
Ebben a bejegyzésben több Go naplókönyvtár előnyeit és kompromisszumait emeltük ki. Javasoltunk olyan módszereket is, amelyekkel biztosíthatja, hogy a naplói elérhetőek és hozzáférhetőek legyenek, amikor szüksége van rájuk, és hogy a bennük található információk konzisztensek és könnyen elemezhetőek legyenek.
Az összes Go naplójának Datadoggal történő elemzéséhez regisztráljon egy ingyenes próbaverzióra.