10 Tips för korrekt Applikationsloggning

vår senaste JCP-partner, Tomasz Nurkiewicz, har skickat in ett antal inlägg som beskriver de grundläggande principerna för korrekt applikationsloggning. Jag tyckte att de var ganska intressanta, så jag bestämde mig för att samla dem i ett mer kompakt format och presentera dem för dig. Så här är hans förslag på rena och hjälpsamma loggar: (notera: De ursprungliga inläggen har redigerats något för att förbättra läsbarheten)

1) Använd lämpliga verktyg för jobbet

många programmerare verkar glömma hur viktigt det är att logga en applikations beteende och dess nuvarande aktivitet. När någon sätter:

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

lyckligtvis någonstans i koden inser han förmodligen inte vikten av applikationsloggar under underhåll, inställning och felidentifiering. Att underskatta värdet av bra stockar är ett hemskt misstag.

enligt min mening är SLF4J det bästa loggnings-API som finns tillgängligt, främst på grund av ett bra stöd för mönsterbyte:

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

i Log4j måste du använda:

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

Detta är inte bara längre och mindre läsbart, men också ineffektivt på grund av omfattande användning av strängsammanfogning. SLF4J lägger till en fin {} substitutionsfunktion. Eftersom strängkonkatenering undviks och toString() inte anropas om loggningsuttalandet filtreras, finns det inget behov av isDebugEnabled() längre. BTW, har du märkt enstaka citat runt filtersträng parameter?

SLF4J är bara en fa portugade. Som en implementering skulle jag rekommendera Loggback framework, som redan annonserats, istället för den väletablerade Log4J. den har många intressanta funktioner och, i motsats till Log4J, är aktivt utvecklad.

det sista verktyget att rekommendera är Perf4J. för att citera deras motto:

Perf4J är att systemet.currentTimeMillis () som log4j är att systemet.ut.println ()

jag har lagt till Perf4J till en befintlig applikation under tung belastning och sett den i aktion i några andra. Både administratörer och företagsanvändare var imponerade av de fina grafer som produceras av denna enkla verktyg. Vi kunde också upptäcka prestandabrister på nolltid. Perf4J själv förtjänar sin egen artikel, men för nu bara kolla deras utvecklarguide.

observera dessutom att Ceki g Exceptlc (grundare av Log4J -, SLF4J-och Logback-projekten) föreslog ett enkelt tillvägagångssätt för att bli av med commons-loggningsberoende (se hans kommentar).

2) glöm inte, loggningsnivåer finns där för dig

varje gång du gör ett loggningsuttalande tror du hårt vilken loggningsnivå som är lämplig för denna typ av händelse, eller hur? På något sätt uppmärksammar 90% av programmerarna aldrig loggningsnivåer, helt enkelt loggar allt på samma nivå, vanligtvis INFO eller DEBUG. Varför? Loggningsramar har två stora fördelar jämfört med systemet.ut., dvs. kategorier och nivåer. Båda låter dig selektivt filtrera loggningsuttalanden permanent eller bara för diagnostiktid. Om du verkligen inte kan se skillnaden, Skriv ut den här tabellen och titta på den varje gång du börjar skriva ”log.”i din IDE:

fel – något fruktansvärt fel hade hänt, som måste undersökas omedelbart. Inget system kan tolerera objekt som är inloggade på denna nivå. Exempel: npe, databas ej tillgänglig, verksamhetskritisk användningsfall kan inte fortsätta.

varning-processen kan fortsätta, men var extra försiktig. Egentligen ville jag alltid ha två nivåer här: en för uppenbara problem där det finns arbete (till exempel: ”Nuvarande data otillgänglig, med cachade värden”) och andra (namnge det: uppmärksamhet) för potentiella problem och förslag. Exempel:” program som körs i utvecklingsläge ”eller”administrationskonsolen är inte säkrad med ett lösenord”. Ansökan kan tolerera varningsmeddelanden, men de bör alltid motiveras och granskas.

