C H A P T E R  6

image

Exceptions and Logging

Exceptions are a way of describing exceptional circumstances within a program. They are a way of signaling that something unexpected (exceptional) has happened. For that reason, exceptions are efficient at interrupting the current flow of the program and signaling that there is something that requires attention. As such, programs that judiciously use exceptions benefit from a better control flow and become more robust. Even so, using exceptions indiscriminately can cause performance degradation.

Within Java, exceptions are said to be thrown and caught. Throwing an exception involves telling the code that you have encountered an exception and involves using the throw keyword to signal the JVM to find any code capable of handling this exceptional circumstance within the current stack. Catching an exception involves telling the compiler which exceptions you can handle, and on which part of the code you want to monitor for these exceptions occurring. This is denoted by the try/catch Java syntax (described in recipe 6-1)

All exceptions inherit from Throwable, as shown in Figure 6-1. Classes that are inherited from Throwable are allowed to be defined in the catch clause of a try/catch statement. The Error classes are primarily used by the JVM to denote serious and/or fatal errors. According to the Java documentation, applications are not expected to catch Error exception as they are considered fatal (think computer being on fire). The bulk of exceptions within a Java program will be inherited from the Exception class.

images

Figure 6-1. Part of the exception class hierarchy in Java

An important aspect of exceptions is that within the JVM there are two types of exceptions: checked and unchecked. Checked exceptions are enforced by methods. In the method signature, one can specify what kind of exceptions the method can throw. This requires any caller of this method to create a try/catch block that handles the exceptions that the method declared on its signature. Unchecked exceptions don't require such as stringent convention and are free to be thrown anywhere without enforcing the implementation of a try/catch block. Even so, unchecked exceptions (as described on recipe 6-6) are usually discouraged because they can lead to threads unraveling (if nothing catches the exception) and poor visibility of problems. Classes that inherit from RuntimeExceptionare considered unchecked exceptions, whereas classes that inherit directly from Exception are considered to be checked exceptions.

Be aware that the act of throwing exceptions is expensive (compared with other language construct alternatives), and as such throwing exceptions makes a poor substitute for control flow. For example, you shouldn't throw an exception to indicate an expected result of a method call (say a method like isUsernameValid (String username). It is a better practice to call the method and return a boolean with the result than trying to throw an InvalidUsernameException to indicate failure.

Logging within an application helps understand what is happening without the need for debugging the code. This is especially true in production environments where there isn't the opportunity for live debugging. In that sense, logging collects clues on what is happening (most likely what went wrong) and helps with troubleshooting production problems. A solid logging framework with a sound logging methodology will save many late-nights at work wondering “what happened?”

Logging for Java is very mature. There are many open-source projects that are widely accepted as the de facto standard for logging. In our recipes we will use Java's Logging framework and the Simple Logging Façade for Java (SLF4). Both of these projects together create a good-enough solution for most logging needs. For the recipes involving SLF4J and Log4j, please download SLF4J (http://www.slf4j.org/), and put them in your project's dependency path.

6-1. Catching Exceptions

Problem

You need to catch an exception from code.

Solution

