10 Tips For Riktig Søknad Logging

Vår nyeste jcp partner, Tomasz Nurkiewicz, har sendt inn en rekke innlegg som beskriver de grunnleggende prinsippene for riktig søknad logging. Jeg fant dem ganske interessante, og derfor bestemte jeg meg for å samle dem i et mer kompakt format og presentere dem til deg. Så, her er hans forslag til rene og nyttige logger: (MERK: De opprinnelige innleggene har blitt litt redigert for å forbedre lesbarheten)

1) Bruk de riktige verktøyene for jobben

Mange programmerere ser Ut til å glemme hvor viktig det er å logge programmets oppførsel og nåværende aktivitet. Når noen setter:

log.info("Happy and carefree logging");

heldigvis et sted i koden, innser han sannsynligvis ikke betydningen av applikasjonslogger under vedlikehold, tuning og feilidentifikasjon. Undervurdering av verdien av gode logger er en forferdelig feil.

ETTER MIN mening ER SLF4J den beste logging API tilgjengelig, hovedsakelig på grunn av en flott mønstererstatning støtte:

log.debug("Found {} records matching filter: '{}'", records, filter); 

I Log4j må du bruke:

log.debug("Found " + records + " records matching filter: '" + filter + "'");

Dette er ikke bare lengre og mindre lesbart, men også ineffektivt på grunn av utstrakt bruk av strengkjeding. SLF4J legger til en fin {} substitusjonsfunksjon. Også fordi streng sammenkobling unngås og toString () ikke kalles hvis logging-setningen er filtrert, er det ikke behov for isDebugEnabled() lenger. BTW, har du lagt merke til enkle sitater rundt filterstrengparameter?

SLF4J er bare a faç. Som en implementering vil jeg anbefale Logback framework, allerede annonsert, i stedet for den veletablerte Log4J. Den har mange interessante funksjoner og, i motsetning Til Log4J, er aktivt utviklet.

Det siste verktøyet å anbefale Er Perf4J. for å sitere deres motto:

Perf4J er Å System.currentTimeMillis () som log4j er Til System.ut.println ()

Jeg har lagt Perf4J til en eksisterende applikasjon under tung belastning og sett den i aksjon i få andre. Både administratorer og forretningsbrukere ble imponert av de fine grafer produsert av dette enkle verktøyet. Også vi var i stand til å oppdage ytelsesfeil på kort tid. Perf4J selv fortjener sin egen artikkel, men for nå bare sjekk Deres Utviklerguide.

vær Også oppmerksom På At Ceki Gü (grunnlegger Av Log4J, SLF4J og Logback-prosjektene) foreslo en enkel tilnærming for å kvitte seg med commons-logging avhengighet (se hans kommentar).

2) ikke glem, loggingsnivåer er der for deg

Hver gang du lager en logging-setning, tror du hardt hvilket loggingsnivå som passer for denne typen hendelse, ikke sant? Somehow 90% av programmerere aldri ta hensyn til logging nivåer, bare logge alt på samme nivå, typisk INFO eller DEBUG. Hvorfor? Logging rammer har to store fordeler Over Systemet.ut. dvs. kategorier og nivåer. Begge lar deg selektivt filtrere logging setninger permanent eller bare for diagnostikk tid. Hvis du virkelig ikke kan se forskjellen, skriv ut denne tabellen og se på den hver gang du begynner å skrive «logg.»I DIN IDE:

FEIL-noe fryktelig galt hadde skjedd, som må undersøkes umiddelbart. Ingen system kan tolerere elementer logget på dette nivået. Eksempel: NPE, database utilgjengelig, virksomhetskritisk brukstilfelle kan ikke videreføres.

