Browse Source

Add experimental thread attributes for logs (#5474)

Trask Stalnaker 3 years ago
parent
commit
627f5d3e1c

+ 5 - 0
instrumentation/java-util-logging/javaagent/build.gradle.kts

@@ -9,3 +9,8 @@ dependencies {
 
 
   testImplementation("org.awaitility:awaitility")
   testImplementation("org.awaitility:awaitility")
 }
 }
+
+tasks.withType<Test>().configureEach {
+  // TODO run tests both with and without experimental log attributes
+  jvmArgs("-Dotel.instrumentation.java-util-logging.experimental-log-attributes=true")
+}

+ 14 - 3
instrumentation/java-util-logging/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jul/JavaUtilLoggingHelper.java

@@ -11,6 +11,7 @@ import io.opentelemetry.api.common.AttributesBuilder;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.appender.internal.LogBuilder;
 import io.opentelemetry.instrumentation.api.appender.internal.LogBuilder;
 import io.opentelemetry.instrumentation.api.appender.internal.Severity;
 import io.opentelemetry.instrumentation.api.appender.internal.Severity;
+import io.opentelemetry.instrumentation.api.config.Config;
 import io.opentelemetry.javaagent.instrumentation.api.appender.internal.AgentLogEmitterProvider;
 import io.opentelemetry.javaagent.instrumentation.api.appender.internal.AgentLogEmitterProvider;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
 import java.io.PrintWriter;
 import java.io.PrintWriter;
@@ -24,6 +25,10 @@ public final class JavaUtilLoggingHelper {
 
 
   private static final Formatter FORMATTER = new AccessibleFormatter();
   private static final Formatter FORMATTER = new AccessibleFormatter();
 
 
+  private static final boolean captureExperimentalAttributes =
+      Config.get()
+          .getBoolean("otel.instrumentation.java-util-logging.experimental-log-attributes", false);
+
   public static void capture(Logger logger, LogRecord logRecord) {
   public static void capture(Logger logger, LogRecord logRecord) {
 
 
     if (!logger.isLoggable(logRecord.getLevel())) {
     if (!logger.isLoggable(logRecord.getLevel())) {
@@ -69,11 +74,11 @@ public final class JavaUtilLoggingHelper {
       builder.setSeverityText(logRecord.getLevel().getName());
       builder.setSeverityText(logRecord.getLevel().getName());
     }
     }
 
 
+    AttributesBuilder attributes = Attributes.builder();
+
     // throwable
     // throwable
     Throwable throwable = logRecord.getThrown();
     Throwable throwable = logRecord.getThrown();
     if (throwable != null) {
     if (throwable != null) {
-      AttributesBuilder attributes = Attributes.builder();
-
       // TODO (trask) extract method for recording exception into
       // TODO (trask) extract method for recording exception into
       // instrumentation-appender-api-internal
       // instrumentation-appender-api-internal
       attributes.put(SemanticAttributes.EXCEPTION_TYPE, throwable.getClass().getName());
       attributes.put(SemanticAttributes.EXCEPTION_TYPE, throwable.getClass().getName());
@@ -81,10 +86,16 @@ public final class JavaUtilLoggingHelper {
       StringWriter writer = new StringWriter();
       StringWriter writer = new StringWriter();
       throwable.printStackTrace(new PrintWriter(writer));
       throwable.printStackTrace(new PrintWriter(writer));
       attributes.put(SemanticAttributes.EXCEPTION_STACKTRACE, writer.toString());
       attributes.put(SemanticAttributes.EXCEPTION_STACKTRACE, writer.toString());
+    }
 
 
-      builder.setAttributes(attributes.build());
+    if (captureExperimentalAttributes) {
+      Thread currentThread = Thread.currentThread();
+      attributes.put(SemanticAttributes.THREAD_NAME, currentThread.getName());
+      attributes.put(SemanticAttributes.THREAD_ID, currentThread.getId());
     }
     }
 
 
+    builder.setAttributes(attributes.build());
+
     // span context
     // span context
     builder.setContext(Context.current());
     builder.setContext(Context.current());
   }
   }

+ 4 - 0
instrumentation/java-util-logging/javaagent/src/test/groovy/JavaUtilLoggingTest.groovy

@@ -54,14 +54,18 @@ class JavaUtilLoggingTest extends AgentInstrumentationSpecification {
       assertThat(log.getSeverity()).isEqualTo(severity)
       assertThat(log.getSeverity()).isEqualTo(severity)
       assertThat(log.getSeverityText()).isEqualTo(severityText)
       assertThat(log.getSeverityText()).isEqualTo(severityText)
       if (exception) {
       if (exception) {
+        assertThat(log.getAttributes().size()).isEqualTo(5)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isEqualTo(IllegalStateException.getName())
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isEqualTo(IllegalStateException.getName())
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isEqualTo("hello")
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isEqualTo("hello")
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).contains(JavaUtilLoggingTest.name)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).contains(JavaUtilLoggingTest.name)
       } else {
       } else {
+        assertThat(log.getAttributes().size()).isEqualTo(2)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).isNull()
       }
       }
+      assertThat(log.getAttributes().get(SemanticAttributes.THREAD_NAME)).isEqualTo(Thread.currentThread().getName())
+      assertThat(log.getAttributes().get(SemanticAttributes.THREAD_ID)).isEqualTo(Thread.currentThread().getId())
       if (parent) {
       if (parent) {
         assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
         assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
       } else {
       } else {

+ 5 - 0
instrumentation/log4j/log4j-appender-1.2/javaagent/build.gradle.kts

@@ -29,3 +29,8 @@ configurations {
     exclude("org.slf4j", "log4j-over-slf4j")
     exclude("org.slf4j", "log4j-over-slf4j")
   }
   }
 }
 }
