Inject span context into log4j2 2.13.2+ context. (#735)
This commit is contained in:
parent
d9e740d089
commit
f2f3624d17
|
@ -0,0 +1,52 @@
|
||||||
|
# Log4j 2 Integration
|
||||||
|
|
||||||
|
This module integrates instrumentation with Log4j 2 by injecting the trace ID and span ID from a
|
||||||
|
mounted span into Log4j's [context data](https://logging.apache.org/log4j/2.x/manual/thread-context.html).
|
||||||
|
|
||||||
|
To use it, just add the module to your application's runtime classpath.
|
||||||
|
|
||||||
|
**Maven**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.opentelemetry.instrumentation</groupId>
|
||||||
|
<artifactId>opentelemetry-log4j-2.13.2</artifactId>
|
||||||
|
<version>0.7.0-SNAPSHOT</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gradle**
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
dependencies {
|
||||||
|
runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-log4j-2.13.2:0.7.0-SNAPSHOT")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Log4j will automatically pick up our integration and will have these keys added to the context when
|
||||||
|
a log statement is made when a span is active.
|
||||||
|
|
||||||
|
- `traceId`
|
||||||
|
- `spanId`
|
||||||
|
- `traceFlags`
|
||||||
|
|
||||||
|
You can use these keys when defining an appender in your `log4j.xml` configuration, for example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration>
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} traceId: %X{traceId} spanId: %X{spanId} - %msg%n" />
|
||||||
|
</Console>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Root>
|
||||||
|
<AppenderRef ref="Console" level="All" />
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
||||||
|
```
|
|
@ -0,0 +1,25 @@
|
||||||
|
ext {
|
||||||
|
minJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||||
|
javaSubPackage = 'log4j.v2_13_2'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/gradle/instrumentation-library.gradle"
|
||||||
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
|
||||||
|
testSets {
|
||||||
|
latestDepTest {
|
||||||
|
dirName = 'test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.2'
|
||||||
|
|
||||||
|
annotationProcessor deps.autoservice
|
||||||
|
compileOnly deps.autoservice
|
||||||
|
|
||||||
|
testImplementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.2'
|
||||||
|
testAnnotationProcessor group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.2'
|
||||||
|
|
||||||
|
latestDepTestImplementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '+'
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.log4j.v2_13_2;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import io.opentelemetry.trace.Span;
|
||||||
|
import io.opentelemetry.trace.SpanContext;
|
||||||
|
import io.opentelemetry.trace.TracingContextUtils;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.logging.log4j.core.util.ContextDataProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of Log4j 2's {@link ContextDataProvider} which is loaded via SPI. {@link
|
||||||
|
* #supplyContextData()} is called when a log entry is created.
|
||||||
|
*/
|
||||||
|
@AutoService(ContextDataProvider.class)
|
||||||
|
public class OpenTelemetryContextDataProvider implements ContextDataProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns context from the current span when available.
|
||||||
|
*
|
||||||
|
* @return A map containing string versions of the traceId, spanId, and traceFlags, which can then
|
||||||
|
* be accessed from layout components
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, String> supplyContextData() {
|
||||||
|
Span currentSpan = TracingContextUtils.getCurrentSpan();
|
||||||
|
if (!currentSpan.getContext().isValid()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> contextData = new HashMap<>();
|
||||||
|
SpanContext spanContext = currentSpan.getContext();
|
||||||
|
contextData.put("traceId", spanContext.getTraceId().toLowerBase16());
|
||||||
|
contextData.put("spanId", spanContext.getSpanId().toLowerBase16());
|
||||||
|
contextData.put("traceFlags", spanContext.getTraceFlags().toLowerBase16());
|
||||||
|
return contextData;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.log4j.v2_13_2
|
||||||
|
|
||||||
|
import io.opentelemetry.auto.test.utils.TraceUtils
|
||||||
|
import io.opentelemetry.trace.Span
|
||||||
|
import io.opentelemetry.trace.TracingContextUtils
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import org.apache.logging.log4j.Logger
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
class Log4j2Test extends Specification {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger("TestLogger")
|
||||||
|
|
||||||
|
def cleanup() {
|
||||||
|
ListAppender.get().clearEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
def "no ids when no span"() {
|
||||||
|
when:
|
||||||
|
logger.info("log message 1")
|
||||||
|
logger.info("log message 2")
|
||||||
|
|
||||||
|
def events = ListAppender.get().getEvents()
|
||||||
|
|
||||||
|
then:
|
||||||
|
events.size() == 2
|
||||||
|
events[0].message.formattedMessage == "log message 1"
|
||||||
|
events[0].getContextData().getValue("traceId") == null
|
||||||
|
events[0].getContextData().getValue("spanId") == null
|
||||||
|
events[0].getContextData().getValue("traceFlags") == null
|
||||||
|
|
||||||
|
events[1].message.formattedMessage == "log message 2"
|
||||||
|
events[1].getContextData().getValue("traceId") == null
|
||||||
|
events[1].getContextData().getValue("spanId") == null
|
||||||
|
events[1].getContextData().getValue("traceFlags") == null
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ids when span"() {
|
||||||
|
when:
|
||||||
|
Span span1
|
||||||
|
TraceUtils.runUnderTrace("test") {
|
||||||
|
span1 = TracingContextUtils.currentSpan
|
||||||
|
logger.info("log message 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("log message 2")
|
||||||
|
|
||||||
|
Span span2
|
||||||
|
TraceUtils.runUnderTrace("test 2") {
|
||||||
|
span2 = TracingContextUtils.currentSpan
|
||||||
|
logger.info("log message 3")
|
||||||
|
}
|
||||||
|
|
||||||
|
def events = ListAppender.get().getEvents()
|
||||||
|
|
||||||
|
then:
|
||||||
|
events.size() == 3
|
||||||
|
events[0].message.formattedMessage == "log message 1"
|
||||||
|
events[0].getContextData().getValue("traceId") == span1.context.traceId.toLowerBase16()
|
||||||
|
events[0].getContextData().getValue("spanId") == span1.context.spanId.toLowerBase16()
|
||||||
|
events[0].getContextData().getValue("traceFlags") == span1.context.traceFlags.toLowerBase16()
|
||||||
|
|
||||||
|
events[1].message.formattedMessage == "log message 2"
|
||||||
|
events[1].getContextData().getValue("traceId") == null
|
||||||
|
events[1].getContextData().getValue("spanId") == null
|
||||||
|
events[1].getContextData().getValue("traceFlags") == null
|
||||||
|
|
||||||
|
events[2].message.formattedMessage == "log message 3"
|
||||||
|
events[2].getContextData().getValue("traceId") == span2.context.traceId.toLowerBase16()
|
||||||
|
events[2].getContextData().getValue("spanId") == span2.context.spanId.toLowerBase16()
|
||||||
|
events[2].getContextData().getValue("traceFlags") == span2.context.traceFlags.toLowerBase16()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.log4j.v2_13_2;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.logging.log4j.core.Appender;
|
||||||
|
import org.apache.logging.log4j.core.Core;
|
||||||
|
import org.apache.logging.log4j.core.LogEvent;
|
||||||
|
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||||
|
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.PluginAttribute;
|
||||||
|
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
|
||||||
|
|
||||||
|
@Plugin(
|
||||||
|
name = "ListAppender",
|
||||||
|
category = Core.CATEGORY_NAME,
|
||||||
|
elementType = Appender.ELEMENT_TYPE,
|
||||||
|
printObject = true)
|
||||||
|
public class ListAppender extends AbstractAppender {
|
||||||
|
|
||||||
|
public static ListAppender get() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ListAppender INSTANCE = new ListAppender();
|
||||||
|
|
||||||
|
private final List<LogEvent> events = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
public ListAppender() {
|
||||||
|
super("ListAppender", null, null, true, Property.EMPTY_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LogEvent> getEvents() {
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearEvents() {
|
||||||
|
events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(LogEvent logEvent) {
|
||||||
|
events.add(logEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginFactory
|
||||||
|
public static ListAppender createAppender(@PluginAttribute("name") String name) {
|
||||||
|
if (!name.equals("ListAppender")) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Use name=\"ListAppender\" in log4j2-test.xml instead of " + name);
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="WARN" packages="com.example.appender">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} traceId: %X{traceId} spanId: %X{spanId} - %msg%n" />
|
||||||
|
</Console>
|
||||||
|
<ListAppender name="ListAppender" />
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Logger name="TestLogger" level="All">
|
||||||
|
<AppenderRef ref="ListAppender" level="All" />
|
||||||
|
<AppenderRef ref="Console" level="All" />
|
||||||
|
</Logger>
|
||||||
|
<Root>
|
||||||
|
<AppenderRef ref="Console" level="All" />
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
|
@ -112,6 +112,7 @@ include ':instrumentation:lettuce:lettuce-5.0'
|
||||||
include ':instrumentation:lettuce:lettuce-5.1'
|
include ':instrumentation:lettuce:lettuce-5.1'
|
||||||
include ':instrumentation:log4j:log4j-1.1'
|
include ':instrumentation:log4j:log4j-1.1'
|
||||||
include ':instrumentation:log4j:log4j-2.0'
|
include ':instrumentation:log4j:log4j-2.0'
|
||||||
|
include ':instrumentation:log4j:log4j-2.13.2:library'
|
||||||
include ':instrumentation:logback-1.0'
|
include ':instrumentation:logback-1.0'
|
||||||
include ':instrumentation:mongo:mongo-3.1'
|
include ':instrumentation:mongo:mongo-3.1'
|
||||||
include ':instrumentation:mongo:mongo-3.7'
|
include ':instrumentation:mongo:mongo-3.7'
|
||||||
|
|
Loading…
Reference in New Issue