Exceptions and Logging - Java 8 Recipes, 2th Edition (2014)

Java 8 Recipes, 2th Edition (2014)

CHAPTER 9. Exceptions and Logging

Exceptions are a way of describing exceptional circumstances within a program. They are an indicator that something unexpected (exceptional) has occurred. 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 utilize exceptions judiciously benefit from a better control flow and become more robust. Even so, using exceptions indiscriminately can cause performance degradation.

Within Java, exceptions can be thrown or caught. Throwing an exception involves telling the code that you have encountered an exception, 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 to occur. This is denoted within the try/catch Java block (described in Recipe 9-1)

All exceptions inherit from Throwable, as shown in Figure 9-1. Classes that are inherited from Throwable can 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 exceptions since they are considered fatal (think of a computer being on fire). The bulk of exceptions within a Java program will be inherited from the Exception class.

9781430268277_Fig09-01.jpg

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

Within the JVM there are two types of exceptions: checked and unchecked. Checked exceptions are enforced by methods. In the method signature, you can specify the kind of exceptions a method can throw. This requires any caller of the method to create a try/catch block, which handles the exceptions that were declared within the method signature. Unchecked exceptions do not require such a stringent convention, and are free to be thrown anywhere without enforcing the implementation of a try/catch block. Even so, unchecked exceptions (as described in Recipe 9-6) are usually discouraged because they can lead to threads unraveling (if nothing catches the exception) and poor visibility of problems. Exception classes that inherit from RuntimeException are considered to be unchecked exceptions, whereas exception 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 try to throw an InvalidUsernameException to indicate failure.

While exceptions play an essential role in solid software development, logging of exceptions can be just as important. Logging within an application helps the developer understand what events are occurring 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 occurring (most likely what went wrong) and helps you troubleshoot production problems. Many developers choose to utilize a structured logging framework to provide more robust logging for an application. A solid logging framework with a sound 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 the recipes in this chapter, you 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, download SLF4J (http://www.slf4j.org/) and put it in your project’s dependency path.

9-1. Catching Exceptions

Problem

You want to gracefully handle any exceptions generated from your code.

Solution

Use the built-in try/catch language construct to catch exceptions. Do so by wrapping any blocks of code that may throw an exception within a try/catch block. In the following example, a method is used to generate a Boolean value to indicate whether a specified string is greater than five characters long. If the string that’s passed as an argument is null, a NullPointerException is thrown by the length() method and caught within the catch block.

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

}

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

How It Works

The try keyword specifies that the enclosed code segment have the potential to raise an exception. The catch clause is placed at the end of the try clause. Each catch clause specifies which exception is being caught. If a catch clause is not provided 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.

If a try clause needs to catch more than one exception, more than one catch clause can be specified. For instance, the following try/catch block could be used for catching both a NumberFormatException and a NullPointerException.

try {
// code here
} catch (NumberFormatException|NullPointerException ex) {
// logging

}

For more information regarding catching multiple exceptions, see Recipe 9-4.

Image Note Be careful when throwing an exception. 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.

9-2. Guaranteeing A Block Of Code is Executed

Problem

You want to write code that executes when control leaves a code segment, even if control leaves due to an error being thrown or the segment ending abnormally. 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. Place the code that you want to have executed regardless of exceptions into the finally clause. 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

Code that is placed within the finally clause of a try/catch/finally block will always be executed. In this example, 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.

Image 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.

9-3. Throwing Exceptions

Problem

You want to abort the execution of the current code path by throwing an exception if a certain situation occurs within your application.

Solution

Use the throw keyword to throw a specified exception when the situation occurs. Using the throw keyword, you can signal the current thread to look for try/catch blocks (at the current level and up the stack), which can process the thrown exception. In the following example, thecallSomeMethodThatMightThrow throws a NullPointerException 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 method callSomeMethodThatMightThrow checks to ensure that a valid argument was passed to it. If the argument is null, it then throws a NullPointerException, 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.

Image Note 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.

9-4. Catching Multiple Exceptions

Problem

A block of code in your application has the tendency to throw multiple exceptions. You want to catch each of the exceptions that may occur within a try block.

Solution 1

More than one catch clause can be specified in situations where multiple exceptions occur within the same block. Each catch clause can specify a different exception to handle, so that each exception can be handled in a different manner. In the following code, two catch clauses are used to handle an IOException and a ClassNotFoundException.