+
+tasks.withType<Test>().configureEach {
+  // TODO run tests both with and without experimental log attributes
+  jvmArgs("-Dotel.instrumentation.log4j-appender.experimental-log-attributes=true")
+}

+ 14 - 3
instrumentation/log4j/log4j-appender-1.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v1_2/Log4jHelper.java

@@ -10,6 +10,7 @@ import io.opentelemetry.api.common.AttributesBuilder;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.appender.internal.LogBuilder;
 import io.opentelemetry.instrumentation.api.appender.internal.LogBuilder;
 import io.opentelemetry.instrumentation.api.appender.internal.Severity;
 import io.opentelemetry.instrumentation.api.appender.internal.Severity;
+import io.opentelemetry.instrumentation.api.config.Config;
 import io.opentelemetry.javaagent.instrumentation.api.appender.internal.AgentLogEmitterProvider;
 import io.opentelemetry.javaagent.instrumentation.api.appender.internal.AgentLogEmitterProvider;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
 import java.io.PrintWriter;
 import java.io.PrintWriter;
@@ -22,6 +23,10 @@ public final class Log4jHelper {
   // copied from org.apache.log4j.Level because it was only introduced in 1.2.12
   // copied from org.apache.log4j.Level because it was only introduced in 1.2.12
   private static final int TRACE_INT = 5000;
   private static final int TRACE_INT = 5000;
 
 
+  private static final boolean captureExperimentalAttributes =
+      Config.get()
+          .getBoolean("otel.instrumentation.log4j-appender.experimental-log-attributes", false);
+
   // TODO (trask) capture MDC
   // TODO (trask) capture MDC
   public static void capture(Category logger, Priority level, Object message, Throwable throwable) {
   public static void capture(Category logger, Priority level, Object message, Throwable throwable) {
     String instrumentationName = logger.getName();
     String instrumentationName = logger.getName();
@@ -42,10 +47,10 @@ public final class Log4jHelper {
       builder.setSeverityText(level.toString());
       builder.setSeverityText(level.toString());
     }
     }
 
 
+    AttributesBuilder attributes = Attributes.builder();
+
     // throwable
     // throwable
     if (throwable != null) {
     if (throwable != null) {
-      AttributesBuilder attributes = Attributes.builder();
-
       // TODO (trask) extract method for recording exception into
       // TODO (trask) extract method for recording exception into
       // instrumentation-appender-api-internal
       // instrumentation-appender-api-internal
       attributes.put(SemanticAttributes.EXCEPTION_TYPE, throwable.getClass().getName());
       attributes.put(SemanticAttributes.EXCEPTION_TYPE, throwable.getClass().getName());
@@ -53,10 +58,16 @@ public final class Log4jHelper {
       StringWriter writer = new StringWriter();
       StringWriter writer = new StringWriter();
       throwable.printStackTrace(new PrintWriter(writer));
       throwable.printStackTrace(new PrintWriter(writer));
       attributes.put(SemanticAttributes.EXCEPTION_STACKTRACE, writer.toString());
       attributes.put(SemanticAttributes.EXCEPTION_STACKTRACE, writer.toString());
+    }
 
 
-      builder.setAttributes(attributes.build());
+    if (captureExperimentalAttributes) {
+      Thread currentThread = Thread.currentThread();
+      attributes.put(SemanticAttributes.THREAD_NAME, currentThread.getName());
+      attributes.put(SemanticAttributes.THREAD_ID, currentThread.getId());
     }
     }
 
 
+    builder.setAttributes(attributes.build());
+
     // span context
     // span context
     builder.setContext(Context.current());
     builder.setContext(Context.current());
 
 

+ 4 - 0
instrumentation/log4j/log4j-appender-1.2/javaagent/src/test/groovy/Log4j1Test.groovy

@@ -52,14 +52,18 @@ class Log4j1Test extends AgentInstrumentationSpecification {
       assertThat(log.getSeverity()).isEqualTo(severity)
       assertThat(log.getSeverity()).isEqualTo(severity)
       assertThat(log.getSeverityText()).isEqualTo(severityText)
       assertThat(log.getSeverityText()).isEqualTo(severityText)
       if (exception) {
       if (exception) {
+        assertThat(log.getAttributes().size()).isEqualTo(5)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isEqualTo(IllegalStateException.getName())
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isEqualTo(IllegalStateException.getName())
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isEqualTo("hello")
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isEqualTo("hello")
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).contains(Log4j1Test.name)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).contains(Log4j1Test.name)
       } else {
       } else {
+        assertThat(log.getAttributes().size()).isEqualTo(2)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).isNull()
       }
       }
+      assertThat(log.getAttributes().get(SemanticAttributes.THREAD_NAME)).isEqualTo(Thread.currentThread().getName())
+      assertThat(log.getAttributes().get(SemanticAttributes.THREAD_ID)).isEqualTo(Thread.currentThread().getId())
       if (parent) {
       if (parent) {
         assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
         assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
       } else {
       } else {

+ 1 - 0
instrumentation/log4j/log4j-appender-2.16/javaagent/build.gradle.kts

@@ -38,4 +38,5 @@ tasks.withType<Test>().configureEach {
   // TODO run tests both with and without experimental log attributes
   // TODO run tests both with and without experimental log attributes
   jvmArgs("-Dotel.instrumentation.log4j-appender.experimental.capture-map-message-attributes=true")
   jvmArgs("-Dotel.instrumentation.log4j-appender.experimental.capture-map-message-attributes=true")
   jvmArgs("-Dotel.instrumentation.log4j-appender.experimental.capture-context-data-attributes=*")
   jvmArgs("-Dotel.instrumentation.log4j-appender.experimental.capture-context-data-attributes=*")
+  jvmArgs("-Dotel.instrumentation.log4j-appender.experimental-log-attributes=true")
 }
 }

+ 16 - 4
instrumentation/log4j/log4j-appender-2.16/javaagent/src/test/groovy/Log4j2Test.groovy

@@ -57,14 +57,18 @@ class Log4j2Test extends AgentInstrumentationSpecification {
       assertThat(log.getSeverity()).isEqualTo(severity)
       assertThat(log.getSeverity()).isEqualTo(severity)
       assertThat(log.getSeverityText()).isEqualTo(severityText)
       assertThat(log.getSeverityText()).isEqualTo(severityText)
       if (exception) {
       if (exception) {
+        assertThat(log.getAttributes().size()).isEqualTo(5)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isEqualTo(IllegalStateException.getName())
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isEqualTo(IllegalStateException.getName())
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isEqualTo("hello")
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isEqualTo("hello")
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).contains(Log4j2Test.name)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).contains(Log4j2Test.name)
       } else {
       } else {
+        assertThat(log.getAttributes().size()).isEqualTo(2)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).isNull()
       }
       }
