Convert logback test to Java (#6613)
This commit is contained in:
parent
5afd6b346b
commit
ec3ba77101
|
@ -47,6 +47,7 @@ dependencies {
|
||||||
|
|
||||||
implementation(project(":instrumentation:logback:logback-appender-1.0:library"))
|
implementation(project(":instrumentation:logback:logback-appender-1.0:library"))
|
||||||
|
|
||||||
|
testImplementation("io.opentelemetry:opentelemetry-sdk-logs-testing")
|
||||||
testImplementation("org.awaitility:awaitility")
|
testImplementation("org.awaitility:awaitility")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,139 +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.sdk.logs.data.Severity
|
|
||||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.slf4j.MDC
|
|
||||||
import spock.lang.Unroll
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat
|
|
||||||
import static org.awaitility.Awaitility.await
|
|
||||||
|
|
||||||
class LogbackTest extends AgentInstrumentationSpecification {
|
|
||||||
|
|
||||||
private static final Logger abcLogger = LoggerFactory.getLogger("abc")
|
|
||||||
private static final Logger defLogger = LoggerFactory.getLogger("def")
|
|
||||||
|
|
||||||
@Unroll
|
|
||||||
def "test logger=#loggerName 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
String jvmVersion = System.getProperty("java.vm.specification.version")
|
|
||||||
int codeAttributes = 3
|
|
||||||
boolean jvmVersionGreaterThanOrEqualTo18 = !jvmVersion.startsWith("1.8") && Integer.parseInt(jvmVersion) >= 18
|
|
||||||
if (jvmVersionGreaterThanOrEqualTo18) {
|
|
||||||
codeAttributes = 4 // Java 18 specificity on line number (lineNumber > 0 check)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (severity != null) {
|
|
||||||
await()
|
|
||||||
.untilAsserted(
|
|
||||||
() -> {
|
|
||||||
assertThat(logs).hasSize(1)
|
|
||||||
})
|
|
||||||
def log = logs.get(0)
|
|
||||||
assertThat(log.getBody().asString()).isEqualTo("xyz: 123")
|
|
||||||
assertThat(log.getInstrumentationScopeInfo().getName()).isEqualTo(loggerName)
|
|
||||||
assertThat(log.getSeverity()).isEqualTo(severity)
|
|
||||||
assertThat(log.getSeverityText()).isEqualTo(severityText)
|
|
||||||
if (exception) {
|
|
||||||
assertThat(log.getAttributes().size()).isEqualTo(5 + codeAttributes)
|
|
||||||
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(LogbackTest.name)
|
|
||||||
} else {
|
|
||||||
assertThat(log.getAttributes().size()).isEqualTo(2 + codeAttributes)
|
|
||||||
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 span is captured
|
|
||||||
logs.size() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
where:
|
|
||||||
[args, exception, parent] << [
|
|
||||||
[
|
|
||||||
[abcLogger, "abc", "debug", null, null],
|
|
||||||
[abcLogger, "abc", "info", Severity.INFO, "INFO"],
|
|
||||||
[abcLogger, "abc", "warn", Severity.WARN, "WARN"],
|
|
||||||
[abcLogger, "abc", "error", Severity.ERROR, "ERROR"],
|
|
||||||
[defLogger, "def", "debug", null, null],
|
|
||||||
[defLogger, "def", "info", null, null],
|
|
||||||
[defLogger, "def", "warn", Severity.WARN, "WARN"],
|
|
||||||
[defLogger, "def", "error", Severity.ERROR, "ERROR"]
|
|
||||||
],
|
|
||||||
[true, false],
|
|
||||||
[true, false]
|
|
||||||
].combinations()
|
|
||||||
|
|
||||||
logger = args[0]
|
|
||||||
loggerName = args[1]
|
|
||||||
testMethod = args[2]
|
|
||||||
severity = args[3]
|
|
||||||
severityText = args[4]
|
|
||||||
}
|
|
||||||
|
|
||||||
def "test mdc"() {
|
|
||||||
when:
|
|
||||||
MDC.put("key1", "val1")
|
|
||||||
MDC.put("key2", "val2")
|
|
||||||
try {
|
|
||||||
abcLogger.info("xyz: {}", 123)
|
|
||||||
} finally {
|
|
||||||
MDC.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
then:
|
|
||||||
|
|
||||||
await()
|
|
||||||
.untilAsserted(
|
|
||||||
() -> {
|
|
||||||
assertThat(logs).hasSize(1)
|
|
||||||
})
|
|
||||||
def log = logs.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(3 + 3) // 3 code attributes
|
|
||||||
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(SemanticAttributes.THREAD_NAME)).isEqualTo(Thread.currentThread().getName())
|
|
||||||
assertThat(log.getAttributes().get(SemanticAttributes.THREAD_ID)).isEqualTo(Thread.currentThread().getId())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 io.opentelemetry.api.common.AttributeKey;
|
||||||
|
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.LogData;
|
||||||
|
import io.opentelemetry.sdk.logs.data.Severity;
|
||||||
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
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;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
class LogbackTest extends AgentInstrumentationSpecification {
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
|
||||||
|
|
||||||
|
private static final Logger abcLogger = LoggerFactory.getLogger("abc");
|
||||||
|
private static final Logger defLogger = LoggerFactory.getLogger("def");
|
||||||
|
|
||||||
|
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(abcLogger, Logger::debug, Logger::debug, logException, withParent, null, null, null);
|
||||||
|
testing.clearData();
|
||||||
|
test(
|
||||||
|
abcLogger,
|
||||||
|
Logger::info,
|
||||||
|
Logger::info,
|
||||||
|
logException,
|
||||||
|
withParent,
|
||||||
|
"abc",
|
||||||
|
Severity.INFO,
|
||||||
|
"INFO");
|
||||||
|
testing.clearData();
|
||||||
|
test(
|
||||||
|
abcLogger,
|
||||||
|
Logger::warn,
|
||||||
|
Logger::warn,
|
||||||
|
logException,
|
||||||
|
withParent,
|
||||||
|
"abc",
|
||||||
|
Severity.WARN,
|
||||||
|
"WARN");
|
||||||
|
testing.clearData();
|
||||||
|
test(
|
||||||
|
abcLogger,
|
||||||
|
Logger::error,
|
||||||
|
Logger::error,
|
||||||
|
logException,
|
||||||
|
withParent,
|
||||||
|
"abc",
|
||||||
|
Severity.ERROR,
|
||||||
|
"ERROR");
|
||||||
|
testing.clearData();
|
||||||
|
test(defLogger, Logger::debug, Logger::debug, logException, withParent, null, null, null);
|
||||||
|
testing.clearData();
|
||||||
|
test(defLogger, Logger::info, Logger::info, logException, withParent, null, null, null);
|
||||||
|
testing.clearData();
|
||||||
|
test(
|
||||||
|
defLogger,
|
||||||
|
Logger::warn,
|
||||||
|
Logger::warn,
|
||||||
|
logException,
|
||||||
|
withParent,
|
||||||
|
"def",
|
||||||
|
Severity.WARN,
|
||||||
|
"WARN");
|
||||||
|
testing.clearData();
|
||||||
|
test(
|
||||||
|
defLogger,
|
||||||
|
Logger::error,
|
||||||
|
Logger::error,
|
||||||
|
logException,
|
||||||
|
withParent,
|
||||||
|
"def",
|
||||||
|
Severity.ERROR,
|
||||||
|
"ERROR");
|
||||||
|
testing.clearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void test(
|
||||||
|
Logger logger,
|
||||||
|
OneArgLoggerMethod oneArgLoggerMethod,
|
||||||
|
TwoArgLoggerMethod twoArgLoggerMethod,
|
||||||
|
boolean logException,
|
||||||
|
boolean withParent,
|
||||||
|
String expectedLoggerName,
|
||||||
|
Severity expectedSeverity,
|
||||||
|
String expectedSeverityText)
|
||||||
|
throws InterruptedException {
|
||||||
|
|
||||||
|
// when
|
||||||
|
if (withParent) {
|
||||||
|
testing.runWithSpan(
|
||||||
|
"parent",
|
||||||
|
() -> {
|
||||||
|
performLogging(logger, oneArgLoggerMethod, twoArgLoggerMethod, logException);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
performLogging(logger, oneArgLoggerMethod, twoArgLoggerMethod, logException);
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
if (withParent) {
|
||||||
|
testing.waitForTraces(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expectedSeverity != null) {
|
||||||
|
await().untilAsserted(() -> assertThat(testing.logs().size()).isEqualTo(1));
|
||||||
|
|
||||||
|
LogData log = testing.logs().get(0);
|
||||||
|
assertThat(log)
|
||||||
|
.hasBody("xyz: 123")
|
||||||
|
// TODO (trask) why is version "" instead of null?
|
||||||
|
.hasInstrumentationScope(
|
||||||
|
InstrumentationScopeInfo.builder(expectedLoggerName).setVersion("").build())
|
||||||
|
.hasSeverity(expectedSeverity)
|
||||||
|
.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())));
|
||||||
|
} else {
|
||||||
|
assertThat(log.getAttributes()).hasSize(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
} else {
|
||||||
|
assertThat(log.getSpanContext().isValid()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Thread.sleep(500); // sleep a bit just to make sure no log is captured
|
||||||
|
assertThat(testing.logs()).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMdc() {
|
||||||
|
MDC.put("key1", "val1");
|
||||||
|
MDC.put("key2", "val2");
|
||||||
|
try {
|
||||||
|
abcLogger.info("xyz: {}", 123);
|
||||||
|
} finally {
|
||||||
|
MDC.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
await().untilAsserted(() -> assertThat(testing.logs().size()).isEqualTo(1));
|
||||||
|
|
||||||
|
LogData log = getLogs().get(0);
|
||||||
|
assertThat(log)
|
||||||
|
.hasBody("xyz: 123")
|
||||||
|
// TODO (trask) why is version "" instead of null?
|
||||||
|
.hasInstrumentationScope(InstrumentationScopeInfo.builder("abc").setVersion("").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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void performLogging(
|
||||||
|
Logger logger,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import io.opentelemetry.instrumentation.testing.util.ContextStorageCloser;
|
||||||
import io.opentelemetry.instrumentation.testing.util.ThrowingRunnable;
|
import io.opentelemetry.instrumentation.testing.util.ThrowingRunnable;
|
||||||
import io.opentelemetry.instrumentation.testing.util.ThrowingSupplier;
|
import io.opentelemetry.instrumentation.testing.util.ThrowingSupplier;
|
||||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||||
|
import io.opentelemetry.sdk.logs.data.LogData;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData;
|
import io.opentelemetry.sdk.metrics.data.MetricData;
|
||||||
import io.opentelemetry.sdk.testing.assertj.TraceAssert;
|
import io.opentelemetry.sdk.testing.assertj.TraceAssert;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
|
@ -74,6 +75,11 @@ public abstract class InstrumentationExtension
|
||||||
return testRunner.getExportedMetrics();
|
return testRunner.getExportedMetrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Return a list of all captured logs. */
|
||||||
|
public List<LogData> logs() {
|
||||||
|
return testRunner.getExportedLogs();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for the assertion applied to all metrics of the given instrumentation and metric name to
|
* Waits for the assertion applied to all metrics of the given instrumentation and metric name to
|
||||||
* pass.
|
* pass.
|
||||||
|
|
Loading…
Reference in New Issue