INFO-viktig affärsprocess är klar. I ideal world bör administratör eller avancerad användare kunna förstå informationsmeddelanden och snabbt ta reda på vad applikationen gör. Till exempel om en ansökan handlar om att boka flygbiljetter, Det bör endast finnas en info uttalande per varje biljett säger ” bokad biljett från till ”. Annan definition av informationsmeddelande: varje åtgärd som ändrar programmets tillstånd avsevärt (databasuppdatering, extern systemförfrågan).

DEBUG-Utvecklare Grejer. Jag kommer senare att diskutera vilken typ av information som förtjänar att loggas.

TRACE – mycket detaljerad information, endast avsedd för utveckling. Du kan behålla spårningsmeddelanden under en kort tid efter distribution på produktionsmiljö, men behandla dessa logguttalanden som tillfälliga, som bör eller kan stängas av så småningom. Skillnaden mellan DEBUG och TRACE är den svåraste, men om du sätter loggning uttalande och ta bort den efter att funktionen har utvecklats och testats, det borde förmodligen vara på spår nivå.

listan ovan är bara ett förslag, Du kan skapa din egen uppsättning instruktioner att följa, men det är viktigt att ha några. Min erfarenhet är att alltid allt loggas utan filtrering (åtminstone från applikationskoden), men att ha möjlighet att snabbt filtrera loggar och extrahera informationen med rätt detaljnivå kan vara en livräddare.

det sista som är värt att nämna är det ökända is*Enabled () – tillståndet. Vissa lägger det före varje loggningsdeklaration:

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

personligen tycker jag att detta idiom bara är röran som bör undvikas. Prestandaförbättringen (speciellt vid användning av SLF4J-mönsterbyte som diskuterats tidigare) verkar irrelevant och luktar som en för tidig optimering. Också, kan du upptäcka dubbelarbete? Det finns mycket sällsynta fall när det är motiverat att ha ett uttryckligt tillstånd-när vi kan bevisa att det är dyrt att bygga loggningsmeddelande. I andra situationer, gör bara ditt jobb med loggning och låt loggningsramen göra sitt jobb (filtrering).

3) Vet du vad du loggar?

varje gång du utfärdar ett loggningsuttalande, ta en stund och titta på vad som exakt kommer att landa i din loggfil. Läs dina loggar efteråt och hitta felaktiga meningar. För det första, undvik npe: er så här:

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

är du helt säker på att begäran inte är null här?

en annan fallgrop är loggningssamlingar. Om du hämtade samling av domänobjekt från databasen med Hibernate och slarvigt logga dem som här:

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

SLF4J kommer att ringa toString () endast när uttalandet faktiskt skrivs ut, vilket är ganska trevligt. Men om det gör… ur minnesfel, n + 1 Välj problem, trådsvält (loggning är synkron!), Lat initiering undantag, loggar Lagring fylld helt-var och en av dessa kan förekomma.

det är en mycket bättre ide att logga till exempel bara ID för domänobjekt (eller till och med bara storleken på samlingen). Men att göra en samling ID när man har en samling objekt som har getid () – metoden är otroligt svårt och besvärligt i Java. Groovy har en stor spridningsoperatör( användare*. id), i Java kan vi emulera det med Commons Beanutils-biblioteket:

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

där collect () – metoden kan implementeras enligt följande:

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

det sista att nämna är felaktig implementering eller användning av toString(). Skapa först toString () för varje klass som visas var som helst i loggningsuttalanden, helst med ToStringBuilder (men inte dess reflekterande motsvarighet). För det andra, se upp för arrays och icke-typiska samlingar. Arrays och några konstiga samlingar kanske inte har toString() implementerat anropande toString() för varje objekt. Använd matriser # deepToString JDK utility metod. Och läs dina loggar ofta för att upptäcka felaktigt formaterade meddelanden.

4) undvik biverkningar

Loggningsuttalanden bör inte ha någon eller liten inverkan på programmets beteende. Nyligen en vän till mig gav ett exempel på ett system som kastade Hibernates’ LazyInitializationException endast när man kör på en viss miljö. Som du antagligen har gissat från sammanhanget orsakade vissa loggningsuttalanden att lazy initialized collection laddades när sessionen bifogades. På denna miljö ökade loggningsnivåerna och insamlingen initierades inte längre. Tänk hur lång tid skulle det ta dig att hitta ett fel utan att veta detta sammanhang?