Using the built-in try/catch language construct allows you to catch exceptions. In this example, there is a function that returns true/false if a string is shorter than five characters long. If the string passed in is null, a nullPointerException is thrown by the length() method and caught by the catch block.

    private void start() {
        System.out.println("Is th string 1234 longer than 5Images
chars?:"+isStringShorterThanFiveCharacters("1234"));
        System.out.println("Is th string 12345 longer than 5Images
chars?:"+isStringShorterThanFiveCharacters("12345"));
        System.out.println("Is th string 123456 longer than 5Images
chars?:"+isStringShorterThanFiveCharacters("123456"));
        System.out.println("Is th string null longer than 5Images
chars?:"+isStringShorterThanFiveCharacters(null));

    }

    private boolean isStringShorterThanFiveCharacters(String aString) {
        try {
            return aString.length() > 5;
        } catch (NullPointerException e) {
            System.out.println("An Exception Happened!");
            return false;
        }
    }

How It Works

The try keyword tells the Java program that the enclosing code segment could raise a potential exception. At the end of the try block we add the catch clauses. Each catch clause then specifies what exception they are catching for. If you do not provide a catch clause for a checked exception, the compiler will generate an error. Two possible solutions are to add a catch clause or to include the exception in the throws clause of the enclosing method. Any checked exceptions that are thrown but not caught will propagate up the call stack. If this method doesn't catch the exception, the thread that executed the code terminates. If the thread terminating is the only thread in the program, it terminates the execution of the program.

Images Caution When throwing an exception, be sure that you really want to throw it. If the thrown exception is not caught, it will propagate up the call stack; and if there isn't any catch clause capable of handling the exception, it will cause the running thread to terminate (also known as unraveling). If your program has only one main thread, an uncaught exception will terminate your program.

6-2. Guaranteeing that Cleanup Code Runs, No Matter What

Problem

You want to write code that executes when control leaves a code segment, no matter whether that control leaves due to an error being thrown or the segment ending normally. For example, you have acquired a lock and want to be sure that you are releasing it correctly. You want to release the lock in the event of an error and also in the event of no error.

Solution

Use a try/catch/finally block to properly release locks and other resources that you acquire in a code segment. In the example, the finally keyword specifies a code block that will always execute, regardless of whether an exception was thrown in the try block. Within the finally block, the lock is released by calling lock.unlock():

    private void callFunctionThatHoldsLock() {
        myLock.lock();
        try {
            int number = random.nextInt(5);
            int result = 100 / number;
            System.out.println("A result is " + result);
            FileOutputStream file = new FileOutputStream("file.out");
            file.write(result);
            file.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            myLock.unlock();
        }
    }

How It Works

By acquiring the lock at the beginning of the function and then releasing it in the finally block, you guarantee that the lock will be released at the end of the function regardless of whether an exception (checked or unchecked) is thrown. In all, acquired locks should always be released in a finally block. In the example, suppose that the mylock.unlock() function call were not in the finally block (but at the end of the try block); if an exception were to happen, the call to mylock.unlock() would not happen because code execution would be interrupted in the location where the exception happened. In that case, the lock would be forever acquired, and never released.

Images Caution If you need to return a value on a method, be very careful of returning values in the finally block. A return statement in the finally block will always execute, regardless of any other return statements that might have happened within the try block.

6-3. Throwing Exceptions

Problem

You need to account for an exceptional problem by throwing an exception from within your code. You don't want the execution to continue on the current code path. You want to abort the execution of the current code path by throwing the exception.

Solution

Using the throw keyword, one can signal the current thread to look for try/catch blocks (at the current level and up the stack) that can process the thrown exception. In our example, the callSomeMethodThatMightThrow throws an exception if the parameter passed in is null.

    private void start() {
        try {
            callSomeMethodThatMightThrow(null);
        } catch (IllegalArgumentException e) {
            System.out.println("There was an illegal argument exception!");
        }

    }

    private void callSomeFunctionThatMightThrow(Object o) {
        if (o == null) throw new NullPointerException("The object is null");

    }

In this code example, the callSomeMethodThatMightThrow checks for valid arguments. If the argument is invalid, it then throws an IllegalArgumentException, signaling that the caller of this method did it with the wrong parameters.

How It Works

The throw keyword allows you to explicitly generate an exceptional condition. When the current thread throws an exception, it doesn't execute anything beyond the throw statement and instead transfers control to the catch clause (if there are any) or terminates the thread.

Images Caution When throwing an exception, be sure that you really want to throw it. If an exception is not caught as it propagates up the stack, it will terminate the thread that is executing (also known as unraveling). If your program has only one main thread, an uncaught exception will terminate your program.

6-4. Catching Multiple Exceptions

Problem

You want to catch multiple exceptions that might happen within a try block.

Solution 1

By using different catch clauses (ordered from the most specific to the most general), you can catch multiple exceptions:

        try {
            Class<?> stringClass = Class.forName("java.lang.String");
            FileInputStream in = new FileInputStream("myFile.log") ; // Can throw Images
IOException
            in.read();

        } catch (IOException e) {
            System.out.println("There was an IOException "+e);
        } catch (ClassNotFoundException e) {
            System.out.println("There was a ClassCastException "+e);
        }

Solution 2

If the code for the different exceptions is the same, in Java 7 you can use the | operator for catching multiple exceptions (known as Multi-catch exception in Java 7):

        try {
            Class<?> stringClass = Class.forName("java.lang.String");
            FileInputStream in = new FileInputStream("myFile.log") ;
// Can throw IOException
            in.read();


        } catch (IOException | ClassNotFoundException e) {
            System.out.println("An exception of type "+e.getClass()+" was thrown! "+e);
        }

How It Works

By using different catch clauses you can adjust what gets executed when a particular exception is thrown. Sometimes we are just catching all the checked exceptions that can be thrown in a given code and want to handle it the same way (logging them, for example). In such a case, we can use the new Java 7 construct that allows us to specify all the exceptions to be catched in a catch block (instead of creating one catch block per checked exception.

Images Note If catching an exception in multiple catch blocks (Solution 1), make sure that the catch blocks are defined from the most specific to the most general. Failure to follow this convention will prevent an exception from being handled by the more-specific blocks. This is most important when there are catch (Exception e) blocks, which catch almost all exceptions.

Having a catch (Exception e) block—called a catch-all or Pokémon® exception handler (gotta catch them all)—is usually poor practice because such a block will catch every exception type and treat them the same. This becomes a problem because the block can catch other exceptions that happen deeper in the call-stack that you may not have intended the block to catch (an OutOfMemoryException).

6-5. Catching the Uncaught Exceptions

Problem

You want to know when a thread is being terminated because of an uncaught exception such as a NullPointerException.

Solution 1

When creating a Java thread, sometimes you want to catch not only checked exceptions but also any type of exceptions, at least to properly log them, or to “keep going” and avoid termination of the executing thread. To that effect, Java allows you to register an ExceptionHandler() either per thread or globally.

For example, the following code registers a per-thread handler:

    private void start() {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("Woa! there was an exception thrown somewhere!  Images
"+t.getName()+": "+e);
            }
        });

        final Random random = new Random();
        for (int j = 0; j < 10; j++) {
            int divisor = random.nextInt(4);
            System.out.println("200 / " + divisor + " Is " + (200 / divisor));
        }
    }

Solution 2

Use the thread's own UncaughtExceptionHandler(). Any exception that happens within the thread and that has not been caught will be handled by the uncaughtException() method of the uncaughtExceptionHandler(). For example:

        Thread.currentThread().setUncaughtExceptionHandler(Images
new Thread.UncaughtExceptionHandler() {


            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("In this thread "+t.getName()+" an Images
                exception was thrown "+e);
            }
        });

        Thread someThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(200/0);
            }
        });
        someThread.setName("Some Unlucky Thread");
        someThread.start();

        System.out.println("In the main thread "+ (200/0));

