Add log4j 1.2 appender MDC capture (#5475)
* Add log4j 1.2 appender MDC capture * Spotless
This commit is contained in:
parent
627f5d3e1c
commit
4a8d6d552e
|
@ -32,5 +32,6 @@ configurations {
|
||||||
|
|
||||||
tasks.withType<Test>().configureEach {
|
tasks.withType<Test>().configureEach {
|
||||||
// TODO run tests both with and without experimental log attributes
|
// TODO run tests both with and without experimental log attributes
|
||||||
|
jvmArgs("-Dotel.instrumentation.log4j-appender.experimental.capture-mdc-attributes=*")
|
||||||
jvmArgs("-Dotel.instrumentation.log4j-appender.experimental-log-attributes=true")
|
jvmArgs("-Dotel.instrumentation.log4j-appender.experimental-log-attributes=true")
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ class Log4jAppenderInstrumentation implements TypeInstrumentation {
|
||||||
// framework delegates to another
|
// framework delegates to another
|
||||||
callDepth = CallDepth.forClass(LogEmitterProvider.class);
|
callDepth = CallDepth.forClass(LogEmitterProvider.class);
|
||||||
if (callDepth.getAndIncrement() == 0) {
|
if (callDepth.getAndIncrement() == 0) {
|
||||||
Log4jHelper.capture(logger, level, message, t);
|
LogEventMapper.INSTANCE.capture(logger, level, message, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,20 +5,33 @@
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.log4j.appender.v1_2;
|
package io.opentelemetry.javaagent.instrumentation.log4j.appender.v1_2;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.common.AttributeKey;
|
||||||
import io.opentelemetry.api.common.Attributes;
|
import io.opentelemetry.api.common.Attributes;
|
||||||
import io.opentelemetry.api.common.AttributesBuilder;
|
import io.opentelemetry.api.common.AttributesBuilder;
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.instrumentation.api.appender.internal.LogBuilder;
|
import io.opentelemetry.instrumentation.api.appender.internal.LogBuilder;
|
||||||
import io.opentelemetry.instrumentation.api.appender.internal.Severity;
|
import io.opentelemetry.instrumentation.api.appender.internal.Severity;
|
||||||
|
import io.opentelemetry.instrumentation.api.cache.Cache;
|
||||||
import io.opentelemetry.instrumentation.api.config.Config;
|
import io.opentelemetry.instrumentation.api.config.Config;
|
||||||
import io.opentelemetry.javaagent.instrumentation.api.appender.internal.AgentLogEmitterProvider;
|
import io.opentelemetry.javaagent.instrumentation.api.appender.internal.AgentLogEmitterProvider;
|
||||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.apache.log4j.Category;
|
import org.apache.log4j.Category;
|
||||||
|
import org.apache.log4j.MDC;
|
||||||
import org.apache.log4j.Priority;
|
import org.apache.log4j.Priority;
|
||||||
|
|
||||||
public final class Log4jHelper {
|
public final class LogEventMapper {
|
||||||
|
|
||||||
|
private static final Cache<String, AttributeKey<String>> mdcAttributeKeys = Cache.bounded(100);
|
||||||
|
|
||||||
|
public static final LogEventMapper INSTANCE = new LogEventMapper();
|
||||||
|
|
||||||
// copied from org.apache.log4j.Level because it was only introduced in 1.2.12
|
// copied from org.apache.log4j.Level because it was only introduced in 1.2.12
|
||||||
private static final int TRACE_INT = 5000;
|
private static final int TRACE_INT = 5000;
|
||||||
|
@ -27,8 +40,25 @@ public final class Log4jHelper {
|
||||||
Config.get()
|
Config.get()
|
||||||
.getBoolean("otel.instrumentation.log4j-appender.experimental-log-attributes", false);
|
.getBoolean("otel.instrumentation.log4j-appender.experimental-log-attributes", false);
|
||||||
|
|
||||||
// TODO (trask) capture MDC
|
private final Map<String, AttributeKey<String>> captureMdcAttributes;
|
||||||
public static void capture(Category logger, Priority level, Object message, Throwable throwable) {
|
|
||||||
|
// cached as an optimization
|
||||||
|
private final boolean captureAllMdcAttributes;
|
||||||
|
|
||||||
|
private LogEventMapper() {
|
||||||
|
List<String> captureMdcAttributes =
|
||||||
|
Config.get()
|
||||||
|
.getList(
|
||||||
|
"otel.instrumentation.log4j-appender.experimental.capture-mdc-attributes",
|
||||||
|
emptyList());
|
||||||
|
this.captureMdcAttributes =
|
||||||
|
captureMdcAttributes.stream()
|
||||||
|
.collect(Collectors.toMap(attr -> attr, LogEventMapper::getMdcAttributeKey));
|
||||||
|
this.captureAllMdcAttributes =
|
||||||
|
captureMdcAttributes.size() == 1 && captureMdcAttributes.get(0).equals("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void capture(Category logger, Priority level, Object message, Throwable throwable) {
|
||||||
String instrumentationName = logger.getName();
|
String instrumentationName = logger.getName();
|
||||||
if (instrumentationName == null || instrumentationName.isEmpty()) {
|
if (instrumentationName == null || instrumentationName.isEmpty()) {
|
||||||
instrumentationName = "ROOT";
|
instrumentationName = "ROOT";
|
||||||
|
@ -60,6 +90,8 @@ public final class Log4jHelper {
|
||||||
attributes.put(SemanticAttributes.EXCEPTION_STACKTRACE, writer.toString());
|
attributes.put(SemanticAttributes.EXCEPTION_STACKTRACE, writer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
captureMdcAttributes(attributes);
|
||||||
|
|
||||||
if (captureExperimentalAttributes) {
|
if (captureExperimentalAttributes) {
|
||||||
Thread currentThread = Thread.currentThread();
|
Thread currentThread = Thread.currentThread();
|
||||||
attributes.put(SemanticAttributes.THREAD_NAME, currentThread.getName());
|
attributes.put(SemanticAttributes.THREAD_NAME, currentThread.getName());
|
||||||
|
@ -74,6 +106,32 @@ public final class Log4jHelper {
|
||||||
builder.emit();
|
builder.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void captureMdcAttributes(AttributesBuilder attributes) {
|
||||||
|
|
||||||
|
Hashtable<?, ?> context = MDC.getContext();
|
||||||
|
|
||||||
|
if (captureAllMdcAttributes) {
|
||||||
|
if (context != null) {
|
||||||
|
for (Map.Entry<?, ?> entry : context.entrySet()) {
|
||||||
|
attributes.put(
|
||||||
|
getMdcAttributeKey(String.valueOf(entry.getKey())), String.valueOf(entry.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, AttributeKey<String>> entry : captureMdcAttributes.entrySet()) {
|
||||||
|
Object value = context.get(entry.getKey());
|
||||||
|
if (value != null) {
|
||||||
|
attributes.put(entry.getValue(), value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AttributeKey<String> getMdcAttributeKey(String key) {
|
||||||
|
return mdcAttributeKeys.computeIfAbsent(key, k -> AttributeKey.stringKey("log4j.mdc." + k));
|
||||||
|
}
|
||||||
|
|
||||||
private static Severity levelToSeverity(Priority level) {
|
private static Severity levelToSeverity(Priority level) {
|
||||||
int lev = level.toInt();
|
int lev = level.toInt();
|
||||||
if (lev <= TRACE_INT) {
|
if (lev <= TRACE_INT) {
|
||||||
|
@ -93,6 +151,4 @@ public final class Log4jHelper {
|
||||||
}
|
}
|
||||||
return Severity.FATAL;
|
return Severity.FATAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Log4jHelper() {}
|
|
||||||
}
|
}
|
|
@ -3,10 +3,12 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import io.opentelemetry.api.common.AttributeKey
|
||||||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||||
import io.opentelemetry.sdk.logs.data.Severity
|
import io.opentelemetry.sdk.logs.data.Severity
|
||||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
||||||
import org.apache.log4j.Logger
|
import org.apache.log4j.Logger
|
||||||
|
import org.apache.log4j.MDC
|
||||||
import spock.lang.Unroll
|
import spock.lang.Unroll
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat
|
import static org.assertj.core.api.Assertions.assertThat
|
||||||
|
@ -90,4 +92,34 @@ class Log4j1Test extends AgentInstrumentationSpecification {
|
||||||
severity = args[1]
|
severity = args[1]
|
||||||
severityText = args[2]
|
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(logs).hasSize(1)
|
||||||
|
})
|
||||||
|
def log = logs.get(0)
|
||||||
|
assertThat(log.getBody().asString()).isEqualTo("xyz")
|
||||||
|
assertThat(log.getInstrumentationLibraryInfo().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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue