Amarengo

Articles and news

9 loggning synder i dina Java-program

loggning runtime information i din Java-program är kritiskt användbar för att förstå beteendet hos en app, särskilt i de fall när du stöter på oväntade scenarier, fel eller bara behöver spåra vissa programhändelser.

i en verklig produktionsmiljö har du vanligtvis inte lyxen att felsöka. Och så kan loggfiler vara det enda du måste gå av när du försöker diagnostisera ett problem som inte är lätt att reproducera.

gjort på rätt sätt, loggfiler kan också spara mycket tid genom att ge ledtrådar till orsaken till problemet, och i tillståndet i systemet när det hände. Loggning kan också vara användbart för revisionsändamål, samla in statistik, extrahera business intelligence och en mängd andra uppgifter.

Sammantaget är loggning verkligen en grundläggande praxis som ger betydande fördelar under applikationens livstid – så det kan vara frestande att börja spela in så mycket loggdata som möjligt.

men felaktig användning av loggning kan också ha betydande nackdelar.

i följande avsnitt tar vi en titt på några av de vanligaste och mest skadliga metoderna som du kan stöta på när du använder loggning i en applikation.

alla exempel och konfigurationer använder det populära log4j 2-biblioteket. Logback är ett annat bra alternativ, som också stöds av Stackify.

9 Java loggning problem och hur man undviker dem

loggning känslig Information

till att börja med, förmodligen den mest skadliga loggning praxis väckt den av ”Logga så mycket som möjligt just in case” tillvägagångssätt visar känslig information i loggarna.

de flesta applikationer hanterar data som ska förbli privata, till exempel användaruppgifter eller ekonomisk information. Risken för att denna typ av information loggas in i en vanlig textfil är tydlig – loggfiler kommer sannolikt att behandlas av flera, osäkra system.

dessutom är loggning av vissa kategorier av data, till exempel finansiell information, också kraftigt reglerad och kan få allvarliga juridiska konsekvenser.

det bästa sättet att undvika detta är helt enkelt att se till att du aldrig loggar denna typ av känslig information.

det finns alternativ, som att kryptera loggfilerna, men det gör i allmänhet dessa filer mycket mindre användbara totalt sett, vilket inte är idealiskt.

innan vi går vidare, här är en mer omfattande lista över de typer av information som du behöver vara mycket försiktig loggning.

logga vanlig användarinmatning

ett annat vanligt säkerhetsproblem i Java-applikationer är JVM Log smide.

enkelt uttryckt kan log smide hända när data från en extern källa som användarinmatning eller annan otillförlitlig källa skrivs direkt till loggarna. En skadlig angripare kan mata in indata som simulerar en loggpost som ”\n\nweb – 2017-04-12 17:47:08,957 info belopp omvänd framgångsrikt” vilket kan resultera i skadade loggdata.

det finns olika sätt att hantera denna typ av sårbarhet:

  • Logga inte in någon användarinmatning – inte alltid möjligt, eftersom användardata kan vara avgörande för att komma till grundorsaken till vissa problem
  • använd validering innan du loggar – den här lösningen kan påverka prestanda, samt avstå från att logga viktig information
  • logga till en databas – säkrare men dyrare när det gäller prestanda, och kan införa en annan sårbarhet – SQL injection
  • använd ett verktyg som Enterprise säkerhets-API från OWASP

att använda esapi är definitivt ett bra sätt att gå; denna öppen källkod säkerhetsbibliotek från OWASP kan koda data innan du skriver det till loggarna:

message = message.replace( '\n' , '_' ).replace( '\r' , '_' ) .replace( '\t' , '_' );message = ESAPI.encoder().encodeForHTML( message );

överdriven loggning

en annan praxis som ska undvikas är att logga för mycket information. Detta kan hända i ett försök att fånga alla potentiellt relevanta data.

ett möjligt och mycket verkligt problem med detta tillvägagångssätt är minskad prestanda. Men med utvecklingen av loggbibliotek har du nu verktygen för att göra detta mindre oroande.

