소스 검색

Convert logging instrumentation tests to Java (#7631)

currently based on #7632
Trask Stalnaker 2 년 전
부모
커밋
dab33810bb
11개의 변경된 파일851개의 추가작업 그리고 649개의 파일을 삭제
  1. 0 103
      instrumentation/java-util-logging/javaagent/src/test/groovy/JavaUtilLoggingTest.groovy
  2. 165 0
      instrumentation/java-util-logging/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jul/JavaUtilLoggingTest.java
  3. 1 0
      instrumentation/jboss-logmanager/jboss-logmanager-appender-1.1/javaagent/build.gradle.kts
  4. 0 129
      instrumentation/jboss-logmanager/jboss-logmanager-appender-1.1/javaagent/src/test/groovy/JbossLogmanagerTest.groovy
  5. 209 0
      instrumentation/jboss-logmanager/jboss-logmanager-appender-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jbosslogmanager/appender/v1_1/JbossLogmanagerTest.java
  6. 0 134
      instrumentation/log4j/log4j-appender-1.2/javaagent/src/test/groovy/Log4j1Test.groovy
  7. 175 0
      instrumentation/log4j/log4j-appender-1.2/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v1_2/Log4j1Test.java
  8. 0 223
      instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/groovy/Log4j2Test.groovy
  9. 238 0
      instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java
  10. 45 58
      instrumentation/logback/logback-appender-1.0/javaagent/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/LogbackTest.java
  11. 18 2
      testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java

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

@@ -1,103 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
-import io.opentelemetry.api.logs.Severity
-import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
-import spock.lang.Unroll
-
-import java.util.logging.Level
-import java.util.logging.Logger
-
-import static org.assertj.core.api.Assertions.assertThat
-import static org.awaitility.Awaitility.await
-
-class JavaUtilLoggingTest extends AgentInstrumentationSpecification {
-
-  private static final Logger logger = Logger.getLogger("abc")
-
-  @Unroll
-  def "test method=#testMethod with testArgs=#testArgs and parent=#parent"() {
-    when:
-    if (parent) {
-      runWithSpan("parent") {
-        if (testArgs == "exception") {
-          logger.log(Level."${testMethod.toUpperCase()}", "xyz", new IllegalStateException("hello"))
-        } else if (testArgs == "params") {
-          logger.log(Level."${testMethod.toUpperCase()}", "xyz: {0}", 123)
-        } else {
-          logger."$testMethod"("xyz")
-        }
-      }
-    } else {
-      if (testArgs == "exception") {
-        logger.log(Level."${testMethod.toUpperCase()}", "xyz", new IllegalStateException("hello"))
-      } else if (testArgs == "params") {
-        logger.log(Level."${testMethod.toUpperCase()}", "xyz: {0}", 123)
-      } else {
-        logger."$testMethod"("xyz")
-      }
-    }
-
-    then:
-    if (parent) {
-      waitForTraces(1)
-    }
-
-    if (severity != null) {
-      await()
-        .untilAsserted(
-          () -> {
-            assertThat(logRecords).hasSize(1)
-          })
-      def log = logRecords.get(0)
-      if (testArgs == "params") {
-        assertThat(log.getBody().asString()).isEqualTo("xyz: 123")
-      } else {
-        assertThat(log.getBody().asString()).isEqualTo("xyz")
-      }
-      assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo("abc")
-      assertThat(log.getSeverity()).isEqualTo(severity)
-      assertThat(log.getSeverityText()).isEqualTo(severityText)
-      if (testArgs == "exception") {
-        assertThat(log.getAttributes().size()).isEqualTo(5)
-        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_STACKTRACE)).contains(JavaUtilLoggingTest.name)
-      } else {
-        assertThat(log.getAttributes().size()).isEqualTo(2)
-        assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
-        assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).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) {
-        assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
-      } else {
-        assertThat(log.getSpanContext().isValid()).isFalse()
-      }
-    } else {
-      Thread.sleep(500) // sleep a bit just to make sure no log is captured
-      logRecords.size() == 0
-    }
-
-    where:
-    [args, testArgs, parent] << [
-      [
-        ["fine", null, null],
-        ["info", Severity.INFO, "INFO"],
-        ["warning", Severity.WARN, "WARNING"],
-        ["severe", Severity.ERROR, "SEVERE"]
-      ],
-      ["none", "exception", "param"],
-      [true, false]
-    ].combinations()
-
-    testMethod = args[0]
-    severity = args[1]
-    severityText = args[2]
-  }
-}

+ 165 - 0
instrumentation/java-util-logging/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jul/JavaUtilLoggingTest.java

