InstrumentationConfig part 5: library logging appenders (#6321)
* InstrumentationConfig part 5: library logging appenders * Logback * remove log4j hackery * fix tests * Remove unused Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
parent
96cac8184c
commit
257009f944
|
@ -5,10 +5,14 @@
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.log4j.appender.v2_17;
|
package io.opentelemetry.javaagent.instrumentation.log4j.appender.v2_17;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
import io.opentelemetry.instrumentation.api.appender.internal.LogBuilder;
|
import io.opentelemetry.instrumentation.api.appender.internal.LogBuilder;
|
||||||
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.javaagent.bootstrap.AgentLogEmitterProvider;
|
import io.opentelemetry.javaagent.bootstrap.AgentLogEmitterProvider;
|
||||||
|
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -19,8 +23,29 @@ import org.apache.logging.log4j.message.Message;
|
||||||
|
|
||||||
public final class Log4jHelper {
|
public final class Log4jHelper {
|
||||||
|
|
||||||
private static final LogEventMapper<Map<String, String>> mapper =
|
private static final LogEventMapper<Map<String, String>> mapper;
|
||||||
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE);
|
|
||||||
|
static {
|
||||||
|
InstrumentationConfig config = InstrumentationConfig.get();
|
||||||
|
|
||||||
|
boolean captureExperimentalAttributes =
|
||||||
|
config.getBoolean("otel.instrumentation.log4j-appender.experimental-log-attributes", false);
|
||||||
|
boolean captureMapMessageAttributes =
|
||||||
|
config.getBoolean(
|
||||||
|
"otel.instrumentation.log4j-appender.experimental.capture-map-message-attributes",
|
||||||
|
false);
|
||||||
|
List<String> captureContextDataAttributes =
|
||||||
|
config.getList(
|
||||||
|
"otel.instrumentation.log4j-appender.experimental.capture-context-data-attributes",
|
||||||
|
emptyList());
|
||||||
|
|
||||||
|
mapper =
|
||||||
|
new LogEventMapper<>(
|
||||||
|
ContextDataAccessorImpl.INSTANCE,
|
||||||
|
captureExperimentalAttributes,
|
||||||
|
captureMapMessageAttributes,
|
||||||
|
captureContextDataAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
public static void capture(Logger logger, Level level, Message message, Throwable throwable) {
|
public static void capture(Logger logger, Level level, Message message, Throwable throwable) {
|
||||||
String instrumentationName = logger.getName();
|
String instrumentationName = logger.getName();
|
||||||
|
|
|
@ -12,9 +12,3 @@ dependencies {
|
||||||
testImplementation("io.opentelemetry:opentelemetry-sdk-logs")
|
testImplementation("io.opentelemetry:opentelemetry-sdk-logs")
|
||||||
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
|
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<Test>().configureEach {
|
|
||||||
// TODO run tests both with and without experimental log attributes
|
|
||||||
jvmArgs("-Dotel.instrumentation.log4j-appender.experimental.capture-map-message-attributes=true")
|
|
||||||
jvmArgs("-Dotel.instrumentation.log4j-appender.experimental.capture-context-data-attributes=*")
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.log4j.appender.v2_17;
|
package io.opentelemetry.instrumentation.log4j.appender.v2_17;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
import io.opentelemetry.instrumentation.api.appender.internal.LogBuilder;
|
import io.opentelemetry.instrumentation.api.appender.internal.LogBuilder;
|
||||||
import io.opentelemetry.instrumentation.api.appender.internal.LogEmitterProvider;
|
import io.opentelemetry.instrumentation.api.appender.internal.LogEmitterProvider;
|
||||||
import io.opentelemetry.instrumentation.api.appender.internal.LogEmitterProviderHolder;
|
import io.opentelemetry.instrumentation.api.appender.internal.LogEmitterProviderHolder;
|
||||||
|
@ -13,9 +15,13 @@ import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMa
|
||||||
import io.opentelemetry.instrumentation.sdk.appender.internal.DelegatingLogEmitterProvider;
|
import io.opentelemetry.instrumentation.sdk.appender.internal.DelegatingLogEmitterProvider;
|
||||||
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
|
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import org.apache.logging.log4j.ThreadContext;
|
||||||
import org.apache.logging.log4j.core.Appender;
|
import org.apache.logging.log4j.core.Appender;
|
||||||
import org.apache.logging.log4j.core.Core;
|
import org.apache.logging.log4j.core.Core;
|
||||||
import org.apache.logging.log4j.core.Filter;
|
import org.apache.logging.log4j.core.Filter;
|
||||||
|
@ -24,8 +30,10 @@ import org.apache.logging.log4j.core.LogEvent;
|
||||||
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||||
import org.apache.logging.log4j.core.config.Property;
|
import org.apache.logging.log4j.core.config.Property;
|
||||||
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
||||||
|
import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
|
||||||
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
|
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
|
||||||
import org.apache.logging.log4j.core.time.Instant;
|
import org.apache.logging.log4j.core.time.Instant;
|
||||||
|
import org.apache.logging.log4j.message.MapMessage;
|
||||||
import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
||||||
|
|
||||||
@Plugin(
|
@Plugin(
|
||||||
|
@ -39,8 +47,7 @@ public class OpenTelemetryAppender extends AbstractAppender {
|
||||||
private static final LogEmitterProviderHolder logEmitterProviderHolder =
|
private static final LogEmitterProviderHolder logEmitterProviderHolder =
|
||||||
new LogEmitterProviderHolder();
|
new LogEmitterProviderHolder();
|
||||||
|
|
||||||
private static final LogEventMapper<ReadOnlyStringMap> mapper =
|
private final LogEventMapper<ReadOnlyStringMap> mapper;
|
||||||
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE);
|
|
||||||
|
|
||||||
@PluginBuilderFactory
|
@PluginBuilderFactory
|
||||||
public static <B extends Builder<B>> B builder() {
|
public static <B extends Builder<B>> B builder() {
|
||||||
|
@ -50,10 +57,43 @@ public class OpenTelemetryAppender extends AbstractAppender {
|
||||||
static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
|
static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
|
||||||
implements org.apache.logging.log4j.core.util.Builder<OpenTelemetryAppender> {
|
implements org.apache.logging.log4j.core.util.Builder<OpenTelemetryAppender> {
|
||||||
|
|
||||||
|
@PluginBuilderAttribute private boolean captureExperimentalAttributes;
|
||||||
|
@PluginBuilderAttribute private boolean captureMapMessageAttributes;
|
||||||
|
@PluginBuilderAttribute private String captureContextDataAttributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether experimental attributes should be set to logs. These attributes may be changed
|
||||||
|
* or removed in the future, so only enable this if you know you do not require attributes
|
||||||
|
* filled by this instrumentation to be stable across versions.
|
||||||
|
*/
|
||||||
|
public B setCaptureExperimentalAttributes(boolean captureExperimentalAttributes) {
|
||||||
|
this.captureExperimentalAttributes = captureExperimentalAttributes;
|
||||||
|
return asBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets whether log4j {@link MapMessage} attributes should be copied to logs. */
|
||||||
|
public B setCaptureMapMessageAttributes(boolean captureMapMessageAttributes) {
|
||||||
|
this.captureMapMessageAttributes = captureMapMessageAttributes;
|
||||||
|
return asBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Configures the {@link ThreadContext} attributes that will be copied to logs. */
|
||||||
|
public B setCaptureContextDataAttributes(String captureContextDataAttributes) {
|
||||||
|
this.captureContextDataAttributes = captureContextDataAttributes;
|
||||||
|
return asBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OpenTelemetryAppender build() {
|
public OpenTelemetryAppender build() {
|
||||||
return new OpenTelemetryAppender(
|
return new OpenTelemetryAppender(
|
||||||
getName(), getLayout(), getFilter(), isIgnoreExceptions(), getPropertyArray());
|
getName(),
|
||||||
|
getLayout(),
|
||||||
|
getFilter(),
|
||||||
|
isIgnoreExceptions(),
|
||||||
|
getPropertyArray(),
|
||||||
|
captureExperimentalAttributes,
|
||||||
|
captureMapMessageAttributes,
|
||||||
|
captureContextDataAttributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,8 +102,28 @@ public class OpenTelemetryAppender extends AbstractAppender {
|
||||||
Layout<? extends Serializable> layout,
|
Layout<? extends Serializable> layout,
|
||||||
Filter filter,
|
Filter filter,
|
||||||
boolean ignoreExceptions,
|
boolean ignoreExceptions,
|
||||||
Property[] properties) {
|
Property[] properties,
|
||||||
|
boolean captureExperimentalAttributes,
|
||||||
|
boolean captureMapMessageAttributes,
|
||||||
|
String captureContextDataAttributes) {
|
||||||
|
|
||||||
super(name, filter, layout, ignoreExceptions, properties);
|
super(name, filter, layout, ignoreExceptions, properties);
|
||||||
|
this.mapper =
|
||||||
|
new LogEventMapper<>(
|
||||||
|
ContextDataAccessorImpl.INSTANCE,
|
||||||
|
captureExperimentalAttributes,
|
||||||
|
captureMapMessageAttributes,
|
||||||
|
splitAndFilterBlanksAndNulls(captureContextDataAttributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> splitAndFilterBlanksAndNulls(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
return Arrays.stream(value.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,15 +5,12 @@
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.log4j.appender.v2_17.internal;
|
package io.opentelemetry.instrumentation.log4j.appender.v2_17.internal;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
|
|
||||||
import io.opentelemetry.api.common.AttributeKey;
|
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.config.Config;
|
|
||||||
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
|
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
|
||||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
@ -33,44 +30,26 @@ public final class LogEventMapper<T> {
|
||||||
|
|
||||||
private static final String SPECIAL_MAP_MESSAGE_ATTRIBUTE = "message";
|
private static final String SPECIAL_MAP_MESSAGE_ATTRIBUTE = "message";
|
||||||
|
|
||||||
private static final boolean captureExperimentalAttributes =
|
|
||||||
Config.get()
|
|
||||||
.getBoolean("otel.instrumentation.log4j-appender.experimental-log-attributes", false);
|
|
||||||
|
|
||||||
private static final Cache<String, AttributeKey<String>> contextDataAttributeKeyCache =
|
private static final Cache<String, AttributeKey<String>> contextDataAttributeKeyCache =
|
||||||
Cache.bounded(100);
|
Cache.bounded(100);
|
||||||
private static final Cache<String, AttributeKey<String>> mapMessageAttributeKeyCache =
|
private static final Cache<String, AttributeKey<String>> mapMessageAttributeKeyCache =
|
||||||
Cache.bounded(100);
|
Cache.bounded(100);
|
||||||
|
|
||||||
private final boolean captureMapMessageAttributes;
|
|
||||||
|
|
||||||
private final List<String> captureContextDataAttributes;
|
|
||||||
|
|
||||||
// cached as an optimization
|
|
||||||
private final boolean captureAllContextDataAttributes;
|
|
||||||
|
|
||||||
private final ContextDataAccessor<T> contextDataAccessor;
|
private final ContextDataAccessor<T> contextDataAccessor;
|
||||||
|
|
||||||
public LogEventMapper(ContextDataAccessor<T> contextDataAccessor) {
|
private final boolean captureExperimentalAttributes;
|
||||||
this(
|
private final boolean captureMapMessageAttributes;
|
||||||
contextDataAccessor,
|
private final List<String> captureContextDataAttributes;
|
||||||
Config.get()
|
private final boolean captureAllContextDataAttributes;
|
||||||
.getBoolean(
|
|
||||||
"otel.instrumentation.log4j-appender.experimental.capture-map-message-attributes",
|
|
||||||
false),
|
|
||||||
Config.get()
|
|
||||||
.getList(
|
|
||||||
"otel.instrumentation.log4j-appender.experimental.capture-context-data-attributes",
|
|
||||||
emptyList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// visible for testing
|
public LogEventMapper(
|
||||||
LogEventMapper(
|
|
||||||
ContextDataAccessor<T> contextDataAccessor,
|
ContextDataAccessor<T> contextDataAccessor,
|
||||||
|
boolean captureExperimentalAttributes,
|
||||||
boolean captureMapMessageAttributes,
|
boolean captureMapMessageAttributes,
|
||||||
List<String> captureContextDataAttributes) {
|
List<String> captureContextDataAttributes) {
|
||||||
|
|
||||||
this.contextDataAccessor = contextDataAccessor;
|
this.contextDataAccessor = contextDataAccessor;
|
||||||
|
this.captureExperimentalAttributes = captureExperimentalAttributes;
|
||||||
this.captureMapMessageAttributes = captureMapMessageAttributes;
|
this.captureMapMessageAttributes = captureMapMessageAttributes;
|
||||||
this.captureContextDataAttributes = captureContextDataAttributes;
|
this.captureContextDataAttributes = captureContextDataAttributes;
|
||||||
this.captureAllContextDataAttributes =
|
this.captureAllContextDataAttributes =
|
||||||
|
|
|
@ -24,7 +24,7 @@ import java.util.function.BiConsumer;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
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.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class LogEventMapperTest {
|
class LogEventMapperTest {
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ class LogEventMapperTest {
|
||||||
void testDefault() {
|
void testDefault() {
|
||||||
// given
|
// given
|
||||||
LogEventMapper<Map<String, String>> mapper =
|
LogEventMapper<Map<String, String>> mapper =
|
||||||
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, emptyList());
|
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, false, emptyList());
|
||||||
Map<String, String> contextData = new HashMap<>();
|
Map<String, String> contextData = new HashMap<>();
|
||||||
contextData.put("key1", "value1");
|
contextData.put("key1", "value1");
|
||||||
contextData.put("key2", "value2");
|
contextData.put("key2", "value2");
|
||||||
|
@ -49,7 +49,7 @@ class LogEventMapperTest {
|
||||||
void testSome() {
|
void testSome() {
|
||||||
// given
|
// given
|
||||||
LogEventMapper<Map<String, String>> mapper =
|
LogEventMapper<Map<String, String>> mapper =
|
||||||
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, singletonList("key2"));
|
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, false, singletonList("key2"));
|
||||||
Map<String, String> contextData = new HashMap<>();
|
Map<String, String> contextData = new HashMap<>();
|
||||||
contextData.put("key1", "value1");
|
contextData.put("key1", "value1");
|
||||||
contextData.put("key2", "value2");
|
contextData.put("key2", "value2");
|
||||||
|
@ -67,7 +67,7 @@ class LogEventMapperTest {
|
||||||
void testAll() {
|
void testAll() {
|
||||||
// given
|
// given
|
||||||
LogEventMapper<Map<String, String>> mapper =
|
LogEventMapper<Map<String, String>> mapper =
|
||||||
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, singletonList("*"));
|
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, false, singletonList("*"));
|
||||||
Map<String, String> contextData = new HashMap<>();
|
Map<String, String> contextData = new HashMap<>();
|
||||||
contextData.put("key1", "value1");
|
contextData.put("key1", "value1");
|
||||||
contextData.put("key2", "value2");
|
contextData.put("key2", "value2");
|
||||||
|
@ -87,7 +87,7 @@ class LogEventMapperTest {
|
||||||
void testCaptureMapMessageDisabled() {
|
void testCaptureMapMessageDisabled() {
|
||||||
// given
|
// given
|
||||||
LogEventMapper<Map<String, String>> mapper =
|
LogEventMapper<Map<String, String>> mapper =
|
||||||
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, singletonList("*"));
|
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, false, singletonList("*"));
|
||||||
|
|
||||||
StringMapMessage message = new StringMapMessage();
|
StringMapMessage message = new StringMapMessage();
|
||||||
message.put("key1", "value1");
|
message.put("key1", "value1");
|
||||||
|
@ -108,7 +108,7 @@ class LogEventMapperTest {
|
||||||
void testCaptureMapMessageWithSpecialAttribute() {
|
void testCaptureMapMessageWithSpecialAttribute() {
|
||||||
// given
|
// given
|
||||||
LogEventMapper<Map<String, String>> mapper =
|
LogEventMapper<Map<String, String>> mapper =
|
||||||
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, true, singletonList("*"));
|
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, true, singletonList("*"));
|
||||||
|
|
||||||
StringMapMessage message = new StringMapMessage();
|
StringMapMessage message = new StringMapMessage();
|
||||||
message.put("key1", "value1");
|
message.put("key1", "value1");
|
||||||
|
@ -129,7 +129,7 @@ class LogEventMapperTest {
|
||||||
void testCaptureMapMessageWithoutSpecialAttribute() {
|
void testCaptureMapMessageWithoutSpecialAttribute() {
|
||||||
// given
|
// given
|
||||||
LogEventMapper<Map<String, String>> mapper =
|
LogEventMapper<Map<String, String>> mapper =
|
||||||
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, true, singletonList("*"));
|
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, true, singletonList("*"));
|
||||||
|
|
||||||
StringMapMessage message = new StringMapMessage();
|
StringMapMessage message = new StringMapMessage();
|
||||||
message.put("key1", "value1");
|
message.put("key1", "value1");
|
||||||
|
@ -153,7 +153,7 @@ class LogEventMapperTest {
|
||||||
void testCaptureStructuredDataMessage() {
|
void testCaptureStructuredDataMessage() {
|
||||||
// given
|
// given
|
||||||
LogEventMapper<Map<String, String>> mapper =
|
LogEventMapper<Map<String, String>> mapper =
|
||||||
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, true, singletonList("*"));
|
new LogEventMapper<>(ContextDataAccessorImpl.INSTANCE, false, true, singletonList("*"));
|
||||||
|
|
||||||
StructuredDataMessage message = new StructuredDataMessage("an id", "a message", "a type");
|
StructuredDataMessage message = new StructuredDataMessage("an id", "a message", "a type");
|
||||||
message.put("key1", "value1");
|
message.put("key1", "value1");
|
||||||
|
|
|
@ -6,13 +6,12 @@
|
||||||
<PatternLayout
|
<PatternLayout
|
||||||
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>
|
||||||
<ListAppender name="ListAppender"/>
|
<!-- TODO run tests both with and without experimental log attributes -->
|
||||||
<OpenTelemetry name="OpenTelemetryAppender"/>
|
<OpenTelemetry name="OpenTelemetryAppender" captureMapMessageAttributes="true" captureContextDataAttributes="*"/>
|
||||||
</Appenders>
|
</Appenders>
|
||||||
<Loggers>
|
<Loggers>
|
||||||
<Logger name="TestLogger" level="All">
|
<Logger name="TestLogger" level="All">
|
||||||
<AppenderRef ref="OpenTelemetryAppender" level="All"/>
|
<AppenderRef ref="OpenTelemetryAppender" level="All"/>
|
||||||
<AppenderRef ref="ListAppender" level="All"/>
|
|
||||||
<AppenderRef ref="Console" level="All"/>
|
<AppenderRef ref="Console" level="All"/>
|
||||||
</Logger>
|
</Logger>
|
||||||
<Root>
|
<Root>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.logback.appender.v1_0;
|
package io.opentelemetry.javaagent.instrumentation.logback.appender.v1_0;
|
||||||
|
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.logback.appender.v1_0.LogbackSingletons.mapper;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
@ -13,7 +14,6 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||||
|
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
import io.opentelemetry.instrumentation.api.appender.internal.LogEmitterProvider;
|
import io.opentelemetry.instrumentation.api.appender.internal.LogEmitterProvider;
|
||||||
import io.opentelemetry.instrumentation.logback.appender.v1_0.internal.LoggingEventMapper;
|
|
||||||
import io.opentelemetry.javaagent.bootstrap.AgentLogEmitterProvider;
|
import io.opentelemetry.javaagent.bootstrap.AgentLogEmitterProvider;
|
||||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||||
|
@ -51,7 +51,7 @@ class LogbackInstrumentation implements TypeInstrumentation {
|
||||||
// logging framework delegates to another
|
// logging framework delegates to another
|
||||||
callDepth = CallDepth.forClass(LogEmitterProvider.class);
|
callDepth = CallDepth.forClass(LogEmitterProvider.class);
|
||||||
if (callDepth.getAndIncrement() == 0) {
|
if (callDepth.getAndIncrement() == 0) {
|
||||||
LoggingEventMapper.INSTANCE.emit(AgentLogEmitterProvider.get(), event);
|
mapper().emit(AgentLogEmitterProvider.get(), event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.logback.appender.v1_0;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.logback.appender.v1_0.internal.LoggingEventMapper;
|
||||||
|
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class LogbackSingletons {
|
||||||
|
|
||||||
|
private static final LoggingEventMapper mapper;
|
||||||
|
|
||||||
|
static {
|
||||||
|
InstrumentationConfig config = InstrumentationConfig.get();
|
||||||
|
|
||||||
|
boolean captureExperimentalAttributes =
|
||||||
|
config.getBoolean(
|
||||||
|
"otel.instrumentation.logback-appender.experimental-log-attributes", false);
|
||||||
|
List<String> captureMdcAttributes =
|
||||||
|
config.getList(
|
||||||
|
"otel.instrumentation.logback-appender.experimental.capture-mdc-attributes",
|
||||||
|
emptyList());
|
||||||
|
|
||||||
|
mapper = new LoggingEventMapper(captureExperimentalAttributes, captureMdcAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LoggingEventMapper mapper() {
|
||||||
|
return mapper;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,8 +11,3 @@ dependencies {
|
||||||
testImplementation("io.opentelemetry:opentelemetry-sdk-logs")
|
testImplementation("io.opentelemetry:opentelemetry-sdk-logs")
|
||||||
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
|
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<Test>().configureEach {
|
|
||||||
// TODO run tests both with and without experimental log attributes
|
|
||||||
jvmArgs("-Dotel.instrumentation.logback-appender.experimental.capture-mdc-attributes=*")
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.logback.appender.v1_0;
|
package io.opentelemetry.instrumentation.logback.appender.v1_0;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
import ch.qos.logback.core.UnsynchronizedAppenderBase;
|
import ch.qos.logback.core.UnsynchronizedAppenderBase;
|
||||||
import io.opentelemetry.instrumentation.api.appender.internal.LogEmitterProvider;
|
import io.opentelemetry.instrumentation.api.appender.internal.LogEmitterProvider;
|
||||||
|
@ -12,17 +14,32 @@ import io.opentelemetry.instrumentation.api.appender.internal.LogEmitterProvider
|
||||||
import io.opentelemetry.instrumentation.logback.appender.v1_0.internal.LoggingEventMapper;
|
import io.opentelemetry.instrumentation.logback.appender.v1_0.internal.LoggingEventMapper;
|
||||||
import io.opentelemetry.instrumentation.sdk.appender.internal.DelegatingLogEmitterProvider;
|
import io.opentelemetry.instrumentation.sdk.appender.internal.DelegatingLogEmitterProvider;
|
||||||
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
|
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
public class OpenTelemetryAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
|
public class OpenTelemetryAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
|
||||||
|
|
||||||
private static final LogEmitterProviderHolder logEmitterProviderHolder =
|
private static final LogEmitterProviderHolder logEmitterProviderHolder =
|
||||||
new LogEmitterProviderHolder();
|
new LogEmitterProviderHolder();
|
||||||
|
|
||||||
|
private volatile boolean captureExperimentalAttributes = false;
|
||||||
|
private volatile List<String> captureMdcAttributes = emptyList();
|
||||||
|
|
||||||
|
private volatile LoggingEventMapper mapper;
|
||||||
|
|
||||||
public OpenTelemetryAppender() {}
|
public OpenTelemetryAppender() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
mapper = new LoggingEventMapper(captureExperimentalAttributes, captureMdcAttributes);
|
||||||
|
super.start();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void append(ILoggingEvent event) {
|
protected void append(ILoggingEvent event) {
|
||||||
LoggingEventMapper.INSTANCE.emit(logEmitterProviderHolder.get(), event);
|
mapper.emit(logEmitterProviderHolder.get(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,6 +53,24 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase<ILoggingEv
|
||||||
logEmitterProviderHolder.set(DelegatingLogEmitterProvider.from(sdkLogEmitterProvider));
|
logEmitterProviderHolder.set(DelegatingLogEmitterProvider.from(sdkLogEmitterProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether experimental attributes should be set to logs. These attributes may be changed or
|
||||||
|
* removed in the future, so only enable this if you know you do not require attributes filled by
|
||||||
|
* this instrumentation to be stable across versions.
|
||||||
|
*/
|
||||||
|
public void setCaptureExperimentalAttributes(boolean captureExperimentalAttributes) {
|
||||||
|
this.captureExperimentalAttributes = captureExperimentalAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Configures the {@link MDC} attributes that will be copied to logs. */
|
||||||
|
public void setCaptureMdcAttributes(String attributes) {
|
||||||
|
if (attributes != null) {
|
||||||
|
captureMdcAttributes = filterBlanksAndNulls(attributes.split(","));
|
||||||
|
} else {
|
||||||
|
captureMdcAttributes = emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsets the global {@link LogEmitterProvider}. This is only meant to be used from tests which
|
* Unsets the global {@link LogEmitterProvider}. This is only meant to be used from tests which
|
||||||
* need to reconfigure {@link LogEmitterProvider}.
|
* need to reconfigure {@link LogEmitterProvider}.
|
||||||
|
@ -43,4 +78,12 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase<ILoggingEv
|
||||||
public static void resetSdkLogEmitterProviderForTest() {
|
public static void resetSdkLogEmitterProviderForTest() {
|
||||||
logEmitterProviderHolder.resetForTest();
|
logEmitterProviderHolder.resetForTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copied from SDK's DefaultConfigProperties
|
||||||
|
private static List<String> filterBlanksAndNulls(String[] values) {
|
||||||
|
return Arrays.stream(values)
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.logback.appender.v1_0.internal;
|
package io.opentelemetry.instrumentation.logback.appender.v1_0.internal;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
import ch.qos.logback.classic.Level;
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
import ch.qos.logback.classic.spi.ThrowableProxy;
|
import ch.qos.logback.classic.spi.ThrowableProxy;
|
||||||
|
@ -17,7 +15,6 @@ 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.LogEmitterProvider;
|
import io.opentelemetry.instrumentation.api.appender.internal.LogEmitterProvider;
|
||||||
import io.opentelemetry.instrumentation.api.appender.internal.Severity;
|
import io.opentelemetry.instrumentation.api.appender.internal.Severity;
|
||||||
import io.opentelemetry.instrumentation.api.config.Config;
|
|
||||||
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
|
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
|
||||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
@ -32,29 +29,15 @@ import java.util.concurrent.TimeUnit;
|
||||||
*/
|
*/
|
||||||
public final class LoggingEventMapper {
|
public final class LoggingEventMapper {
|
||||||
|
|
||||||
public static final LoggingEventMapper INSTANCE = new LoggingEventMapper();
|
|
||||||
|
|
||||||
private static final boolean captureExperimentalAttributes =
|
|
||||||
Config.get()
|
|
||||||
.getBoolean("otel.instrumentation.logback-appender.experimental-log-attributes", false);
|
|
||||||
|
|
||||||
private static final Cache<String, AttributeKey<String>> mdcAttributeKeys = Cache.bounded(100);
|
private static final Cache<String, AttributeKey<String>> mdcAttributeKeys = Cache.bounded(100);
|
||||||
|
|
||||||
|
private final boolean captureExperimentalAttributes;
|
||||||
private final List<String> captureMdcAttributes;
|
private final List<String> captureMdcAttributes;
|
||||||
|
|
||||||
// cached as an optimization
|
|
||||||
private final boolean captureAllMdcAttributes;
|
private final boolean captureAllMdcAttributes;
|
||||||
|
|
||||||
private LoggingEventMapper() {
|
public LoggingEventMapper(
|
||||||
this(
|
boolean captureExperimentalAttributes, List<String> captureMdcAttributes) {
|
||||||
Config.get()
|
this.captureExperimentalAttributes = captureExperimentalAttributes;
|
||||||
.getList(
|
|
||||||
"otel.instrumentation.logback-appender.experimental.capture-mdc-attributes",
|
|
||||||
emptyList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// visible for testing
|
|
||||||
LoggingEventMapper(List<String> captureMdcAttributes) {
|
|
||||||
this.captureMdcAttributes = captureMdcAttributes;
|
this.captureMdcAttributes = captureMdcAttributes;
|
||||||
this.captureAllMdcAttributes =
|
this.captureAllMdcAttributes =
|
||||||
captureMdcAttributes.size() == 1 && captureMdcAttributes.get(0).equals("*");
|
captureMdcAttributes.size() == 1 && captureMdcAttributes.get(0).equals("*");
|
||||||
|
|
|
@ -22,7 +22,7 @@ class LoggingEventMapperTest {
|
||||||
@Test
|
@Test
|
||||||
void testDefault() {
|
void testDefault() {
|
||||||
// given
|
// given
|
||||||
LoggingEventMapper mapper = new LoggingEventMapper(emptyList());
|
LoggingEventMapper mapper = new LoggingEventMapper(false, emptyList());
|
||||||
Map<String, String> contextData = new HashMap<>();
|
Map<String, String> contextData = new HashMap<>();
|
||||||
contextData.put("key1", "value1");
|
contextData.put("key1", "value1");
|
||||||
contextData.put("key2", "value2");
|
contextData.put("key2", "value2");
|
||||||
|
@ -38,7 +38,7 @@ class LoggingEventMapperTest {
|
||||||
@Test
|
@Test
|
||||||
void testSome() {
|
void testSome() {
|
||||||
// given
|
// given
|
||||||
LoggingEventMapper mapper = new LoggingEventMapper(singletonList("key2"));
|
LoggingEventMapper mapper = new LoggingEventMapper(false, singletonList("key2"));
|
||||||
Map<String, String> contextData = new HashMap<>();
|
Map<String, String> contextData = new HashMap<>();
|
||||||
contextData.put("key1", "value1");
|
contextData.put("key1", "value1");
|
||||||
contextData.put("key2", "value2");
|
contextData.put("key2", "value2");
|
||||||
|
@ -55,7 +55,7 @@ class LoggingEventMapperTest {
|
||||||
@Test
|
@Test
|
||||||
void testAll() {
|
void testAll() {
|
||||||
// given
|
// given
|
||||||
LoggingEventMapper mapper = new LoggingEventMapper(singletonList("*"));
|
LoggingEventMapper mapper = new LoggingEventMapper(false, singletonList("*"));
|
||||||
Map<String, String> contextData = new HashMap<>();
|
Map<String, String> contextData = new HashMap<>();
|
||||||
contextData.put("key1", "value1");
|
contextData.put("key1", "value1");
|
||||||
contextData.put("key2", "value2");
|
contextData.put("key2", "value2");
|
||||||
|
|
|
@ -8,12 +8,15 @@
|
||||||
</pattern>
|
</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
<appender name="OpenTelemetry" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
|
<appender name="OpenTelemetry"
|
||||||
|
class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
|
||||||
|
<captureExperimentalAttributes>false</captureExperimentalAttributes>
|
||||||
|
<captureMdcAttributes>*</captureMdcAttributes>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
<appender-ref ref="OpenTelemetry" />
|
<appender-ref ref="OpenTelemetry"/>
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
Loading…
Reference in New Issue