Skip to content

Commit 99d479f

Browse files
Send Log4j2 logs to Sentry as logs (#4517)
* Send Log4j2 logs to Sentry as logs * changelog * better javadoc for deprecation * Format code * trigger ci again --------- Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
1 parent 6796acc commit 99d479f

File tree

8 files changed

+187
-1
lines changed

8 files changed

+187
-1
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@
2020
```properties
2121
io.sentry.jul.SentryHandler.minimumLevel=CONFIG
2222
```
23+
- Send Log4j2 logs to Sentry as logs ([#4517](https://github.com/getsentry/sentry-java/pull/4517))
24+
- You need to enable the logs feature either in `sentry.properties`:
25+
```properties
26+
logs.enabled=true
27+
```
28+
- If you manually initialize Sentry, you may also enable logs on `Sentry.init`:
29+
```java
30+
Sentry.init(options -> {
31+
...
32+
options.getLogs().setEnabled(true);
33+
});
34+
```
35+
- 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:
36+
```xml
37+
<Sentry name="Sentry"
38+
dsn="your DSN"
39+
minimumBreadcrumbLevel="DEBUG"
40+
minimumEventLevel="WARN"
41+
minimumLevel="DEBUG"
42+
/>
43+
```
2344

2445
## 8.15.1
2546

sentry-log4j2/api/sentry-log4j2.api

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ public final class io/sentry/log4j2/BuildConfig {
66
public class io/sentry/log4j2/SentryAppender : org/apache/logging/log4j/core/appender/AbstractAppender {
77
public static final field MECHANISM_TYPE Ljava/lang/String;
88
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
9+
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
910
public fun append (Lorg/apache/logging/log4j/core/LogEvent;)V
10-
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;
11+
protected fun captureLog (Lorg/apache/logging/log4j/core/LogEvent;)V
12+
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;
1113
protected fun createBreadcrumb (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/Breadcrumb;
1214
protected fun createEvent (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/SentryEvent;
1315
public fun start ()V

sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@
1212
import io.sentry.InitPriority;
1313
import io.sentry.ScopesAdapter;
1414
import io.sentry.Sentry;
15+
import io.sentry.SentryAttribute;
16+
import io.sentry.SentryAttributes;
1517
import io.sentry.SentryEvent;
1618
import io.sentry.SentryIntegrationPackageStorage;
1719
import io.sentry.SentryLevel;
20+
import io.sentry.SentryLogLevel;
1821
import io.sentry.SentryOptions;
1922
import io.sentry.exception.ExceptionMechanismException;
23+
import io.sentry.logger.SentryLogParameters;
2024
import io.sentry.protocol.Mechanism;
2125
import io.sentry.protocol.Message;
2226
import io.sentry.protocol.SdkVersion;
@@ -50,6 +54,7 @@ public class SentryAppender extends AbstractAppender {
5054
private final @Nullable ITransportFactory transportFactory;
5155
private @NotNull Level minimumBreadcrumbLevel = Level.INFO;
5256
private @NotNull Level minimumEventLevel = Level.ERROR;
57+
private @NotNull Level minimumLevel = Level.INFO;
5358
private final @Nullable Boolean debug;
5459
private final @NotNull IScopes scopes;
5560
private final @Nullable List<String> contextTags;
@@ -59,12 +64,42 @@ public class SentryAppender extends AbstractAppender {
5964
.addPackage("maven:io.sentry:sentry-log4j2", BuildConfig.VERSION_NAME);
6065
}
6166

67+
/**
68+
* @deprecated This constructor is deprecated. Please use {@link #SentryAppender(String, Filter,
69+
* String, Level, Level, Level, Boolean, ITransportFactory, IScopes, String[])} instead.
70+
*/
71+
@Deprecated
72+
@SuppressWarnings("InlineMeSuggester")
73+
public SentryAppender(
74+
final @NotNull String name,
75+
final @Nullable Filter filter,
76+
final @Nullable String dsn,
77+
final @Nullable Level minimumBreadcrumbLevel,
78+
final @Nullable Level minimumEventLevel,
79+
final @Nullable Boolean debug,
80+
final @Nullable ITransportFactory transportFactory,
81+
final @NotNull IScopes scopes,
82+
final @Nullable String[] contextTags) {
83+
this(
84+
name,
85+
filter,
86+
dsn,
87+
minimumBreadcrumbLevel,
88+
minimumEventLevel,
89+
null,
90+
debug,
91+
transportFactory,
92+
scopes,
93+
contextTags);
94+
}
95+
6296
public SentryAppender(
6397
final @NotNull String name,
6498
final @Nullable Filter filter,
6599
final @Nullable String dsn,
66100
final @Nullable Level minimumBreadcrumbLevel,
67101
final @Nullable Level minimumEventLevel,
102+
final @Nullable Level minimumLevel,
68103
final @Nullable Boolean debug,
69104
final @Nullable ITransportFactory transportFactory,
70105
final @NotNull IScopes scopes,
@@ -77,6 +112,9 @@ public SentryAppender(
77112
if (minimumEventLevel != null) {
78113
this.minimumEventLevel = minimumEventLevel;
79114
}
115+
if (minimumLevel != null) {
116+
this.minimumLevel = minimumLevel;
117+
}
80118
this.debug = debug;
81119
this.transportFactory = transportFactory;
82120
this.scopes = scopes;
@@ -89,6 +127,7 @@ public SentryAppender(
89127
* @param name The name of the Appender.
90128
* @param minimumBreadcrumbLevel The min. level of the breadcrumb.
91129
* @param minimumEventLevel The min. level of the event.
130+
* @param minimumLevel The min. level of the log event.
92131
* @param dsn the Sentry DSN.
93132
* @param debug if Sentry debug mode should be on
94133
* @param filter The filter, if any, to use.
@@ -99,6 +138,7 @@ public SentryAppender(
99138
@Nullable @PluginAttribute("name") final String name,
100139
@Nullable @PluginAttribute("minimumBreadcrumbLevel") final Level minimumBreadcrumbLevel,
101140
@Nullable @PluginAttribute("minimumEventLevel") final Level minimumEventLevel,
141+
@Nullable @PluginAttribute("minimumLevel") final Level minimumLevel,
102142
@Nullable @PluginAttribute("dsn") final String dsn,
103143
@Nullable @PluginAttribute("debug") final Boolean debug,
104144
@Nullable @PluginElement("filter") final Filter filter,
@@ -114,6 +154,7 @@ public SentryAppender(
114154
dsn,
115155
minimumBreadcrumbLevel,
116156
minimumEventLevel,
157+
minimumLevel,
117158
debug,
118159
null,
119160
ScopesAdapter.getInstance(),
@@ -150,6 +191,9 @@ public void start() {
150191

151192
@Override
152193
public void append(final @NotNull LogEvent eventObject) {
194+
if (eventObject.getLevel().isMoreSpecificThan(minimumLevel)) {
195+
captureLog(eventObject);
196+
}
153197
if (eventObject.getLevel().isMoreSpecificThan(minimumEventLevel)) {
154198
final Hint hint = new Hint();
155199
hint.set(SENTRY_SYNTHETIC_EXCEPTION, eventObject);
@@ -164,6 +208,29 @@ public void append(final @NotNull LogEvent eventObject) {
164208
}
165209
}
166210

211+
/**
212+
* Captures a Sentry log from Log4j2's {@link LogEvent}.
213+
*
214+
* @param loggingEvent the log4j2 event
215+
*/
216+
// for the Android compatibility we must use old Java Date class
217+
@SuppressWarnings("JdkObsolete")
218+
protected void captureLog(@NotNull LogEvent loggingEvent) {
219+
final @NotNull SentryLogLevel sentryLevel = toSentryLogLevel(loggingEvent.getLevel());
220+
221+
final @Nullable Object[] arguments = loggingEvent.getMessage().getParameters();
222+
final @NotNull SentryAttributes attributes = SentryAttributes.of();
223+
224+
attributes.add(
225+
SentryAttribute.stringAttribute(
226+
"sentry.message.template", loggingEvent.getMessage().getFormat()));
227+
228+
final @NotNull String formattedMessage = loggingEvent.getMessage().getFormattedMessage();
229+
final @NotNull SentryLogParameters params = SentryLogParameters.create(attributes);
230+
231+
Sentry.logger().log(sentryLevel, params, formattedMessage, arguments);
232+
}
233+
167234
/**
168235
* Creates {@link SentryEvent} from Log4j2 {@link LogEvent}.
169236
*
@@ -271,6 +338,28 @@ public void append(final @NotNull LogEvent eventObject) {
271338
}
272339
}
273340

341+
/**
342+
* Transforms a {@link Level} into an {@link SentryLogLevel}.
343+
*
344+
* @param level original level as defined in log4j.
345+
* @return log level used within sentry.
346+
*/
347+
private static @NotNull SentryLogLevel toSentryLogLevel(final @NotNull Level level) {
348+
if (level.isMoreSpecificThan(Level.FATAL)) {
349+
return SentryLogLevel.FATAL;
350+
} else if (level.isMoreSpecificThan(Level.ERROR)) {
351+
return SentryLogLevel.ERROR;
352+
} else if (level.isMoreSpecificThan(Level.WARN)) {
353+
return SentryLogLevel.WARN;
354+
} else if (level.isMoreSpecificThan(Level.INFO)) {
355+
return SentryLogLevel.INFO;
356+
} else if (level.isMoreSpecificThan(Level.DEBUG)) {
357+
return SentryLogLevel.DEBUG;
358+
} else {
359+
return SentryLogLevel.TRACE;
360+
}
361+
}
362+
274363
private @NotNull SdkVersion createSdkVersion(final @NotNull SentryOptions sentryOptions) {
275364
SdkVersion sdkVersion = sentryOptions.getSdkVersion();
276365

sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import io.sentry.InitPriority
55
import io.sentry.ScopesAdapter
66
import io.sentry.Sentry
77
import io.sentry.SentryLevel
8+
import io.sentry.SentryLogLevel
89
import io.sentry.checkEvent
10+
import io.sentry.checkLogs
911
import io.sentry.test.initForTest
1012
import io.sentry.transport.ITransport
1113
import java.time.Instant
@@ -49,6 +51,7 @@ class SentryAppenderTest {
4951
transportFactory: ITransportFactory? = null,
5052
minimumBreadcrumbLevel: Level? = null,
5153
minimumEventLevel: Level? = null,
54+
minimumLevel: Level? = null,
5255
debug: Boolean? = null,
5356
contextTags: List<String>? = null,
5457
): ExtendedLogger {
@@ -64,6 +67,7 @@ class SentryAppenderTest {
6467
"http://key@localhost/proj",
6568
minimumBreadcrumbLevel,
6669
minimumEventLevel,
70+
minimumLevel,
6771
debug,
6872
this.transportFactory,
6973
ScopesAdapter.getInstance(),
@@ -239,6 +243,72 @@ class SentryAppenderTest {
239243
.send(checkEvent { event -> assertEquals(SentryLevel.FATAL, event.level) }, anyOrNull())
240244
}
241245

246+
@Test
247+
fun `converts trace log level to Sentry log level`() {
248+
val logger = fixture.getSut(minimumLevel = Level.TRACE)
249+
logger.trace("testing trace level")
250+
251+
Sentry.flush(1000)
252+
253+
verify(fixture.transport)
254+
.send(checkLogs { event -> assertEquals(SentryLogLevel.TRACE, event.items.first().level) })
255+
}
256+
257+
@Test
258+
fun `converts debug log level to Sentry log level`() {
259+
val logger = fixture.getSut(minimumLevel = Level.DEBUG)
260+
logger.debug("testing debug level")
261+
262+
Sentry.flush(1000)
263+
264+
verify(fixture.transport)
265+
.send(checkLogs { event -> assertEquals(SentryLogLevel.DEBUG, event.items.first().level) })
266+
}
267+
268+
@Test
269+
fun `converts info log level to Sentry log level`() {
270+
val logger = fixture.getSut(minimumLevel = Level.INFO)
271+
logger.info("testing info level")
272+
273+
Sentry.flush(1000)
274+
275+
verify(fixture.transport)
276+
.send(checkLogs { event -> assertEquals(SentryLogLevel.INFO, event.items.first().level) })
277+
}
278+
279+
@Test
280+
fun `converts warn log level to Sentry log level`() {
281+
val logger = fixture.getSut(minimumLevel = Level.WARN)
282+
logger.warn("testing warn level")
283+
284+
Sentry.flush(1000)
285+
286+
verify(fixture.transport)
287+
.send(checkLogs { event -> assertEquals(SentryLogLevel.WARN, event.items.first().level) })
288+
}
289+
290+
@Test
291+
fun `converts error log level to Sentry log level`() {
292+
val logger = fixture.getSut(minimumLevel = Level.ERROR)
293+
logger.error("testing error level")
294+
295+
Sentry.flush(1000)
296+
297+
verify(fixture.transport)
298+
.send(checkLogs { event -> assertEquals(SentryLogLevel.ERROR, event.items.first().level) })
299+
}
300+
301+
@Test
302+
fun `converts fatal log level to Sentry log level`() {
303+
val logger = fixture.getSut(minimumLevel = Level.FATAL)
304+
logger.fatal("testing fatal level")
305+
306+
Sentry.flush(1000)
307+
308+
verify(fixture.transport)
309+
.send(checkLogs { event -> assertEquals(SentryLogLevel.FATAL, event.items.first().level) })
310+
}
311+
242312
@Test
243313
fun `attaches thread information`() {
244314
val logger = fixture.getSut(minimumEventLevel = Level.WARN)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
release=release from sentry.properties
2+
logs.enabled=true

sentry-samples/sentry-samples-log4j2/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ configure<JavaPluginExtension> {
1414
dependencies {
1515
implementation(projects.sentryLog4j2)
1616
implementation(libs.log4j.api)
17+
implementation(libs.log4j.core)
1718
}

sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
dsn="https://502f25099c204a2fbf4cb16edc5975d1@o447951.ingest.sentry.io/5428563"
1212
minimumBreadcrumbLevel="DEBUG"
1313
minimumEventLevel="WARN"
14+
minimumLevel="DEBUG"
1415
debug="true"
1516
contextTags="userId,requestId"
1617
/>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
in-app-includes="io.sentry.samples"
2+
logs.enabled=true

0 commit comments

Comments
 (0)