How It Works

The Thread.defaultUncaughtExceptionHandler() will be invoked for each unchecked exception that has not being caught. When the UncaughtExceptionHandler() handles the exception it means that there was no try/catch block that was capable of catching the exception and as such bubbled all the way up the thread stack. This is the last code executed on that thread before it terminates. When an exception is caught on either the thread's or the default's UncaughtExceptionHandler(), the thread will terminate. The UncaughtExceptionHandler() can be used to log information on the exception to help pinpoint the reason of the exception.

In the second solution, the UncaughtExceptionHandler() is set up specifically for the current thread. When the thread throws an exception that is not caught, it will bubble up to the UncaughtExceptionHandler() of the thread. If this is not present, it will bubble up to the defaultUncaughtExceptionHandler(). Again, in either situation, the thread originating the exception will terminate.

Images Tip When dealing with multiple threads, it is always a good practice to explicitly name these threads. It makes life easier to know that the exception was caused by the DatabaseThread, instead of Thread-## (the default naming pattern of unnamed threads).

6-6. Managing Resources with try/catch Blocks

Problem

You need to make sure that if an exception is thrown, the resources that are being used in the try/catch block are released.

Solution

By using the new Java 7 Automatic Resource Management (ARM) feature, you can specify a try-with-resources block:

        try (
                FileOutputStream fos = new FileOutputStream("out.log");
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                DataOutputStream dos = new DataOutputStream(bos)

        ) {
            dos.writeUTF("This is being written");
        } catch (Exception e) {
            System.out.println("Some bad exception happened ");
        }

How It Works

