Skip to content

Commit bc8226c

Browse files
authored
Merge af2bcad into 6796acc
2 parents 6796acc + af2bcad commit bc8226c

File tree

8 files changed

+186
-1
lines changed

8 files changed

+186
-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+
- You may set `minimumLevel` in `log4j2.xml`:
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: 88 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,41 @@ 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, String, Level, Level, Level, Boolean, ITransportFactory, IScopes, String[])} instead.
69+
*/
70+
@Deprecated
71+
@SuppressWarnings("InlineMeSuggester")
72+
public SentryAppender(
73+
final @NotNull String name,
74+
final @Nullable Filter filter,
75+
final @Nullable String dsn,
76+
final @Nullable Level minimumBreadcrumbLevel,
77+
final @Nullable Level minimumEventLevel,
78+
final @Nullable Boolean debug,
79+
final @Nullable ITransportFactory transportFactory,
80+
final @NotNull IScopes scopes,
81+
final @Nullable String[] contextTags) {
82+
this(
83+
name,
84+
filter,
85+
dsn,
86+
minimumBreadcrumbLevel,
87+
minimumEventLevel,
88+
null,
89+
debug,
90+
transportFactory,
91+
scopes,
92+
contextTags);
93+
}
94+
6295
public SentryAppender(
6396
final @NotNull String name,
6497
final @Nullable Filter filter,
6598
final @Nullable String dsn,
6699
final @Nullable Level minimumBreadcrumbLevel,
67100
final @Nullable Level minimumEventLevel,
101+
final @Nullable Level minimumLevel,
68102
final @Nullable Boolean debug,
69103
final @Nullable ITransportFactory transportFactory,
70104
final @NotNull IScopes scopes,
@@ -77,6 +111,9 @@ public SentryAppender(
77111
if (minimumEventLevel != null) {
78112
this.minimumEventLevel = minimumEventLevel;
79113
}
114+
if (minimumLevel != null) {
115+
this.minimumLevel = minimumLevel;
116+
}
80117
this.debug = debug;
81118
this.transportFactory = transportFactory;
82119
this.scopes = scopes;
@@ -89,6 +126,7 @@ public SentryAppender(
89126
* @param name The name of the Appender.
90127
* @param minimumBreadcrumbLevel The min. level of the breadcrumb.
91128
* @param minimumEventLevel The min. level of the event.
129+
* @param minimumLevel The min. level of the log event.
92130
* @param dsn the Sentry DSN.
93131
* @param debug if Sentry debug mode should be on
94132
* @param filter The filter, if any, to use.
@@ -99,6 +137,7 @@ public SentryAppender(
99137
@Nullable @PluginAttribute("name") final String name,
100138
@Nullable @PluginAttribute("minimumBreadcrumbLevel") final Level minimumBreadcrumbLevel,
101139
@Nullable @PluginAttribute("minimumEventLevel") final Level minimumEventLevel,
140+
@Nullable @PluginAttribute("minimumLevel") final Level minimumLevel,
102141
@Nullable @PluginAttribute("dsn") final String dsn,
103142
@Nullable @PluginAttribute("debug") final Boolean debug,
104143
@Nullable @PluginElement("filter") final Filter filter,
@@ -114,6 +153,7 @@ public SentryAppender(
114153
dsn,
115154
minimumBreadcrumbLevel,
116155
minimumEventLevel,
156+
minimumLevel,
117157
debug,
118158
null,
119159
ScopesAdapter.getInstance(),
@@ -150,6 +190,9 @@ public void start() {
150190

151191
@Override
152192
public void append(final @NotNull LogEvent eventObject) {
193+
if (eventObject.getLevel().isMoreSpecificThan(minimumLevel)) {
194+
captureLog(eventObject);
195+
}
153196
if (eventObject.getLevel().isMoreSpecificThan(minimumEventLevel)) {
154197
final Hint hint = new Hint();
155198
hint.set(SENTRY_SYNTHETIC_EXCEPTION, eventObject);
@@ -164,6 +207,29 @@ public void append(final @NotNull LogEvent eventObject) {
164207
}
165208
}
166209

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

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

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)