Logging exceptions in Java
Logging is an essential component of any application, especially when a critical error occurs. Whether or not you recover from an exception, logging the problem can help you identify the cause of – and ultimately the solution to – potential problems in your application.
In this post, we’ll look at common approaches to Java exception handling and logging. The examples use Log4j2, but the concepts apply to almost all modern logging frameworks.
When to Log Java Exceptions
One of the first questions that often comes up when logging exceptions is: Where should I put the logging code? Logging can take place in the method where the exception occurred, or it can follow the exception up the call stack until it’s eventually handled by one of the calling methods. Choosing one option over the other depends mostly on how your application is structured. In either case, the logger will need enough information to record the cause of the error.
Log at the Source
Logging close to the source of the error provides as much detail about the error as possible. For example, imagine we have a program that writes a string to a file. In our program, we have a class that opens a file, writes to it, and then closes it. In this class, we catch and log any IOExceptions:
While this approach lets us create specific log messages, it limits the available context. We have access to the stack trace, but we don’t know what particular course of action triggered the exception. Logging close to the source also adds complexity to the application, since it requires each component to provide its own logging implementation. This can be mitigated with frameworks such as SLF4J, but it still ends up adding redundancy and overhead.
Log Further up the Stack
Rather than log directly at the source, we can pass the exception up the stack and log it at a point where we have more contextual information. This can take place in the middle of the call stack, or even by an umbrella handler at the top. This can even be done using Thread.setDefaultUncaughtExceptionHandler(), which lets you specify an exception handler for uncaught exceptions for the entire application. Building off of the previous example, we can set the MyFileWriter class to throw an IOException, which we’ll catch in WriterClass.
With this approach, logging becomes more centralized and fewer components need to implement their own logging. The downside is that some of the granular information provided by the bottom-level component is lost. Fortunately, we still have access to the stack trace, which doesn’t change no matter how many times the exception is rethrown.
Linking Separate Log Events
Regardless of which approach you take, having the ability to associate multiple log events can greatly improve your ability to track down the cause of an exception. Imagine our WriterClass component is part of a large multi-user service. When it fails, we need to know which user triggered the exception and what they were doing when the exception was triggered. We can associate each event with a unique identifier – such as a user ID – that persists throughout the lifetime of the component.
Log4j2 introduced the concept of the Thread Context, which is built off of the Mapped and Nested Diagnostic Contexts used by Logback and other frameworks. As the name implies, the Thread Context is a thread-wide repository of related data. This data can be accessed by the logger to associate unique information with a log event, such as a user ID or session ID. This way, you can relate log events even if they originate from different sources or at different times.
Using the Thread Context Map, we can associate values with keys using the static
%X conversion pattern (or by using a structured layout), you can add values from the Thread Context into your logs. Using a log management service such as Loggly or even a command line tool such as grep, you can then search and sort entries for this particular user by matching the user ID.
What to Avoid When Logging Exceptions
The following tips are geared towards exception handling in general, but they can also prevent problems when logging.
Don’t Log and Throw
No matter which approach you take when handling exceptions, log the exception at the same time you handle the exception. If you log the exception and then throw it, there’s a chance that the exception will be logged again further up the call stack, resulting in two log events for the same error.
To prevent confusion, log once and log consistently.
Don’t Catch and Drop
A surprisingly common approach to exception handling is to catch the exception, then ignore it. Not only is the issue not handled, but we have no record of a problem ever occurring.
As a bare minimum, add a logging statement or display an alert to notify the user that an error occurred. Ideally you should log the stack trace provided by the exception, or throw the exception and log the event further up the stack.
Don’t Log Too Little
Another common (and misleading) practice is to log only a portion of the exception. While this does provide some details about the error, it usually results in the loss of more useful information. For example, this statement creates a log entry with a generic message followed by a very brief description of the exception:
With Log4j2 and other logging frameworks, you can pass the exception as a parameter to the logging method. By default, Log4j2 logs the exception’s stack trace along with the message provided. By extending Log4j2’s PatternLayout, or by switching to a more structured layout, you can include more detailed information about the stack trace.
The key is to provide enough information for a developer to discover what went wrong, where it went wrong, and why it went wrong.
Don’t Catch General Java Exceptions
Catching the Exception class is like using a trawling net to catch a single fish. Not only does it make it more difficult to handle the exception in a specific way, but your program could end up catching exceptions it was never designed to handle.
Generally speaking, you should only catch exceptions that you can handle or delegate to the appropriate handler. Instead of using a catch-all, catch only the specific exception types that your code is likely to throw. You can do this by implementing separate catch blocks, each with their own exception-handling logic:
Starting with Java 7, you can catch multiple exception types in a single block by separating the exception type with a vertical bar ( | ).
While it’s still recommended to catch unique exceptions per catch block, this lets you simplify your code.
You should also consider using the
Thread.setDefaultUncaughtExceptionHandler() method. As mentioned earlier in Log Further Up the Stack,
setDefaultUncaughtExceptionHandler() can be used to log the event or perform a final action even if the error is unrecoverable.
Other Tips and Techniques
This post only covers a small subset of logging and exception-handling methods for Java. If you have any recommendations, feel free to leave them in the comments below.
Andre Newman is a software developer and writer on all things tech. With over eight years of experience in Java, .NET, C++, and Python, Andre has developed software for remote systems management, online retail, and real-time web applications. He currently manages 8-bit Buddhism, a blog that blends technology with spirituality.
The Loggly and SolarWinds trademarks, service marks, and logos are the exclusive property of SolarWinds Worldwide, LLC or its affiliates. All other trademarks are the property of their respective owners.
Logging exception katika Java Logging ni sehemu muhimu ya maombi yoyote, hasa wakati kosa muhimu hutokea. Kama au si wewe kupona kutoka exception, logging tatizo inaweza kukusaidia kutambua sababu ya <unk> na hatimaye ufumbuzi wa <unk> matatizo ya uwezekano katika maombi yako. Katika chapisho hili, tutaangalia mbinu za kawaida za utunzaji wa ubaguzi wa Java na kurekodi. Mifano hutumia Log4j2, lakini dhana zinatumika kwa karibu mifumo yote ya kisasa ya kurekodi. Wakati wa Log Java Exceptions Moja ya maswali ya kwanza ambayo mara nyingi inakuja wakati wa kuingiza tofauti ni: Ambapo mimi lazima kuweka nambari ya kuingiza? Logging inaweza kutokea katika njia ambapo exception ilitokea, au inaweza kufuata exception juu ya wito stack mpaka ni hatimaye kushughulikiwa na moja ya njia wito. Kuchagua chaguo moja juu ya nyingine inategemea hasa jinsi maombi yako ni muundo. Katika hali yoyote, logger itahitaji habari ya kutosha kurekodi sababu ya kosa. Log katika chanzo Logging karibu na chanzo cha kosa hutoa maelezo mengi kuhusu kosa kama inawezekana. Kwa mfano, fikiria tuna programu ambayo anaandika kamba kwa faili. Katika programu yetu, tuna darasa ambayo inafungua faili, anaandika, na kisha inafunga. Katika darasa hili, sisi kukamata na kumbukumbu yoyote IOExceptions: Wakati mbinu hii inaruhusu sisi kujenga maalum ujumbe wa kumbukumbu, ni mipaka ya muktadha inapatikana. Tuna upatikanaji wa kufuatilia stack, lakini hatujui nini hasa kozi ya hatua triggered ubaguzi. Kuandika karibu na chanzo pia huongeza utata kwa maombi, kwa kuwa inahitaji kila sehemu kutoa utekelezaji wake mwenyewe wa kuandika. Hii inaweza kupunguzwa na mifumo kama vile SLF4J, lakini bado inaishia kuongeza redundancy na overhead. Zaidi ya kumbukumbu juu ya Stack Badala ya kumbukumbu moja kwa moja katika chanzo, tunaweza kupita exception juu ya stack na kumbukumbu yake katika hatua ambapo tuna zaidi ya habari contextual. Hii inaweza kutokea katikati ya wito stack, au hata na umbrella handler juu. Hii inaweza hata kufanyika kwa kutumia Thread.setDefaultUncaughtExceptionHandler(), ambayo inaruhusu wewe kutaja udhibiti wa ubaguzi kwa ubaguzi uncaught kwa maombi yote. Kujenga nje ya mfano uliopita, tunaweza kuweka darasa MyFileWriter kutupa IOException, ambayo sisi <unk>ll kukamata katika WriterClass. Kwa mbinu hii, logging inakuwa zaidi ya kati na vipengele chache haja ya kutekeleza logging yao wenyewe. Hasara ni kwamba baadhi ya habari granular zinazotolewa na sehemu ya chini ya ngazi ni kupotea. Kwa bahati nzuri, bado tuna upatikanaji wa kufuatilia stack, ambayo haina kubadilika bila kujali ni mara ngapi ubaguzi ni retrowned. Kuunganisha matukio tofauti ya kumbukumbu: Bila kujali mbinu unayochukua, kuwa na uwezo wa kuunganisha matukio mengi ya kumbukumbu inaweza kuboresha uwezo wako wa kufuatilia sababu ya ubaguzi. Fikiria sehemu yetu ya WriterClass ni sehemu ya huduma kubwa ya watumiaji wengi. Wakati inashindwa, tunahitaji kujua ambayo mtumiaji triggered ubaguzi na nini walikuwa kufanya wakati ubaguzi ilikuwa triggered. Tunaweza kuhusisha kila tukio na kitambulisho cha kipekee <unk> kama vile kitambulisho cha mtumiaji <unk> ambayo inadumu katika maisha yote ya sehemu. Log4j2 ilianzisha dhana ya Thread Context, ambayo ni kujengwa nje ya Mapped na Nested Diagnostic Contexts kutumika na Logback na mifumo mingine. Kama jina linavyodokeza, Thread Context ni thread-wide hazina ya data kuhusiana. Data hii inaweza kupatikana na logger kuhusisha habari ya kipekee na tukio log, kama vile ID ya mtumiaji au ID ya kikao. Kwa njia hii, unaweza kuhusiana matukio ya kumbukumbu hata kama wao hutoka vyanzo tofauti au katika nyakati tofauti. Kwa kutumia ramani ya muktadha wa thread, tunaweza kuhusisha maadili na funguo kwa kutumia muundo wa kubadilisha wa % X (au kwa kutumia mpangilio uliopangwa), unaweza kuongeza maadili kutoka kwa muktadha wa thread kwenye kumbukumbu zako. Kutumia huduma ya usimamizi wa kumbukumbu kama vile Loggly au hata chombo cha mstari wa amri kama vile grep, unaweza kisha kutafuta na kupanga viingilio kwa mtumiaji huyu maalum kwa kulinganisha ID ya mtumiaji. Nini Kuepuka Wakati Logging Exceptions vidokezo vifuatavyo ni geared kuelekea exception utunzaji kwa ujumla, lakini wanaweza pia kuzuia matatizo wakati wa logging. Don't Log na Throw Haijalishi mbinu gani wewe kuchukua wakati wa kushughulikia tofauti, log tofauti wakati huo huo wewe kushughulikia tofauti. Kama wewe kumbukumbu ya ubaguzi na kisha kutupa, kuna nafasi kwamba ubaguzi itakuwa kumbukumbu tena zaidi juu ya wito stack, na kusababisha matukio mawili kumbukumbu kwa kosa moja. Ili kuzuia kuchanganyikiwa, kuingia mara moja na kuingia mara kwa mara. Don't Catch and Drop Njia ya kushangaza ya kawaida ya utunzaji wa ubaguzi ni kukamata ubaguzi, kisha kuupuuza. Si kwamba tu suala hilo halijashughulikiwa, bali hatuna rekodi ya tatizo lolote lililotokea. Kama kiwango cha chini kabisa, kuongeza taarifa ya kurekodi au kuonyesha onyo kwa kumjulisha mtumiaji kwamba kosa lilitokea. Kwa kawaida unapaswa kumbukumbu stack kufuatilia zinazotolewa na ubaguzi, au kutupa ubaguzi na kumbukumbu tukio zaidi juu ya stack. Don't Log Too Little Mazoea mengine ya kawaida (na ya kupotosha) ni kurekodi sehemu tu ya ubaguzi. Wakati hii haina kutoa baadhi ya maelezo kuhusu kosa, ni kawaida husababisha hasara ya habari zaidi muhimu. Kwa mfano, taarifa hii huunda log entry na ujumbe generic ikifuatiwa na maelezo mafupi sana ya exception: na Log4j2 na mifumo mingine ya logging, unaweza kupita exception kama parameter kwa njia ya logging. Kwa default, Log4j2 logs exception<unk>s stack trace pamoja na ujumbe zinazotolewa. Kwa kupanua Log4j2's PatternLayout, au kwa kubadili kwa mpangilio zaidi iliyoundwa, unaweza kujumuisha habari zaidi ya kina kuhusu kufuatilia stack. Jambo kuu ni kutoa habari ya kutosha kwa msanidi programu kugundua nini kilichokwenda vibaya, wapi kilikwenda vibaya, na kwa nini kilikwenda vibaya. Don't Catch General Java Exceptions Kukamata darasa la Exception ni kama kutumia wavu wa kuvuta samaki mmoja. Si tu inafanya kuwa vigumu zaidi kushughulikia ubaguzi katika njia maalum, lakini programu yako inaweza kuishia kukamata ubaguzi ni kamwe iliyoundwa kushughulikia. Kwa ujumla, unapaswa tu kukamata tofauti ambazo unaweza kushughulikia au kuwakabidhi kwa msimamizi sahihi. Badala ya kutumia catch-wote, kukamata tu aina maalum ya ubaguzi kwamba code yako ni uwezekano wa kutupa. Kwa mfano, unaweza kutumia block ya catch block, ambayo kila moja ina logic yake ya kushughulikia exception, na unaweza kuunganisha aina nyingi za exception katika block moja kwa kutenganisha aina ya exception na bar ya wima (_). Wakati bado inapendekezwa kukamata tofauti ya kipekee kwa block kukamata, hii inaruhusu kurahisisha code yako. Unapaswa pia kuzingatia kutumia Thread.setDefaultUncaughtExceptionHandler() njia. Kama ilivyoelezwa hapo awali katika Log Further Up stack, setDefaultUncaughtExceptionHandler() inaweza kutumika kurekodi tukio au kufanya hatua ya mwisho hata kama kosa ni unrecoverable. mengine Tips na Techniques Chapisho hili tu inashughulikia ndogo subset ya kurekodi na tofauti-kutunza mbinu kwa Java. Kama una mapendekezo yoyote, kujisikia huru kuondoka yao katika maoni hapa chini. Andre Newman ni msanidi programu na mwandishi juu ya mambo yote ya teknolojia. Akiwa na uzoefu wa zaidi ya miaka nane katika Java,.NET, C ++ na Python, Andre amebuni programu kwa usimamizi wa mifumo ya mbali, rejareja mtandaoni, na maombi ya wavuti ya wakati halisi. Kwa sasa anaendesha 8-bit Buddhism, blogi ambayo inachanganya teknolojia na kiroho. Loggly na SolarWinds alama za biashara, alama za huduma, na nembo ni mali ya kipekee ya SolarWinds Worldwide, LLC au washirika wake. Alama nyingine zote za biashara ni mali ya wamiliki wao husika.