WARN-prosessen kan fortsette, men vær ekstra forsiktig. Egentlig ønsket jeg alltid å ha to nivåer her: en for åpenbare problemer der arbeid eksisterer (for eksempel: «Nåværende data utilgjengelig, ved hjelp av bufrede verdier») og andre (navn det: OPPMERKSOMHET) for potensielle problemer og forslag. Eksempel: «Program som kjører i utviklingsmodus» eller «Administrasjonskonsoll er ikke sikret med et passord». Søknaden kan tolerere advarsler, men de bør alltid være berettiget og undersøkt.

INFO-Viktig forretningsprosess er ferdig. I ideell verden bør administrator eller avansert bruker kunne forstå INFOMELDINGER og raskt finne ut hva programmet gjør. For eksempel hvis en søknad handler om å bestille flybilletter, bør det bare være EN INFO-setning per hver billett som sier «bestilt billett fra til». Annen definisjon AV INFO melding: hver handling som endrer tilstanden til programmet betydelig (database oppdatering, ekstern systemforespørsel).

DEBUG – Utviklere ting. Jeg vil diskutere senere hva slags informasjon fortjener å bli logget.

TRACE – svært detaljert informasjon, kun beregnet for utvikling. Du kan beholde sporingsmeldinger i en kort periode etter distribusjon på produksjonsmiljøet, men behandle disse loggerklæringene som midlertidige, som bør eller kan slås av til slutt. Sondringen MELLOM DEBUG og TRACE er det vanskeligste, men hvis du setter logging-setningen og fjerner den etter at funksjonen er utviklet og testet, bør den nok være PÅ SPORNIVÅ.

listen ovenfor er bare et forslag, du kan lage ditt eget sett med instruksjoner å følge, men det er viktig å ha noen. Min erfaring er at alt alltid logges uten filtrering (i hvert fall fra programkoden), men å ha muligheten til raskt å filtrere logger og trekke ut informasjonen med riktig detaljnivå, kan være en livredder.

det siste som er verdt å nevne er infamous is * Enabled () – tilstanden. Noen legger det før hver logging uttalelse:

if(log.isDebugEnabled()) log.debug("Place for your commercial");

Personlig finner jeg dette idiomet bare rot som bør unngås. Ytelsesforbedringen (spesielt ved BRUK AV SLF4J mønsterutskifting diskutert tidligere) virker irrelevant og lukter som en for tidlig optimalisering. Også, kan du få øye på duplisering? Det er svært sjeldne tilfeller når du har eksplisitt tilstand er berettiget – når vi kan bevise at bygge logging melding er dyrt. I andre situasjoner, bare gjør jobben din med å logge og la logging framework gjøre jobben sin (filtrering).

3) vet du hva du logger?

hver gang du utsteder en logging uttalelse, ta et øyeblikk og ta en titt på hva som vil lande i loggfilen. Les loggene etterpå og spot misdannede setninger. Først av Alt, unngå NPEs som dette:

log.debug("Processing request with id: {}", request.getId());

er du helt sikker på at forespørselen ikke er null her?

En annen fallgruve er logging samlinger. Hvis du hentet samling av domeneobjekter fra databasen ved Hjelp Av Hibernate og uforsiktig logge dem som her:

log.debug("Returning users: {}", users);

SLF4J vil ringe toString () bare nar setningen faktisk skrives ut, noe som er ganske fint. Men hvis det gjør det… ut av minnefeil, N+1 velg problem, tråd sult (logging er synkron!), lat initialisering unntak, logger lagring fylt helt-hver av disse kan forekomme.

det er en mye bedre ide å logge for eksempel bare id-er for domeneobjekter (eller til og med bare størrelsen på samlingen). Men å lage en samling av ider når du har en samling objekter som har getId () – metoden, er utrolig vanskelig og tungvint I Java. Groovy har en stor spredning operatør (brukere*. id), I Java kan vi etterligne den ved Hjelp Av Commons Beanutils bibliotek:

log.debug("Returning user ids: {}", collect(users, "id"));

hvor collect () – metoden kan implementeres som følger:

public static Collection collect(Collection collection, String propertyName) { return CollectionUtils.collect(collection, new BeanToPropertyValueTransformer(propertyName));}

