Flogger Flydende logning
1. Oversigt
I denne vejledning skal vi tale om Flogger-rammen, en flydende logning-API til Java designet af Google.
2. Hvorfor bruge Flogger?
Med alle de logningsrammer, der i øjeblikket findes på markedet, som Log4j og Logback, hvorfor har vi brug for endnu en logningsramme?
Det viser sig, at Flogger har flere fordele i forhold til andre rammer - lad os tage et kig.
2.1. Læsbarhed
Den flydende karakter af Flogger's API går langt for at gøre det mere læsbart.
Lad os se på et eksempel, hvor vi vil logge en besked hver tiende iteration.
Med en traditionel logningsramme ville vi se noget som:
int i = 0; // ... if (i% 10 == 0) {logger.info ("Denne log viser hver 10. iteration"); i ++; }
Men nu med Flogger kan ovenstående forenkles til:
logger.atInfo (). hver (10) .log ("Denne log viser hver 10. iteration");
Mens man vil hævde, at Flogger-versionen af loggerudtalelsen ser lidt mere detaljeret ud end de traditionelle versioner, det tillader større funktionalitet og fører i sidste ende til mere læsbare og udtryksfulde logsætninger.
2.2. Ydeevne
Loggeobjekter er optimeret, så længe vi undgår at ringe toString på de loggede objekter:
Brugerbruger = ny bruger (); logger.atInfo (). log ("Brugeren er:% s", bruger);
Hvis vi logger, som vist ovenfor, har backend muligheden for at optimere logning. På den anden side, hvis vi ringer toString direkte, eller sammenkæd strengene, så er denne mulighed tabt:
logger.atInfo (). log ("Denne bruger er:% s", user.toString ()); logger.atInfo (). log ("Denne bruger er:% s" + bruger);
2.3. Udvidelse
Flogger-rammen dækker allerede det meste af den grundlæggende funktionalitet, som vi forventer af en logningsramme.
Der er dog tilfælde, hvor vi bliver nødt til at tilføje funktionaliteten. I disse tilfælde er det muligt at udvide API'en.
I øjeblikket kræver dette en separat støtteklasse. Vi kunne for eksempel udvide Flogger API ved at skrive en UserLogger klasse:
logger.at (INFO) .forUserId (id) .withUsername (username) .log ("Message:% s", param);
Dette kan være nyttigt i tilfælde, hvor vi vil formatere meddelelsen konsekvent. Det UserLogger ville derefter give implementeringen af de brugerdefinerede metoder forUserId (streng-id) og withUsername (String username).
At gøre dette, det UserLogger klassen bliver nødt til at udvide AbstractLogger klasse og give en implementering af API'en. Hvis vi ser på FluentLogger, det er bare en logger uden yderligere metoder, vi kan derfor start med at kopiere denne klasse som den er, og opbyg derefter fra dette fundament ved at tilføje metoder til den.
2.4. Effektivitet
Traditionelle rammer bruger i vid udstrækning varargs. Disse metoder kræver en ny Objekt[] tildeles og udfyldes, før metoden kan påberåbes. Derudover skal alle grundlæggende typer, der sendes ind, være automatisk markeret.
Alt dette koster ekstra bytekode og ventetid på opkaldsstedet. Det er særligt uheldigt hvis logerklæringen faktisk ikke er aktiveret. Omkostningerne bliver mere tydelige i debug-niveau-logfiler, der ofte vises i sløjfer. Flogger dækker disse omkostninger ved helt at undgå varargs.
Flogger omgår dette problem ved hjælp af en flydende opkaldskæde, hvorfra loggeopgørelser kan bygges. Dette gør det muligt for rammen kun at have et lille antal tilsidesættelser af log metode og dermed være i stand til at undgå ting som varargs og auto-boksning. Dette betyder, at API'en kan rumme en række nye funktioner uden en kombinatorisk eksplosion.
En typisk logningsramme ville have disse metoder:
niveau (String, Object) niveau (String, Object ...)
hvor niveau kan være et af cirka syv lognavne (alvorlig for eksempel) samt have en kanonisk logmetode, der accepterer et ekstra logniveau:
log (niveau, objekt ...)
Ud over dette er der normalt varianter af de metoder, der har en årsag (a Kan kastes forekomst), der er knyttet til logsætningen:
niveau (Throwable, String, Object) niveau (Throwable, String, Object ...)
Det er klart, at API'en kobler tre bekymringer til en metodeopkald:
- Det forsøger at specificere logniveauet (metodevalg)
- Forsøger at vedhæfte metadata til logerklæringen (Kan kastes årsag)
- Og også angive logbesked og argumenter.
Denne tilgang multiplicerer hurtigt antallet af forskellige logningsmetoder, der er nødvendige for at tilfredsstille disse uafhængige bekymringer.
Vi kan nu se, hvorfor det er vigtigt at have to metoder i kæden:
logger.atInfo (). withCause (e) .log ("Besked:% s", arg);
Lad os nu se på, hvordan vi kan bruge det i vores codebase.
3. Afhængigheder
Det er ret simpelt at konfigurere Flogger. Vi skal bare tilføje flogger og flogger-system-backend til vores pom:
com.google.flogger flogger 0.4 com.google.flogger flogger-system-backend 0.4 runtime
Med disse afhængigheder oprettet, kan vi nu gå på at udforske den API, der er til vores rådighed.
4. Udforskning af den flydende API
Lad os først erklære en statisk eksempel for vores logger:
privat statisk endelig FluentLogger logger = FluentLogger.forEnclosingClass ();
Og nu kan vi begynde at logge. Vi starter med noget simpelt:
int resultat = 45/3; logger.atInfo (). log ("Resultatet er% d", resultat);
Logbeskederne kan bruge en hvilken som helst Java printf formatspecifikatorer, såsom % s,% d eller % 016x.
4.1. Undgå arbejde på logsteder
Flogger-skabere anbefaler, at vi undgår at udføre arbejde på log-webstedet.
Lad os sige, at vi har følgende langvarige metode til opsummering af den aktuelle tilstand for en komponent:
offentlig statisk String collectSummaries () {longRunningProcess (); int-poster = 110; int s = 30; return String.format ("% d sekunder er forløbet indtil videre.% d varer afventer behandling", s, items); }
Det er fristende at ringe samleSummaries direkte i vores logopgørelse:
logger.atFine (). log ("stats =% s", collectSummaries ());
Uanset de konfigurerede logniveauer eller hastighedsbegrænsende er samleSummaries metode kaldes nu hver gang.
At gøre omkostningerne ved deaktiverede logafgørelser praktisk talt gratis er kernen i logningsrammen. Dette betyder igen, at flere af dem kan efterlades intakte i koden uden skade. At skrive logopgørelsen som vi netop gjorde fjerner denne fordel.
I stedet skal vi bruge LazyArgs.lazy metode:
logger.atFine (). log ("stats =% s", LazyArgs.lazy (() -> collectSummaries ()));
Nu udføres næsten intet arbejde på log-webstedet - bare instans oprettelse for lambda-udtrykket. Flogger evaluerer kun denne lambda, hvis den faktisk har til hensigt at logge meddelelsen.
Selv om det er tilladt at beskytte logopgørelser ved hjælp af isEnabled:
if (logger.atFine (). isEnabled ()) {logger.atFine (). log ("summaries =% s", collectSummaries ()); }
Dette er ikke nødvendigt, og vi bør undgå det, fordi Flogger foretager disse kontroller for os. Denne tilgang beskytter også kun logopgørelser efter niveau og hjælper ikke med satsbegrænsede logopgørelser.
4.2. Håndtering af undtagelser
Hvad med undtagelser, hvordan håndterer vi dem?
Flogger kommer med en medStackTrace metode, som vi kan bruge til at logge en Kan kastes eksempel:
prøv {int resultat = 45/0; } fange (RuntimeException re) {logger.atInfo (). medStackTrace (StackSize.FULL) .withCause (re) .log ("Message"); }
Hvor medStackTrace tager som argument StackSize enum med konstante værdier LITEN, MEDIUM, STOR eller FULD. Et stakspor genereret af withStackTrace () vises som en LogSiteStackTrace undtagelse i standard java.util.logging bagende. Andre backends kan dog vælge at håndtere dette anderledes.
4.3. Logningskonfiguration og niveauer
Indtil videre har vi brugt logger.atInfo i de fleste af vores eksempler, men Flogger understøtter mange andre niveauer. Vi ser på disse, men lad os først introducere, hvordan man konfigurerer logningsindstillingerne.
For at konfigurere logning bruger vi LoggerConfig klasse.
For eksempel når vi vil indstille logningsniveauet til BØDE:
LoggerConfig.of (logger) .setLevel (Level.FINE);
Og Flogger understøtter forskellige logningsniveauer:
logger.atInfo (). log ("Info Message"); logger.atWarning (). log ("Advarselsmeddelelse"); logger.atSevere (). log ("Alvorlig besked"); logger.atFine (). log ("Fin meddelelse"); logger.atFiner (). log ("Finere meddelelse"); logger.atFinest (). log ("Finest Message"); logger.atConfig (). log ("Config Message");
4.4. Satsbegrænsning
Hvad med spørgsmålet om satsbegrænsning? Hvordan håndterer vi sagen, hvor vi ikke ønsker at logge enhver iteration?
Flogger kommer os til undsætning med hver (int n) metode:
IntStream.range (0, 100) .forEach (værdi -> {logger.atInfo (). Hver (40) .log ("Denne log viser hver 40 iterationer =>% d", værdi);});
Vi får følgende output, når vi kører ovenstående kode:
18. september 2019 17:04:02 com.baeldung.flogger.FloggerUnitTest lambda $ givenAnInterval_shouldLogAfterEveryTInterval $ 0 INFO: Denne log viser hver 40 iterationer => 0 [CONTEXT ratelimit_count = 40] 18. september 2019 17:04:02 com. baeldung.flogger.FloggerUnitTest lambda $ givenAnInterval_shouldLogAfterEveryTInterval $ 0 INFO: Denne log viser hver 40 iterationer => 40 [CONTEXT ratelimit_count = 40] 18. september 2019 17:04:02 com.baeldung.flogger.FloggerUnitTestInvalaTalInvalTalInAverTalInvalTalInAverTalInAverTalInvalTal er givet log viser hver 40 iterationer => 80 [CONTEXT ratelimit_count = 40]
Hvad hvis vi vil logge sig hvert 10. sekund? Derefter, vi kan bruge atMostEvery (int n, TimeUnit enhed):
IntStream.range (0, 1_000_0000) .forEach (værdi -> {logger.atInfo (). AtMostEvery (10, TimeUnit.SECONDS) .log ("Denne log viser [hvert 10. sekund] =>% d", værdi); });
Med dette bliver resultatet nu:
18. september 2019 17:08:06 com.baeldung.flogger.FloggerUnitTest lambda $ givenATimeInterval_shouldLogAfterEveryTimeInterval $ 1 INFO: Denne log viser [hvert 10. sekund] => 0 [CONTEXT ratelimit_period = "10 SECONDS"] 18. september 2019 5:08 : 16 PM com.baeldung.flogger.FloggerUnitTest lambda $ givenATimeInterval_shouldLogAfterEveryTimeInterval $ 1 INFO: Denne log viser [hvert 10. sekund] => 3545373 [CONTEXT ratelimit_period = "10 SECONDS [springet over: 3545372]"] 18. september 2019 5:08 PM com.baeldung.flogger.FloggerUnitTest lambda $ givenATimeInterval_shouldLogAfterEveryTimeInterval $ 1 INFO: Denne log viser [hvert 10. sekund] => 7236301 [CONTEXT ratelimit_period = "10 SECONDS [sprunget over: 3690927]"]
5. Brug af Flogger med andre backends
Så hvad hvis vi gerne vil tilføj Flogger til vores eksisterende applikation, der allerede bruger sig Slf4j eller Log4j for eksempel? Dette kan være nyttigt i tilfælde, hvor vi ønsker at drage fordel af vores eksisterende konfigurationer. Flogger understøtter flere backends, som vi får se.
5.1 Flogger med Slf4j
Det er nemt at konfigurere en Slf4j-back-end. Først skal vi tilføje flogger-slf4j-backend afhængighed af vores pom:
com.google.flogger flogger-slf4j-backend 0.4
Dernæst er vi nødt til at fortælle Flogger, at vi gerne vil bruge en anden back-end fra den standard. Vi gør dette ved at registrere en Flogger-fabrik gennem systemegenskaber:
System.setProperty ("flogger.backend_factory", "com.google.common.flogger.backend.slf4j.Slf4jBackendFactory # getInstance");
Og nu bruger vores applikation den eksisterende konfiguration.
5.1 Flogger med Log4j
Vi følger lignende trin til konfiguration af Log4j-back-end. Lad os tilføje flogger-log4j-backend afhængighed af vores pom:
com.google.flogger flogger-log4j-backend 0.4 com.sun.jmx jmxri com.sun.jdmk jmxtools javax.jms jms log4j log4j 1.2.17 log4j apache-log4j-ekstras 1.2.17
Vi har også brug for at registrere en Flogger back-end fabrik til Log4j:
System.setProperty ("flogger.backend_factory", "com.google.common.flogger.backend.log4j.Log4jBackendFactory # getInstance");
Og det er det, vores applikation er nu indstillet til at bruge eksisterende Log4j-konfigurationer!
6. Konklusion
I denne vejledning har vi set, hvordan vi bruger Flogger-rammen som et alternativ til de traditionelle logningsrammer. Vi har set nogle kraftfulde funktioner, som vi kan drage fordel af, når vi bruger rammen.
Vi har også set, hvordan vi kan udnytte vores eksisterende konfigurationer ved at registrere forskellige bagende som Slf4j og Log4j.
Som normalt er kildekoden til denne vejledning tilgængelig på GitHub.