io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of opentelemetry-log4j-appender-2.17 Show documentation
Show all versions of opentelemetry-log4j-appender-2.17 Show documentation
Instrumentation of Java libraries using OpenTelemetry.
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.log4j.appender.v2_17.internal;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.logs.LogRecordBuilder;
import io.opentelemetry.api.logs.Severity;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
import io.opentelemetry.semconv.SemanticAttributes;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.message.MapMessage;
import org.apache.logging.log4j.message.Message;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class LogEventMapper {
private static final String SPECIAL_MAP_MESSAGE_ATTRIBUTE = "message";
private static final Cache> contextDataAttributeKeyCache =
Cache.bounded(100);
private static final Cache> mapMessageAttributeKeyCache =
Cache.bounded(100);
private static final AttributeKey LOG_MARKER = AttributeKey.stringKey("log4j.marker");
private final ContextDataAccessor contextDataAccessor;
private final boolean captureExperimentalAttributes;
private final boolean captureMapMessageAttributes;
private final boolean captureMarkerAttribute;
private final List captureContextDataAttributes;
private final boolean captureAllContextDataAttributes;
public LogEventMapper(
ContextDataAccessor contextDataAccessor,
boolean captureExperimentalAttributes,
boolean captureMapMessageAttributes,
boolean captureMarkerAttribute,
List captureContextDataAttributes) {
this.contextDataAccessor = contextDataAccessor;
this.captureExperimentalAttributes = captureExperimentalAttributes;
this.captureMapMessageAttributes = captureMapMessageAttributes;
this.captureMarkerAttribute = captureMarkerAttribute;
this.captureContextDataAttributes = captureContextDataAttributes;
this.captureAllContextDataAttributes =
captureContextDataAttributes.size() == 1 && captureContextDataAttributes.get(0).equals("*");
}
/**
* Map the {@link LogEvent} data model onto the {@link LogRecordBuilder}. Unmapped fields include:
*
*
* - Fully qualified class name - {@link LogEvent#getLoggerFqcn()}
*
- Thread name - {@link LogEvent#getThreadName()}
*
- Thread id - {@link LogEvent#getThreadId()}
*
- Thread priority - {@link LogEvent#getThreadPriority()}
*
- Marker - {@link LogEvent#getMarker()}
*
- Nested diagnostic context - {@link LogEvent#getContextStack()}
*
*/
public void mapLogEvent(
LogRecordBuilder builder,
Message message,
Level level,
@Nullable Marker marker,
@Nullable Throwable throwable,
T contextData,
String threadName,
long threadId) {
AttributesBuilder attributes = Attributes.builder();
captureMessage(builder, attributes, message);
if (captureMarkerAttribute) {
if (marker != null) {
String markerName = marker.getName();
attributes.put(LOG_MARKER, markerName);
}
}
if (level != null) {
builder.setSeverity(levelToSeverity(level));
builder.setSeverityText(level.name());
}
if (throwable != null) {
setThrowable(attributes, throwable);
}
captureContextDataAttributes(attributes, contextData);
if (captureExperimentalAttributes) {
attributes.put(SemanticAttributes.THREAD_NAME, threadName);
attributes.put(SemanticAttributes.THREAD_ID, threadId);
}
builder.setAllAttributes(attributes.build());
builder.setContext(Context.current());
}
// visible for testing
void captureMessage(LogRecordBuilder builder, AttributesBuilder attributes, Message message) {
if (message == null) {
return;
}
if (!(message instanceof MapMessage)) {
builder.setBody(message.getFormattedMessage());
return;
}
MapMessage, ?> mapMessage = (MapMessage, ?>) message;
String body = mapMessage.getFormat();
boolean checkSpecialMapMessageAttribute = (body == null || body.isEmpty());
if (checkSpecialMapMessageAttribute) {
body = mapMessage.get(SPECIAL_MAP_MESSAGE_ATTRIBUTE);
}
if (body != null && !body.isEmpty()) {
builder.setBody(body);
}
if (captureMapMessageAttributes) {
// TODO (trask) this could be optimized in 2.9 and later by calling MapMessage.forEach()
mapMessage
.getData()
.forEach(
(key, value) -> {
if (value != null
&& (!checkSpecialMapMessageAttribute
|| !key.equals(SPECIAL_MAP_MESSAGE_ATTRIBUTE))) {
attributes.put(getMapMessageAttributeKey(key), value.toString());
}
});
}
}
// visible for testing
void captureContextDataAttributes(AttributesBuilder attributes, T contextData) {
if (captureAllContextDataAttributes) {
contextDataAccessor.forEach(
contextData,
(key, value) -> {
if (value != null) {
attributes.put(getContextDataAttributeKey(key), value.toString());
}
});
return;
}
for (String key : captureContextDataAttributes) {
Object value = contextDataAccessor.getValue(contextData, key);
if (value != null) {
attributes.put(getContextDataAttributeKey(key), value.toString());
}
}
}
public static AttributeKey getContextDataAttributeKey(String key) {
return contextDataAttributeKeyCache.computeIfAbsent(
key, k -> AttributeKey.stringKey("log4j.context_data." + k));
}
public static AttributeKey getMapMessageAttributeKey(String key) {
return mapMessageAttributeKeyCache.computeIfAbsent(
key, k -> AttributeKey.stringKey("log4j.map_message." + k));
}
private static void setThrowable(AttributesBuilder attributes, Throwable throwable) {
// TODO (trask) extract method for recording exception into
// io.opentelemetry:opentelemetry-api
attributes.put(SemanticAttributes.EXCEPTION_TYPE, throwable.getClass().getName());
attributes.put(SemanticAttributes.EXCEPTION_MESSAGE, throwable.getMessage());
StringWriter writer = new StringWriter();
throwable.printStackTrace(new PrintWriter(writer));
attributes.put(SemanticAttributes.EXCEPTION_STACKTRACE, writer.toString());
}
private static Severity levelToSeverity(Level level) {
switch (level.getStandardLevel()) {
case ALL:
case TRACE:
return Severity.TRACE;
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;
}
}