den siste tingen å nevne er feil implementering eller bruk av toString (). Opprett først toString() for hver klasse som vises hvor som helst i logging-setninger, helst ved Hjelp Av ToStringBuilder (men ikke reflekterende motpart). For det andre, se opp for arrays og ikke-typiske samlinger. Arrays og noen merkelige samlinger har kanskje ikke toString () implementert calling toString () av hvert element. Bruk Arrays #deepToString jdk verktøyet metoden. Og les loggene dine ofte for å få øye på feilformaterte meldinger.

4) Unngå bivirkninger

Logging uttalelser skal ha ingen eller liten innvirkning på programmets atferd. Nylig ga en venn av meg et eksempel på et system som kastet Hibernates’ LazyInitializationException bare når du kjører på et bestemt miljø. Som du sikkert har gjettet fra konteksten, forårsaket noen logging setning lat initialisert samling lastes når økten ble vedlagt. På dette miljøet ble loggingsnivåene økt, og samlingen ble ikke lenger initialisert. Tenk hvor lang tid vil det ta deg å finne en feil uten å vite denne konteksten?

En annen bivirkning reduserer applikasjonen. Raskt svar: hvis du logger for mye eller feilaktig bruker toString() og/eller strengkjeding, har logging en ytelsesbivirkning. Hvor stor? Vel, jeg har sett serveren starte på nytt hvert 15. minutt på grunn av en tråd sult forårsaket av overdreven logging. Nå er dette en bivirkning! Fra min erfaring er få hundre MiB trolig den øvre grensen for hvor mye du kan logge på disk per time.

selvfølgelig hvis logging setningen selv mislykkes og forårsaker forretningsprosessen å avslutte på grunn av unntak, dette er også en stor bivirkning. Jeg har sett en slik konstruksjon for å unngå dette:

try { log.trace("Id=" + request.getUser().getId() + " accesses " + manager.getPage().getUrl().toString())} catch(NullPointerException e) {}

Dette er en ekte kode, men vær så snill å gjøre verden litt bedre sted og ikke gjør det, noensinne.

5) Vær kortfattet og beskrivende

hver logging setning bør inneholde både data og beskrivelse. Vurder følgende eksempler:

log.debug("Message processed");log.debug(message.getJMSMessageID());log.debug("Message with id '{}' processed", message.getJMSMessageID());

Hvilken logg vil du se mens du diagnostiserer feil i et ukjent program? Tro meg, alle eksemplene ovenfor er nesten like vanlige. Et annet anti-mønster:

if(message instanceof TextMessage) //...else log.warn("Unknown message type");

Var det så vanskelig å inkludere deg selve meldingstype, melding id, etc. i advarselsstrengen? Jeg vet at noe gikk galt, men hva? Hva var konteksten?

et tredje anti-mønster er «magic-log». Real life eksempel: de fleste programmerere i teamet visste at 3 ampersands etterfulgt av utropstegn, etterfulgt av hash, etterfulgt av pseudorandom alfanumerisk strenglogg betyr «Melding med XYZ id mottatt». Ingen gidder å endre loggen, bare noen treffer tastaturet og valgte noen unike «&&&!# «string, slik at den lett kan bli funnet av seg selv.

som en konsekvens ser hele loggfilen ut som en tilfeldig sekvens av tegn. Noen kan til og med vurdere at filen er et gyldig Perl-program. I stedet bør en loggfil være lesbar, ren og beskrivende. Ikke bruk magiske tall, loggverdier, tall, ider og inkludere deres kontekst. Vis dataene som behandles og vis dens betydning. Vis hva programmet faktisk gjør. Gode logger kan tjene som en god dokumentasjon av programkoden selv.

nevnte jeg ikke å logge passord og personlig informasjon? Ikke!

6) Tune ditt mønster