+      assertThat(log.getAttributes().get(SemanticAttributes.THREAD_NAME)).isEqualTo(Thread.currentThread().getName())
+      assertThat(log.getAttributes().get(SemanticAttributes.THREAD_ID)).isEqualTo(Thread.currentThread().getId())
       if (parent) {
       if (parent) {
         assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
         assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
       } else {
       } else {
@@ -114,9 +118,11 @@ class Log4j2Test extends AgentInstrumentationSpecification {
     assertThat(log.getInstrumentationLibraryInfo().getName()).isEqualTo("abc")
     assertThat(log.getInstrumentationLibraryInfo().getName()).isEqualTo("abc")
     assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
     assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
     assertThat(log.getSeverityText()).isEqualTo("INFO")
     assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(2)
+    assertThat(log.getAttributes().size()).isEqualTo(4)
     assertThat(log.getAttributes().get(AttributeKey.stringKey("log4j.context_data.key1"))).isEqualTo("val1")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("log4j.context_data.key1"))).isEqualTo("val1")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("log4j.context_data.key2"))).isEqualTo("val2")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("log4j.context_data.key2"))).isEqualTo("val2")
+    assertThat(log.getAttributes().get(SemanticAttributes.THREAD_NAME)).isEqualTo(Thread.currentThread().getName())
+    assertThat(log.getAttributes().get(SemanticAttributes.THREAD_ID)).isEqualTo(Thread.currentThread().getId())
   }
   }
 
 
   def "test string map message"() {
   def "test string map message"() {
@@ -138,9 +144,11 @@ class Log4j2Test extends AgentInstrumentationSpecification {
     assertThat(log.getInstrumentationLibraryInfo().getName()).isEqualTo("abc")
     assertThat(log.getInstrumentationLibraryInfo().getName()).isEqualTo("abc")
     assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
     assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
     assertThat(log.getSeverityText()).isEqualTo("INFO")
     assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(2)
+    assertThat(log.getAttributes().size()).isEqualTo(4)
     assertThat(log.getAttributes().get(AttributeKey.stringKey("key1"))).isEqualTo("val1")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("key1"))).isEqualTo("val1")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("key2"))).isEqualTo("val2")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("key2"))).isEqualTo("val2")
