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:log4j:log4j-1.1'
|
||||
include ':instrumentation:log4j:log4j-2.0'
|
||||
include ':instrumentation:log4j:log4j-2.13.2:library'
|
||||
include ':instrumentation:logback-1.0'
|
||||
include ':instrumentation:mongo:mongo-3.1'
|
||||
include ':instrumentation:mongo:mongo-3.7'
|
||||
|
|
Loading…
Reference in New Issue