en annan biverkning saktar ner applikationen. Snabbsvar: om du loggar för mycket eller felaktigt använder toString() och/eller sträng sammanfogning, har loggning en prestanda bieffekt. Hur stor? Tja, jag har sett servern starta om var 15: e minut på grund av en trådsvält orsakad av överdriven loggning. Nu är det en bieffekt! Från min erfarenhet är några hundra MiB förmodligen den övre gränsen för hur mycket du kan logga in på disken per timme.

naturligtvis om loggning uttalande själv misslyckas och orsakar affärsprocessen att avsluta på grund av undantag, detta är också en enorm bieffekt. Jag har sett en sådan konstruktion för att undvika detta:

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

det här är en riktig kod, men snälla gör världen lite bättre och gör det inte någonsin.

5) var kortfattad och beskrivande

varje loggningsuttalande ska innehålla både data och beskrivning. Tänk på följande exempel:

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

vilken logg skulle du vilja se när du diagnostiserar fel i en okänd applikation? Tro mig, alla exemplen ovan är nästan lika vanliga. Ett annat anti-mönster:

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

var det så svårt att inkludera dig faktiska meddelandetyp, meddelande-id, etc. i varningssträngen? Jag vet att något gick fel, men vad? Vad var sammanhanget?

ett tredje antimönster är ”magic-log”. Verkliga livet exempel: de flesta programmerare i laget visste att 3 ampersands följt av utropstecken, följt av hash, följt av pseudorandom alfanumerisk stränglogg betyder ”meddelande med XYZ id mottaget”. Ingen brytt sig om att ändra loggen, helt enkelt någon slog tangentbordet och valde några unika ”&&&!# ”sträng, så att den lätt kan hittas av sig själv.

som en konsekvens ser hela loggfilen ut som en slumpmässig sekvens av tecken. Någon kanske till och med anser att filen är ett giltigt Perl-program. Istället bör en loggfil vara läsbar, ren och beskrivande. Använd inte magiska nummer, loggvärden, siffror, ID och inkludera deras sammanhang. Visa data som behandlas och visa dess betydelse. Visa vad programmet faktiskt gör. Bra loggar kan fungera som en bra dokumentation av själva applikationskoden.

nämnde jag att jag inte loggade lösenord och någon personlig information? Gör det inte!

6) Ställ in ditt mönster

Loggningsmönster är ett underbart verktyg som transparent lägger till ett meningsfullt sammanhang för varje loggningsuttalande du gör. Men du måste överväga mycket noggrant vilken information som ska ingå i ditt mönster. Till exempel är loggningsdatum när dina loggar rullar varje timme meningslöst eftersom datumet redan ingår i loggfilnamnet. Tvärtom, utan att logga trådnamnet skulle du inte kunna spåra någon process med loggar när två trådar fungerar samtidigt – loggarna överlappar varandra. Detta kan vara bra i enkelgängade applikationer-som är nästan döda nuförtiden.

från min erfarenhet bör det ideala loggmönstret innehålla (naturligtvis utom det loggade meddelandet själv): aktuell tid (utan datum, millisekunder precision), loggningsnivå, trådens namn, enkelt loggnamn (inte fullt kvalificerat) och meddelandet. I Logback är det något 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 aldrig inkludera filnamn, klassnamn och radnummer, även om det är mycket frestande. Jag har till och med sett tomma logguttalanden utfärdade från koden:

log.info("");

eftersom programmeraren antog att radnumret kommer att vara en del av loggningsmönstret och han visste att ”om tomt loggningsmeddelande visas i 67: e raden i filen (i autentisera () – metoden) betyder det att användaren är autentiserad”. Dessutom har loggningsklassnamn, metodnamn och/eller radnummer en allvarlig prestandapåverkan.

en något mer avancerad funktion i ett loggningsramverk är begreppet Mapped Diagnostic Context. MDC är helt enkelt en karta hanteras på en tråd-lokal basis. Du kan sätta valfritt nyckelvärdespar i den här kartan och sedan dess kommer varje loggningsuttalande från den här tråden att ha detta värde bifogat som en del av mönstret.

7) Loggmetodargument och returvärden