@@ -0,0 +1,165 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jul;
+
+import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
+
+import io.opentelemetry.api.logs.Severity;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
+import io.opentelemetry.sdk.logs.data.LogRecordData;
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class JavaUtilLoggingTest {
+
+  private static final Logger logger = Logger.getLogger("abc");
+
+  @RegisterExtension
+  static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+  private static Stream<Arguments> provideParameters() {
+    return Stream.of(
+        Arguments.of(false, false, false),
+        Arguments.of(false, false, true),
+        Arguments.of(false, true, false),
+        Arguments.of(false, true, true),
+        Arguments.of(true, false, false),
+        Arguments.of(true, false, true),
+        Arguments.of(true, true, false),
+        Arguments.of(true, true, true));
+  }
+
+  @ParameterizedTest
+  @MethodSource("provideParameters")
+  public void test(boolean withParam, boolean logException, boolean withParent)
+      throws InterruptedException {
+    test(Level.FINE, Logger::fine, withParam, logException, withParent, null, null, null);
+    testing.clearData();
+    test(
+        Level.INFO,
+        Logger::info,
+        withParam,
+        logException,
+        withParent,
+        "abc",
+        Severity.INFO,
+        "INFO");
+    testing.clearData();
+    test(
+        Level.WARNING,
+        Logger::warning,
+        withParam,
+        logException,
+        withParent,
+        "abc",
+        Severity.WARN,
+        "WARNING");
+    testing.clearData();
+    test(
+        Level.SEVERE,
+        Logger::severe,
+        withParam,
+        logException,
+        withParent,
+        "abc",
+        Severity.ERROR,
+        "SEVERE");
+    testing.clearData();
+  }
+
+  private static void test(
+      Level level,
+      LoggerMethod loggerMethod,
+      boolean withParam,
+      boolean logException,
+      boolean withParent,
+      String expectedLoggerName,
+      Severity expectedSeverity,
+      String expectedSeverityText)
+      throws InterruptedException {
+
+    // when
+    if (withParent) {
+      testing.runWithSpan(
+          "parent", () -> performLogging(level, loggerMethod, withParam, logException));
+    } else {
+      performLogging(level, loggerMethod, withParam, logException);
+    }
+
+    // then
+    if (withParent) {
+      testing.waitForTraces(1);
+    }
+
+    if (expectedSeverity != null) {
+      LogRecordData log = testing.waitForLogRecords(1).get(0);
+      assertThat(log)
+          .hasBody(withParam ? "xyz: 123" : "xyz")
+          .hasInstrumentationScope(InstrumentationScopeInfo.builder(expectedLoggerName).build())
+          .hasSeverity(expectedSeverity)
+          .hasSeverityText(expectedSeverityText);
+      if (logException) {
+        assertThat(log)
+            .hasAttributesSatisfyingExactly(
+                equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+                equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()),
+                equalTo(SemanticAttributes.EXCEPTION_TYPE, IllegalStateException.class.getName()),
+                equalTo(SemanticAttributes.EXCEPTION_MESSAGE, "hello"),
+                satisfies(
+                    SemanticAttributes.EXCEPTION_STACKTRACE,
+                    v -> v.contains(JavaUtilLoggingTest.class.getName())));
+      } else {
+        assertThat(log)
+            .hasAttributesSatisfyingExactly(
+                equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+                equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()));
+      }
+
+      if (withParent) {
+        assertThat(log).hasSpanContext(testing.spans().get(0).getSpanContext());
+      } else {
+        assertThat(log.getSpanContext().isValid()).isFalse();
+      }
+
+    } else {
+      Thread.sleep(500); // sleep a bit just to make sure no log is captured
+      assertThat(testing.logRecords()).isEmpty();
+    }
+  }
+
+  private static void performLogging(
+      Level level, LoggerMethod loggerMethod, boolean withParam, boolean logException) {
+    if (logException) {
+      if (withParam) {
+        // this is the best j.u.l. can do
+        logger.log(level, new IllegalStateException("hello"), () -> "xyz: 123");
+      } else {
+        logger.log(level, "xyz", new IllegalStateException("hello"));
+      }
+    } else {
+      if (withParam) {
+        logger.log(level, "xyz: {0}", 123);
+      } else {
+        loggerMethod.call(logger, "xyz");
+      }
+    }
+  }
+
+  @FunctionalInterface
+  interface LoggerMethod {
+    void call(Logger logger, String msg);
+  }
+}

+ 1 - 0
instrumentation/jboss-logmanager/jboss-logmanager-appender-1.1/javaagent/build.gradle.kts

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

+ 0 - 129
instrumentation/jboss-logmanager/jboss-logmanager-appender-1.1/javaagent/src/test/groovy/JbossLogmanagerTest.groovy

