Prototype Log4j2 Appender (#4375)
* WIP * Use Log SDK snapshot * Update to 1.9.0 release, refine * Remove appenders from common log config * Respond to PR feedback * Update readme * Switch to compileOnly log sdk dependency, use logger name as instrumentation name * Switch to minimalist LogEvent mapping * PR feedback * PR feedback
This commit is contained in:
parent
f525f3e03f
commit
5c36ed3d23
|
@ -9,7 +9,7 @@ import io.opentelemetry.instrumentation.test.InstrumentationSpecification
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
|
||||||
abstract class Log4j2Test extends InstrumentationSpecification {
|
abstract class Log4j2Test extends InstrumentationSpecification {
|
||||||
def cleanup() {
|
def setup() {
|
||||||
ListAppender.get().clearEvents()
|
ListAppender.get().clearEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
# Log4j 2 Integration
|
# Log4j 2 Integration
|
||||||
|
|
||||||
This module integrates instrumentation with Log4j 2 by injecting the trace ID and span ID from a
|
This module provides Log4j2 extensions related to OpenTelemetry.
|
||||||
mounted span into
|
|
||||||
Log4j's [context data](https://logging.apache.org/log4j/2.x/manual/thread-context.html).
|
|
||||||
|
|
||||||
**Note**: Depending on your application, you may run into a [critical bug](https://issues.apache.org/jira/browse/LOG4J2-2838)
|
To use it, add the module to your application's runtime classpath.
|
||||||
with Log4j 2.13.2. If log messages show a `NullPointerException` when adding this instrumentation,
|
|
||||||
please update to 2.13.3 or higher. The only change between 2.13.2 and 2.13.3 is the fix to this
|
|
||||||
issue.
|
|
||||||
|
|
||||||
To use it, just add the module to your application's runtime classpath.
|
|
||||||
|
|
||||||
**Maven**
|
**Maven**
|
||||||
|
|
||||||
|
@ -33,14 +26,26 @@ dependencies {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Log4j will automatically pick up our integration and will have these keys added to the context when
|
## OpenTelemetry Context Data Provider
|
||||||
a log statement is made when a span is active.
|
|
||||||
|
`OpenTelemetryContextDataProvider` implements the Log4j2 `ContextDataProvider` SPI, and injects the
|
||||||
|
trace ID and span ID from an active span into
|
||||||
|
Log4j's [context data](https://logging.apache.org/log4j/2.x/manual/thread-context.html).
|
||||||
|
|
||||||
|
**Note**: Depending on your application, you may run into
|
||||||
|
a [critical bug](https://issues.apache.org/jira/browse/LOG4J2-2838)
|
||||||
|
with Log4j 2.13.2. If log messages show a `NullPointerException` when adding this instrumentation,
|
||||||
|
please update to 2.13.3 or higher. The only change between 2.13.2 and 2.13.3 is the fix to this
|
||||||
|
issue.
|
||||||
|
|
||||||
|
Log4j will automatically pick up the integration when you include this module. The following keys
|
||||||
|
will be added to the context when a log statement is made when a span is active:
|
||||||
|
|
||||||
- `trace_id`
|
- `trace_id`
|
||||||
- `span_id`
|
- `span_id`
|
||||||
- `trace_flags`
|
- `trace_flags`
|
||||||
|
|
||||||
You can use these keys when defining an appender in your `log4j.xml` configuration, for example
|
You can use these keys when defining an appender in your `log4j.xml` configuration, for example:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
@ -58,3 +63,46 @@ You can use these keys when defining an appender in your `log4j.xml` configurati
|
||||||
</Loggers>
|
</Loggers>
|
||||||
</Configuration>
|
</Configuration>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## OpenTelemetry Appender
|
||||||
|
|
||||||
|
`OpenTelemetryAppender` is a Log4j2 [appender](https://logging.apache.org/log4j/2.x/manual/appenders.html) that can be used to forward log events to the [OpenTelemetry Log SDK](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk/logs).
|
||||||
|
|
||||||
|
The following demonstrates how you might configure the appender in your `log4j.xml` configuration:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="WARN" packages="io.opentelemetry.instrumentation.log4j.v2_13_2">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<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" />
|
||||||
|
</Console>
|
||||||
|
<OpenTelemetry name="OpenTelemetryAppender" />
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Root>
|
||||||
|
<AppenderRef ref="OpenTelemetryAppender" level="All" />
|
||||||
|
<AppenderRef ref="Console" level="All" />
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, associate the `OpenTelemetryAppender` with a `SdkLogEmitterProvider` in your application:
|
||||||
|
|
||||||
|
```
|
||||||
|
SdkLogEmitterProvider logEmitterProvider =
|
||||||
|
SdkLogEmitterProvider.builder()
|
||||||
|
.setResource(Resource.create(...))
|
||||||
|
.addLogProcessor(...)
|
||||||
|
.build();
|
||||||
|
OpenTelemetryLog4j.initialize(logEmitterProvider);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** In order to initialize the `OpenTelemetryAppender` your application must depend on the
|
||||||
|
OpenTelemetry log sdk (`io.opentelemetry:opentelemetry-sdk-logs`).
|
||||||
|
|
||||||
|
In this example Log4j2 logs will be sent to both the console appender and
|
||||||
|
the `OpenTelemetryAppender`, which will drop the logs until `OpenTelemetryLog4j.initialize(..)` is
|
||||||
|
called. Once initialized, logs will be emitted to a `LogEmitter` obtained from
|
||||||
|
the `SdkLogEmitterProvider`.
|
||||||
|
|
|
@ -3,6 +3,8 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compileOnly("io.opentelemetry:opentelemetry-sdk-logs")
|
||||||
|
|
||||||
library("org.apache.logging.log4j:log4j-core:2.13.2")
|
library("org.apache.logging.log4j:log4j-core:2.13.2")
|
||||||
|
|
||||||
// Library instrumentation cannot be applied to 2.13.2 due to a bug in Log4J. The agent works
|
// Library instrumentation cannot be applied to 2.13.2 due to a bug in Log4J. The agent works
|
||||||
|
@ -10,4 +12,7 @@ dependencies {
|
||||||
testLibrary("org.apache.logging.log4j:log4j-core:2.13.3")
|
testLibrary("org.apache.logging.log4j:log4j-core:2.13.3")
|
||||||
|
|
||||||
testImplementation(project(":instrumentation:log4j:log4j-2-common:testing"))
|
testImplementation(project(":instrumentation:log4j:log4j-2-common:testing"))
|
||||||
|
testImplementation("io.opentelemetry:opentelemetry-sdk-logs")
|
||||||
|
|
||||||
|
testImplementation("org.mockito:mockito-core")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.log4j.v2_13_2;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.common.AttributeKey;
|
||||||
|
import io.opentelemetry.api.common.Attributes;
|
||||||
|
import io.opentelemetry.api.common.AttributesBuilder;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.sdk.logs.LogBuilder;
|
||||||
|
import io.opentelemetry.sdk.logs.data.Severity;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.apache.logging.log4j.Level;
|
||||||
|
import org.apache.logging.log4j.core.LogEvent;
|
||||||
|
import org.apache.logging.log4j.core.time.Instant;
|
||||||
|
import org.apache.logging.log4j.message.Message;
|
||||||
|
|
||||||
|
final class LogEventMapper {
|
||||||
|
|
||||||
|
// Visible for testing
|
||||||
|
static final AttributeKey<String> ATTR_THROWABLE_MESSAGE =
|
||||||
|
AttributeKey.stringKey("throwable.message");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the {@link LogEvent} data model onto the {@link LogBuilder}. Unmapped fields include:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Fully qualified class name - {@link LogEvent#getLoggerFqcn()}
|
||||||
|
* <li>Thread name - {@link LogEvent#getThreadName()}
|
||||||
|
* <li>Thread id - {@link LogEvent#getThreadId()}
|
||||||
|
* <li>Thread priority - {@link LogEvent#getThreadPriority()}
|
||||||
|
* <li>Thread priority - {@link LogEvent#getThreadPriority()}
|
||||||
|
* <li>Thrown details (stack trace, class name) - {@link LogEvent#getThrown()}
|
||||||
|
* <li>Marker - {@link LogEvent#getMarker()}
|
||||||
|
* <li>Nested diagnostic context - {@link LogEvent#getContextStack()}
|
||||||
|
* <li>Mapped diagnostic context - {@link LogEvent#getContextData()}
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
static void mapLogEvent(LogBuilder builder, LogEvent logEvent) {
|
||||||
|
// TODO: map the LogEvent more completely when semantic conventions allow it
|
||||||
|
AttributesBuilder attributes = Attributes.builder();
|
||||||
|
|
||||||
|
// message
|
||||||
|
Message message = logEvent.getMessage();
|
||||||
|
if (message != null) {
|
||||||
|
builder.setBody(message.getFormattedMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// time
|
||||||
|
Instant instant = logEvent.getInstant();
|
||||||
|
if (instant != null) {
|
||||||
|
builder.setEpoch(
|
||||||
|
TimeUnit.MILLISECONDS.toNanos(instant.getEpochMillisecond())
|
||||||
|
+ instant.getNanoOfMillisecond(),
|
||||||
|
TimeUnit.NANOSECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// level
|
||||||
|
Level level = logEvent.getLevel();
|
||||||
|
if (level != null) {
|
||||||
|
builder.setSeverity(levelToSeverity(level));
|
||||||
|
builder.setSeverityText(logEvent.getLevel().name());
|
||||||
|
}
|
||||||
|
|
||||||
|
// throwable
|
||||||
|
Throwable throwable = logEvent.getThrown();
|
||||||
|
if (throwable != null) {
|
||||||
|
attributes.put(ATTR_THROWABLE_MESSAGE, throwable.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// span context
|
||||||
|
builder.setContext(Context.current());
|
||||||
|
|
||||||
|
builder.setAttributes(attributes.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Severity levelToSeverity(Level level) {
|
||||||
|
switch (level.getStandardLevel()) {
|
||||||
|
case ALL:
|
||||||
|
return Severity.TRACE;
|
||||||
|
case TRACE:
|
||||||
|
return Severity.TRACE2;
|
||||||
|
case DEBUG:
|
||||||
|
return Severity.DEBUG;
|
||||||
|
case INFO:
|
||||||
|
return Severity.INFO;
|
||||||
|
case WARN:
|
||||||
|
return Severity.WARN;
|
||||||
|
case ERROR:
|
||||||
|
return Severity.ERROR;
|
||||||
|
case FATAL:
|
||||||
|
return Severity.FATAL;
|
||||||
|
case OFF:
|
||||||
|
return Severity.UNDEFINED_SEVERITY_NUMBER;
|
||||||
|
}
|
||||||
|
return Severity.UNDEFINED_SEVERITY_NUMBER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LogEventMapper() {}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.log4j.v2_13_2;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.logs.LogBuilder;
|
||||||
|
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import org.apache.logging.log4j.core.Appender;
|
||||||
|
import org.apache.logging.log4j.core.Core;
|
||||||
|
import org.apache.logging.log4j.core.Filter;
|
||||||
|
import org.apache.logging.log4j.core.Layout;
|
||||||
|
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.PluginBuilderFactory;
|
||||||
|
|
||||||
|
@Plugin(
|
||||||
|
name = OpenTelemetryAppender.PLUGIN_NAME,
|
||||||
|
category = Core.CATEGORY_NAME,
|
||||||
|
elementType = Appender.ELEMENT_TYPE)
|
||||||
|
public class OpenTelemetryAppender extends AbstractAppender {
|
||||||
|
|
||||||
|
static final String PLUGIN_NAME = "OpenTelemetry";
|
||||||
|
|
||||||
|
@PluginBuilderFactory
|
||||||
|
public static <B extends Builder<B>> B builder() {
|
||||||
|
return new Builder<B>().asBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
|
||||||
|
implements org.apache.logging.log4j.core.util.Builder<OpenTelemetryAppender> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpenTelemetryAppender build() {
|
||||||
|
OpenTelemetryAppender appender =
|
||||||
|
new OpenTelemetryAppender(
|
||||||
|
getName(), getLayout(), getFilter(), isIgnoreExceptions(), getPropertyArray());
|
||||||
|
OpenTelemetryLog4j.registerInstance(appender);
|
||||||
|
return appender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AtomicReference<SdkLogEmitterProvider> sdkLogEmitterProviderRef =
|
||||||
|
new AtomicReference<>();
|
||||||
|
|
||||||
|
private OpenTelemetryAppender(
|
||||||
|
String name,
|
||||||
|
Layout<? extends Serializable> layout,
|
||||||
|
Filter filter,
|
||||||
|
boolean ignoreExceptions,
|
||||||
|
Property[] properties) {
|
||||||
|
super(name, filter, layout, ignoreExceptions, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(LogEvent event) {
|
||||||
|
SdkLogEmitterProvider logEmitterProvider = sdkLogEmitterProviderRef.get();
|
||||||
|
if (logEmitterProvider == null) {
|
||||||
|
// appender hasn't been initialized
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogBuilder builder =
|
||||||
|
logEmitterProvider.logEmitterBuilder(event.getLoggerName()).build().logBuilder();
|
||||||
|
LogEventMapper.mapLogEvent(builder, event);
|
||||||
|
builder.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize(SdkLogEmitterProvider sdkLogEmitterProvider) {
|
||||||
|
if (!sdkLogEmitterProviderRef.compareAndSet(null, sdkLogEmitterProvider)) {
|
||||||
|
throw new IllegalStateException("OpenTelemetryAppender has already been initialized.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.log4j.v2_13_2;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
|
||||||
|
public final class OpenTelemetryLog4j {
|
||||||
|
|
||||||
|
private static final Object lock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private static SdkLogEmitterProvider sdkLogEmitterProvider;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private static Throwable initializeCaller;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private static final List<OpenTelemetryAppender> APPENDERS = new ArrayList<>();
|
||||||
|
|
||||||
|
public static void initialize(SdkLogEmitterProvider sdkLogEmitterProvider) {
|
||||||
|
List<OpenTelemetryAppender> instances;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (OpenTelemetryLog4j.sdkLogEmitterProvider != null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"OpenTelemetryLog4j.initialize has already been called. OpenTelemetryLog4j.initialize "
|
||||||
|
+ "must be called only once. Previous invocation set to cause of this exception.",
|
||||||
|
initializeCaller);
|
||||||
|
}
|
||||||
|
OpenTelemetryLog4j.sdkLogEmitterProvider = sdkLogEmitterProvider;
|
||||||
|
instances = new ArrayList<>(APPENDERS);
|
||||||
|
initializeCaller = new Throwable();
|
||||||
|
}
|
||||||
|
for (OpenTelemetryAppender instance : instances) {
|
||||||
|
instance.initialize(sdkLogEmitterProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registerInstance(OpenTelemetryAppender appender) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (sdkLogEmitterProvider != null) {
|
||||||
|
appender.initialize(sdkLogEmitterProvider);
|
||||||
|
}
|
||||||
|
APPENDERS.add(appender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visible for testing
|
||||||
|
static void resetForTest() {
|
||||||
|
synchronized (lock) {
|
||||||
|
sdkLogEmitterProvider = null;
|
||||||
|
APPENDERS.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OpenTelemetryLog4j() {}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.log4j.v2_13_2;
|
||||||
|
|
||||||
|
import static io.opentelemetry.instrumentation.log4j.v2_13_2.LogEventMapper.ATTR_THROWABLE_MESSAGE;
|
||||||
|
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.common.Attributes;
|
||||||
|
import io.opentelemetry.api.trace.Span;
|
||||||
|
import io.opentelemetry.api.trace.SpanContext;
|
||||||
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
||||||
|
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
|
||||||
|
import io.opentelemetry.sdk.logs.data.LogData;
|
||||||
|
import io.opentelemetry.sdk.logs.data.Severity;
|
||||||
|
import io.opentelemetry.sdk.logs.export.InMemoryLogExporter;
|
||||||
|
import io.opentelemetry.sdk.logs.export.SimpleLogProcessor;
|
||||||
|
import io.opentelemetry.sdk.resources.Resource;
|
||||||
|
import io.opentelemetry.sdk.trace.SdkTracerProvider;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.apache.logging.log4j.ThreadContext;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class OpenTelemetryAppenderConfigTest {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger("TestLogger");
|
||||||
|
|
||||||
|
private static InMemoryLogExporter logExporter;
|
||||||
|
private static Resource resource;
|
||||||
|
private static InstrumentationLibraryInfo instrumentationLibraryInfo;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupAll() {
|
||||||
|
logExporter = InMemoryLogExporter.create();
|
||||||
|
resource = Resource.getDefault();
|
||||||
|
instrumentationLibraryInfo = InstrumentationLibraryInfo.create("TestLogger", null);
|
||||||
|
|
||||||
|
SdkLogEmitterProvider logEmitterProvider =
|
||||||
|
SdkLogEmitterProvider.builder()
|
||||||
|
.setResource(resource)
|
||||||
|
.addLogProcessor(SimpleLogProcessor.create(logExporter))
|
||||||
|
.build();
|
||||||
|
OpenTelemetryLog4j.initialize(logEmitterProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void afterAll() {
|
||||||
|
OpenTelemetryLog4j.resetForTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
logExporter.reset();
|
||||||
|
ThreadContext.clearAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void logNoSpan() {
|
||||||
|
logger.info("log message 1");
|
||||||
|
|
||||||
|
List<LogData> logDataList = logExporter.getFinishedLogItems();
|
||||||
|
assertThat(logDataList).hasSize(1);
|
||||||
|
LogData logData = logDataList.get(0);
|
||||||
|
assertThat(logData.getResource()).isEqualTo(resource);
|
||||||
|
assertThat(logData.getInstrumentationLibraryInfo()).isEqualTo(instrumentationLibraryInfo);
|
||||||
|
assertThat(logData.getBody().asString()).isEqualTo("log message 1");
|
||||||
|
assertThat(logData.getAttributes()).isEqualTo(Attributes.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void logWithSpan() {
|
||||||
|
Span span1 = runWithSpan("span1", () -> logger.info("log message 1"));
|
||||||
|
|
||||||
|
logger.info("log message 2");
|
||||||
|
|
||||||
|
Span span2 = runWithSpan("span2", () -> logger.info("log message 3"));
|
||||||
|
|
||||||
|
List<LogData> logDataList = logExporter.getFinishedLogItems();
|
||||||
|
assertThat(logDataList).hasSize(3);
|
||||||
|
assertThat(logDataList.get(0).getSpanContext()).isEqualTo(span1.getSpanContext());
|
||||||
|
assertThat(logDataList.get(1).getSpanContext()).isEqualTo(SpanContext.getInvalid());
|
||||||
|
assertThat(logDataList.get(2).getSpanContext()).isEqualTo(span2.getSpanContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Span runWithSpan(String spanName, Runnable runnable) {
|
||||||
|
Span span = SdkTracerProvider.builder().build().get("tracer").spanBuilder(spanName).startSpan();
|
||||||
|
try (Scope unused = span.makeCurrent()) {
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
span.end();
|
||||||
|
}
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void logWithExtras() {
|
||||||
|
logger.info("log message 1", new IllegalStateException("Error!"));
|
||||||
|
|
||||||
|
List<LogData> logDataList = logExporter.getFinishedLogItems();
|
||||||
|
assertThat(logDataList).hasSize(1);
|
||||||
|
LogData logData = logDataList.get(0);
|
||||||
|
assertThat(logData.getResource()).isEqualTo(resource);
|
||||||
|
assertThat(logData.getInstrumentationLibraryInfo()).isEqualTo(instrumentationLibraryInfo);
|
||||||
|
assertThat(logData.getBody().asString()).isEqualTo("log message 1");
|
||||||
|
assertThat(logData.getEpochNanos())
|
||||||
|
.isGreaterThan(TimeUnit.MILLISECONDS.toNanos(Instant.now().toEpochMilli() - 1000));
|
||||||
|
assertThat(logData.getSeverity()).isEqualTo(Severity.INFO);
|
||||||
|
assertThat(logData.getSeverityText()).isEqualTo("INFO");
|
||||||
|
assertThat(logData.getAttributes()).isEqualTo(Attributes.of(ATTR_THROWABLE_MESSAGE, "Error!"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.log4j.v2_13_2;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
|
||||||
|
import io.opentelemetry.sdk.logs.export.InMemoryLogExporter;
|
||||||
|
import io.opentelemetry.sdk.logs.export.SimpleLogProcessor;
|
||||||
|
import org.apache.logging.log4j.ThreadContext;
|
||||||
|
import org.apache.logging.log4j.core.LogEvent;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class OpenTelemetryAppenderTest {
|
||||||
|
|
||||||
|
private OpenTelemetryAppender appender;
|
||||||
|
private InMemoryLogExporter logExporter;
|
||||||
|
private SdkLogEmitterProvider logEmitterProvider;
|
||||||
|
private LogEvent logEvent;
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void afterAll() {
|
||||||
|
OpenTelemetryLog4j.resetForTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
appender = OpenTelemetryAppender.builder().setName("my-otel-appender").build();
|
||||||
|
|
||||||
|
logEvent = mock(LogEvent.class);
|
||||||
|
when(logEvent.getContextStack()).thenReturn(mock(ThreadContext.ContextStack.class));
|
||||||
|
|
||||||
|
logExporter = InMemoryLogExporter.create();
|
||||||
|
logEmitterProvider =
|
||||||
|
SdkLogEmitterProvider.builder()
|
||||||
|
.addLogProcessor(SimpleLogProcessor.create(logExporter))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void append_Uninitialized() {
|
||||||
|
appender.append(logEvent);
|
||||||
|
|
||||||
|
assertThat(logExporter.getFinishedLogItems()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void append_Initialized() {
|
||||||
|
appender.initialize(logEmitterProvider);
|
||||||
|
appender.append(logEvent);
|
||||||
|
|
||||||
|
assertThat(logExporter.getFinishedLogItems()).hasSize(1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.log4j.v2_13_2;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class OpenTelemetryLog4jTest {
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void afterAll() {
|
||||||
|
OpenTelemetryLog4j.resetForTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerAndInitialize() {
|
||||||
|
OpenTelemetryAppender appender1 = mock(OpenTelemetryAppender.class);
|
||||||
|
OpenTelemetryAppender appender2 = mock(OpenTelemetryAppender.class);
|
||||||
|
OpenTelemetryLog4j.registerInstance(appender1);
|
||||||
|
OpenTelemetryLog4j.registerInstance(appender2);
|
||||||
|
|
||||||
|
OpenTelemetryLog4j.initialize(SdkLogEmitterProvider.builder().build());
|
||||||
|
|
||||||
|
verify(appender1).initialize(any());
|
||||||
|
verify(appender2).initialize(any());
|
||||||
|
|
||||||
|
assertThatCode(() -> OpenTelemetryLog4j.initialize(SdkLogEmitterProvider.builder().build()))
|
||||||
|
.isInstanceOf(IllegalStateException.class)
|
||||||
|
.hasMessageContaining("OpenTelemetryLog4j.initialize has already been called.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="WARN" packages="com.example.appender,io.opentelemetry.instrumentation.log4j.v2_13_2">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<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" />
|
||||||
|
</Console>
|
||||||
|
<ListAppender name="ListAppender" />
|
||||||
|
<OpenTelemetry name="OpenTelemetryAppender" />
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Logger name="TestLogger" level="All">
|
||||||
|
<AppenderRef ref="OpenTelemetryAppender" level="All" />
|
||||||
|
<AppenderRef ref="ListAppender" level="All" />
|
||||||
|
<AppenderRef ref="Console" level="All" />
|
||||||
|
</Logger>
|
||||||
|
<Root>
|
||||||
|
<AppenderRef ref="Console" level="All" />
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
Loading…
Reference in New Issue