try {
Class<?> stringClass = Class.forName("java.lang.String");
FileInputStream in = new FileInputStream("myFile.log") ; // Can throw 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 your application has the tendency to throw multiple exceptions within a single block, then a vertical bar operator (|) can be utilized for handling each of the exceptions in the same manner. In the following example, the catch clause specifies multiple exception types separated with a vertical bar (|) to handle each of the exceptions in the same manner.

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

There are a couple of different ways to handle situations where multiple exceptions may be thrown. You can specify separate catch clauses to handle each of the exceptions in a different way. To handle each of the exceptions in the same manner, you can utilize a single catch clause and specify each exception separated with a vertical bar operator.

Image Note If you’re 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 all the same. This becomes a problem because the block can catch other exceptions that may occur deeper within the call stack that you may not have intended the block to catch (an OutOfMemoryException). It is a best practice to specify each possible exception, rather than specifying a catch-all exception handler to catch all exceptions.

9-5. Catching the Uncaught Exceptions

Problem

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

Solution 1

When creating a Java thread, sometimes you need to ensure that any exception is caught and handled properly to help determine the reason for the thread termination. To that effect, Java allows you to register an ExceptionHandler() either per thread or globally. The following code demonstrates an example of registering an exception handler on a per-thread basis.

private void start() {
Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> {
System.out.println("Woa! there was an exception thrown somewhere! "+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));
}
}

The for loop in this thread will execute properly until an exception is encountered, at which time the DefaultUncaughtExceptionHandler will be invoked. UncaughtExceptionHandler is a functional interface, so it is possible to utilize a lambda expression to implement the exception handler.

Solution 2

It is possible to register an UncaughtExceptionHandler on a specific thread. After doing so, any exception that occurs within the thread and that has not been caught will be handled by the uncaughtException() method of the UncaughtExceptionHandler(). For example:

private void startForCurrentThread() {
Thread.currentThread().setUncaughtExceptionHandler((Thread t, Throwable e) -> {
System.out.println("In this thread "+t.getName()+" an exception was thrown "+e);
});

Thread someThread = new Thread(() -> {
System.out.println(200/0);
});
someThread.setName("Some Unlucky Thread");
someThread.start();

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

In the previous code, an UncaughtExceptionHandler is registered on the currentThread. Just like Solution 1, UncaughtExceptionHandler is a functional interface, so it is possible to utilize a lambda expression to implement the exception handler.

How It Works

The Thread.defaultUncaughtExceptionHandler() will be invoked for each unchecked exception that has not been caught. When the UncaughtExceptionHandler() handles an exception, it means that there was no try/catch block in place to catch the exception. As such, the exception 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. TheUncaughtExceptionHandler() 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 thedefaultUncaughtExceptionHandler(). Again, in either situation, the thread originating the exception will terminate.

Image Tip When dealing with multiple threads, it is always good practice to explicitly name the threads. It makes life easier to know exactly which thread caused the exception, rather than having to trace down an unknown thread named like Thread-## (the default naming pattern of unnamed threads).

9-6. Managing Resources with try/catch Blocks

Problem

In the event of an exception, you need to ensure that any resources used within a try/catch block are released.

Solution

Make use of the Automatic Resource Management (ARM) feature, which can be specified with a try-with-resources statement. When using a try-with-resources statement, any resources that are specified within the try clause are automatically released when the block terminates. In the following code, the FileOutputStream, BufferedOutputStream, and DataOutputStream resources are automatically handled by the 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

In most cases, you want to cleanly close/dispose of resources that are acquired within a try/catch block after the block execution is complete. If a program does not close/dispose of its resources or does so improperly, the resources could be acquired indefinitely, causing issues such as memory leaks to occur. Most resources are limited (file handles or database connections), and as such will cause performance degradation (and more exceptions to be thrown). To avoid these situations, Java provides a way of automatically releasing resources when an exception occurs within a try/catch block. By declaring a try-with-resources block, the resource on which the try block was checked will be closed if there is an exception thrown within the block. Most of the resources that are built into Java will work properly within a try-with-resourcesstatement (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 statement involves the try keyword, followed by an opening parenthesis and then followed by all the resource declarations that you want to have released in the event of an exception or when the block completes, and ending with a closing parenthesis. Note that if you try to declare a resource/variable that doesn’t implement the AutoCloseable interface, you will receive a compiler error. After the closing parenthesis, the syntax of the try/catch block is the same as a normal block.

The main advantage of the try-with-resources 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 resources in exceptional conditions requires checking the status of each dependent resource and carefully disposing of it, and doing so requires that you write a lot of code. By contrast, the try-with-resources construct allows the JVM to take care of proper disposal of resources, even in exceptional conditions.

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

9-7. Creating an Exception Class

Problem

You want to create a new type of exception that can be used to indicate a particular event.

Solution 1

Create a class that extends java.lang.RuntimeException to create an exception class that can be thrown at any time. In the following code, a class identified by IllegalChatServerException extends RuntimeException and accepts a string as an argument to the constructor. The exception is then thrown when a specified event occurs within the code.

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

Create a class that extends java.lang.Exception to generate a checked exception class. A checked exception is required to be caught or re-thrown up the stack. In the following example, a class identified as ConnectionUnavailableException extendsjava.lang.Exception and accepts a string as an argument to the constructor. The checked exception is then thrown by a method in the code.

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 there is a requirement to create custom exceptions, especially in situations when you’re 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 theIllegalArgumentException for illegal parameters. If there isn’t a JDK exception that fits cleanly, you can always extend java.lang.Exception or java.lang.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 it to be caught up the stack. 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 the exception is not caught. Extending Exception instead allows you to clearly force any code that throws the exception to be able to handle it within 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), then extend RuntimeException.

9-8. Rethrowing the Caught Exception

Problem

Your application contains a multi-catch exception, and you want to re-throw an exception that was previously caught.

Solution

Throw the exception from a catch block, and it will rethrow it on the same type as it was caught. In the following example, exceptions are caught within a block of code and rethrown to the method’s caller.

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

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

It is possible to simply throw the exception that has been previously caught, and the JVM will bubble the exception to the appropriate type. Note that you’re if throwing a checked exception, it must also be defined in the method declaration.

9-9. Logging Events Within Your Application

Problem

You want to log events, debug messages, error conditions, and other events within your application.

Solution

Utilize SLF4J within your application, along with the Java Logging API, to implement a 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 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 9-10.

SLF4J provides a common API using a simple facade 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 provides the flexibility to choose (and swap) logging frameworks and allows projects that use SLF4J to quickly become integrated into an application’s selected logging framework.

To use SLF4J in an application, download the SLF4J binaries located at http://www.slf4j.org/. Once they’re downloaded, extract the contents and add slf4j-api-x.x.x.jar to the project. This is the main .jar file that contains the SLF4J API (on which a program can call to log information). After adding the slf4j-api-x.x.x.jar file to the project, find slf4j-jdk14-x.x.x.jar and add that to the 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 class path and picks the first .jar that implements the SLF4J API. In the example case, the slf4j-jdk14-x.x.x.jar is found and loaded. This .jar represents the native Java Logging Framework (known as jdk.1.4logging). If, for example, you wanted to use another logging framework, replace slf4j-jdk14-x.x.x.jar with the corresponding SLF4J implementation for the desired logger. For example, to use Apache’s Log4J logging framework, include slf4j-log4j12-x.x.x.jar.

Image 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 may 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 can be made visible in the logs without changing any code. The ability to filter messages through their level is referred to as setting the log level. Each logging framework reference contains 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 thejava.util.logging framework to log, you would need to configure the java.util.logging properties for the desired logging. See Table 9-1.

Table 9-1. Logging Levels

Logging Level

Recommendation

Trace

Least important of the logging events

Debug

Use for extra information that helps with debugging

Info

Use for everyday logging messages

Warn

Use for recoverable issues, or where the suspicions of a wrong setting/nonstandard behavior happens

Error

Use for exceptions, actual errors, and things that you really need to know

Fatal

Most important

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

9-10. Rotating and Purging Logs

Problem

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

Solution

Use SLF4J with java.util.logging to configure rolling logs. In this example, a logger named recipeLogger is used to log many messages. The output will produce rolled log files with the most recent logged information in the important Log0.log file.

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

To control the size of log files, 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 inImportantApplication1.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 maintain is determined by the java.util.logging.FileHandler.count property, which is set to 4 in this recipe’s example.

The logging.properties file begins 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 theConsoleHandler (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 Handlerabstract class.

Next, the logging levels are defined. 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. The 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 section 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 XMLFormatter, which will create XML markup containing the date, time, logger name, level, thread, and message information for each log event. You can create custom formatters by extending theFormatter 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 (50,000 bytes ~ 50kb). The count defines the maximum index of log files to keep (in this recipe’s case, it’s 4).

Image Note 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 thejava.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.

9-11. Logging Exceptions

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

Problem

You want to record exceptions in your log file.

Solution

Configure your application to use SLF4J. Utilize try/catch blocks to log exceptions within the error log. In the following example, an SLF4J Logger is used to log messages from within an exception handler.

static Logger rootLogger = LoggerFactory.getLogger("");
private void start() {
loadLoggingConfiguration();
Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> {
rootLogger.error("Error in thread "+t+" caused by ",e);
});

int c = 20/0;
}
private void loadLoggingConfiguration() {
FileInputStream ins = null;
try {
ins = new FileInputStream(new File("logging.properties"));
LogManager.getLogManager().readConfiguration(ins);
} catch (IOException e) {
e.printStackTrace();
}
}

How It Works

The example demonstrates how to 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. In the example, a thread contains anUncaughtExceptionHandler, which utilizes a lambda expression containing a logger. The logger is used to write any caught exceptions to a log file.

Image 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.