Logging mønster er et fantastisk verktøy, som transparent legger en meningsfull kontekst til hver logging uttalelse du gjør. Men du må vurdere veldig nøye hvilken informasjon som skal inkluderes i mønsteret ditt. For eksempel logging dato når loggene rulle hver time er meningsløst som datoen er allerede inkludert i loggfilnavnet. Tvert imot, uten å logge trådnavnet ville du ikke kunne spore noen prosess ved hjelp av logger når to tråder fungerer samtidig-loggene vil overlappe. Dette kan være bra i single-threaded applikasjoner – som er nesten døde i dag.

fra min erfaring bør det ideelle loggingsmønsteret inkludere (selvfølgelig unntatt den loggede meldingen selv): nåværende tid (uten dato, millisekunders presisjon), loggingsnivå, navn på tråden, enkelt loggernavn (ikke fullt kvalifisert) og meldingen. I Logback er det noe som:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} %-5level %m%n</pattern> </encoder></appender>

du bør aldri inkludere filnavn, klassenavn og linjenummer, selv om det er veldig fristende. Jeg har selv sett tomme loggerklæringer utstedt fra koden:

log.info("");

fordi programmereren antok at linjenummeret vil være en del av loggingsmønsteret, og han visste at «hvis tom loggingsmelding vises i 67. linje i filen (i autentisering () metode), betyr det at brukeren er autentisert». Dessuten logging klasse navn, metode navn og / eller linjenummer har en alvorlig ytelse innvirkning.

en noe mer avansert funksjon i en logging rammeverk er begrepet Kartlagt Diagnostisk Kontekst. MDC er rett og slett et kart forvaltes på en tråd-lokal basis. Du kan sette noen nøkkel-verdi par i dette kartet, og siden da hver logging uttalelse utstedt fra denne tråden kommer til å ha denne verdien knyttet som en del av mønsteret.

7) Loggmetodeargumenter og returverdier

når du finner en feil under utviklingen, kjører du vanligvis en debugger som prøver å spore den potensielle årsaken. Forestill deg en stund at du ikke kan bruke en debugger. For eksempel, fordi feilen manifesterte seg på et kundemiljø for noen dager siden, og alt du har er logger. Vil du kunne finne noe i dem?

hvis du følger den enkle regelen for å logge hver metode inngang og utgang (argumenter og returverdier), trenger du ikke engang en debugger lenger. Selvfølgelig må du være rimelig, men hver metode som: tilgang til eksternt system (inkludert database), blokker, venter, etc. bør vurderes. Bare følg dette mønsteret:

public String printDocument(Document doc, Mode mode) { log.debug("Entering printDocument(doc={}, mode={})", doc, mode); String id = //Lengthy printing operation log.debug("Leaving printDocument(): {}", id); return id;}

Fordi du logger både begynnelsen og slutten av metoden påkalling, kan du manuelt oppdage ineffektiv kode og selv oppdage mulige årsaker til vranglås og sult – bare ved å se etter «inn» uten tilsvarende «forlate». Hvis metodene dine har meningsfulle navn, vil det være en glede å lese logger. Også å analysere hva som gikk galt er mye enklere, siden du på hvert trinn vet nøyaktig hva som har blitt behandlet. Du kan til og med bruke et enkelt aop-aspekt for å logge et bredt spekter av metoder i koden din. Dette reduserer kode duplisering, men vær forsiktig, siden det kan føre til enorme mengder store logger.

DU bør vurdere FEILSØKINGS-eller SPORINGSNIVÅER som best egnet for disse loggene. Og hvis du oppdager noen metode kalles for ofte, og logging kan skade ytelsen, bare redusere loggingsnivået for den klassen eller fjern loggen helt (kanskje forlater bare en for hele metoden påkalling?) Men det er alltid bedre å ha for mye heller enn for få logging uttalelser. Behandle logging uttalelser med samme respekt som enhetstester-koden din skal dekkes med loggingsrutiner som det er med enhetstester. Ingen del av systemet skal forbli uten logger i det hele tatt. Husk, noen ganger observere logger rullende av er den eneste måten å fortelle om programmet fungerer eller henger alltid.

8) Se opp for eksterne systemer