@@ -1,129 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import io.opentelemetry.api.common.AttributeKey
-import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
-import io.opentelemetry.api.logs.Severity
-import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
-import org.jboss.logmanager.MDC
-import org.jboss.logmanager.Level
-import org.jboss.logmanager.LogContext
-import spock.lang.Unroll
-import org.jboss.logmanager.Logger
-
-import static org.assertj.core.api.Assertions.assertThat
-import static org.awaitility.Awaitility.await
-
-class JbossLogmanagerTest extends AgentInstrumentationSpecification {
-  private static final Logger logger = LogContext.getLogContext().getLogger("abc")
-  static {
-    logger.setLevel(Level.INFO)
-  }
-
-  @Unroll
-  def "test testMethod=#testMethod, exception=#exception, parent=#parent"(Level testMethod, boolean exception, boolean parent) {
-    when:
-    if (parent) {
-      runWithSpan("parent") {
-        if (exception) {
-          logger.log(testMethod, "xyz", new IllegalStateException("hello"))
-        } else {
-          logger.log(testMethod, "xyz")
-        }
-      }
-    } else {
-      if (exception) {
-        logger.log(testMethod, "xyz", new IllegalStateException("hello"))
-      } else {
-        logger.log(testMethod, "xyz")
-      }
-    }
-
-    then:
-    if (parent) {
-      waitForTraces(1)
-    }
-
-    if (severity != null) {
-      await()
-        .untilAsserted(
-          () -> {
-            assertThat(logRecords).hasSize(1)
-          })
-      def log = logRecords.get(0)
-      assertThat(log.getBody().asString()).isEqualTo("xyz")
-      assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo("abc")
-      assertThat(log.getSeverity()).isEqualTo(severity)
-      assertThat(log.getSeverityText()).isEqualTo(severityText)
-      if (exception) {
-        assertThat(log.getAttributes().size()).isEqualTo(5)
-        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_STACKTRACE)).contains(JbossLogmanagerTest.name)
-      } else {
-        assertThat(log.getAttributes().size()).isEqualTo(2)
-        assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
-        assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).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) {
-        assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
-      } else {
-        assertThat(log.getSpanContext().isValid()).isFalse()
-      }
-    } else {
-      Thread.sleep(500) // sleep a bit just to make sure no log is captured
-      assertThat(logRecords.size() == 0).isTrue()
-    }
-
-    where:
-    [args, exception, parent] << [
-      [
-        [Level.DEBUG, null, null],
-        [Level.INFO, Severity.INFO, "INFO"],
-        [Level.WARN, Severity.WARN, "WARN"],
-        [Level.ERROR, Severity.ERROR, "ERROR"]
-      ],
-      [true, false],
-      [true, false]
-    ].combinations()
-
-    testMethod = args[0] as Level
-    severity = args[1]
-    severityText = args[2]
-  }
-
-  def "test mdc"() {
-    when:
-    MDC.put("key1", "val1")
-    MDC.put("key2", "val2")
-    try {
-      logger.info("xyz")
-    } finally {
-      MDC.remove("key1")
-      MDC.remove("key2")
-    }
-
-    then:
-
-    await()
-      .untilAsserted(
-        () -> {
-          assertThat(logRecords).hasSize(1)
-        })
-    def log = logRecords.get(0)
-    assertThat(log.getBody().asString()).isEqualTo("xyz")
-    assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo("abc")
-    assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
-    assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(4)
-    assertThat(log.getAttributes().get(AttributeKey.stringKey("jboss-logmanager.mdc.key1"))).isEqualTo("val1")
-    assertThat(log.getAttributes().get(AttributeKey.stringKey("jboss-logmanager.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())
-  }
-}

+ 209 - 0
instrumentation/jboss-logmanager/jboss-logmanager-appender-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jbosslogmanager/appender/v1_1/JbossLogmanagerTest.java

@@ -0,0 +1,209 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jbosslogmanager.appender.v1_1;
+
+import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.logs.Severity;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
+import io.opentelemetry.sdk.logs.data.LogRecordData;
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
+import java.util.stream.Stream;
+import org.jboss.logmanager.Level;
+import org.jboss.logmanager.LogContext;
+import org.jboss.logmanager.Logger;
+import org.jboss.logmanager.MDC;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class JbossLogmanagerTest {
+
+  private static final Logger logger = LogContext.getLogContext().getLogger("abc");
+
+  @RegisterExtension
+  static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+  static {
+    logger.setLevel(Level.INFO);
+  }
+
+  private static Stream<Arguments> provideParameters() {
+    return Stream.of(
+        Arguments.of(false, false, false),
+        Arguments.of(false, false, true),
+        Arguments.of(false, true, false),
+        Arguments.of(false, true, true),
+        Arguments.of(true, false, false),
+        Arguments.of(true, false, true),
+        Arguments.of(true, true, false),
+        Arguments.of(true, true, true));
+  }
+
+  @ParameterizedTest
+  @MethodSource("provideParameters")
+  public void test(boolean withParam, boolean logException, boolean withParent)
+      throws InterruptedException {
+    test(
+        java.util.logging.Level.FINE,
+        java.util.logging.Logger::fine,
+        withParam,
+        logException,
+        withParent,
+        null,
+        null,
+        null);
+    testing.clearData();
+    test(
+        java.util.logging.Level.INFO,
+        java.util.logging.Logger::info,
+        withParam,
+        logException,
+        withParent,
+        "abc",
+        Severity.INFO,
+        "INFO");
+    testing.clearData();
+    test(
+        java.util.logging.Level.WARNING,
+        java.util.logging.Logger::warning,
+        withParam,
+        logException,
+        withParent,
+        "abc",
+        Severity.WARN,
+        "WARNING");
+    testing.clearData();
+    test(
+        java.util.logging.Level.SEVERE,
+        java.util.logging.Logger::severe,
+        withParam,
+        logException,
+        withParent,
+        "abc",
+        Severity.ERROR,
+        "SEVERE");
+    testing.clearData();
+  }
+
+  private static void test(
+      java.util.logging.Level level,
+      LoggerMethod loggerMethod,
+      boolean withParam,
+      boolean logException,
+      boolean withParent,
+      String expectedLoggerName,
+      Severity expectedSeverity,
+      String expectedSeverityText)
+      throws InterruptedException {
+
+    // when
+    if (withParent) {
+      testing.runWithSpan(
+          "parent", () -> performLogging(level, loggerMethod, withParam, logException));
+    } else {
+      performLogging(level, loggerMethod, withParam, logException);
+    }
+
+    // then
+    if (withParent) {
+      testing.waitForTraces(1);
+    }
+
+    if (expectedSeverity != null) {
+      LogRecordData log = testing.waitForLogRecords(1).get(0);
+      assertThat(log)
+          .hasBody(withParam ? "xyz: 123" : "xyz")
+          .hasInstrumentationScope(InstrumentationScopeInfo.builder(expectedLoggerName).build())
+          .hasSeverity(expectedSeverity)
+          .hasSeverityText(expectedSeverityText);
+      if (logException) {
+        assertThat(log)
+            .hasAttributesSatisfyingExactly(
+                equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+                equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()),
+                equalTo(SemanticAttributes.EXCEPTION_TYPE, IllegalStateException.class.getName()),
+                equalTo(SemanticAttributes.EXCEPTION_MESSAGE, "hello"),
+                satisfies(
+                    SemanticAttributes.EXCEPTION_STACKTRACE,
+                    v -> v.contains(JbossLogmanagerTest.class.getName())));
+      } else {
+        assertThat(log)
+            .hasAttributesSatisfyingExactly(
+                equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+                equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()));
+      }
+
+      if (withParent) {
+        assertThat(log).hasSpanContext(testing.spans().get(0).getSpanContext());
+      } else {
+        assertThat(log.getSpanContext().isValid()).isFalse();
+      }
+
+    } else {
+      Thread.sleep(500); // sleep a bit just to make sure no log is captured
+      assertThat(testing.logRecords()).isEmpty();
+    }
+  }
+
+  private static void performLogging(
+      java.util.logging.Level level,
+      LoggerMethod loggerMethod,
+      boolean withParam,
+      boolean logException) {
+    if (logException) {
+      if (withParam) {
+        // this is the best j.u.l. can do
+        logger.log(level, new IllegalStateException("hello"), () -> "xyz: 123");
+      } else {
+        logger.log(level, "xyz", new IllegalStateException("hello"));
+      }
+    } else {
+      if (withParam) {
+        logger.log(level, "xyz: {0}", 123);
+      } else {
+        loggerMethod.call(logger, "xyz");
+      }
+    }
+  }
+
+  @Test
+  void testMdc() {
+    MDC.put("key1", "val1");
+    MDC.put("key2", "val2");
+    try {
+      logger.info("xyz");
+    } finally {
+      MDC.remove("key1");
+      MDC.remove("key2");
+    }
+
+    LogRecordData log = testing.waitForLogRecords(1).get(0);
+    assertThat(log)
+        .hasBody("xyz")
+        .hasInstrumentationScope(InstrumentationScopeInfo.builder("abc").build())
+        .hasSeverity(Severity.INFO)
+        .hasSeverityText("INFO")
+        .hasAttributesSatisfyingExactly(
+            equalTo(AttributeKey.stringKey("jboss-logmanager.mdc.key1"), "val1"),
+            equalTo(AttributeKey.stringKey("jboss-logmanager.mdc.key2"), "val2"),
+            equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+            equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()));
+  }
+
+  @FunctionalInterface
+  interface LoggerMethod {
+    void call(java.util.logging.Logger logger, String msg);
+  }
+}

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

