Skip to content

Commit 60b38dd

Browse files
authored
Merge 817e001 into bdbe1f4
2 parents bdbe1f4 + 817e001 commit 60b38dd

File tree

9 files changed

+166
-30
lines changed

9 files changed

+166
-30
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Attach MDC properties to logs as attributes ([#4786](https://github.com/getsentry/sentry-java/pull/4786))
8+
- MDC properties set using supported logging frameworks (Logback, Log4j2, java.util.Logging) are now attached to structured logs as attributes.
9+
- This means that you will be able to filter/aggregate logs in the product based on these properties.
10+
- Only properties with keys matching the configured `contextTags` are sent as log attributes.
11+
- You can configure which properties are sent using `options.setContextTags` if initalizing manually, or by specifying a comma-separated list of keys with a `context-tags` entry in `sentry.properties` or `sentry.contex-tags` in `application.properties`.
12+
- Note that keys containing spaces are not supported.
13+
514
### Fixes
615

716
- Use logger from options for JVM profiler ([#4771](https://github.com/getsentry/sentry-java/pull/4771))

sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.sentry.protocol.Message;
2323
import io.sentry.protocol.SdkVersion;
2424
import io.sentry.util.CollectionUtils;
25+
import io.sentry.util.LoggerPropertiesUtil;
2526
import java.text.MessageFormat;
2627
import java.util.ArrayList;
2728
import java.util.Date;
@@ -160,6 +161,12 @@ protected void captureLog(@NotNull LogRecord loggingEvent) {
160161
attributes.add(SentryAttribute.stringAttribute("sentry.message.template", message));
161162
}
162163

164+
final @Nullable Map<String, String> mdcProperties = MDC.getMDCAdapter().getCopyOfContextMap();
165+
if (mdcProperties != null) {
166+
final List<String> contextTags = ScopesAdapter.getInstance().getOptions().getContextTags();
167+
LoggerPropertiesUtil.applyPropertiesToAttributes(attributes, contextTags, mdcProperties);
168+
}
169+
163170
final @NotNull SentryLogParameters params = SentryLogParameters.create(attributes);
164171
params.setOrigin("auto.log.jul");
165172

@@ -312,16 +319,7 @@ SentryEvent createEvent(final @NotNull LogRecord record) {
312319
// get tags from ScopesAdapter options to allow getting the correct tags if Sentry has been
313320
// initialized somewhere else
314321
final List<String> contextTags = ScopesAdapter.getInstance().getOptions().getContextTags();
315-
if (!contextTags.isEmpty()) {
316-
for (final String contextTag : contextTags) {
317-
// if mdc tag is listed in SentryOptions, apply as event tag
318-
if (mdcProperties.containsKey(contextTag)) {
319-
event.setTag(contextTag, mdcProperties.get(contextTag));
320-
// remove from all tags applied to logging event
321-
mdcProperties.remove(contextTag);
322-
}
323-
}
324-
}
322+
LoggerPropertiesUtil.applyPropertiesToEvent(event, contextTags, mdcProperties);
325323
// put the rest of mdc tags in contexts
326324
if (!mdcProperties.isEmpty()) {
327325
event.getContexts().put("MDC", mdcProperties);

sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,4 +555,26 @@ class SentryHandlerTest {
555555
}
556556
)
557557
}
558+
559+
@Test
560+
fun `sets properties from MDC as attributes on logs`() {
561+
fixture = Fixture(minimumLevel = Level.INFO, contextTags = listOf("someTag"))
562+
563+
MDC.put("someTag", "someValue")
564+
MDC.put("otherTag", "otherValue")
565+
fixture.logger.info("testing MDC properties in logs")
566+
567+
Sentry.flush(1000)
568+
569+
verify(fixture.transport)
570+
.send(
571+
checkLogs { logs ->
572+
val log = logs.items.first()
573+
assertEquals("testing MDC properties in logs", log.body)
574+
val attributes = log.attributes!!
575+
assertEquals("someValue", attributes["someTag"]?.value)
576+
assertNull(attributes["otherTag"])
577+
}
578+
)
579+
}
558580
}

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.sentry.protocol.Message;
2626
import io.sentry.protocol.SdkVersion;
2727
import io.sentry.util.CollectionUtils;
28+
import io.sentry.util.LoggerPropertiesUtil;
2829
import java.util.Arrays;
2930
import java.util.Collections;
3031
import java.util.List;
@@ -230,6 +231,11 @@ protected void captureLog(@NotNull LogEvent loggingEvent) {
230231
SentryAttribute.stringAttribute("sentry.message.template", nonFormattedMessage));
231232
}
232233

234+
final @NotNull Map<String, String> contextData = loggingEvent.getContextData().toMap();
235+
final @NotNull List<String> contextTags =
236+
ScopesAdapter.getInstance().getOptions().getContextTags();
237+
LoggerPropertiesUtil.applyPropertiesToAttributes(attributes, contextTags, contextData);
238+
233239
final @NotNull SentryLogParameters params = SentryLogParameters.create(attributes);
234240
params.setOrigin("auto.log.log4j2");
235241

@@ -279,16 +285,7 @@ protected void captureLog(@NotNull LogEvent loggingEvent) {
279285
// get tags from ScopesAdapter options to allow getting the correct tags if Sentry has been
280286
// initialized somewhere else
281287
final List<String> contextTags = scopes.getOptions().getContextTags();
282-
if (contextTags != null && !contextTags.isEmpty()) {
283-
for (final String contextTag : contextTags) {
284-
// if mdc tag is listed in SentryOptions, apply as event tag
285-
if (contextData.containsKey(contextTag)) {
286-
event.setTag(contextTag, contextData.get(contextTag));
287-
// remove from all tags applied to logging event
288-
contextData.remove(contextTag);
289-
}
290-
}
291-
}
288+
LoggerPropertiesUtil.applyPropertiesToEvent(event, contextTags, contextData);
292289
// put the rest of mdc tags in contexts
293290
if (!contextData.isEmpty()) {
294291
event.getContexts().put("Context Data", contextData);

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,4 +591,27 @@ class SentryAppenderTest {
591591
}
592592
)
593593
}
594+
595+
@Test
596+
fun `sets properties from ThreadContext as attributes on logs`() {
597+
val logger = fixture.getSut(minimumLevel = Level.INFO, contextTags = listOf("someTag"))
598+
ScopesAdapter.getInstance().options.logs.isEnabled = true
599+
600+
ThreadContext.put("someTag", "someValue")
601+
ThreadContext.put("otherTag", "otherValue")
602+
logger.info("testing MDC properties in logs")
603+
604+
Sentry.flush(1000)
605+
606+
verify(fixture.transport)
607+
.send(
608+
checkLogs { logs ->
609+
val log = logs.items.first()
610+
assertEquals("testing MDC properties in logs", log.body)
611+
val attributes = log.attributes!!
612+
assertEquals("someValue", attributes["someTag"]?.value)
613+
assertNull(attributes["otherTag"])
614+
}
615+
)
616+
}
594617
}

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import io.sentry.protocol.Message;
3030
import io.sentry.protocol.SdkVersion;
3131
import io.sentry.util.CollectionUtils;
32+
import io.sentry.util.LoggerPropertiesUtil;
3233
import java.nio.charset.StandardCharsets;
3334
import java.util.Arrays;
3435
import java.util.Collections;
@@ -152,16 +153,7 @@ protected void append(@NotNull ILoggingEvent eventObject) {
152153
// get tags from ScopesAdapter options to allow getting the correct tags if Sentry has been
153154
// initialized somewhere else
154155
final List<String> contextTags = ScopesAdapter.getInstance().getOptions().getContextTags();
155-
if (!contextTags.isEmpty()) {
156-
for (final String contextTag : contextTags) {
157-
// if mdc tag is listed in SentryOptions, apply as event tag
158-
if (mdcProperties.containsKey(contextTag)) {
159-
event.setTag(contextTag, mdcProperties.get(contextTag));
160-
// remove from all tags applied to logging event
161-
mdcProperties.remove(contextTag);
162-
}
163-
}
164-
}
156+
LoggerPropertiesUtil.applyPropertiesToEvent(event, contextTags, mdcProperties);
165157
// put the rest of mdc tags in contexts
166158
if (!mdcProperties.isEmpty()) {
167159
event.getContexts().put("MDC", mdcProperties);
@@ -195,6 +187,11 @@ protected void captureLog(@NotNull ILoggingEvent loggingEvent) {
195187
arguments = loggingEvent.getArgumentArray();
196188
}
197189

190+
final @NotNull Map<String, String> mdcProperties = loggingEvent.getMDCPropertyMap();
191+
final @NotNull List<String> contextTags =
192+
ScopesAdapter.getInstance().getOptions().getContextTags();
193+
LoggerPropertiesUtil.applyPropertiesToAttributes(attributes, contextTags, mdcProperties);
194+
198195
final @NotNull SentryLogParameters params = SentryLogParameters.create(attributes);
199196
params.setOrigin("auto.log.logback");
200197

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,4 +821,25 @@ class SentryAppenderTest {
821821
}
822822
)
823823
}
824+
825+
@Test
826+
fun `sets properties from MDC as attributes on logs`() {
827+
fixture = Fixture(minimumLevel = Level.INFO, enableLogs = true, contextTags = listOf("someTag"))
828+
MDC.put("someTag", "someValue")
829+
MDC.put("otherTag", "otherValue")
830+
fixture.logger.info("testing MDC properties in logs")
831+
832+
Sentry.flush(1000)
833+
834+
verify(fixture.transport)
835+
.send(
836+
checkLogs { logs ->
837+
val log = logs.items.first()
838+
assertEquals("testing MDC properties in logs", log.body)
839+
val attributes = log.attributes!!
840+
assertEquals("someValue", attributes["someTag"]?.value)
841+
assertNull(attributes["otherTag"])
842+
}
843+
)
844+
}
824845
}