som ett exempel på förbättrad prestanda, den 2.X-versionen av log4j använder asynkron loggning, vilket innebär att utföra I / O-operationer i en separat tråd.

för många loggmeddelanden kan också leda till svårigheter att läsa en loggfil och identifiera relevant information när ett problem uppstår.

ett sätt att minska antalet loggkodslinjer är att logga viktig information över övergripande problem i systemet.

om du till exempel vill logga början och slutet på vissa metoder kan du lägga till en aspekt som gör detta för varje metod som har en angiven Anpassad anteckning:

@Aspectpublic class MyLogger { private static final Logger logger = LogManager.getLogger(MyLogger.class); @Around("execution(* *(..)) && @annotation(LogMethod)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { logger.info("Starting method execution: " + joinPoint.getSignature().getName() + " in class:"+joinPoint.getSignature().getDeclaringTypeName()); Object result = joinPoint.proceed(); logger.info("Exiting method execution: " + joinPoint.getSignature().getName() + " in class:"+joinPoint.getSignature().getDeclaringTypeName()); return result; }}

med hjälp av den anpassade aspekten kan vi nu vara mycket selektiva och välja de exakta områdena i applikationen där vi faktiskt behöver den informationen i loggarna. Och som ett resultat kan vi avsevärt minska systemets totala loggningsavtryck.

kryptiska loggmeddelanden

vid tolkning av loggfiler kan det vara frustrerande att stöta på en rad som inte ger tillräcklig information. En vanlig fallgrop är bristen på specificitet eller sammanhang i loggmeddelanden.

för att illustrera problemet, låt oss ta en titt på ett loggmeddelande som saknar specificitet:

Operation failed.

istället kan du lägga till mer specifik och identifierbar information:

File upload picture.jpg failed.

Tänk alltid på att dina loggar säkert kommer att läsas av en annan utvecklare eller systemadministratör, och de måste förstå vad som har hänt i applikationen.

ett bra sätt att lägga till sammanhang i loggmeddelanden är att inkludera tidsstämpel, loggnivå, trådnamn och fullt kvalificerat klassnamn för händelsen. På så sätt kan du lättare identifiera när och var specifika händelser inträffar vid körning.

om du vill lägga till denna information när du använder log4j 2 kan du konfigurera en Mönsterlayout med alternativen %d för datumet, % p för loggnivå, %t för trådnamn och % c för klassnamn:

<PatternLayout> <Pattern>%d %p %c - %m%n</Pattern></PatternLayout>

ett loggmeddelande med ovanstående layout kommer att se ut så här:

2017-05-11 22:51:43,223 INFO com.stackify.service.MyService - User info updated

att använda en enda loggfil

nackdelen med att bara använda en loggfil för applikationen är att detta med tiden blir ganska stort och svårt att arbeta med.

en bra metod för att snabbt hitta relevant information är att skapa en ny loggfil varje dag, med datumet som en del av filnamnet.

Låt oss ta en titt på ett exempel på hur du skapar en loggfil med namnet lika med det aktuella datumet om du använder log4j2-biblioteket:

SimpleLayout layout = new SimpleLayout();FileAppender appender = new FileAppender(layout, LocalDate.now().toString(), false);logger.addAppender(appender);

samma bibliotek ger också möjlighet att konfigurera en rullande fil Appender som kommer att skapa nya loggfiler vid vissa tidsintervall:

<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz"> <PatternLayout> <Pattern>%d %p %c - %m%n</Pattern> </PatternLayout> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="250 MB"/> </Policies> <DefaultRolloverStrategy max="20"/></RollingFile>

konfigurationen ovan kommer att resultera i en eller flera filer som skapats för varje dag upp till 250 MB per fil med det aktuella datumet som filnamn, placeras i mappar med namnen på formuläret år-månad.

att välja felaktiga Loggnivåer

att välja en otillräcklig loggnivå leder antingen till att betydande händelser saknas eller översvämmas med mycket mindre viktiga data.

enkelt uttryckt, att välja rätt loggnivå för de olika loggarna i ditt system är en av de viktigaste sakerna du behöver för att få rätt att få en bra upplevelse att förstå dina loggar.

de flesta loggningsramar har en uppsättning nivåer som liknar FATAL, ERROR, WARN, INFO, DEBUG, TRACE, beställt från Högsta till lägsta.

tillgängliga loggnivåer

Låt oss ta en titt på var och en av dessa och vilken typ av loggmeddelanden de ska innehålla baserat på svårighetsgrad:

  • dödlig bör reserveras för fel som orsakar att programmet kraschar eller misslyckas med att starta (ex: JVM ur minnet)
  • fel bör innehålla tekniska problem som måste lösas för att systemet ska fungera korrekt (ex: kunde inte ansluta till databasen)
  • varning används bäst för tillfälliga problem eller oväntat beteende som inte väsentligt hindrar programmets funktion (ex: misslyckad användarinloggning)
  • INFO ska innehålla meddelanden som beskriver vad som händer i applikationen (ex: användarregistrerad, beställd)
  • Debug är avsedd för meddelanden som kan vara användbara vid felsökning av ett problem (ex: 4306 >
  • TRACE liknar DEBUG men innehåller mer detaljerade händelser (ex: datamodell uppdaterad)

kontrollera Loggnivåer

loggnivån för ett meddelande ställs in när det skrivs:

logger.info("Order ID:" + order.getId() + " placed.");

loggnings-API: er låter dig vanligtvis ställa in den nivå upp till vilken du vill se meddelanden. Vad detta betyder är att om du ställer in loggnivån för applikationen eller vissa klasser till INFO, till exempel, kommer du bara att se meddelanden på nivåerna FATAL, ERROR, WARN och INFO, medan DEBUG och TRACE-meddelanden inte kommer att inkluderas.

detta är till hjälp eftersom du vanligtvis skulle visa felsökning eller lägre meddelanden under utveckling, men inte i produktion.

så här kan du ställa in loggnivån för en klass, ett paket eller en hel applikation i log4j 2 med en log4j2.egenskaper fil:

loggers=classLogger,packageLoggerlogger.classLogger.name=com.stackify.service.MyServicelogger.classLogger.level=infologger.packageLogger.name=com.stackify.configlogger.packageLogger.level=debug
rootLogger.level=debug

för att visa loggnivån i meddelandet kan du lägga till alternativet %p i log4j2PatternLayout.

innan vi går vidare, kom ihåg att om ingen av de befintliga nivåerna är lämpliga för dina applikationsbehov, har du också möjlighet att definiera en anpassad loggnivå.

spåra en enda Operation över flera system och loggar

i distribuerade system med flera, oberoende distribuerade tjänster som arbetar tillsammans för att behandla inkommande förfrågningar kan det vara svårt att spåra en enda begäran över alla dessa system.

en enda förfrågan kommer sannolikt att träffa flera av dessa tjänster, och om ett problem uppstår måste vi bekräfta alla enskilda loggar i dessa system för att få en fullständig bild av vad som hände.

för att ta itu med denna typ av arkitektur har vi nu en ny generation loggningshjälpverktyg i ekosystemet, som Zipkin och Spring Cloud Sleuth.

Zipkin spårar förfrågningar över mikroservicearkitekturer för att hjälpa dig att identifiera vilket program som orsakar problemet. Det kommer också med ett användbart användargränssnitt där du kan filtrera spår baserat på applikation, spårlängd eller tidsstämpel.

och Spring Cloud Sleuth-projektet fungerar genom att lägga till ett unikt 64-bitars ID till varje spår; en webbförfrågan kan till exempel utgöra ett spår. På så sätt kan begäran identifieras över flera tjänster.

dessa verktyg adresserar begränsningarna i kärnbiblioteken och du navigerar i de nya realiteterna i den mer distribuerade arkitekturstilen.

loggar inte med JSON

medan loggning i ett klartextformat är mycket vanligt har tillkomsten av logglagrings-och dataanalyssystem skiftat det mot JSON.

JSON som det primära applikationsloggformatet har fördelen att det är lika läsbart som vanlig text, samtidigt som det är mycket lättare att analysera med automatiserade bearbetningsverktyg.

till exempel erbjuder Log4j 2 JSONLayout för detta exakta syfte:

<JSONLayout complete="true" compact="false"/>

detta kommer att producera ett välformat JSON-dokument:

som JSON kommer loggdata att vara semantiskt rikare när de bearbetas av ett logghanteringssystem som Retrace – vilket omedelbart möjliggör dess kraftfulla strukturerade/semantiska loggningsfunktioner.

Loggningspåverkan på prestanda

slutligen, låt oss överväga ett problem som är oundvikligt när du lägger till loggning i en applikation: påverkan på prestanda.

en liten minskning av prestanda kan förväntas. Det är dock viktigt att spåra detta så att du kan minimera det och inte sakta ner systemet.

några prestationsrelaterade aspekter att tänka på när du väljer ett loggnings-API är:

  • File I/O – operationer med en buffert – detta är kritiskt eftersom file I/O är en dyr operation
  • asynkron loggning – detta bör övervägas så att loggning inte blockerar andra applikationsprocesser
  • loggning svarstid – den tid det tar att skriva en loggpost och returnera
  • antal trådar som används för loggning
  • loggnivåfiltrering-detta görs för att verifiera om loggnivån som motsvarar ett meddelande är aktiverad och kan göras genom att korsa hierarkin eller ha Loggerpunkten direkt till loggerkonfigurationen; det senare tillvägagångssättet är att föredra när det gäller prestanda

naturligtvis, om du behöver hålla valet öppet och systemet flexibelt, kan du alltid använda en högre abstraktion som slf4j.

innan vi flyttar en, här är bara några steg du kan vidta för att förbättra loggningsprestanda för ditt system:

  • Ställ in loggnivån för applikationen för verbose – paket
  • undvik loggning av källplatsinformation vid körning, eftersom man tittar upp den aktuella tråden, filen, en metod är en kostsam operation
  • undvik loggfel med långa stackspår
  • kontrollera om en specifik loggnivå är aktiverad innan du skriver ett meddelande med den nivån-på så sätt kommer meddelandet inte att konstrueras om det inte behövs
  • granska loggarna innan du flyttar till produktion för att kontrollera om någon loggning kan tas bort

hedersomnämnanden

innan vi avslutar, låt oss ta en titt på en sista praxis som du bör undvika – och det är att använda standard ut istället för att logga.

Medan Systemet.ut () kan vara ett snabbt sätt att börja mycket tidigt i utvecklingscykeln, det är definitivt inte en bra praxis att följa efter den punkten.

förutom det faktum att du förlorar alla de kraftfulla funktionerna i ett dedikerat loggnings-API, är denna primära nackdel här helt enkelt det faktum att loggningsdata inte kommer att kvarstå någonstans.

slutligen är ett annat hederligt omnämnande en praxis som kan göra läsning och analys av loggdata mycket enklare – standardiserade loggmeddelanden. Enkelt uttryckt bör liknande händelser ha liknande meddelanden i loggen.

om du behöver söka efter alla instanser av den specifika händelsen eller extrahera meningsfulla insikter ur dina loggdata är standardloggmeddelanden ganska viktiga.

till exempel, om en uppladdningsoperation misslyckas-att ha dessa olika meddelanden i loggen skulle vara förvirrande:

Could not upload file picture.jpg
File upload picture.jpg failed.

istället, när filöverföringen misslyckas, bör du konsekvent använda ett av dessa meddelanden för att logga felet.

slutsats

användningen av loggning har blivit allestädes närvarande i applikationsutveckling på grund av de mycket användbara och handlingsbara insikter som det ger i systemets körtid.

för att få ut det mesta av dina loggdata är det dock viktigt att gå utöver grunderna, utveckla en loggkultur och förstå de finare punkterna att arbeta med dessa data i skala och i produktion.

Lämna ett svar

Din e-postadress kommer inte publiceras.