@@ -1,134 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import io.opentelemetry.api.common.AttributeKey
-import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
-import io.opentelemetry.api.logs.Severity
-import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
-import org.apache.log4j.Logger
-import org.apache.log4j.MDC
-import org.apache.log4j.helpers.Loader
-import spock.lang.Unroll
-
-import static org.assertj.core.api.Assertions.assertThat
-import static org.awaitility.Awaitility.await
-
-class Log4j1Test extends AgentInstrumentationSpecification {
-
-  static {
-    // this is needed because log4j1 incorrectly thinks the initial releases of Java 10-19
-    // (which have no '.' in their versions since there is no minor version) are Java 1.1,
-    // which is before ThreadLocal was introduced and so log4j1 disables MDC functionality
-    // (and the MDC tests below fail)
-    Loader.java1 = false
-  }
-
-  private static final Logger logger = Logger.getLogger("abc")
-
-  @Unroll
-  def "test method=#testMethod with exception=#exception and parent=#parent"() {
-    when:
-    if (parent) {
-      runWithSpan("parent") {
-        if (exception) {
-          logger."$testMethod"("xyz", new IllegalStateException("hello"))
-        } else {
-          logger."$testMethod"("xyz")
-        }
-      }
-    } else {
-      if (exception) {
-        logger."$testMethod"("xyz", new IllegalStateException("hello"))
-      } else {
-        logger."$testMethod"("xyz")
-      }
-    }
-
-    then:
-    if (parent) {
-      waitForTraces(1)
-    }
-
-    if (severity != null) {
-      await()
-        .untilAsserted(
-          () -> {
-            assertThat(logRecords).hasSize(1)
-          })
-      def log = logRecords.get(0)
-      assertThat(log.getBody().asString()).isEqualTo("xyz")
-      assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo("abc")
-      assertThat(log.getSeverity()).isEqualTo(severity)
-      assertThat(log.getSeverityText()).isEqualTo(severityText)
-      if (exception) {
-        assertThat(log.getAttributes().size()).isEqualTo(5)
-        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_STACKTRACE)).contains(Log4j1Test.name)
-      } else {
-        assertThat(log.getAttributes().size()).isEqualTo(2)
-        assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
-        assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).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) {
-        assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
-      } else {
-        assertThat(log.getSpanContext().isValid()).isFalse()
-      }
-    } else {
-      Thread.sleep(500) // sleep a bit just to make sure no log is captured
-      logRecords.size() == 0
-    }
-
-    where:
-    [args, exception, parent] << [
-      [
-        ["debug", null, null],
-        ["info", Severity.INFO, "INFO"],
-        ["warn", Severity.WARN, "WARN"],
-        ["error", Severity.ERROR, "ERROR"]
-      ],
-      [true, false],
-      [true, false]
-    ].combinations()
-
-    testMethod = args[0]
-    severity = args[1]
-    severityText = args[2]
-  }
-
-  def "test mdc"() {
-    when:
-    MDC.put("key1", "val1")
-    MDC.put("key2", "val2")
-    try {
-      logger.info("xyz")
-    } finally {
-      MDC.remove("key1")
-      MDC.remove("key2")
-    }
-
-    then:
-
-    await()
-      .untilAsserted(
-        () -> {
-          assertThat(logRecords).hasSize(1)
-        })
-    def log = logRecords.get(0)
-    assertThat(log.getBody().asString()).isEqualTo("xyz")
-    assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo("abc")
-    assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
-    assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(4)
-    assertThat(log.getAttributes().get(AttributeKey.stringKey("log4j.mdc.key1"))).isEqualTo("val1")
-    assertThat(log.getAttributes().get(AttributeKey.stringKey("log4j.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())
-  }
-}

+ 175 - 0
instrumentation/log4j/log4j-appender-1.2/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v1_2/Log4j1Test.java

@@ -0,0 +1,175 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.log4j.appender.v1_2;
+
+import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.logs.Severity;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
+import io.opentelemetry.sdk.logs.data.LogRecordData;
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
+import java.lang.reflect.Field;
+import java.util.stream.Stream;
+import org.apache.log4j.Logger;
+import org.apache.log4j.MDC;
+import org.apache.log4j.helpers.Loader;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class Log4j1Test {
+
+  static {
+    // this is needed because log4j1 incorrectly thinks the initial releases of Java 10-19
+    // (which have no '.' in their versions since there is no minor version) are Java 1.1,
+    // which is before ThreadLocal was introduced and so log4j1 disables MDC functionality
+    // (and the MDC tests below fail)
+    try {
+      Field java1 = Loader.class.getDeclaredField("java1");
+      java1.setAccessible(true);
+      java1.set(null, false);
+    } catch (NoSuchFieldException | IllegalAccessException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @RegisterExtension
+  static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+  private static final Logger logger = Logger.getLogger("abc");
+
+  private static Stream<Arguments> provideParameters() {
+    return Stream.of(
+        Arguments.of(false, false),
+        Arguments.of(false, true),
+        Arguments.of(true, false),
+        Arguments.of(true, true));
+  }
+
+  @ParameterizedTest
+  @MethodSource("provideParameters")
+  public void test(boolean logException, boolean withParent) throws InterruptedException {
+    test(Logger::debug, Logger::debug, logException, withParent, null, null, null);
+    testing.clearData();
+    test(Logger::info, Logger::info, logException, withParent, "abc", Severity.INFO, "INFO");
+    testing.clearData();
+    test(Logger::warn, Logger::warn, logException, withParent, "abc", Severity.WARN, "WARN");
+    testing.clearData();
+    test(Logger::error, Logger::error, logException, withParent, "abc", Severity.ERROR, "ERROR");
+    testing.clearData();
+  }
+
+  private static void test(
+      LoggerMethod loggerMethod,
+      ExceptionLoggerMethod exceptionLoggerMethod,
+      boolean logException,
+      boolean withParent,
+      String expectedLoggerName,
+      Severity expectedSeverity,
+      String expectedSeverityText)
+      throws InterruptedException {
+
+    // when
+    if (withParent) {
+      testing.runWithSpan(
+          "parent", () -> performLogging(loggerMethod, exceptionLoggerMethod, logException));
+    } else {
+      performLogging(loggerMethod, exceptionLoggerMethod, logException);
+    }
+
+    // then
+    if (withParent) {
+      testing.waitForTraces(1);
+    }
+
+    if (expectedSeverity != null) {
+      LogRecordData log = testing.waitForLogRecords(1).get(0);
+      assertThat(log)
+          .hasBody("xyz")
+          .hasInstrumentationScope(InstrumentationScopeInfo.builder(expectedLoggerName).build())
+          .hasSeverity(expectedSeverity)
+          .hasSeverityText(expectedSeverityText);
+      if (logException) {
+        assertThat(log)
+            .hasAttributesSatisfyingExactly(
+                equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+                equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()),
+                equalTo(SemanticAttributes.EXCEPTION_TYPE, IllegalStateException.class.getName()),
+                equalTo(SemanticAttributes.EXCEPTION_MESSAGE, "hello"),
+                satisfies(
+                    SemanticAttributes.EXCEPTION_STACKTRACE,
+                    v -> v.contains(Log4j1Test.class.getName())));
+      } else {
+        assertThat(log)
+            .hasAttributesSatisfyingExactly(
+                equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+                equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()));
+      }
+
+      if (withParent) {
+        assertThat(log).hasSpanContext(testing.spans().get(0).getSpanContext());
+      } else {
+        assertThat(log.getSpanContext().isValid()).isFalse();
+      }
+
+    } else {
+      Thread.sleep(500); // sleep a bit just to make sure no log is captured
+      assertThat(testing.logRecords()).isEmpty();
+    }
+  }
+
+  @Test
+  void testMdc() {
+    MDC.put("key1", "val1");
+    MDC.put("key2", "val2");
+    try {
+      logger.info("xyz");
+    } finally {
+      MDC.remove("key1");
+      MDC.remove("key2");
+    }
+
+    LogRecordData log = testing.waitForLogRecords(1).get(0);
+    assertThat(log)
+        .hasBody("xyz")
+        .hasInstrumentationScope(InstrumentationScopeInfo.builder("abc").build())
+        .hasSeverity(Severity.INFO)
+        .hasSeverityText("INFO")
+        .hasAttributesSatisfyingExactly(
+            equalTo(AttributeKey.stringKey("log4j.mdc.key1"), "val1"),
+            equalTo(AttributeKey.stringKey("log4j.mdc.key2"), "val2"),
+            equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+            equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()));
+  }
+
+  private static void performLogging(
+      LoggerMethod loggerMethod,
+      ExceptionLoggerMethod exceptionLoggerMethod,
+      boolean logException) {
+    if (logException) {
+      exceptionLoggerMethod.call(logger, "xyz", new IllegalStateException("hello"));
+    } else {
+      loggerMethod.call(logger, "xyz");
+    }
+  }
+
+  @FunctionalInterface
+  interface LoggerMethod {
+    void call(Logger logger, String msg);
+  }
+
+  @FunctionalInterface
+  interface ExceptionLoggerMethod {
+    void call(Logger logger, String msg, Exception e);
+  }
+}

+ 0 - 223
instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/groovy/Log4j2Test.groovy

@@ -1,223 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
-import io.opentelemetry.api.logs.Severity
-import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions
-import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
-import org.apache.logging.log4j.LogManager
-import org.apache.logging.log4j.Logger
-import org.apache.logging.log4j.MarkerManager
-import org.apache.logging.log4j.ThreadContext
-import org.apache.logging.log4j.message.StringMapMessage
-import org.apache.logging.log4j.message.StructuredDataMessage
-import spock.lang.Unroll
-
-import static org.assertj.core.api.Assertions.assertThat
-import static org.awaitility.Awaitility.await
-
-class Log4j2Test extends AgentInstrumentationSpecification {
-
-  private static final Logger logger = LogManager.getLogger("abc")
-
-  @Unroll
-  def "test method=#testMethod with exception=#exception and parent=#parent"() {
-    when:
-    if (parent) {
-      runWithSpan("parent") {
-        if (exception) {
-          logger."$testMethod"("xyz: {}", 123, new IllegalStateException("hello"))
-        } else {
-          logger."$testMethod"("xyz: {}", 123)
-        }
-      }
-    } else {
-      if (exception) {
-        logger."$testMethod"("xyz: {}", 123, new IllegalStateException("hello"))
-      } else {
-        logger."$testMethod"("xyz: {}", 123)
-      }
-    }
-
-    then:
-    if (parent) {
-      waitForTraces(1)
-    }
-
-    if (severity != null) {
-      await()
-        .untilAsserted(
-          () -> {
-            assertThat(logRecords).hasSize(1)
-          })
-      def log = logRecords.get(0)
-      assertThat(log.getBody().asString()).isEqualTo("xyz: 123")
-      assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo("abc")
-      assertThat(log.getSeverity()).isEqualTo(severity)
-      assertThat(log.getSeverityText()).isEqualTo(severityText)
-      if (exception) {
-        assertThat(log.getAttributes().size()).isEqualTo(5)
-        OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.EXCEPTION_TYPE, IllegalStateException.getName())
-        OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.EXCEPTION_MESSAGE, "hello")
-        OpenTelemetryAssertions.assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).contains(Log4j2Test.name)
-      } else {
-        assertThat(log.getAttributes().size()).isEqualTo(2)
-        assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE)).isNull()
-        assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE)).isNull()
-        assertThat(log.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE)).isNull()
-      }
-      OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName())
-      OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_ID, Thread.currentThread().getId())
-      if (parent) {
-        assertThat(log.getSpanContext()).isEqualTo(traces.get(0).get(0).getSpanContext())
-      } else {
-        assertThat(log.getSpanContext().isValid()).isFalse()
-      }
-    } else {
-      Thread.sleep(500) // sleep a bit just to make sure no log is captured
-      logRecords.size() == 0
-    }
-
-    where:
-    [args, exception, parent] << [
-      [
-        ["debug", null, null],
-        ["info", Severity.INFO, "INFO"],
-        ["warn", Severity.WARN, "WARN"],
-        ["error", Severity.ERROR, "ERROR"]
-      ],
-      [true, false],
-      [true, false]
-    ].combinations()
-
-    testMethod = args[0]
-    severity = args[1]
-    severityText = args[2]
-  }
-
-  def "test context data"() {
-    when:
-    ThreadContext.put("key1", "val1")
-    ThreadContext.put("key2", "val2")
-    try {
-      logger.info("xyz: {}", 123)
-    } finally {
-      ThreadContext.clearMap()
-    }
-
-    then:
-
-    await()
-      .untilAsserted(
-        () -> {
-          assertThat(logRecords).hasSize(1)
-        })
-    def log = logRecords.get(0)
-    assertThat(log.getBody().asString()).isEqualTo("xyz: 123")
-    assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo("abc")
-    assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
-    assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(4)
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("log4j.context_data.key1", "val1")
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("log4j.context_data.key2", "val2")
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName())
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_ID, Thread.currentThread().getId())
-  }
-
-  def "test string map message"() {
-    when:
-    StringMapMessage message = new StringMapMessage()
-    message.put("key1", "val1")
-    message.put("key2", "val2")
-    logger.info(message)
-
-    then:
-
-    await()
-      .untilAsserted(
-        () -> {
-          assertThat(logRecords).hasSize(1)
-        })
-    def log = logRecords.get(0)
-    assertThat(log.getBody().asString()).isEmpty()
-    assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo("abc")
-    assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
-    assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(4)
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("log4j.map_message.key1", "val1")
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("log4j.map_message.key2", "val2")
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName())
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_ID, Thread.currentThread().getId())
-  }
-
-  def "test string map message with special attribute"() {
-    when:
-    StringMapMessage message = new StringMapMessage()
-    message.put("key1", "val1")
-    message.put("message", "val2")
-    logger.info(message)
-
-    then:
-
-    await()
-      .untilAsserted(
-        () -> {
-          assertThat(logRecords).hasSize(1)
-        })
-    def log = logRecords.get(0)
-    assertThat(log.getBody().asString()).isEqualTo("val2")
-    assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo("abc")
-    assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
-    assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(3)
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("log4j.map_message.key1", "val1")
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName())
-    OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_ID, Thread.currentThread().getId())
-  }
-
-  def "test structured data map message"() {
-    when:
-    StructuredDataMessage message = new StructuredDataMessage("an id", "a message", "a type")
-    message.put("key1", "val1")
-    message.put("key2", "val2")
-    logger.info(message)
-
-    then:
-
-    await()
-      .untilAsserted(
-        () -> {
-          assertThat(logRecords).hasSize(1)
-        })
-    def log = logRecords.get(0)
-    assertThat(log.getBody().asString()).isEqualTo("a message")
-    assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo("abc")
-    assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
-    assertThat(log.getSeverityText()).isEqualTo("INFO")
-    assertThat(log.getAttributes().size()).isEqualTo(4)
-    OpenTelemetryAssertions.assertThat(log.getAttributes())
-        .containsEntry("log4j.map_message.key1","val1")
-        .containsEntry("log4j.map_message.key2", "val2")
-        .containsEntry(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName())
-        .containsEntry(SemanticAttributes.THREAD_ID, Thread.currentThread().getId())
-  }
-
-  @Unroll
-  def "marker test"() {
-    def markerName = "aMarker"
-    def marker = MarkerManager.getMarker(markerName)
-    when:
-    logger.info(marker, "message")
-
-    then:
-    await()
-      .untilAsserted(
-        () -> {
-          assertThat(logRecords).hasSize(1)
-          def log = logRecords.get(0)
-          OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("log4j.marker", markerName)
-        })
-  }
-}