+    assertThat(log.getAttributes().get(SemanticAttributes.THREAD_NAME)).isEqualTo(Thread.currentThread().getName())
+    assertThat(log.getAttributes().get(SemanticAttributes.THREAD_ID)).isEqualTo(Thread.currentThread().getId())
   }
   }
 
 
   def "test string map message with special attribute"() {
   def "test string map message with special attribute"() {
@@ -162,8 +170,10 @@ class Log4j2Test extends AgentInstrumentationSpecification {
     assertThat(log.getInstrumentationLibraryInfo().getName()).isEqualTo("abc")
     assertThat(log.getInstrumentationLibraryInfo().getName()).isEqualTo("abc")
     assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
     assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
     assertThat(log.getSeverityText()).isEqualTo("INFO")
     assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(1)
+    assertThat(log.getAttributes().size()).isEqualTo(3)
     assertThat(log.getAttributes().get(AttributeKey.stringKey("key1"))).isEqualTo("val1")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("key1"))).isEqualTo("val1")
+    assertThat(log.getAttributes().get(SemanticAttributes.THREAD_NAME)).isEqualTo(Thread.currentThread().getName())
+    assertThat(log.getAttributes().get(SemanticAttributes.THREAD_ID)).isEqualTo(Thread.currentThread().getId())
   }
   }
 
 
   def "test structured data map message"() {
   def "test structured data map message"() {
@@ -185,8 +195,10 @@ class Log4j2Test extends AgentInstrumentationSpecification {
     assertThat(log.getInstrumentationLibraryInfo().getName()).isEqualTo("abc")
     assertThat(log.getInstrumentationLibraryInfo().getName()).isEqualTo("abc")
     assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
     assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
     assertThat(log.getSeverityText()).isEqualTo("INFO")
     assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(2)
+    assertThat(log.getAttributes().size()).isEqualTo(4)
     assertThat(log.getAttributes().get(AttributeKey.stringKey("key1"))).isEqualTo("val1")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("key1"))).isEqualTo("val1")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("key2"))).isEqualTo("val2")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("key2"))).isEqualTo("val2")
