Capturing Java Exceptions with Sentry
Getting started with Java exception handling can be an intimidating prospect. The large number of dependency managers, logging frameworks, configuration methods, and programming languages that run on the JVM can create a dizzying number of options for a programmer to choose between, so we’ve put this guide together to help you navigate the world of modern Java error tracking with Sentry.
Choosing an Integration
The Sentry Java SDK (sentry-java
) is hosted at Maven Central, a repository
that hosts many widely distributed Java packages (JARs) and works with many of
the popular JVM dependency managers, including Maven. A search for Sentry on
Maven Central yields several results for different distributions of the
project, and in this post we’ll help you figure out which is the right one to
use with your application.
The major components of the SDK can be divided into two categories:
- Logging integrations, commonly referred to as appenders, used to
translate log records generated by your application via a logging framework
into Sentry events. These integrations are packaged as
sentry-logback
,sentry-log4j
, andsentry-log4j2
. - Sentry, used by the logging integrations to send events to a Sentry server.
This is packaged as
sentry
and also includes the logging handler forjava.util.logging
.
For most applications, we recommend using one of the logging integrations to communicate with a Sentry server, and we’ll be mainly focusing on those in this post. For existing applications that are already using a logging framework, integrating with Sentry is often as simple as adding or changing a few lines in your logging configuration files. We recommend using the logging integrations over interfacing with Sentry directly for several reasons:
- The logging APIs are easy to understand. The patterns and vocabulary used by logging APIs are familiar to many developers, even across different platforms.
- Producing messages via the logging frameworks reduce the amount of
boilerplate code. For example, compare the logback appender event building
method with a call to the
logger.error
method. - Using a logging framework provides greater control over reporting configuration. It is easy to replace reporting to Sentry in your development environment with console logging by using a different logging configuration file. Contrast this with having to wrap all calls in conditionals or mocking out the Sentry interface with a fake instance.
- Many other libraries also utilize logging frameworks. Using the logging framework enables collecting better data from other libraries in your application that also utilize the logging system, such as database drivers or a web framework.
The Java Logging Ecosystem
If you’re new to Java and have a background in a platform such as Python with a de facto logging solution, one of the first things that you will notice is the large number of logging frameworks that are available and remain used today. To understand why so many different frameworks exist, we’ll start with a quick history lesson.
Log4j was introduced in 1999 and was one of the first widely adopted logging frameworks. A few years later in 2002, Sun introduced an alternative framework, java.util.logging (commonly referred to by the acronym “JUL”), which is distributed with the Java platform. In reaction, the Apache Commons Logging project was created with the intention of providing a unified logging API that could be used to interact with whichever backing logging implementation that the user preferred, including Log4j and JUL (among others.) However, yet another alternative “unified” logging API emerged in 2005 from the author of Log4j — SLF4J, the “Simple Logging Facade for Java.” Later, the author of Log4j and SLF4J released logback, which provided a native implementation of the SLF4J interface. Around the same time, development also began on the (now stable) Log4j 2 — without the author of the first version. Simple, right?
There was a lot of code that was written at different points in time during this history and like so much software there was — and still is — lot of debates on which framework is the best choice. Since many of these projects are still in use today, Sentry provides integrations for several of the most widely adopted frameworks including JUL, logback, Log4j, and Log4j 2. Getting started is typically a matter of configuration, and the documentation for each integration contains instructions on how to include the integration as a dependency as well as a sample configuration to help you get started.
Choosing the Right Integration for Your Projects
When choosing which logging framework to use for your own projects, that decision is often mandated to you by your choice of framework. For projects that don’t depend on a framework, targeting the SLF4J interface is often recommended due to its ubiquity and flexibility. The backing logging implementation is largely a matter of developer preference, with logback and Log4j 2 being common choices.
Starting from Scratch
For example, it only takes a few moments to start reporting to Sentry with SLF4J and logback.
-
Add sentry-logback to your project dependencies. If you’re using Maven, dependencies are declared in
pom.xml
, and you can see an example in our logback example project. If you’re not using Maven, the project details on Maven Central have dependency information for a variety of different package managers.<dependency> <groupId>io.sentry</groupId> <artifactId>sentry-logback</artifactId> <version>1.2.0</version> </dependency>
-
Add the Sentry appender to your
logback.xml
configuration file. You can
set the Sentry DSN for your project via JDNI, as the SENTRY_DSN
environment
variable or sentry.dsn
system property.
<appender name="Sentry" class="io.sentry.logback.SentryAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="Sentry"/>
</root>
- Add logging calls to your application. It is usually most effective to
add logging.error
calls as far towards the top of the stack in your
application as possible — good [examples are a main
method in a console
application] _logback-example/app, or request handler in a web application —
to capture as many exceptions as possible. It can also be helpful to log errors
as a warning or lower level in other try
/catch
blocks where checked
exceptions are captured.
public static void main(String[] args)
{
try {
application.run()
} catch (Exception e) {
logger.error("Caught exception!", e);
}
}
- That’s it! Your project should now be reporting Java errors to Sentry. Tell your
boss that you deserve a promotion!
We have several other example integrations in our sentry-java-examples
repository, and are adding more all the time.
Beyond Log Messages
The Sentry Java SDK also provides additional features that extend beyond simple log messages and tracebacks.
HTTP Request Details
When developing a web application, much of the state that is useful for identifying patterns among exception reports is contained within the attributes of the HTTP request, such as the logged in user account, request URL, headers, and query parameters. If your deployment environment utilizes Java Servlets, these attributes are included automatically on all log messages that occur during the context of a request.
Mapped Diagnostic Context
In addition to HTTP request attributes, it can often be helpful to add additional context to exception reports that are specific to your application. For example, an if you’re running an e-commerce application, it can be helpful to track what step of a checkout process a customer was in when an error occurred. For a consumer product, it can be helpful to track the subscription that the user is a member of so you can effectively triage issues by customer impact. For a stream processing system, it can be helpful to track the component in the processing pipeline where the error occurred.
The SLF4J API provides a feature called the Mapped Diagnostic Context, or MDC, for recording this data. This data is represented as tags or extra data in the Sentry UI. (This feature is available when using the Log4j, Log4j 2, and logback appenders.)
MDC.put("checkout-step", "submit");
try {
// Any exceptions raised during order submission will include the
// `checkout-step` tag.
order.submit();
} finally {
MDC.remove("checkout-step");
}
In addition to user supplied tags, a few additional tags are included in each event, including the logger, log level, and hostname.
Beyond Log Messages
Asynchronous Logging
A common concern with over-the-network logging is the performance hit incurred from communicating synchronously with a remote server. Asynchronous logging prevents network latency due to logging from directly impacting your application. The SDK design also enables asynchronous logging by frameworks that do not natively support asynchronous appenders. The ability to tune thread pool size, priority, and queue size also allows you to have fine-grained control over how much logging effects your application’s CPU, memory, and latency profile.
Not Just for Java
Although the project is named sentry-java
on GitHub, the Sentry Java SDK can
be used by any JVM language that provides Java interoperability capabilities,
including Clojure, Scala, Kotlin, and Groovy. Because idiomatic Clojure is quite
different than the other languages, we offer a sentry-clojure SDK wrapper
for a more native experience.
Take a look at the documentation for details on implementing the Sentry SDK. If you’re not yet a Sentry user, you can sign up at any time.