+ 238 - 0
instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java

@@ -0,0 +1,238 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.log4j.appender.v2_17;
+
+import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.logs.Severity;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
+import io.opentelemetry.sdk.logs.data.LogRecordData;
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
+import java.util.stream.Stream;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.MarkerManager;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.message.StringMapMessage;
+import org.apache.logging.log4j.message.StructuredDataMessage;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class Log4j2Test {
+
+  @RegisterExtension
+  static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+  private static final Logger logger = LogManager.getLogger("abc");
+
+  private static Stream<Arguments> provideParameters() {
+    return Stream.of(
+        Arguments.of(false, false),
+        Arguments.of(false, true),
+        Arguments.of(true, false),
+        Arguments.of(true, true));
+  }
+
+  @ParameterizedTest
+  @MethodSource("provideParameters")
+  public void test(boolean logException, boolean withParent) throws InterruptedException {
+    test(Logger::debug, Logger::debug, logException, withParent, null, null, null);
+    testing.clearData();
+    test(Logger::info, Logger::info, logException, withParent, "abc", Severity.INFO, "INFO");
+    testing.clearData();
+    test(Logger::warn, Logger::warn, logException, withParent, "abc", Severity.WARN, "WARN");
+    testing.clearData();
+    test(Logger::error, Logger::error, logException, withParent, "abc", Severity.ERROR, "ERROR");
+    testing.clearData();
+  }
+
+  private static void test(
+      OneArgLoggerMethod oneArgLoggerMethod,
+      TwoArgLoggerMethod twoArgLoggerMethod,
+      boolean logException,
+      boolean withParent,
+      String expectedLoggerName,
+      Severity expectedSeverity,
+      String expectedSeverityText)
+      throws InterruptedException {
+
+    // when
+    if (withParent) {
+      testing.runWithSpan(
+          "parent", () -> performLogging(oneArgLoggerMethod, twoArgLoggerMethod, logException));
+    } else {
+      performLogging(oneArgLoggerMethod, twoArgLoggerMethod, logException);
+    }
+
+    // then
+    if (withParent) {
+      testing.waitForTraces(1);
+    }
+
+    if (expectedSeverity != null) {
+      LogRecordData log = testing.waitForLogRecords(1).get(0);
+      assertThat(log)
+          .hasBody("xyz: 123")
+          .hasInstrumentationScope(InstrumentationScopeInfo.builder(expectedLoggerName).build())
+          .hasSeverity(expectedSeverity)
+          .hasSeverityText(expectedSeverityText);
+      if (logException) {
+        assertThat(log)
+            .hasAttributesSatisfyingExactly(
+                equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+                equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()),
+                equalTo(SemanticAttributes.EXCEPTION_TYPE, IllegalStateException.class.getName()),
+                equalTo(SemanticAttributes.EXCEPTION_MESSAGE, "hello"),
+                satisfies(
+                    SemanticAttributes.EXCEPTION_STACKTRACE,
+                    v -> v.contains(Log4j2Test.class.getName())));
+      } else {
+        assertThat(log)
+            .hasAttributesSatisfyingExactly(
+                equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+                equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()));
+      }
+
+      if (withParent) {
+        assertThat(log).hasSpanContext(testing.spans().get(0).getSpanContext());
+      } else {
+        assertThat(log.getSpanContext().isValid()).isFalse();
+      }
+
+    } else {
+      Thread.sleep(500); // sleep a bit just to make sure no log is captured
+      assertThat(testing.logRecords()).isEmpty();
+    }
+  }
+
+  @Test
+  void testContextData() {
+    ThreadContext.put("key1", "val1");
+    ThreadContext.put("key2", "val2");
+    try {
+      logger.info("xyz: {}", 123);
+    } finally {
+      ThreadContext.clearMap();
+    }
+
+    LogRecordData log = testing.waitForLogRecords(1).get(0);
+    assertThat(log)
+        .hasBody("xyz: 123")
+        .hasInstrumentationScope(InstrumentationScopeInfo.builder("abc").build())
+        .hasSeverity(Severity.INFO)
+        .hasSeverityText("INFO")
+        .hasAttributesSatisfyingExactly(
+            equalTo(AttributeKey.stringKey("log4j.context_data.key1"), "val1"),
+            equalTo(AttributeKey.stringKey("log4j.context_data.key2"), "val2"),
+            equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+            equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()));
+  }
+
+  @Test
+  void testStringMapMessage() {
+    StringMapMessage message = new StringMapMessage();
+    message.put("key1", "val1");
+    message.put("key2", "val2");
+    logger.info(message);
+
+    LogRecordData log = testing.waitForLogRecords(1).get(0);
+    assertThat(log)
+        .hasBody("")
+        .hasInstrumentationScope(InstrumentationScopeInfo.builder("abc").build())
+        .hasSeverity(Severity.INFO)
+        .hasSeverityText("INFO")
+        .hasAttributesSatisfyingExactly(
+            equalTo(AttributeKey.stringKey("log4j.map_message.key1"), "val1"),
+            equalTo(AttributeKey.stringKey("log4j.map_message.key2"), "val2"),
+            equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+            equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()));
+  }
+
+  @Test
+  void testStringMapMessageWithSpecialAttribute() {
+    StringMapMessage message = new StringMapMessage();
+    message.put("key1", "val1");
+    message.put("message", "val2");
+    logger.info(message);
+
+    LogRecordData log = testing.waitForLogRecords(1).get(0);
+    assertThat(log)
+        .hasBody("val2")
+        .hasInstrumentationScope(InstrumentationScopeInfo.builder("abc").build())
+        .hasSeverity(Severity.INFO)
+        .hasSeverityText("INFO")
+        .hasAttributesSatisfyingExactly(
+            equalTo(AttributeKey.stringKey("log4j.map_message.key1"), "val1"),
+            equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+            equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()));
+  }
+
+  @Test
+  void testStructuredDataMapMessage() {
+    StructuredDataMessage message = new StructuredDataMessage("an id", "a message", "a type");
+    message.put("key1", "val1");
+    message.put("key2", "val2");
+    logger.info(message);
+
+    LogRecordData log = testing.waitForLogRecords(1).get(0);
+    assertThat(log)
+        .hasBody("a message")
+        .hasInstrumentationScope(InstrumentationScopeInfo.builder("abc").build())
+        .hasSeverity(Severity.INFO)
+        .hasSeverityText("INFO")
+        .hasAttributesSatisfyingExactly(
+            equalTo(AttributeKey.stringKey("log4j.map_message.key1"), "val1"),
+            equalTo(AttributeKey.stringKey("log4j.map_message.key2"), "val2"),
+            equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+            equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()));
+  }
+
+  @Test
+  public void testMarker() {
+
+    String markerName = "aMarker";
+    Marker marker = MarkerManager.getMarker(markerName);
+
+    logger.info(marker, "Message");
+
+    LogRecordData log = testing.waitForLogRecords(1).get(0);
+    assertThat(log)
+        .hasAttributesSatisfyingExactly(
+            equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+            equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()),
+            equalTo(AttributeKey.stringKey("log4j.marker"), markerName));
+  }
+
+  private static void performLogging(
+      OneArgLoggerMethod oneArgLoggerMethod,
+      TwoArgLoggerMethod twoArgLoggerMethod,
+      boolean logException) {
+    if (logException) {
+      twoArgLoggerMethod.call(logger, "xyz: {}", 123, new IllegalStateException("hello"));
+    } else {
+      oneArgLoggerMethod.call(logger, "xyz: {}", 123);
+    }
+  }
+
+  @FunctionalInterface
+  interface OneArgLoggerMethod {
+    void call(Logger logger, String msg, Object arg);
+  }
+
+  @FunctionalInterface
+  interface TwoArgLoggerMethod {
+    void call(Logger logger, String msg, Object arg1, Object arg2);
+  }
+}