+    assertThat(log.getAttributes().get(SemanticAttributes.THREAD_NAME)).isEqualTo(Thread.currentThread().getName())
+    assertThat(log.getAttributes().get(SemanticAttributes.THREAD_ID)).isEqualTo(Thread.currentThread().getId())
   }
   }
 }
 }

+ 10 - 0
instrumentation/log4j/log4j-appender-2.16/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_16/internal/LogEventMapper.java

@@ -35,6 +35,10 @@ public final class LogEventMapper<T> {
 
 
   private static final String SPECIAL_MAP_MESSAGE_ATTRIBUTE = "message";
   private static final String SPECIAL_MAP_MESSAGE_ATTRIBUTE = "message";
 
 
+  private static final boolean captureExperimentalAttributes =
+      Config.get()
+          .getBoolean("otel.instrumentation.log4j-appender.experimental-log-attributes", false);
+
   private static final Cache<String, AttributeKey<String>> contextDataAttributeKeyCache =
   private static final Cache<String, AttributeKey<String>> contextDataAttributeKeyCache =
       Cache.bounded(100);
       Cache.bounded(100);
   private static final Cache<String, AttributeKey<String>> mapMessageAttributeKeyCache =
   private static final Cache<String, AttributeKey<String>> mapMessageAttributeKeyCache =
@@ -110,6 +114,12 @@ public final class LogEventMapper<T> {
 
 
     captureContextDataAttributes(attributes, contextData);
     captureContextDataAttributes(attributes, contextData);
 
 
+    if (captureExperimentalAttributes) {
+      Thread currentThread = Thread.currentThread();
+      attributes.put(SemanticAttributes.THREAD_NAME, currentThread.getName());
+      attributes.put(SemanticAttributes.THREAD_ID, currentThread.getId());
+    }
+
     builder.setAttributes(attributes.build());
     builder.setAttributes(attributes.build());
 
 
     builder.setContext(Context.current());
     builder.setContext(Context.current());

+ 1 - 0
instrumentation/logback/logback-appender-1.0/javaagent/build.gradle.kts

@@ -27,4 +27,5 @@ dependencies {
 tasks.withType<Test>().configureEach {
 tasks.withType<Test>().configureEach {
   // TODO run tests both with and without experimental log attributes
   // TODO run tests both with and without experimental log attributes
   jvmArgs("-Dotel.instrumentation.logback-appender.experimental.capture-mdc-attributes=*")
   jvmArgs("-Dotel.instrumentation.logback-appender.experimental.capture-mdc-attributes=*")
+  jvmArgs("-Dotel.instrumentation.logback-appender.experimental-log-attributes=true")
 }
 }

+ 7 - 1
instrumentation/logback/logback-appender-1.0/javaagent/src/test/groovy/LogbackTest.groovy

@@ -56,14 +56,18 @@ class LogbackTest extends AgentInstrumentationSpecification {
       assertThat(log.getSeverity()).isEqualTo(severity)
       assertThat(log.getSeverity()).isEqualTo(severity)
       assertThat(log.getSeverityText()).isEqualTo(severityText)
       assertThat(log.getSeverityText()).isEqualTo(severityText)
       if (exception) {
       if (exception) {
+        assertThat(log.getAttributes().size()).isEqualTo(5)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isEqualTo(IllegalStateException.getName())
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isEqualTo(IllegalStateException.getName())
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isEqualTo("hello")
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isEqualTo("hello")
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).contains(LogbackTest.name)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).contains(LogbackTest.name)
       } else {
       } else {
+        assertThat(log.getAttributes().size()).isEqualTo(2)
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).isNull()
         assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).isNull()
       }
       }
