Inject span context into log4j2 2.13.2+ context. (#735)

This commit is contained in:
Anuraag Agrawal 2020-07-22 10:49:13 +09:00 committed by GitHub
parent d9e740d089
commit f2f3624d17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 311 additions and 0 deletions

View File

@ -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>
```

View File

@ -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: '+'
}

View File

@ -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;
}
}

View File

@ -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()
}
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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'