+ 45 - 58
instrumentation/logback/logback-appender-1.0/javaagent/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/LogbackTest.java

@@ -6,18 +6,18 @@
 package io.opentelemetry.instrumentation.logback.appender.v1_0;
 
 import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
-import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
-import static org.awaitility.Awaitility.await;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
 
 import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.logs.Severity;
-import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification;
 import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
 import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
 import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
 import io.opentelemetry.sdk.logs.data.LogRecordData;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
 import java.util.stream.Stream;
+import org.assertj.core.api.AbstractLongAssert;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -29,7 +29,7 @@ import org.slf4j.MDC;
 import org.slf4j.Marker;
 import org.slf4j.MarkerFactory;
 
-class LogbackTest extends AgentInstrumentationSpecification {
+class LogbackTest {
 
   @RegisterExtension
   static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
@@ -134,9 +134,7 @@ class LogbackTest extends AgentInstrumentationSpecification {
     }
 
     if (expectedSeverity != null) {
-      await().untilAsserted(() -> assertThat(testing.logRecords().size()).isEqualTo(1));
-
-      LogRecordData log = testing.logRecords().get(0);
+      LogRecordData log = testing.waitForLogRecords(1).get(0);
       assertThat(log)
           .hasBody("xyz: 123")
           .hasInstrumentationScope(InstrumentationScopeInfo.builder(expectedLoggerName).build())
@@ -144,36 +142,31 @@ class LogbackTest extends AgentInstrumentationSpecification {
           .hasSeverityText(expectedSeverityText);
       if (logException) {
         assertThat(log)
-            .hasAttributesSatisfying(
-                attributes ->
-                    assertThat(attributes)
-                        .hasSize(9)
-                        .containsEntry(
-                            SemanticAttributes.EXCEPTION_TYPE,
-                            IllegalStateException.class.getName())
-                        .containsEntry(SemanticAttributes.EXCEPTION_MESSAGE, "hello")
-                        .hasEntrySatisfying(
-                            SemanticAttributes.EXCEPTION_STACKTRACE,
-                            value -> assertThat(value).contains(LogbackTest.class.getName())));
+            .hasAttributesSatisfyingExactly(
+                equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+                equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()),
+                equalTo(SemanticAttributes.CODE_NAMESPACE, LogbackTest.class.getName()),
+                equalTo(SemanticAttributes.CODE_FUNCTION, "performLogging"),
+                satisfies(SemanticAttributes.CODE_LINENO, AbstractLongAssert::isPositive),
+                equalTo(SemanticAttributes.CODE_FILEPATH, "LogbackTest.java"),
+                equalTo(SemanticAttributes.EXCEPTION_TYPE, IllegalStateException.class.getName()),
+                equalTo(SemanticAttributes.EXCEPTION_MESSAGE, "hello"),
+                satisfies(
+                    SemanticAttributes.EXCEPTION_STACKTRACE,
+                    v -> v.contains(LogbackTest.class.getName())));
       } else {
-        assertThat(log.getAttributes()).hasSize(6);
+        assertThat(log)
+            .hasAttributesSatisfyingExactly(
+                equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+                equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()),
+                equalTo(SemanticAttributes.CODE_NAMESPACE, LogbackTest.class.getName()),
+                equalTo(SemanticAttributes.CODE_FUNCTION, "performLogging"),
+                satisfies(SemanticAttributes.CODE_LINENO, AbstractLongAssert::isPositive),
+                equalTo(SemanticAttributes.CODE_FILEPATH, "LogbackTest.java"));
       }
 
-      assertThat(log)
-          .hasAttributesSatisfying(
-              attributes ->
-                  assertThat(attributes)
-                      .containsEntry(
-                          SemanticAttributes.THREAD_NAME, Thread.currentThread().getName())
-                      .containsEntry(SemanticAttributes.THREAD_ID, Thread.currentThread().getId())
-                      .containsEntry(SemanticAttributes.CODE_NAMESPACE, LogbackTest.class.getName())
-                      .containsEntry(SemanticAttributes.CODE_FUNCTION, "performLogging")
-                      .hasEntrySatisfying(
-                          SemanticAttributes.CODE_LINENO, value -> assertThat(value).isPositive())
-                      .containsEntry(SemanticAttributes.CODE_FILEPATH, "LogbackTest.java"));
-
       if (withParent) {
-        assertThat(log.getSpanContext()).isEqualTo(testing.spans().get(0).getSpanContext());
+        assertThat(log).hasSpanContext(testing.spans().get(0).getSpanContext());
       } else {
         assertThat(log.getSpanContext().isValid()).isFalse();
       }
@@ -194,28 +187,21 @@ class LogbackTest extends AgentInstrumentationSpecification {
       MDC.clear();
     }
 
-    await().untilAsserted(() -> assertThat(testing.logRecords().size()).isEqualTo(1));
-
-    LogRecordData log = getLogRecords().get(0);
+    LogRecordData log = testing.waitForLogRecords(1).get(0);
     assertThat(log)
         .hasBody("xyz: 123")
         .hasInstrumentationScope(InstrumentationScopeInfo.builder("abc").build())
         .hasSeverity(Severity.INFO)
         .hasSeverityText("INFO")
-        // TODO (trask) convert to hasAttributesSatisfyingExactly once that's available for logs
-        .hasAttributesSatisfying(
-            attributes ->
-                assertThat(attributes)
-                    .hasSize(8)
-                    .containsEntry(AttributeKey.stringKey("logback.mdc.key1"), "val1")
-                    .containsEntry(AttributeKey.stringKey("logback.mdc.key2"), "val2")
-                    .containsEntry(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName())
-                    .containsEntry(SemanticAttributes.THREAD_ID, Thread.currentThread().getId())
-                    .containsEntry(SemanticAttributes.CODE_NAMESPACE, LogbackTest.class.getName())
-                    .containsEntry(SemanticAttributes.CODE_FUNCTION, "testMdc")
-                    .hasEntrySatisfying(
-                        SemanticAttributes.CODE_LINENO, value -> assertThat(value).isPositive())
-                    .containsEntry(SemanticAttributes.CODE_FILEPATH, "LogbackTest.java"));
+        .hasAttributesSatisfyingExactly(
+            equalTo(AttributeKey.stringKey("logback.mdc.key1"), "val1"),
+            equalTo(AttributeKey.stringKey("logback.mdc.key2"), "val2"),
+            equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+            equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()),
+            equalTo(SemanticAttributes.CODE_NAMESPACE, LogbackTest.class.getName()),
+            equalTo(SemanticAttributes.CODE_FUNCTION, "testMdc"),
+            satisfies(SemanticAttributes.CODE_LINENO, AbstractLongAssert::isPositive),
+            equalTo(SemanticAttributes.CODE_FILEPATH, "LogbackTest.java"));
   }
 
   @Test