Dette er det spesielle tilfellet med det forrige tipset: hvis du kommuniserer med et eksternt system, bør du vurdere å logge alle data som kommer ut fra søknaden din og kommer inn. Periode. Integrering er en tøff jobb, og det er spesielt vanskelig å diagnostisere problemer mellom to applikasjoner (tenk to forskjellige leverandører, miljøer, teknologistabler og lag). Nylig har vi for eksempel oppdaget at logging av hele meldingsinnhold, inkludert SOAP og HTTP-overskrifter I Apache cxf web services, er ekstremt nyttig under integrasjon og systemtesting.

Dette er en stor overhead, og hvis ytelsen er et problem, kan du alltid deaktivere logging. Men hva er poenget med å ha en rask, men ødelagt applikasjon, som ingen kan fikse? Vær ekstra forsiktig når du integrerer med eksterne systemer og forbered deg på å betale den kostnaden. Hvis du er heldig og all integrasjon håndteres AV EN ESB, er bussen sannsynligvis det beste stedet å logge hver innkommende forespørsel og svar. Se For Eksempel Muldyr ‘ log-komponent.

noen ganger gjør mengden informasjon utvekslet med eksterne systemer det uakseptabelt å logge alt. På den annen side under testing og for en kort periode på produksjon (for eksempel når noe galt skjer), vil vi gjerne ha alt lagret i logger og er klare til å betale ytelseskostnad. Dette kan oppnås ved å nøye bruke loggingsnivåer. Bare ta en titt på følgende idiom:

Collection<Integer> requestIds = //...if(log.isDebugEnabled()) log.debug("Processing ids: {}", requestIds);else log.info("Processing ids size: {}", requestIds.size());

Hvis denne bestemte loggeren er konfigurert til å logge FEILSØKINGSMELDINGER, vil den skrive ut hele requestids samling innholdet. Men hvis DEN er konfigurert til å skrive UT INFORMASJONSMELDINGER, vil bare størrelsen på samlingen bli sendt ut. Hvis du lurer på hvorfor jeg glemte isInfoEnabled () tilstand, gå tilbake til tips #2. En ting verdt å nevne er at requestIds samling ikke bør være null i dette tilfellet. Selv om DET vil bli logget riktig som null hvis DEBUG er aktivert, men big fat NullPointerException vil bli kastet hvis logger er konfigurert TIL INFO. Husk min leksjon om bivirkninger i tips #4?

9) Logg unntak riktig

først av Alt, unngå å logge unntak, la rammen eller beholderen (uansett hva det er) gjøre det for deg. Det er ett, ekhem, unntak fra denne regelen: hvis du kaster unntak fra noen ekstern tjeneste(RMI, EJB remote session bean, etc.), som er i stand til å serialisere unntak, sørg for at alle er tilgjengelige for klienten(er en DEL AV API). Ellers vil klienten motta NoClassDefFoundError: SomeFancyException i stedet for den» sanne » feilen.

Logging unntak er en av de viktigste rollene for logging i det hele tatt, men mange programmerere har en tendens til å behandle logging som en måte å håndtere unntaket. De returnerer noen ganger standardverdien (vanligvis null, 0 eller tom streng) og late som ingenting har skjedd. Andre ganger logger de først unntaket og deretter pakker det og kaster det tilbake:

log.error("IO exception", e);throw new MyCustomException(e);

denne konstruksjonen vil nesten alltid skrive ut samme stakkspor to ganger, fordi noe til slutt vil fange MyCustomException og logge årsaken. Logg, eller vikle og kaste tilbake (som er å foretrekke), aldri begge, ellers loggene vil være forvirrende.