när du hittar ett fel under utvecklingen kör du vanligtvis en felsökare som försöker spåra den potentiella orsaken. Föreställ dig nu ett tag att du inte kan använda en debugger. Till exempel, eftersom felet manifesterade sig i en kundmiljö för några dagar sedan och allt du har är loggar. Skulle du kunna hitta något i dem?

om du följer den enkla regeln att logga varje metod input och output (argument och returvärden), behöver du inte ens en debugger längre. Naturligtvis måste du vara rimlig men varje metod som: åtkomst externt system (inklusive databas), block, väntar, etc. bör övervägas. Följ bara detta mönster:

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;}

eftersom du loggar både början och slutet av metoden åkallan, kan du manuellt upptäcka ineffektiv kod och även upptäcka möjliga orsaker till dödläge och svält – helt enkelt genom att titta efter ”IN” utan motsvarande ”lämnar”. Om dina metoder har meningsfulla namn skulle det vara ett nöje att läsa loggar. Att analysera vad som gick fel är också mycket enklare, eftersom du på varje steg vet exakt vad som har bearbetats. Du kan till och med använda en enkel AOP-aspekt för att logga ett brett spektrum av metoder i din kod. Detta minskar koddubblering, men var försiktig, eftersom det kan leda till enorma mängder stora loggar.

du bör överväga felsöknings-eller SPÅRNINGSNIVÅER som bäst passar för dessa typer av loggar. Och om du upptäcker någon metod kallas för ofta och loggning kan skada prestanda, helt enkelt minska loggning nivå för den klassen eller ta bort loggen helt (kanske lämnar bara en för hela metoden åkallan?) Men det är alltid bättre att ha för mycket snarare än för få loggningsuttalanden. Behandla loggning uttalanden med samma respekt som enhetstester – din kod bör täckas med loggning rutiner som det är med enhetstester. Ingen del av systemet ska stanna utan loggar alls. Kom ihåg att ibland observera loggar som rullar av är det enda sättet att berätta om din ansökan fungerar korrekt eller hänger för alltid.

8) Se upp för externa system

detta är det speciella fallet med föregående tips: om du kommunicerar med ett externt system, överväg att logga in varje data som kommer ut från din ansökan och kommer in. Period. Integration är ett tufft jobb och det är särskilt svårt att diagnostisera problem mellan två applikationer (tänk två olika leverantörer, miljöer, teknikstackar och team). Nyligen har vi till exempel upptäckt att loggning av fullständiga meddelanden, inklusive SOAP-och HTTP-rubriker i Apache CXF web services, är extremt användbart under integration och systemtestning.

Detta är en stor overhead och om prestanda är ett problem kan du alltid inaktivera loggning. Men vad är meningen med att ha en snabb, men trasig applikation, som ingen kan fixa? Var extra försiktig när du integrerar med externa system och förbered dig på att betala den kostnaden. Om du har tur och all din integration hanteras av en ESB, är bussen förmodligen det bästa stället att logga varje inkommande begäran och svar. Se till exempel mulor’ log-komponent.

ibland gör mängden information som utbyts med externa system det oacceptabelt att logga allt. Å andra sidan under testning och under en kort tid på produktion (till exempel när något fel händer) vill vi ha allt sparat i loggar och är redo att betala prestandakostnad. Detta kan uppnås genom att noggrant använda loggningsnivåer. Ta bara en titt på följande idiom:

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

om den här loggen är konfigurerad för att logga felsökningsmeddelanden kommer den att skriva ut hela innehållet i requestIds-samlingen. Men om den är konfigurerad för att skriva ut informationsmeddelanden kommer endast storleken på samlingen att matas ut. Om du undrar varför jag glömde isInfoEnabled () tillstånd, gå tillbaka till tips #2. En sak som är värt att nämna är att requestids samling inte bör vara noll i det här fallet. Även om det kommer att loggas korrekt som null om DEBUG är aktiverat, men big fat NullPointerException kommer att kastas om logger är konfigurerad till INFO. Kom ihåg min lektion om biverkningar i Tips # 4?

9) Loggundantag korrekt