@@ -226,15 +212,16 @@ class LogbackTest extends AgentInstrumentationSpecification {
 
     abcLogger.info(marker, "Message");
 
-    await().untilAsserted(() -> assertThat(testing.logRecords().size()).isEqualTo(1));
-
-    LogRecordData log = getLogRecords().get(0);
-
+    LogRecordData log = testing.waitForLogRecords(1).get(0);
     assertThat(log)
-        .hasAttributesSatisfying(
-            attributes ->
-                assertThat(attributes)
-                    .containsEntry(AttributeKey.stringKey("logback.marker"), markerName));
+        .hasAttributesSatisfyingExactly(
+            equalTo(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName()),
+            equalTo(SemanticAttributes.THREAD_ID, Thread.currentThread().getId()),
+            equalTo(AttributeKey.stringKey("logback.marker"), markerName),
+            equalTo(SemanticAttributes.CODE_NAMESPACE, LogbackTest.class.getName()),
+            equalTo(SemanticAttributes.CODE_FUNCTION, "testMarker"),
+            satisfies(SemanticAttributes.CODE_LINENO, AbstractLongAssert::isPositive),
+            equalTo(SemanticAttributes.CODE_FILEPATH, "LogbackTest.java"));
   }
 
   private static void performLogging(

+ 18 - 2
testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java

@@ -20,6 +20,7 @@ import io.opentelemetry.sdk.logs.data.LogRecordData;
 import io.opentelemetry.sdk.metrics.data.MetricData;
 import io.opentelemetry.sdk.testing.assertj.TraceAssert;
 import io.opentelemetry.sdk.trace.data.SpanData;
+import java.time.Duration;
 import java.util.Comparator;
 import java.util.List;
 import java.util.function.Consumer;
@@ -109,13 +110,28 @@ public abstract class InstrumentationExtension
 
   /**
    * Wait until at least {@code numberOfTraces} traces are completed and return all captured traces.
-   * Note that there may be more than {@code numberOfTraces} collected. By default this waits up to
-   * 20 seconds, then times out.
+   * Note that there may be more than {@code numberOfTraces} collected. This waits up to 20 seconds,
+   * then times out.
    */
   public List<List<SpanData>> waitForTraces(int numberOfTraces) {
     return testRunner.waitForTraces(numberOfTraces, true);
   }
 
+  /**
+   * Wait until at least {@code numberOfLogRecords} log records are completed and return all
+   * captured log records. Note that there may be more than {@code numberOfLogRecords} collected.
+   * This waits up to 20 seconds, then times out.
+   */
+  public List<LogRecordData> waitForLogRecords(int numberOfLogRecords) {
+    await()
+        .timeout(Duration.ofSeconds(20))
+        .untilAsserted(
+            () ->
+                assertThat(testRunner.getExportedLogRecords().size())
+                    .isEqualTo(numberOfLogRecords));
+    return testRunner.getExportedLogRecords();
+  }
+
   @SafeVarargs
   @SuppressWarnings("varargs")
   public final void waitAndAssertSortedTraces(