Fix capturing context in log4j library instrumentation with async logger (#12176)
This commit is contained in:
parent
c2713e16d8
commit
e8c5c066d6
|
@ -37,7 +37,7 @@ dependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is needed for the async logging test
|
// this is needed for the async logging test
|
||||||
testImplementation("com.lmax:disruptor:3.4.2")
|
testLibrary("com.lmax:disruptor:3.4.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<Test>().configureEach {
|
tasks.withType<Test>().configureEach {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
import io.opentelemetry.api.GlobalOpenTelemetry;
|
import io.opentelemetry.api.GlobalOpenTelemetry;
|
||||||
import io.opentelemetry.api.logs.LogRecordBuilder;
|
import io.opentelemetry.api.logs.LogRecordBuilder;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
|
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
|
||||||
import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor;
|
import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor;
|
||||||
import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMapper;
|
import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMapper;
|
||||||
|
@ -77,7 +78,15 @@ public final class Log4jHelper {
|
||||||
threadId = currentThread.getId();
|
threadId = currentThread.getId();
|
||||||
}
|
}
|
||||||
mapper.mapLogEvent(
|
mapper.mapLogEvent(
|
||||||
builder, message, level, marker, throwable, contextData, threadName, threadId);
|
builder,
|
||||||
|
message,
|
||||||
|
level,
|
||||||
|
marker,
|
||||||
|
throwable,
|
||||||
|
contextData,
|
||||||
|
threadName,
|
||||||
|
threadId,
|
||||||
|
Context.current());
|
||||||
builder.setTimestamp(Instant.now());
|
builder.setTimestamp(Instant.now());
|
||||||
builder.emit();
|
builder.emit();
|
||||||
}
|
}
|
||||||
|
@ -87,12 +96,12 @@ public final class Log4jHelper {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Object getValue(Map<String, String> contextData, String key) {
|
public String getValue(Map<String, String> contextData, String key) {
|
||||||
return contextData.get(key);
|
return contextData.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forEach(Map<String, String> contextData, BiConsumer<String, Object> action) {
|
public void forEach(Map<String, String> contextData, BiConsumer<String, String> action) {
|
||||||
contextData.forEach(action);
|
contextData.forEach(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,26 @@ dependencies {
|
||||||
library("org.apache.logging.log4j:log4j-core:2.17.0")
|
library("org.apache.logging.log4j:log4j-core:2.17.0")
|
||||||
annotationProcessor("org.apache.logging.log4j:log4j-core:2.17.0")
|
annotationProcessor("org.apache.logging.log4j:log4j-core:2.17.0")
|
||||||
|
|
||||||
|
implementation(project(":instrumentation:log4j:log4j-context-data:log4j-context-data-2.17:library-autoconfigure"))
|
||||||
|
|
||||||
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
|
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
|
||||||
|
testLibrary("com.lmax:disruptor:3.3.4")
|
||||||
|
|
||||||
if (findProperty("testLatestDeps") as Boolean) {
|
if (findProperty("testLatestDeps") as Boolean) {
|
||||||
testCompileOnly("biz.aQute.bnd:biz.aQute.bnd.annotation:7.0.0")
|
testCompileOnly("biz.aQute.bnd:biz.aQute.bnd.annotation:7.0.0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<Test>().configureEach {
|
tasks {
|
||||||
jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
|
withType<Test>().configureEach {
|
||||||
|
jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
|
||||||
|
}
|
||||||
|
|
||||||
|
val testAsyncLogger by registering(Test::class) {
|
||||||
|
jvmArgs("-DLog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector")
|
||||||
|
}
|
||||||
|
|
||||||
|
check {
|
||||||
|
dependsOn(testAsyncLogger)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.apache.logging.log4j.message.Message;
|
||||||
import org.apache.logging.log4j.message.StringMapMessage;
|
import org.apache.logging.log4j.message.StringMapMessage;
|
||||||
import org.apache.logging.log4j.message.StructuredDataMessage;
|
import org.apache.logging.log4j.message.StructuredDataMessage;
|
||||||
import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
||||||
|
import org.apache.logging.log4j.util.SortedArrayStringMap;
|
||||||
|
|
||||||
class LogEventToReplay implements LogEvent {
|
class LogEventToReplay implements LogEvent {
|
||||||
|
|
||||||
|
@ -59,7 +60,8 @@ class LogEventToReplay implements LogEvent {
|
||||||
this.instant = logEvent.getInstant();
|
this.instant = logEvent.getInstant();
|
||||||
this.thrown = logEvent.getThrown();
|
this.thrown = logEvent.getThrown();
|
||||||
this.marker = logEvent.getMarker();
|
this.marker = logEvent.getMarker();
|
||||||
this.contextData = logEvent.getContextData();
|
// copy context data, context data map may be reused
|
||||||
|
this.contextData = new SortedArrayStringMap(logEvent.getContextData());
|
||||||
this.threadName = logEvent.getThreadName();
|
this.threadName = logEvent.getThreadName();
|
||||||
this.threadId = logEvent.getThreadId();
|
this.threadId = logEvent.getThreadId();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,14 @@ import static java.util.Collections.emptyList;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import io.opentelemetry.api.OpenTelemetry;
|
import io.opentelemetry.api.OpenTelemetry;
|
||||||
import io.opentelemetry.api.logs.LogRecordBuilder;
|
import io.opentelemetry.api.logs.LogRecordBuilder;
|
||||||
|
import io.opentelemetry.api.trace.Span;
|
||||||
|
import io.opentelemetry.api.trace.SpanContext;
|
||||||
|
import io.opentelemetry.api.trace.TraceFlags;
|
||||||
|
import io.opentelemetry.api.trace.TraceState;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor;
|
import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor;
|
||||||
import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMapper;
|
import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMapper;
|
||||||
|
import io.opentelemetry.instrumentation.log4j.contextdata.v2_17.internal.ContextDataKeys;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -272,6 +278,28 @@ public class OpenTelemetryAppender extends AbstractAppender {
|
||||||
LogRecordBuilder builder =
|
LogRecordBuilder builder =
|
||||||
openTelemetry.getLogsBridge().loggerBuilder(instrumentationName).build().logRecordBuilder();
|
openTelemetry.getLogsBridge().loggerBuilder(instrumentationName).build().logRecordBuilder();
|
||||||
ReadOnlyStringMap contextData = event.getContextData();
|
ReadOnlyStringMap contextData = event.getContextData();
|
||||||
|
Context context = Context.current();
|
||||||
|
// when using async logger we'll be executing on a different thread than what started logging
|
||||||
|
// reconstruct the context from context data
|
||||||
|
if (context == Context.root()) {
|
||||||
|
ContextDataAccessor<ReadOnlyStringMap> contextDataAccessor = ContextDataAccessorImpl.INSTANCE;
|
||||||
|
String traceId = contextDataAccessor.getValue(contextData, ContextDataKeys.TRACE_ID_KEY);
|
||||||
|
String spanId = contextDataAccessor.getValue(contextData, ContextDataKeys.SPAN_ID_KEY);
|
||||||
|
String traceFlags =
|
||||||
|
contextDataAccessor.getValue(contextData, ContextDataKeys.TRACE_FLAGS_KEY);
|
||||||
|
if (traceId != null && spanId != null && traceFlags != null) {
|
||||||
|
context =
|
||||||
|
Context.root()
|
||||||
|
.with(
|
||||||
|
Span.wrap(
|
||||||
|
SpanContext.create(
|
||||||
|
traceId,
|
||||||
|
spanId,
|
||||||
|
TraceFlags.fromHex(traceFlags, 0),
|
||||||
|
TraceState.getDefault())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mapper.mapLogEvent(
|
mapper.mapLogEvent(
|
||||||
builder,
|
builder,
|
||||||
event.getMessage(),
|
event.getMessage(),
|
||||||
|
@ -280,7 +308,8 @@ public class OpenTelemetryAppender extends AbstractAppender {
|
||||||
event.getThrown(),
|
event.getThrown(),
|
||||||
contextData,
|
contextData,
|
||||||
event.getThreadName(),
|
event.getThreadName(),
|
||||||
event.getThreadId());
|
event.getThreadId(),
|
||||||
|
context);
|
||||||
|
|
||||||
Instant timestamp = event.getInstant();
|
Instant timestamp = event.getInstant();
|
||||||
if (timestamp != null) {
|
if (timestamp != null) {
|
||||||
|
@ -297,12 +326,12 @@ public class OpenTelemetryAppender extends AbstractAppender {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Object getValue(ReadOnlyStringMap contextData, String key) {
|
public String getValue(ReadOnlyStringMap contextData, String key) {
|
||||||
return contextData.getValue(key);
|
return contextData.getValue(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forEach(ReadOnlyStringMap contextData, BiConsumer<String, Object> action) {
|
public void forEach(ReadOnlyStringMap contextData, BiConsumer<String, String> action) {
|
||||||
contextData.forEach(action::accept);
|
contextData.forEach(action::accept);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import javax.annotation.Nullable;
|
||||||
public interface ContextDataAccessor<T> {
|
public interface ContextDataAccessor<T> {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Object getValue(T contextData, String key);
|
String getValue(T contextData, String key);
|
||||||
|
|
||||||
void forEach(T contextData, BiConsumer<String, Object> action);
|
void forEach(T contextData, BiConsumer<String, String> action);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,8 @@ public final class LogEventMapper<T> {
|
||||||
@Nullable Throwable throwable,
|
@Nullable Throwable throwable,
|
||||||
T contextData,
|
T contextData,
|
||||||
String threadName,
|
String threadName,
|
||||||
long threadId) {
|
long threadId,
|
||||||
|
Context context) {
|
||||||
|
|
||||||
AttributesBuilder attributes = Attributes.builder();
|
AttributesBuilder attributes = Attributes.builder();
|
||||||
|
|
||||||
|
@ -116,8 +117,7 @@ public final class LogEventMapper<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setAllAttributes(attributes.build());
|
builder.setAllAttributes(attributes.build());
|
||||||
|
builder.setContext(context);
|
||||||
builder.setContext(Context.current());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// visible for testing
|
// visible for testing
|
||||||
|
@ -165,16 +165,16 @@ public final class LogEventMapper<T> {
|
||||||
contextData,
|
contextData,
|
||||||
(key, value) -> {
|
(key, value) -> {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
attributes.put(getContextDataAttributeKey(key), value.toString());
|
attributes.put(getContextDataAttributeKey(key), value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String key : captureContextDataAttributes) {
|
for (String key : captureContextDataAttributes) {
|
||||||
Object value = contextDataAccessor.getValue(contextData, key);
|
String value = contextDataAccessor.getValue(contextData, key);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
attributes.put(getContextDataAttributeKey(key), value.toString());
|
attributes.put(getContextDataAttributeKey(key), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equal
|
||||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
|
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
import io.opentelemetry.api.common.Attributes;
|
|
||||||
import io.opentelemetry.api.logs.Severity;
|
import io.opentelemetry.api.logs.Severity;
|
||||||
import io.opentelemetry.api.trace.SpanContext;
|
import io.opentelemetry.api.trace.SpanContext;
|
||||||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||||
|
@ -19,6 +18,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
|
||||||
import io.opentelemetry.sdk.logs.data.LogRecordData;
|
import io.opentelemetry.sdk.logs.data.LogRecordData;
|
||||||
import io.opentelemetry.sdk.resources.Resource;
|
import io.opentelemetry.sdk.resources.Resource;
|
||||||
import io.opentelemetry.semconv.ExceptionAttributes;
|
import io.opentelemetry.semconv.ExceptionAttributes;
|
||||||
|
import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
@ -93,7 +93,12 @@ abstract class AbstractOpenTelemetryAppenderTest {
|
||||||
.hasResource(resource)
|
.hasResource(resource)
|
||||||
.hasInstrumentationScope(instrumentationScopeInfo)
|
.hasInstrumentationScope(instrumentationScopeInfo)
|
||||||
.hasBody("log message 1")
|
.hasBody("log message 1")
|
||||||
.hasAttributes(Attributes.empty()));
|
.hasAttributesSatisfyingExactly(
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_NAME,
|
||||||
|
Thread.currentThread().getName()),
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -123,6 +128,9 @@ abstract class AbstractOpenTelemetryAppenderTest {
|
||||||
.hasSeverity(Severity.INFO)
|
.hasSeverity(Severity.INFO)
|
||||||
.hasSeverityText("INFO")
|
.hasSeverityText("INFO")
|
||||||
.hasAttributesSatisfyingExactly(
|
.hasAttributesSatisfyingExactly(
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_NAME, Thread.currentThread().getName()),
|
||||||
|
equalTo(ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId()),
|
||||||
equalTo(
|
equalTo(
|
||||||
ExceptionAttributes.EXCEPTION_TYPE,
|
ExceptionAttributes.EXCEPTION_TYPE,
|
||||||
IllegalStateException.class.getName()),
|
IllegalStateException.class.getName()),
|
||||||
|
@ -158,7 +166,13 @@ abstract class AbstractOpenTelemetryAppenderTest {
|
||||||
.hasInstrumentationScope(instrumentationScopeInfo)
|
.hasInstrumentationScope(instrumentationScopeInfo)
|
||||||
.hasBody("log message 1")
|
.hasBody("log message 1")
|
||||||
.hasAttributesSatisfyingExactly(
|
.hasAttributesSatisfyingExactly(
|
||||||
equalTo(stringKey("key1"), "val1"), equalTo(stringKey("key2"), "val2")));
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_NAME,
|
||||||
|
Thread.currentThread().getName()),
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId()),
|
||||||
|
equalTo(stringKey("key1"), "val1"),
|
||||||
|
equalTo(stringKey("key2"), "val2")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -177,6 +191,11 @@ abstract class AbstractOpenTelemetryAppenderTest {
|
||||||
.hasResource(resource)
|
.hasResource(resource)
|
||||||
.hasInstrumentationScope(instrumentationScopeInfo)
|
.hasInstrumentationScope(instrumentationScopeInfo)
|
||||||
.hasAttributesSatisfyingExactly(
|
.hasAttributesSatisfyingExactly(
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_NAME,
|
||||||
|
Thread.currentThread().getName()),
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId()),
|
||||||
equalTo(stringKey("log4j.map_message.key1"), "val1"),
|
equalTo(stringKey("log4j.map_message.key1"), "val1"),
|
||||||
equalTo(stringKey("log4j.map_message.key2"), "val2")));
|
equalTo(stringKey("log4j.map_message.key2"), "val2")));
|
||||||
}
|
}
|
||||||
|
@ -198,6 +217,11 @@ abstract class AbstractOpenTelemetryAppenderTest {
|
||||||
.hasInstrumentationScope(instrumentationScopeInfo)
|
.hasInstrumentationScope(instrumentationScopeInfo)
|
||||||
.hasBody("val2")
|
.hasBody("val2")
|
||||||
.hasAttributesSatisfyingExactly(
|
.hasAttributesSatisfyingExactly(
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_NAME,
|
||||||
|
Thread.currentThread().getName()),
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId()),
|
||||||
equalTo(stringKey("log4j.map_message.key1"), "val1")));
|
equalTo(stringKey("log4j.map_message.key1"), "val1")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,6 +257,11 @@ abstract class AbstractOpenTelemetryAppenderTest {
|
||||||
.hasInstrumentationScope(instrumentationScopeInfo)
|
.hasInstrumentationScope(instrumentationScopeInfo)
|
||||||
.hasBody("a message")
|
.hasBody("a message")
|
||||||
.hasAttributesSatisfyingExactly(
|
.hasAttributesSatisfyingExactly(
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_NAME,
|
||||||
|
Thread.currentThread().getName()),
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId()),
|
||||||
equalTo(stringKey("log4j.map_message.key1"), "val1"),
|
equalTo(stringKey("log4j.map_message.key1"), "val1"),
|
||||||
equalTo(stringKey("log4j.map_message.key2"), "val2")));
|
equalTo(stringKey("log4j.map_message.key2"), "val2")));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,11 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equal
|
||||||
|
|
||||||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||||
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
|
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
|
||||||
|
import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes;
|
||||||
import org.apache.logging.log4j.message.StringMapMessage;
|
import org.apache.logging.log4j.message.StringMapMessage;
|
||||||
import org.apache.logging.log4j.message.StructuredDataMessage;
|
import org.apache.logging.log4j.message.StructuredDataMessage;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Assumptions;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
@ -43,8 +45,16 @@ class LogReplayOpenTelemetryAppenderTest extends AbstractOpenTelemetryAppenderTe
|
||||||
OpenTelemetryAppender.install(testing.getOpenTelemetry());
|
OpenTelemetryAppender.install(testing.getOpenTelemetry());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isAsyncLogger() {
|
||||||
|
return logger.getClass().getName().contains("AsyncLogger");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void twoLogs() {
|
void twoLogs() {
|
||||||
|
// with async logger OpenTelemetryAppender.install may be called before second log message is
|
||||||
|
// captured, so we get 2 log records instead of the expected 1
|
||||||
|
Assumptions.assumeFalse(isAsyncLogger());
|
||||||
|
|
||||||
logger.info("log message 1");
|
logger.info("log message 1");
|
||||||
logger.info(
|
logger.info(
|
||||||
"log message 2"); // Won't be instrumented because cache size is 1 (see log4j2.xml file)
|
"log message 2"); // Won't be instrumented because cache size is 1 (see log4j2.xml file)
|
||||||
|
@ -61,6 +71,10 @@ class LogReplayOpenTelemetryAppenderTest extends AbstractOpenTelemetryAppenderTe
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void twoLogsStringMapMessage() {
|
void twoLogsStringMapMessage() {
|
||||||
|
// with async logger OpenTelemetryAppender.install may be called before second log message is
|
||||||
|
// captured, so we get 2 log records instead of the expected 1
|
||||||
|
Assumptions.assumeFalse(isAsyncLogger());
|
||||||
|
|
||||||
StringMapMessage message = new StringMapMessage();
|
StringMapMessage message = new StringMapMessage();
|
||||||
message.put("key1", "val1");
|
message.put("key1", "val1");
|
||||||
message.put("key2", "val2");
|
message.put("key2", "val2");
|
||||||
|
@ -81,12 +95,19 @@ class LogReplayOpenTelemetryAppenderTest extends AbstractOpenTelemetryAppenderTe
|
||||||
.hasResource(resource)
|
.hasResource(resource)
|
||||||
.hasInstrumentationScope(instrumentationScopeInfo)
|
.hasInstrumentationScope(instrumentationScopeInfo)
|
||||||
.hasAttributesSatisfyingExactly(
|
.hasAttributesSatisfyingExactly(
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_NAME, Thread.currentThread().getName()),
|
||||||
|
equalTo(ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId()),
|
||||||
equalTo(stringKey("log4j.map_message.key1"), "val1"),
|
equalTo(stringKey("log4j.map_message.key1"), "val1"),
|
||||||
equalTo(stringKey("log4j.map_message.key2"), "val2")));
|
equalTo(stringKey("log4j.map_message.key2"), "val2")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void twoLogsStructuredDataMessage() {
|
void twoLogsStructuredDataMessage() {
|
||||||
|
// with async logger OpenTelemetryAppender.install may be called before second log message is
|
||||||
|
// captured, so we get 2 log records instead of the expected 1
|
||||||
|
Assumptions.assumeFalse(isAsyncLogger());
|
||||||
|
|
||||||
StructuredDataMessage message = new StructuredDataMessage("an id", "a message", "a type");
|
StructuredDataMessage message = new StructuredDataMessage("an id", "a message", "a type");
|
||||||
message.put("key1", "val1");
|
message.put("key1", "val1");
|
||||||
message.put("key2", "val2");
|
message.put("key2", "val2");
|
||||||
|
@ -107,6 +128,9 @@ class LogReplayOpenTelemetryAppenderTest extends AbstractOpenTelemetryAppenderTe
|
||||||
.hasInstrumentationScope(instrumentationScopeInfo)
|
.hasInstrumentationScope(instrumentationScopeInfo)
|
||||||
.hasBody("a message")
|
.hasBody("a message")
|
||||||
.hasAttributesSatisfyingExactly(
|
.hasAttributesSatisfyingExactly(
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_NAME, Thread.currentThread().getName()),
|
||||||
|
equalTo(ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId()),
|
||||||
equalTo(stringKey("log4j.map_message.key1"), "val1"),
|
equalTo(stringKey("log4j.map_message.key1"), "val1"),
|
||||||
equalTo(stringKey("log4j.map_message.key2"), "val2")));
|
equalTo(stringKey("log4j.map_message.key2"), "val2")));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,12 @@
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.log4j.appender.v2_17;
|
package io.opentelemetry.instrumentation.log4j.appender.v2_17;
|
||||||
|
|
||||||
|
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
|
||||||
|
|
||||||
import io.opentelemetry.api.trace.Span;
|
import io.opentelemetry.api.trace.Span;
|
||||||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||||
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
|
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
|
||||||
|
import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
@ -30,7 +33,7 @@ class OpenTelemetryAppenderTest extends AbstractOpenTelemetryAppenderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void logWithSpan() { // Does not work for log replay but it is not likely to occur because
|
void logWithSpan() { // Does not work for log replay, but it is not likely to occur because
|
||||||
// the log replay is related to the case where an OpenTelemetry object is not yet available
|
// the log replay is related to the case where an OpenTelemetry object is not yet available
|
||||||
// at the time the log is executed (and if no OpenTelemetry is available, the context
|
// at the time the log is executed (and if no OpenTelemetry is available, the context
|
||||||
// propagation can't happen)
|
// propagation can't happen)
|
||||||
|
@ -44,6 +47,13 @@ class OpenTelemetryAppenderTest extends AbstractOpenTelemetryAppenderTest {
|
||||||
|
|
||||||
executeAfterLogsExecution();
|
executeAfterLogsExecution();
|
||||||
|
|
||||||
testing.waitAndAssertLogRecords(logRecord -> logRecord.hasSpanContext(span1.getSpanContext()));
|
testing.waitAndAssertLogRecords(
|
||||||
|
logRecord ->
|
||||||
|
logRecord
|
||||||
|
.hasSpanContext(span1.getSpanContext())
|
||||||
|
.hasAttributesSatisfying(
|
||||||
|
equalTo(
|
||||||
|
ThreadIncubatingAttributes.THREAD_NAME, Thread.currentThread().getName()),
|
||||||
|
equalTo(ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,12 +180,12 @@ class LogEventMapperTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Object getValue(Map<String, String> contextData, String key) {
|
public String getValue(Map<String, String> contextData, String key) {
|
||||||
return contextData.get(key);
|
return contextData.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forEach(Map<String, String> contextData, BiConsumer<String, Object> action) {
|
public void forEach(Map<String, String> contextData, BiConsumer<String, String> action) {
|
||||||
contextData.forEach(action);
|
contextData.forEach(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} traceId: %X{trace_id} spanId: %X{span_id} flags: %X{trace_flags} - %msg%n"/>
|
pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} traceId: %X{trace_id} spanId: %X{span_id} flags: %X{trace_flags} - %msg%n"/>
|
||||||
</Console>
|
</Console>
|
||||||
<!-- TODO run tests both with and without experimental log attributes -->
|
<!-- TODO run tests both with and without experimental log attributes -->
|
||||||
<OpenTelemetry name="OpenTelemetryAppender" numLogsCapturedBeforeOtelInstall="1" captureMapMessageAttributes="true" captureMarkerAttribute="true" captureContextDataAttributes="*"/>
|
<OpenTelemetry name="OpenTelemetryAppender" numLogsCapturedBeforeOtelInstall="1" captureMapMessageAttributes="true" captureMarkerAttribute="true" captureContextDataAttributes="*" captureExperimentalAttributes="true"/>
|
||||||
</Appenders>
|
</Appenders>
|
||||||
<Loggers>
|
<Loggers>
|
||||||
<Logger name="TestLogger" level="All">
|
<Logger name="TestLogger" level="All">
|
||||||
|
|
|
@ -10,8 +10,8 @@ import io.opentelemetry.api.baggage.BaggageEntry;
|
||||||
import io.opentelemetry.api.trace.Span;
|
import io.opentelemetry.api.trace.Span;
|
||||||
import io.opentelemetry.api.trace.SpanContext;
|
import io.opentelemetry.api.trace.SpanContext;
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.instrumentation.api.incubator.log.LoggingContextConstants;
|
|
||||||
import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil;
|
import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil;
|
||||||
|
import io.opentelemetry.instrumentation.log4j.contextdata.v2_17.internal.ContextDataKeys;
|
||||||
import io.opentelemetry.javaagent.bootstrap.internal.ConfiguredResourceAttributesHolder;
|
import io.opentelemetry.javaagent.bootstrap.internal.ConfiguredResourceAttributesHolder;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -25,15 +25,6 @@ import org.apache.logging.log4j.core.util.ContextDataProvider;
|
||||||
public class OpenTelemetryContextDataProvider implements ContextDataProvider {
|
public class OpenTelemetryContextDataProvider implements ContextDataProvider {
|
||||||
private static final boolean BAGGAGE_ENABLED =
|
private static final boolean BAGGAGE_ENABLED =
|
||||||
ConfigPropertiesUtil.getBoolean("otel.instrumentation.log4j-context-data.add-baggage", false);
|
ConfigPropertiesUtil.getBoolean("otel.instrumentation.log4j-context-data.add-baggage", false);
|
||||||
private static final String TRACE_ID_KEY =
|
|
||||||
ConfigPropertiesUtil.getString(
|
|
||||||
"otel.instrumentation.common.logging.trace-id", LoggingContextConstants.TRACE_ID);
|
|
||||||
private static final String SPAN_ID_KEY =
|
|
||||||
ConfigPropertiesUtil.getString(
|
|
||||||
"otel.instrumentation.common.logging.span-id", LoggingContextConstants.SPAN_ID);
|
|
||||||
private static final String TRACE_FLAGS_KEY =
|
|
||||||
ConfigPropertiesUtil.getString(
|
|
||||||
"otel.instrumentation.common.logging.trace-flags", LoggingContextConstants.TRACE_FLAGS);
|
|
||||||
private static final boolean configuredResourceAttributeAccessible =
|
private static final boolean configuredResourceAttributeAccessible =
|
||||||
isConfiguredResourceAttributeAccessible();
|
isConfiguredResourceAttributeAccessible();
|
||||||
private static final Map<String, String> staticContextData = getStaticContextData();
|
private static final Map<String, String> staticContextData = getStaticContextData();
|
||||||
|
@ -76,13 +67,11 @@ public class OpenTelemetryContextDataProvider implements ContextDataProvider {
|
||||||
return staticContextData;
|
return staticContextData;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> contextData = new HashMap<>();
|
Map<String, String> contextData = new HashMap<>(staticContextData);
|
||||||
contextData.putAll(staticContextData);
|
|
||||||
|
|
||||||
SpanContext spanContext = currentSpan.getSpanContext();
|
SpanContext spanContext = currentSpan.getSpanContext();
|
||||||
contextData.put(TRACE_ID_KEY, spanContext.getTraceId());
|
contextData.put(ContextDataKeys.TRACE_ID_KEY, spanContext.getTraceId());
|
||||||
contextData.put(SPAN_ID_KEY, spanContext.getSpanId());
|
contextData.put(ContextDataKeys.SPAN_ID_KEY, spanContext.getSpanId());
|
||||||
contextData.put(TRACE_FLAGS_KEY, spanContext.getTraceFlags().asHex());
|
contextData.put(ContextDataKeys.TRACE_FLAGS_KEY, spanContext.getTraceFlags().asHex());
|
||||||
|
|
||||||
if (BAGGAGE_ENABLED) {
|
if (BAGGAGE_ENABLED) {
|
||||||
Baggage baggage = Baggage.fromContext(context);
|
Baggage baggage = Baggage.fromContext(context);
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.log4j.contextdata.v2_17.internal;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.incubator.log.LoggingContextConstants;
|
||||||
|
import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
|
||||||
|
* any time.
|
||||||
|
*/
|
||||||
|
public final class ContextDataKeys {
|
||||||
|
public static final String TRACE_ID_KEY =
|
||||||
|
ConfigPropertiesUtil.getString(
|
||||||
|
"otel.instrumentation.common.logging.trace-id", LoggingContextConstants.TRACE_ID);
|
||||||
|
public static final String SPAN_ID_KEY =
|
||||||
|
ConfigPropertiesUtil.getString(
|
||||||
|
"otel.instrumentation.common.logging.span-id", LoggingContextConstants.SPAN_ID);
|
||||||
|
public static final String TRACE_FLAGS_KEY =
|
||||||
|
ConfigPropertiesUtil.getString(
|
||||||
|
"otel.instrumentation.common.logging.trace-flags", LoggingContextConstants.TRACE_FLAGS);
|
||||||
|
|
||||||
|
private ContextDataKeys() {}
|
||||||
|
}
|
Loading…
Reference in New Issue