Men HVIS vi virkelig vil logge unntaket? Av en eller annen grunn (fordi vi ikke leser Apier og dokumentasjon?), omtrent halvparten av logging uttalelser jeg ser er feil. Quick quiz, hvilke av de følgende loggen uttalelser vil logge NPE riktig?

try { Integer x = null; ++x;} catch (Exception e) { log.error(e); //A log.error(e, e); //B log.error("" + e); //C log.error(e.toString()); //D log.error(e.getMessage()); //E log.error(null, e); //F log.error("", e); //G log.error("{}", e); //H log.error("{}", e.getMessage()); //I log.error("Error reading configuration file: " + e); //J log.error("Error reading configuration file: " + e.getMessage()); //K log.error("Error reading configuration file", e); //L}

Overraskende er Bare G og helst L riktig! A og B ikke engang kompilere I SLF4J, andre forkaste stakk spor og / eller skrive upassende meldinger. For Eksempel vil E ikke skrive ut noe SOM NPE vanligvis ikke gir noen unntaksmelding, og stakksporingen vil ikke bli skrevet ut også. Husk at det første argumentet alltid er tekstmeldingen, skriv noe om problemets natur. Ikke ta med unntaksmelding, som det vil bli skrevet ut automatisk etter loggen setningen, foregående stakk spor. Men for å gjøre det må du passere unntaket selv som det andre argumentet.

10) Logger lett å lese, lett å analysere

det er to grupper av mottakere som er spesielt interessert i programloggene dine: mennesker (du kan være uenig, men programmerere tilhører også denne gruppen) og datamaskiner (vanligvis skallskript skrevet av systemadministratorer). Logger bør være egnet for begge disse gruppene. Hvis noen ser fra bak ryggen din på programloggene dine ser (kilde Wikipedia):

da har du sannsynligvis ikke fulgt mine tips. Logger skal være lesbare og enkle å forstå akkurat som koden skal.

på den annen side, hvis søknaden din produserer halv GB logger hver time, vil ingen mann og ingen grafisk tekstredigerer noensinne klare å lese dem helt. Det er her old-school grep, sed og awk kommer til nytte. Hvis det er mulig, prøv å skrive loggmeldinger på en slik måte at de kan forstås både av mennesker og datamaskiner, f. eks. unngå formatering av tall, bruk mønstre som lett kan gjenkjennes av vanlige uttrykk, etc. Hvis det ikke er mulig, skriv ut dataene i to formater:

log.debug("Request TTL set to: {} ({})", new Date(ttl), ttl);// Request TTL set to: Wed Apr 28 20:14:12 CEST 2010 (1272478452437)final String duration = DurationFormatUtils.formatDurationWords(durationMillis, true, true);log.info("Importing took: {}ms ({})", durationMillis, duration);//Importing took: 123456789ms (1 day 10 hours 17 minutes 36 seconds)

Datamaskiner vil sette pris på «ms etter 1970 epoch» tidsformat, mens folk ville være glade for å se» 1 dag 10 timer 17 minutter 36 sekunder » tekst. BTW ta en titt På DurationFormatUtils, fint verktøy.

Det er alle gutta, en «logging tips extravaganza» Fra VÅR jcp partner, Tomasz Nurkiewicz. Ikke glem å dele!

Relaterte Artikler :
  • Logging Antipatterns
  • Ting Hver Programmerer Bør Vite
  • Lover Programvare Design
  • Java Beste Praksis – Vektor vs ArrayList vs HashSet
  • Java Beste Praksis – DateFormat I En Multithreading Miljø
  • Java Beste Praksis – Høy ytelse Serialisering
  • beste praksis for java – strengytelse Og Nøyaktig Strengmatching
Relaterte Utdrag :
  • Opprett egendefinert Formatter for Loggerhåndterer
  • Loggmetodekall
  • Angi filter På Loggerhåndterer
  • Angi Formatter for Loggerhåndterer
  • Eksempel På Logging av aspekt
  • log4j Maven eksempel
  • hibernate logging konfigurasjon – slf4j + log4j og logback