sentry/api/sentry.api

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7080,6 +7080,12 @@ public final class io/sentry/util/LogUtils {
70807080
public static fun logNotInstanceOf (Ljava/lang/Class;Ljava/lang/Object;Lio/sentry/ILogger;)V
70817081
}
70827082

7083+
public final class io/sentry/util/LoggerPropertiesUtil {
7084+
public fun <init> ()V
7085+
public static fun applyPropertiesToAttributes (Lio/sentry/SentryAttributes;Ljava/util/List;Ljava/util/Map;)V
7086+
public static fun applyPropertiesToEvent (Lio/sentry/SentryEvent;Ljava/util/List;Ljava/util/Map;)V
7087+
}
7088+
70837089
public final class io/sentry/util/MapObjectReader : io/sentry/ObjectReader {
70847090
public fun <init> (Ljava/util/Map;)V
70857091
public fun beginArray ()V
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.sentry.util;
2+
3+
import io.sentry.SentryAttribute;
4+
import io.sentry.SentryAttributes;
5+
import io.sentry.SentryEvent;
6+
import java.util.List;
7+
import java.util.Map;
8+
import org.jetbrains.annotations.ApiStatus;
9+
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.Nullable;
11+
12+
/** Utility class for applying logger properties (e.g. MDC) to Sentry events and log attributes. */
13+
@ApiStatus.Internal
14+
public final class LoggerPropertiesUtil {
15+
16+
/**
17+
* Applies logger properties from a map to a Sentry event as tags. Only the properties with keys
18+
* that are found in `targetKeys` will be applied as tags.
19+
*
20+
* @param event the Sentry event to add tags to
21+
* @param targetKeys the list of property keys to apply as tags
22+
* @param properties the properties map (e.g. MDC) - this map will be modified by removing
23+
* properties which were applied as tags
24+
*/
25+
@ApiStatus.Internal
26+
public static void applyPropertiesToEvent(
27+
final @NotNull SentryEvent event,
28+
final @NotNull List<String> targetKeys,
29+
final @NotNull Map<String, String> properties) {
30+
if (!targetKeys.isEmpty() && !properties.isEmpty()) {
31+
for (final String key : targetKeys) {
32+
if (properties.containsKey(key)) {
33+
event.setTag(key, properties.get(key));
34+
properties.remove(key);
35+
}
36+
}
37+
}
38+
}
39+
40+
/**
41+
* Applies logger properties from a properties map to SentryAttributes for logs. Only the
42+
* properties with keys that are found in `targetKeys` will be applied as attributes. Properties
43+
* with null values are filtered out.
44+
*
45+
* @param attributes the SentryAttributes to add the properties to
46+
* @param targetKeys the list of property keys to apply as attributes
47+
* @param properties the properties map (e.g. MDC)
48+
*/
49+
@ApiStatus.Internal
50+
public static void applyPropertiesToAttributes(
51+
final @NotNull SentryAttributes attributes,
52+
final @NotNull List<String> targetKeys,
53+
final @NotNull Map<String, String> properties) {
54+
if (!targetKeys.isEmpty() && !properties.isEmpty()) {
55+
for (final String key : targetKeys) {
56+
final @Nullable String value = properties.get(key);
57+
if (value != null) {
58+
attributes.add(SentryAttribute.stringAttribute(key, value));
59+
}
60+
}
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)