först och främst, undvik loggningsundantag, låt din ram eller behållare (vad det än är) göra det åt dig. Det finns ett, ekhem, undantag från denna regel: om du kastar undantag från någon fjärrtjänst (RMI, EJB remote session bean, etc.), som kan serialisera undantag, se till att alla är tillgängliga för klienten (ingår i API). Annars kommer klienten att få NoClassDefFoundError: SomeFancyException istället för det” sanna ” felet.

Loggningsundantag är en av de viktigaste rollerna för loggning alls, men många programmerare tenderar att behandla loggning som ett sätt att hantera undantaget. De returnerar ibland standardvärde (vanligtvis null, 0 eller tom sträng) och låtsas att ingenting har hänt. Andra gånger loggar de först undantaget och lägger sedan in det och kastar det tillbaka:

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

denna konstruktion kommer nästan alltid skriva ut samma stack spår två gånger, eftersom något så småningom kommer att fånga MyCustomException och logga dess orsak. Logga, eller linda och kasta tillbaka (vilket är att föredra), aldrig båda, annars kommer dina loggar att vara förvirrande.

men om vi verkligen vill logga undantaget? Av någon anledning (för att vi inte läser API: er och dokumentation?), ungefär hälften av loggnings uttalanden Jag ser är fel. Quick quiz, Vilken av följande log uttalanden kommer att logga NPE ordentligt?

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}

överraskande är bara G och helst L korrekta! A och B kompilerar inte ens i SLF4J, andra kasserar stackspår och/eller skriver ut felaktiga meddelanden. Till exempel kommer E inte att skriva ut något eftersom NPE vanligtvis inte ger något undantagsmeddelande och stackspåret kommer inte att skrivas ut också. Kom ihåg att det första argumentet alltid är textmeddelandet, skriv något om problemets art. Inkludera inte undantagsmeddelande, eftersom det kommer att skrivas ut automatiskt efter logguttalandet, föregående stackspårning. Men för att göra det måste du skicka undantaget själv som det andra argumentet.

10) loggar lätt att läsa, lätt att tolka

det finns två grupper av mottagare som är särskilt intresserade av dina applikationsloggar: människor (du kanske inte håller med, men programmerare tillhör också denna grupp) och datorer (vanligtvis skalskript skrivna av systemadministratörer). Loggar bör vara lämpliga för båda dessa grupper. Om någon tittar bakom ryggen på din ansökan loggar ser (källa Wikipedia):

då har du förmodligen inte följt mina tips. Loggar ska vara läsbara och lätta att förstå precis som koden ska.

å andra sidan, om din ansökan producerar halv GB loggar varje timme, kommer ingen människa och ingen grafisk textredigerare någonsin lyckas läsa dem helt. Det är här old-school grep, sed och awk kommer till nytta. Om det är möjligt, försök att skriva loggmeddelanden på ett sådant sätt att de kan förstås både av människor och datorer, t. ex. undvik formatering av siffror, använd mönster som lätt kan identifieras med reguljära uttryck etc. Om det inte är möjligt, skriv ut data i två format:

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)

datorer kommer att uppskatta” ms efter 1970 epoch ”tidsformat, medan folk skulle vara glada att se” 1 dag 10 timmar 17 minuter 36 sekunder ” text. BTW ta en titt på Varaktighetformatutils, trevligt verktyg.

det är alla killar, en ”loggningstips extravaganza” från vår JCP-partner, Tomasz Nurkiewicz. Glöm inte att dela!

Relaterade Artiklar :
  • loggning Antipatterns
  • saker varje programmerare bör veta
  • lagar av mjukvarudesign
  • Java bästa praxis – vektor vs ArrayList vs HashSet
  • Java bästa praxis – DateFormat i en Multithreading miljö
  • Java bästa praxis – hög prestanda serialisering
  • Java bästa praxis – Strängprestanda och exakt Strängmatchning
relaterade Utdrag :
  • Skapa anpassad Formaterare för Loggerhanterare
  • Loggmetod samtal
  • Ställ in filter på Loggerhanterare
  • Ställ in Formaterare för Loggerhanterare
  • Loggningsaspekt exempel
  • Log4j Maven exempel
  • hibernate loggning konfiguration – slf4j + log4j och logback