Before Java 7, managing resources was problematic if an exceptional condition happened. Usually you want to cleanly close/dispose of resources that are acquired within a try/catch block. If a program doesn't close/dispose of its resources or does so improperly, the resources could be acquired indefinitely. Most resources are limited (file handles or database connections) and as such will cause performance degradation (and more exceptions to be thrown). To avoid that, Java 7 provides a way of automatically releasing resources when an exception occurs within a try/catch block. By declaring a try-with-resources block, you are guaranteed that if there is an exception thrown within the block, the resource on which the try block was checked will be closed. Most of the resources that come with Java 7 can be added to the try-with-resources statement (for a full list, see implementers of the java.lang.AutoCloseable interface). Also, third-party implementers can create resources that will work with the try-with-resources statements by implementing the AutoCloseable interface.

The syntax for the try-with-resources block involves the try keyword, followed by an opening parenthesis and then followed by all the resource declarations that you would want to be released in case of an exception, and ending with a closing parenthesis. Please note that if you try to declare a resource/variable that doesn't implement the AutoCloseable interface, you will get a compiler error. After the closing parenthesis, the syntax of the try/catch block is the same as previously used.

The main advantage of the try-with-resources Java 7 feature is that it allows a cleaner release of resources. Usually when acquiring a resource, there are a lot of interdependencies (creating file handlers, which are wrapped in output streams, which are wrapped in buffered streams). Properly closing and disposing of these in exceptional conditions requires checking the status of each dependent resource and carefully disposing of it, and doing that requires that you write a lot of code. By contrast, the try-with-resources construct causes the JVM to take care of proper disposal of resources, even in exceptional conditions.

Images Note A try-with-resources block will always close the defined resources, even if there were no exceptions thrown.

6-7. Creating an Exception Class

Problem

You need to create a new type of exception.

Solution 1

