Reader

A Guide to Logging and Debugging in Java

| Sentry Blog | Default

Logging and Debugging in Java: Why Does it Matter?

During the development of your program, you might rely on simple println() statements to trace program execution flows and identify issues in your code. But as projects grow in size and complexity, print statements quickly become messy. A better approach to tracing program execution is logging, an approach that provides a consistent and organized way to track your application’s behavior, allowing you to systematically identify and resolve issues.

In this guide, we’ll explore how to set up logging in Java, use essential debugging tools effectively, and apply best practices to create stable and maintainable applications.

We’ll also look at how Sentry can help you monitor and manage errors in your Java applications. By understanding the fundamentals and advanced techniques, you’ll be equipped to tackle issues confidently within your Java projects.

How to Set Up Logging in Java

You may have used System.out.println() to log and debug your code, for example:

Would output:

Debugging: Starting the program Debugging: Result is 8

This kind of console logging can be helpful for small projects or quick tests, but it has many limitations. Unsaved console logs disappear when the program stops, making it difficult to keep a history of what happened. They also don’t allow you to categorize messages by severity, which makes it hard to spot critical issues in the output.

Basic logging in Java can be achieved using the built-in java.util.logging package. This built-in framework allows you to easily record and output different events in your application, such as informational messages, warnings, or errors.

Using java.util.logging, you can specify log levels to indicate the importance of each message:

  • SEVERE: Indicates a severe error that may prevent the application from functioning correctly.

  • WARNING: Indicates a potential issue that may require attention.

  • INFO: Provides general information about the application’s execution progress.

  • CONFIG: Indicates configuration-related messages.

  • FINE, FINER, FINEST: Used for debugging purposes to provide detailed information.

You can use these log levels with the following syntax: logger.<LEVEL>("<YOUR_MESSAGE_HERE>").

You may need to add the following to your module-info.java file to allow logging:

requires java.logging;

Here’s how you can set up a logger and log messages with different levels:

This outputs:

Output of the logging example

Notice how the log messages are timestamped and include the class name and method where the log statement was called. This information can be invaluable when tracking down bugs in complex applications.

Setting the Log Level in Java

In the example above you’ll notice that only the INFO, WARNING, and SEVERE messages are logged, while the CONFIG and FINE messages are not.

This is because the default log level is set to INFO, which means only messages of INFO level and above will be recorded.

Back to the Log level hierarchy:

  • SEVERE

  • WARNING

  • INFO Only this level and above are logged by default

  • CONFIG

  • FINE

  • FINER

  • FINEST

You can change the log level to control which messages are recorded. For example, to log messages of FINE level and above, you can use a ConsoleHandler to set the log level to FINE:

Now the output will include the FINE and CONFIG messages:

Output of the logging example with FINE level

How to Log to a File in Java

You can also save logs to a file, which allows you to keep a persistent record of your application’s behavior for future analysis. Saving logs to a file makes it easier to track down issues after the application has stopped running or to compare logs from different runs to identify patterns across different configurations.

Let’s extend the previous example to save the log messages to a file. We’ll use the FileHandler class to write logs to a file called application.log:

Running this code saves the log messages to a file named application.log in the project directory. You can then open this file to review the logs:

Screenshot of the log file content

Logging Variables and Exceptions in Java

When debugging, it’s often helpful to log the values of variables or exceptions in Java to understand the state of the application at a particular point in time.

Suppose we have a method that divides two numbers, and we want to log the values of the numbers being divided:

This will output: 2024-11-18 14:30:00 INFO LoggingVariablesExample: Dividing 50 by 10 2024-11-18 14:30:00 INFO LoggingVariablesExample: Result: 5

Now, say we want to log an exception that occurs when dividing by zero, and we want to see the stack trace. We’ll change the b variable’s value to 0:

This will output:

Notice how the stack trace is included in the log message, which lets us know exactly where the exception occurred.

For more on Java exception handling, we did a full breakdown here.

Best Practices for Logging in Java

Here are some best practices to keep in mind:

Set the optimal logging level: Logs are helpful only when you can use them to track down important errors that need to be fixed. Depending on the specific application, be sure to set the optimal logging level. Logging too many events can be suboptimal from a debugging viewpoint, as it’s difficult to filter through the logs to identify errors that require immediate attention.

Avoid logging sensitive information: Be cautious about logging sensitive information such as passwords, API keys, or personal data.

Rotate the log files to facilitate easier debugging: Log files for extensive applications with several modules are likely to be large. Rotating log files periodically can make it easier to debug such applications. Rotate log files by setting a maximum file size or time interval after which a new log file is created. This way, you can easily locate issues by looking at the most recent log files.

For example, you can set up a FileHandler with the following syntax:

This will create a new log file when the current file reaches the specified size, up to the maximum number of files specified.