+      assertThat(log.getAttributes().get(SemanticAttributes.THREAD_NAME)).isEqualTo(Thread.currentThread().getName())
+      assertThat(log.getAttributes().get(SemanticAttributes.THREAD_ID)).isEqualTo(Thread.currentThread().getId())
       if (parent) {
       if (parent) {
         assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
         assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
       } else {
       } else {
@@ -119,8 +123,10 @@ class LogbackTest extends AgentInstrumentationSpecification {
     assertThat(log.getInstrumentationLibraryInfo().getName()).isEqualTo("abc")
     assertThat(log.getInstrumentationLibraryInfo().getName()).isEqualTo("abc")
     assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
     assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
     assertThat(log.getSeverityText()).isEqualTo("INFO")
     assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(2)
+    assertThat(log.getAttributes().size()).isEqualTo(4)
     assertThat(log.getAttributes().get(AttributeKey.stringKey("logback.mdc.key1"))).isEqualTo("val1")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("logback.mdc.key1"))).isEqualTo("val1")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("logback.mdc.key2"))).isEqualTo("val2")
     assertThat(log.getAttributes().get(AttributeKey.stringKey("logback.mdc.key2"))).isEqualTo("val2")
+    assertThat(log.getAttributes().get(SemanticAttributes.THREAD_NAME)).isEqualTo(Thread.currentThread().getName())
+    assertThat(log.getAttributes().get(SemanticAttributes.THREAD_ID)).isEqualTo(Thread.currentThread().getId())
   }
   }
 }
 }

+ 10 - 0
instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java

@@ -34,6 +34,10 @@ public final class LoggingEventMapper {
 
 
   public static final LoggingEventMapper INSTANCE = new LoggingEventMapper();
   public static final LoggingEventMapper INSTANCE = new LoggingEventMapper();
 
 
+  private static final boolean captureExperimentalAttributes =
+      Config.get()
+          .getBoolean("otel.instrumentation.logback-appender.experimental-log-attributes", false);
+
   private static final Cache<String, AttributeKey<String>> mdcAttributeKeys = Cache.bounded(100);
   private static final Cache<String, AttributeKey<String>> mdcAttributeKeys = Cache.bounded(100);
 
 
   private final List<String> captureMdcAttributes;
   private final List<String> captureMdcAttributes;
@@ -110,6 +114,12 @@ public final class LoggingEventMapper {
 
 
     captureMdcAttributes(attributes, loggingEvent.getMDCPropertyMap());
     captureMdcAttributes(attributes, loggingEvent.getMDCPropertyMap());
 
 
+    if (captureExperimentalAttributes) {
+      Thread currentThread = Thread.currentThread();
+      attributes.put(SemanticAttributes.THREAD_NAME, currentThread.getName());
+      attributes.put(SemanticAttributes.THREAD_ID, currentThread.getId());
+    }
+
     builder.setAttributes(attributes.build());
     builder.setAttributes(attributes.build());
 
 
     // span context
     // span context