By extending RuntimeException, you can create exceptions that are not required to be checked, but instead can happen at any time. For example:

    class IllegalChatServerException extends RuntimeException {
        IllegalChatServerException(String message) {
            super(message);
        }
    }

    private void disconnectChatServer(Object chatServer) {
        if (chatServer == null) throw new IllegalChatServerException("Chat server is
empty");
    }

Solution 2

By extending Exception, you create a checked exception that needs to be either caught or rethrown up the stack. For example:

    class ConnectionUnavailableException extends Exception {
        ConnectionUnavailableException(String message) {
            super(message);
        }
    }

    private void sendChat(String chatMessage) throws ConnectionUnavailableException {
        if (chatServer == null)
                throw new ConnectionUnavailableException("Can't find the chat server");
    }

How It Works

Sometimes you want to create your own exceptions, especially when creating an API. The usual recommendation is to use one of the available Exception classes provided by the JDK. For example, use IOException for IO-related issues or the IllegalArgumentException for illegal parameters. If there isn't a JDK exception that fits cleanly, you can always extend Exception or RuntimeException and implement its own family of exceptions.

Depending on the base class, creating an Exception class is fairly straightforward. Extending RuntimeException allows you to be able to throw the resulting exception any time without requiring anyone to catch it. This is advantageous in that RuntimeException is a more lax contract to work with, but throwing such an exception can lead to thread termination if there isn't anyone catching the exception. Extending Exception instead allows you to clearly force any code that throws the exception to be able to handle it in a catch clause. The checked exception is then forced by contract to implement a catch handler, potentially avoiding a thread termination.

In practice, we discourage extending RuntimeException because it can lead to poor exception handling. Our rule of thumb is that if it's possible to recover from an exception, you should create the associated exception class by extending Exception. If a developer cannot reasonably be expected to recover from the exception (say a NullPointerException), extend RuntimeException.

6-8. Rethrowing the caught Exception

Sometimes you have a multicatch statement, but want to re-throw the original exception with the exception type that was caught. Before Java 7, re-throwing the same exception involves a series of catch statements for each specific exception and then a throw on that exception, making the code verbose. Now with Java 7, the steps are much clearer.

Problem

You need to rethrow the same exception type as you caught.

Solution

Now you can just throw the exception from a catch block, and it will rethrow it on the same type as it was caught:

    private void doSomeWork() throws IOException, InterruptedException {
        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<String>();

        try {
            FileOutputStream fos = new FileOutputStream("out.log");
            DataOutputStream dos = new DataOutputStream(fos);
            while (!queue.isEmpty()) {
                dos.writeUTF(queue.take());
            }
        } catch (InterruptedException | IOException e ) {
            e.printStackTrace();
throw e;
        }

    }

How It Works

Starting with Java 7, you can simply throw the exception that you've caught, and the JVM will bubble the exception to the appropriate type. Note that if you are throwing a checked exception, then these have to be also defined in the Method declaration.

6-9. Logging Events in Your Application

Problem

You like to start logging events, debug messages, error conditions, and so on in your application.

Solution

Use SLF4J, along with the Java Logging API to implement your logging solution. The following example first creates a logger object with the name of recipeLogger. The example then proceeds to log an informational message, a warning message, and an error message:

    private void loadLoggingConfiguration() {
        FileInputStream ins = null;
        try {
            ins = new FileInputStream(new File("logging.properties"));
            LogManager.getLogManager().readConfiguration(ins);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private void start() {
        loadLoggingConfiguration();
        Logger logger = LoggerFactory.getLogger("recipeLogger");
        logger.info("Logging for the first Time!");
        logger.warn("A warning to be had");
        logger.error("This is an error!");
    }

How It Works

The loadLogConfiguration() function opens a stream to the logging.properties file and passes it to the java.util.logging.LogManager(). Doing so configures the java.util.logging framework to use the settings specified in the logging.properties file. Then, within the start method of the recipe, the code acquires a logger object named recipeLogger. The example proceeds to log messages to through recipeLogger. More information on the actual logging parameters can be found in recipe 6-10.

SLF4J provides a common API using a simple façade pattern that abstracts the underlying logging implementation. SLF4J can be used with most of the common logging frameworks, such as the Java Logging API (java.util.logging), Log4j, Jakarta Commons Logging, and others. In practice, SLF4J gives you the flexibility to choose (and swap) your logging framework, and allows projects that use SLF4J to quickly be integrated into your selected logging framework.

To use SLF4J in your application, you first have to download the SLF4J binaries located at http://www.slf4j.org/. Once downloaded, extract their contents and add slf4j-api-1.6.2.jar to your project. This is the main .jar file that contains the SLF4J API (on which your program can call and log information). After adding the slf4j-api-1.6.2.jar file to your project, find slf4j-jdk14-1.6.2.jar and add that to your project. This second file indicates that SLF4j will use the java.util.logging classes to log information.

The way SLF4J works is that at runtime SLF4J scans the classpath and picks the first .jar that implements the SLF4J API. In the example case, the slf4j-jdk14-1.6.2.jar is found and loaded. This .jar represents the native Java Logging Framework (known as jdk.1.4 logging). If for example, you wanted to use another logging framework, you would replace slf4j-jdk14-1.6.2.jar with the corresponding SLF4J implementation for your logger. For example, to use Apache's Log4J logging framework, you would include slf4j-log4j12-1.6.2.jar.

Images Note The java.util.logging framework is configured by the properties log file.

Once SLF4J is configured, you can log information in your application by calling the SLF4J logging methods. The methods log information depending on the logging level. The logging level can then be used to filter which messages are actually logged. The ability to filter messages by log level is useful because there might be a lot of informational or debugging information being logged. If there is the need to troubleshoot an application, the logging level can be changed, and more information is visible in the logs without changing any code. The ability to filter messages through their level is called setting the log level. Each logging framework reference has its own configuration file that sets the log level (among other things, such as the logging file name and logging-file configurations). In the example case, because SLF4j is using the java.util.logging framework to log, one would need to configure the java.util.logging properties for logging.

Images

Images Note When setting the log level, loggers will log at that level and below. So if a logging configuration sets the log level to info, messages at the Info, Warn, Error, and Fatal levels will be logged.

6-10. Rotating and Purging Logs

Problem

You have started to log information, but the information logged keeps growing out of control. You would like to only keep the last 250KB worth of log entries in your log files.

Solution

Use SLF4J with java.util.logging to configure rolling logs. In this recipe, we get the logger named recipeLogger and log many times into this logger. The output will produce rolled log files with the most recent logged information in the important Log0.log file. Here is the recipe code:

          loadLoggingConfiguration();

          Logger logger = LoggerFactory.getLogger("recipeLogger");
          logger.info("Logging for the first Time!");
          logger.warn("A warning to be had");
          logger.error("This is an error!");

          Logger rollingLogger = LoggerFactory.getLogger("rollingLogger");
          for (int i =0;i < 5000;i++) {
              rollingLogger.info("Logging for an event with :"+i);
        }

logging.properties file

handlers = java.util.logging.FileHandler

recipeLogger.level=INFO

.level=ALL

java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.pattern=importantLog%g.log
java.util.logging.FileHandler.limit=50000
java.util.logging.FileHandler.count=4

How It Works

Configure the java.util.logging framework and specify rolling log files. Choosing the rolling log files option causes the latest information to be kept in ImportantApplication0.log. Progressively older information will be in ImportantApplication1.log, ImportantApplication2.log, and so forth. When ImportantApplication0.log fills to the limit you specify (50,000 bytes in this example), its name will be rotated to ImportantApplicationLog1.log, and the other files will have their names similarly rotated downward. The number of log files to keep is determined by the java.util.logging.FileHandler.count property, which is set to 4 in this recipe's example.

The logging.properties file starts by defining the handlers that the java.util.logging framework will use. Handlers are objects that take care of logging messages. FileHandler is specified in the recipe, which logs messages to files. Other possible handlers are the ConsoleHandler (logs to the system.output device), SocketHandler (logs to a socket), and MemoryHandler (keeps logs in a circular buffer in memory). There is also the possibility of specifying your own handler implementation by creating a class that extends the Handler abstract class.

Next comes the defining of logging levels. Within a logging framework there is the concept of separate logger objects. A logger can carry different configurations (for example, different logging levels), and can be identified in the log file. Our example configures the recipeLogger's level to info, whereas the root logger's level is ALL (root loggers in the java.util.logging framework are denoted by not having any prefix before the property).

The next part of the logging.properties file defines the FileHandler configuration. The formatter indicates how the log information will be written to disk. The simpleFormatter writes the information as plain text, with a line indicating the date and time, a line with the logging level, and the message to be logged. The other default choice for the formatter is the XMLFormatter, which will create XML markup containing the date, time, logger name, level, thread, and message information for each log event. You can create your own formatters by extending the Formatter abstract class.

Following the formatter, the fileHandler pattern is defined. This specifies the file name and location of the log files (the %d is replaced by the rolling log number [0 ~ 4]). The Limit property defines how many bytes the log can have before rolling over (50000 bytes ~ 50kb). The count defines the maximum index of log files to keep (in this recipe's case: 4).

Images Caution Logging can be expensive; if you are logging a lot of information, your Java program will start consuming memory (as the java.util.logging framework will try to keep all the information that needs to be written to disk in-memory until it can be flushed). If the java.util.logging framework cannot write the log file as fast as log entries are created, you will run into OutOfMemory errors. The best approach is to log only the necessary information, and, if needed, check to see Logger.isDebugEnabled() before writing out debugging log messages. The logging level can be changed from the logging configuration file.

6-11. Logging Exceptions

From the previous recipes you know how to catch exceptions and how to log information. This recipe will put these two recipes together

Problem

You want to be able to record exceptions in your log file.

Solution

By using SLF4J and try/catch blocks you can log exceptions to the error log. For example:

    private void start() {
        loadLoggingConfiguration();
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                rootLogger.error("Error in thread "+t+" caused by ",e);
            }
        });

        int c = 20/0;
    }

How It Works

The examples use an UncaughtExceptionHandler in conjunction with SLF4J to log exceptions to a logging file. When logging an exception, it is good to include the stack trace showing where the exception was thrown. The applicationLogger object includes a method that takes a String and a Throwable as parameters. When you invoke that function, the logger will record the stack trace of the Throwable object in the log file.

Images Note If an exception is thrown repeatedly, the JVM tends to stop populating the stack trace in the Exception object. This is done for performance reasons because retrieving the same stack trace becomes expensive. If this happens, you will see an exception with no stack trace being logged. When that happens, check the log's previous entries and see whether the same exception was thrown. If the same exception has been thrown previously, the full stack trace will be present on the first logged instance of the exception.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.159.139