Use log formatting to make logs more readable: Log formatting can help make your logs more readable and consistent. You can use a Formatter to customize the format of your log messages. For example, you can include the timestamp, log level, class name, and message in a specific format.

This will format the log messages with a timestamp down to the second, the log level, and the message:'

Java Debugging 101: How to Debug in Eclipse

Java debugging involves tools and techniques that help identify and fix issues in your code. Understanding how to effectively use breakpoints, inspect variables, and step through the code is key to debugging faster.

Let’s debug our division example in Eclipse. Eclipse is one of the most popular Java IDEs and offers powerful debugging features that make it easier to find and resolve bugs.

We’ll be debugging the following code, which has an error where b is set to x - x rather than x + x:

Let’s say we wanted to set the value of the variable b by adding x + x, but we made an error and set the value by subtracting x - x, as in the code above. This would lead to the variable b holding a value of zero. This value is set as the value for the y argument in the divide method, leading to a division-by-zero error.

Stack trace

The divide() function will throw an ArithmeticException when trying to divide by zero. Ideally, we would want to add an exception handler to handle this case, but for the purpose of debugging, we will focus on identifying the issue and fixing it temporarily.

Notice the program execution stack trace shows us the line where the error occurred in our code. We can then set a breakpoint on this line to start debugging.

Let’s debug this example in Eclipse:

1. Set a breakpoint: Click in the left margin of the code editor to set a breakpoint. A breakpoint is a marker that tells the debugger to pause the program at that specific line. In the above code, set a breakpoint on the line int answer = x / y;. This will allow you to pause the program before the division by zero occurs.

Setting a breakpoint

2. Start the program in debug mode: Click the bug icon to start the program in debug mode. This allows you to run the code up to the breakpoint and inspect the state of your program.

Starting debug mode

3. Inspect variables at the breakpoint: Once the program pauses at the breakpoint, you can inspect the values of variables in the Variables view. In this case, you’ll notice that the value of y is 0, which leads to the division-by-zero error.

Inspecting variables and stepping through code

4. Temporarily fix the issue: We can temporarily fix the issue by directly changing the value of y to 10, which is 5 + 5, in the variables view pane, and resume program execution to see a result.

Temporary fixing the issue

Clicking the Resume button will continue running the program after making the temporary fix. This will allow you to see a result without the division by zero error.

Click the resume button to continue running the program

You can then correct the code responsible for setting the variable b value to zero and leading to the y argument holding a zero value. Change the code from int b = x - x; to int b = x + x; and run the program to see the corrected output:

Corrected output after temporary fix

As you can see from this example, debugging allows you to identify issues in your code, understand how the program behaves, and make the necessary corrections to ensure it runs correctly.

Using Third-Party Java Logging Libraries

While the built-in java.util.logging package is sufficient for basic logging needs, third-party logging libraries offer additional features and flexibility that can be beneficial in more complex projects.

Two popular logging libraries in the Java ecosystem are Log4j and Logback. These libraries offer features such as logging to multiple destinations, filtering log messages, and configuring log levels dynamically.

To learn more about these libraries and how they can be integrated into your Java projects, you can refer to the official documentation:

Sentry offers integration with both libraries. Consult the guides to learn how to set up Sentry with Log4j and with Logback.

How Sentry Can Help with Logging and Debugging in Java

Sentry helps you track issues in real time and provides insights into the errors that occur in your application. You can integrate Sentry into your Java application to catch errors and monitor performance.

To get started, you’ll need to add the Sentry Java SDK to your project and configure it using your data source name (DSN).

You can obtain a DSN by creating a project in the Sentry dashboard and navigating to [Your-Project] > Project Settings > Client Keys (DSN).

You can add Sentry to your project by including the plugin in your build tool. Here’s how you can add Sentry to your project using Maven:

Add the following configuration in the pom.xml file (replace the values with your own):

Consult the Sentry for Java documentation for detailed instructions on how to set up Sentry in your Java project.

Let’s see how to integrate Sentry into our example division program from earlier:

After configuring the application with your DSN value, you can run your Java application and any errors will be logged to your Sentry project.

Exception captured by Sentry

Once an error is captured, you can head over to the Sentry dashboard, where you will see a report of the error, including the affected class, the frequency of the issue, and any breadcrumbs that led to the error.

Sentry Dashboard

Sentry also captures the full stack trace, allowing you to pinpoint the exact location of the error in your code.

By using Sentry, you can also monitor unhandled exceptions and track performance issues to keep your code running smoothly.

Track and Manage Errors with Sentry

This guide discusses the importance of logging and debugging in Java, how to set up logging using the built-in java.util.logging package, and essential debugging techniques in Eclipse.

We’ve also looked at how third-party logging libraries like Log4j and Logback can help manage logs in complex projects.

Finally, we looked at how Sentry can enhance your Java application by providing real-time error monitoring and performance insights. By integrating Sentry into your project, you can effectively track and manage errors, ensuring your application runs smoothly and efficiently.