-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prototype Log4j2 Appender (open-telemetry#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
- Loading branch information
Showing
10 changed files
with
552 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
.../library/src/main/java/io/opentelemetry/instrumentation/log4j/v2_13_2/LogEventMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() {} | ||
} |
78 changes: 78 additions & 0 deletions
78
...y/src/main/java/io/opentelemetry/instrumentation/log4j/v2_13_2/OpenTelemetryAppender.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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."); | ||
} | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
...rary/src/main/java/io/opentelemetry/instrumentation/log4j/v2_13_2/OpenTelemetryLog4j.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() {} | ||
} |
Oops, something went wrong.