Skip to content

Send Log4j2 logs to Sentry as logs #4517

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@
```properties
io.sentry.jul.SentryHandler.minimumLevel=CONFIG
```
- Send Log4j2 logs to Sentry as logs ([#4517](https://github.com/getsentry/sentry-java/pull/4517))
- You need to enable the logs feature either in `sentry.properties`:
```properties
logs.enabled=true
```
- If you manually initialize Sentry, you may also enable logs on `Sentry.init`:
```java
Sentry.init(options -> {
...
options.getLogs().setEnabled(true);
});
```
- It is also possible to set the `minimumLevel` in `log4j2.xml`, meaning any log message >= the configured level will be sent to Sentry and show up under Logs:
```xml
<Sentry name="Sentry"
dsn="your DSN"
minimumBreadcrumbLevel="DEBUG"
minimumEventLevel="WARN"
minimumLevel="DEBUG"
/>
```

## 8.15.1

Expand Down
4 changes: 3 additions & 1 deletion sentry-log4j2/api/sentry-log4j2.api
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ public final class io/sentry/log4j2/BuildConfig {
public class io/sentry/log4j2/SentryAppender : org/apache/logging/log4j/core/appender/AbstractAppender {
public static final field MECHANISM_TYPE Ljava/lang/String;
public fun <init> (Ljava/lang/String;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Ljava/lang/Boolean;Lio/sentry/ITransportFactory;Lio/sentry/IScopes;[Ljava/lang/String;)V
public fun <init> (Ljava/lang/String;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Ljava/lang/Boolean;Lio/sentry/ITransportFactory;Lio/sentry/IScopes;[Ljava/lang/String;)V
public fun append (Lorg/apache/logging/log4j/core/LogEvent;)V
public static fun createAppender (Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Ljava/lang/String;Ljava/lang/Boolean;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;)Lio/sentry/log4j2/SentryAppender;
protected fun captureLog (Lorg/apache/logging/log4j/core/LogEvent;)V
public static fun createAppender (Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Ljava/lang/String;Ljava/lang/Boolean;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;)Lio/sentry/log4j2/SentryAppender;
protected fun createBreadcrumb (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/Breadcrumb;
protected fun createEvent (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/SentryEvent;
public fun start ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@
import io.sentry.InitPriority;
import io.sentry.ScopesAdapter;
import io.sentry.Sentry;
import io.sentry.SentryAttribute;
import io.sentry.SentryAttributes;
import io.sentry.SentryEvent;
import io.sentry.SentryIntegrationPackageStorage;
import io.sentry.SentryLevel;
import io.sentry.SentryLogLevel;
import io.sentry.SentryOptions;
import io.sentry.exception.ExceptionMechanismException;
import io.sentry.logger.SentryLogParameters;
import io.sentry.protocol.Mechanism;
import io.sentry.protocol.Message;
import io.sentry.protocol.SdkVersion;
Expand Down Expand Up @@ -50,6 +54,7 @@ public class SentryAppender extends AbstractAppender {
private final @Nullable ITransportFactory transportFactory;
private @NotNull Level minimumBreadcrumbLevel = Level.INFO;
private @NotNull Level minimumEventLevel = Level.ERROR;
private @NotNull Level minimumLevel = Level.INFO;
private final @Nullable Boolean debug;
private final @NotNull IScopes scopes;
private final @Nullable List<String> contextTags;
Expand All @@ -59,12 +64,42 @@ public class SentryAppender extends AbstractAppender {
.addPackage("maven:io.sentry:sentry-log4j2", BuildConfig.VERSION_NAME);
}

/**
* @deprecated This constructor is deprecated. Please use {@link #SentryAppender(String, Filter,
* String, Level, Level, Level, Boolean, ITransportFactory, IScopes, String[])} instead.
*/
@Deprecated
@SuppressWarnings("InlineMeSuggester")
public SentryAppender(
final @NotNull String name,
final @Nullable Filter filter,
final @Nullable String dsn,
final @Nullable Level minimumBreadcrumbLevel,
final @Nullable Level minimumEventLevel,
final @Nullable Boolean debug,
final @Nullable ITransportFactory transportFactory,
final @NotNull IScopes scopes,
final @Nullable String[] contextTags) {
this(
name,
filter,
dsn,
minimumBreadcrumbLevel,
minimumEventLevel,
null,
debug,
transportFactory,
scopes,
contextTags);
}

public SentryAppender(
final @NotNull String name,
final @Nullable Filter filter,
final @Nullable String dsn,
final @Nullable Level minimumBreadcrumbLevel,
final @Nullable Level minimumEventLevel,
final @Nullable Level minimumLevel,
final @Nullable Boolean debug,
final @Nullable ITransportFactory transportFactory,
final @NotNull IScopes scopes,
Expand All @@ -77,6 +112,9 @@ public SentryAppender(
if (minimumEventLevel != null) {
this.minimumEventLevel = minimumEventLevel;
}
if (minimumLevel != null) {
this.minimumLevel = minimumLevel;
}
this.debug = debug;
this.transportFactory = transportFactory;
this.scopes = scopes;
Expand All @@ -89,6 +127,7 @@ public SentryAppender(
* @param name The name of the Appender.
* @param minimumBreadcrumbLevel The min. level of the breadcrumb.
* @param minimumEventLevel The min. level of the event.
* @param minimumLevel The min. level of the log event.
* @param dsn the Sentry DSN.
* @param debug if Sentry debug mode should be on
* @param filter The filter, if any, to use.
Expand All @@ -99,6 +138,7 @@ public SentryAppender(
@Nullable @PluginAttribute("name") final String name,
@Nullable @PluginAttribute("minimumBreadcrumbLevel") final Level minimumBreadcrumbLevel,
@Nullable @PluginAttribute("minimumEventLevel") final Level minimumEventLevel,
@Nullable @PluginAttribute("minimumLevel") final Level minimumLevel,
@Nullable @PluginAttribute("dsn") final String dsn,
@Nullable @PluginAttribute("debug") final Boolean debug,
@Nullable @PluginElement("filter") final Filter filter,
Expand All @@ -114,6 +154,7 @@ public SentryAppender(
dsn,
minimumBreadcrumbLevel,
minimumEventLevel,
minimumLevel,
debug,
null,
ScopesAdapter.getInstance(),
Expand Down Expand Up @@ -150,6 +191,9 @@ public void start() {

@Override
public void append(final @NotNull LogEvent eventObject) {
if (eventObject.getLevel().isMoreSpecificThan(minimumLevel)) {
captureLog(eventObject);
}
if (eventObject.getLevel().isMoreSpecificThan(minimumEventLevel)) {
final Hint hint = new Hint();
hint.set(SENTRY_SYNTHETIC_EXCEPTION, eventObject);
Expand All @@ -164,6 +208,29 @@ public void append(final @NotNull LogEvent eventObject) {
}
}

/**
* Captures a Sentry log from Log4j2's {@link LogEvent}.
*
* @param loggingEvent the log4j2 event
*/
// for the Android compatibility we must use old Java Date class
@SuppressWarnings("JdkObsolete")
protected void captureLog(@NotNull LogEvent loggingEvent) {
final @NotNull SentryLogLevel sentryLevel = toSentryLogLevel(loggingEvent.getLevel());

final @Nullable Object[] arguments = loggingEvent.getMessage().getParameters();
final @NotNull SentryAttributes attributes = SentryAttributes.of();

attributes.add(
SentryAttribute.stringAttribute(
"sentry.message.template", loggingEvent.getMessage().getFormat()));

final @NotNull String formattedMessage = loggingEvent.getMessage().getFormattedMessage();
final @NotNull SentryLogParameters params = SentryLogParameters.create(attributes);

Sentry.logger().log(sentryLevel, params, formattedMessage, arguments);
}

/**
* Creates {@link SentryEvent} from Log4j2 {@link LogEvent}.
*
Expand Down Expand Up @@ -271,6 +338,28 @@ public void append(final @NotNull LogEvent eventObject) {
}
}

/**
* Transforms a {@link Level} into an {@link SentryLogLevel}.
*
* @param level original level as defined in log4j.
* @return log level used within sentry.
*/
private static @NotNull SentryLogLevel toSentryLogLevel(final @NotNull Level level) {
if (level.isMoreSpecificThan(Level.FATAL)) {
return SentryLogLevel.FATAL;
} else if (level.isMoreSpecificThan(Level.ERROR)) {
return SentryLogLevel.ERROR;
} else if (level.isMoreSpecificThan(Level.WARN)) {
return SentryLogLevel.WARN;
} else if (level.isMoreSpecificThan(Level.INFO)) {
return SentryLogLevel.INFO;
} else if (level.isMoreSpecificThan(Level.DEBUG)) {
return SentryLogLevel.DEBUG;
} else {
return SentryLogLevel.TRACE;
}
}

private @NotNull SdkVersion createSdkVersion(final @NotNull SentryOptions sentryOptions) {
SdkVersion sdkVersion = sentryOptions.getSdkVersion();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import io.sentry.InitPriority
import io.sentry.ScopesAdapter
import io.sentry.Sentry
import io.sentry.SentryLevel
import io.sentry.SentryLogLevel
import io.sentry.checkEvent
import io.sentry.checkLogs
import io.sentry.test.initForTest
import io.sentry.transport.ITransport
import java.time.Instant
Expand Down Expand Up @@ -49,6 +51,7 @@ class SentryAppenderTest {
transportFactory: ITransportFactory? = null,
minimumBreadcrumbLevel: Level? = null,
minimumEventLevel: Level? = null,
minimumLevel: Level? = null,
debug: Boolean? = null,
contextTags: List<String>? = null,
): ExtendedLogger {
Expand All @@ -64,6 +67,7 @@ class SentryAppenderTest {
"http://key@localhost/proj",
minimumBreadcrumbLevel,
minimumEventLevel,
minimumLevel,
debug,
this.transportFactory,
ScopesAdapter.getInstance(),
Expand Down Expand Up @@ -239,6 +243,72 @@ class SentryAppenderTest {
.send(checkEvent { event -> assertEquals(SentryLevel.FATAL, event.level) }, anyOrNull())
}

@Test
fun `converts trace log level to Sentry log level`() {
val logger = fixture.getSut(minimumLevel = Level.TRACE)
logger.trace("testing trace level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.TRACE, event.items.first().level) })
}

@Test
fun `converts debug log level to Sentry log level`() {
val logger = fixture.getSut(minimumLevel = Level.DEBUG)
logger.debug("testing debug level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.DEBUG, event.items.first().level) })
}

@Test
fun `converts info log level to Sentry log level`() {
val logger = fixture.getSut(minimumLevel = Level.INFO)
logger.info("testing info level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.INFO, event.items.first().level) })
}

@Test
fun `converts warn log level to Sentry log level`() {
val logger = fixture.getSut(minimumLevel = Level.WARN)
logger.warn("testing warn level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.WARN, event.items.first().level) })
}

@Test
fun `converts error log level to Sentry log level`() {
val logger = fixture.getSut(minimumLevel = Level.ERROR)
logger.error("testing error level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.ERROR, event.items.first().level) })
}

@Test
fun `converts fatal log level to Sentry log level`() {
val logger = fixture.getSut(minimumLevel = Level.FATAL)
logger.fatal("testing fatal level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.FATAL, event.items.first().level) })
}

@Test
fun `attaches thread information`() {
val logger = fixture.getSut(minimumEventLevel = Level.WARN)
Expand Down
1 change: 1 addition & 0 deletions sentry-log4j2/src/test/resources/sentry.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
release=release from sentry.properties
logs.enabled=true
1 change: 1 addition & 0 deletions sentry-samples/sentry-samples-log4j2/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ configure<JavaPluginExtension> {
dependencies {
implementation(projects.sentryLog4j2)
implementation(libs.log4j.api)
implementation(libs.log4j.core)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
dsn="https://[email protected]/5428563"
minimumBreadcrumbLevel="DEBUG"
minimumEventLevel="WARN"
minimumLevel="DEBUG"
debug="true"
contextTags="userId,requestId"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
in-app-includes="io.sentry.samples"
logs.enabled=true
Loading