Skip to content

feat: log4j2 otel appender configured through env vars #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: example/javaagent-log4j2-appender
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions javaagent-log-appender/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ OTEL_LOGS_EXPORTER=otlp
../gradlew run
```

Allows usage of these env vars or system properties defined in `log4j2.xml`:
- `LOG_LEVEL` env var or `logLevel` property - controls the log4j2 Root (and inherited) loggers
level
- `OTEL_X_LOG_SENDING_DISABLED` env var or `otel.x.log_sending.disabled` property - set to "true" to fully disable the
`OpenTelemetryAppender`
- `OTEL_X_LOG_SENDING_LEVEL` env var or `otel.x.log_sending.level` property - defaults to "all", set to relevant log4j2 [Level](https://logging.apache.org/log4j/2.x/manual/customloglevels.html)
(`off`, `fatal`, `error`, `warn`, `info`, `debug`, `trace`, `all`) to additionally filter what goes to Otel Collector

Watch the collector logs to see exported log records

NOTE: The OpenTelemetry Java Agent uses `http/protobuf` by default, optionally switch to use `grpc` protocol
Expand Down
19 changes: 8 additions & 11 deletions javaagent-log-appender/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ dependencies {
implementation("io.opentelemetry:opentelemetry-api")
implementation("io.opentelemetry.semconv:opentelemetry-semconv")

// OpenTelemetry Java Agent, this brings its own standalone log4j / logback appenders
// OpenTelemetry log4j / logback appenders
implementation("io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17")
implementation("io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0")

// OpenTelemetry JavaAgent
agent("io.opentelemetry.javaagent:opentelemetry-javaagent:2.8.0")
}

Expand All @@ -45,15 +49,8 @@ application {
tasks.named<JavaExec>("run") {
doFirst {
jvmArgs("-javaagent:${agent.singleFile}")
// log4j-appender properties
jvmArgs(
"-Dotel.instrumentation.log4j-appender.experimental.capture-map-message-attributes=true",
"-Dotel.instrumentation.log4j-appender.experimental-log-attributes=true"
)
// logback-appender properties
jvmArgs(
"-Dotel.instrumentation.logback-appender.experimental-log-attributes=true",
"-Dotel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes=true"
)
// disable the OpenTelemetry JavaAgent brought `log4j-appender`
// as we bring our own that is configured from `log4j2.xml`
jvmArgs("-Dotel.instrumentation.log4j-appender.enabled=false")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.message.MapMessage;
Expand All @@ -19,17 +21,24 @@ public class Application {

private static final org.apache.logging.log4j.Logger log4jLogger =
LogManager.getLogger("log4j-logger");
private static final org.slf4j.Logger slf4jLogger = LoggerFactory.getLogger("slf4j-logger");
private static final java.util.logging.Logger julLogger = Logger.getLogger("jul-logger");
// private static final org.slf4j.Logger slf4jLogger = LoggerFactory.getLogger("slf4j-logger");
// private static final java.util.logging.Logger julLogger = Logger.getLogger("jul-logger");

public static void main(String[] args) {
// Route JUL logs to slf4j
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
// // Route JUL logs to slf4j
// SLF4JBridgeHandler.removeHandlersForRootLogger();
// SLF4JBridgeHandler.install();

// Must set up the appenders with the current OpenTelemetry instance
// from OpenTelemetry JavaAgent
// It will do nothing in case no log4j2 appenders are added
// or disabled through `OTEL_X_LOG_SENDING_DISABLED=true` / `-Dotel.x.log_sending.disabled=true`
OpenTelemetryAppender.install(GlobalOpenTelemetry.get());

// Log using log4j API
maybeRunWithSpan(() -> log4jLogger.info("A log4j log message without a span"), false);
maybeRunWithSpan(() -> log4jLogger.info("A log4j log message with a span"), true);
maybeRunWithSpan(() -> log4jLogger.error("A log4j log message with a span"), true);
Map<String, Object> mapMessage = new HashMap<>();
mapMessage.put("key", "value");
mapMessage.put("message", "A log4j structured message");
Expand All @@ -39,54 +48,54 @@ public static void main(String[] args) {
() -> log4jLogger.info("A log4j log message with an exception", new Exception("error!")),
false);

// Log using slf4j API w/ logback backend
maybeRunWithSpan(() -> slf4jLogger.info("A slf4j log message without a span"), false);
maybeRunWithSpan(() -> slf4jLogger.info("A slf4j log message with a span"), true);
maybeRunWithSpan(
() ->
slf4jLogger
.atInfo()
.setMessage("A slf4j structured message")
.addKeyValue("key", "value")
.log(),
false);
maybeRunWithSpan(
() -> slf4jLogger.info("A slf4j log message with an exception", new Exception("error!")),
false);

// Log using JUL API, bridged to slf4j, w/ logback backend
maybeRunWithSpan(() -> julLogger.info("A JUL log message without a span"), false);
maybeRunWithSpan(() -> julLogger.info("A JUL log message with a span"), true);
maybeRunWithSpan(
() ->
julLogger.log(
Level.INFO, "A JUL log message with an exception", new Exception("error!")),
false);

// Log using OpenTelemetry Log Bridge API
// WARNING: This illustrates how to write appenders which bridge logs from
// existing frameworks into the OpenTelemetry Log Bridge API. These APIs
// SHOULD NOT be used by end users in place of existing log APIs (i.e. Log4j, Slf4, JUL).
io.opentelemetry.api.logs.Logger customAppenderLogger =
GlobalOpenTelemetry.get().getLogsBridge().get("custom-log-appender");
maybeRunWithSpan(
() ->
customAppenderLogger
.logRecordBuilder()
.setSeverity(Severity.INFO)
.setBody("A log message from a custom appender without a span")
.setAttribute(AttributeKey.stringKey("key"), "value")
.emit(),
false);
maybeRunWithSpan(
() ->
customAppenderLogger
.logRecordBuilder()
.setSeverity(Severity.INFO)
.setBody("A log message from a custom appender with a span")
.setAttribute(AttributeKey.stringKey("key"), "value")
.emit(),
true);
// // Log using slf4j API w/ logback backend
// maybeRunWithSpan(() -> slf4jLogger.info("A slf4j log message without a span"), false);
// maybeRunWithSpan(() -> slf4jLogger.info("A slf4j log message with a span"), true);
// maybeRunWithSpan(
// () ->
// slf4jLogger
// .atInfo()
// .setMessage("A slf4j structured message")
// .addKeyValue("key", "value")
// .log(),
// false);
// maybeRunWithSpan(
// () -> slf4jLogger.info("A slf4j log message with an exception", new Exception("error!")),
// false);
//
// // Log using JUL API, bridged to slf4j, w/ logback backend
// maybeRunWithSpan(() -> julLogger.info("A JUL log message without a span"), false);
// maybeRunWithSpan(() -> julLogger.info("A JUL log message with a span"), true);
// maybeRunWithSpan(
// () ->
// julLogger.log(
// Level.INFO, "A JUL log message with an exception", new Exception("error!")),
// false);
//
// // Log using OpenTelemetry Log Bridge API
// // WARNING: This illustrates how to write appenders which bridge logs from
// // existing frameworks into the OpenTelemetry Log Bridge API. These APIs
// // SHOULD NOT be used by end users in place of existing log APIs (i.e. Log4j, Slf4, JUL).
// io.opentelemetry.api.logs.Logger customAppenderLogger =
// GlobalOpenTelemetry.get().getLogsBridge().get("custom-log-appender");
// maybeRunWithSpan(
// () ->
// customAppenderLogger
// .logRecordBuilder()
// .setSeverity(Severity.INFO)
// .setBody("A log message from a custom appender without a span")
// .setAttribute(AttributeKey.stringKey("key"), "value")
// .emit(),
// false);
// maybeRunWithSpan(
// () ->
// customAppenderLogger
// .logRecordBuilder()
// .setSeverity(Severity.INFO)
// .setBody("A log message from a custom appender with a span")
// .setAttribute(AttributeKey.stringKey("key"), "value")
// .emit(),
// true);
}

private static void maybeRunWithSpan(Runnable runnable, boolean withSpan) {
Expand Down
23 changes: 22 additions & 1 deletion javaagent-log-appender/src/main/resources/log4j2.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="logLevel" value="${env:LOG_LEVEL:-info}"/>
<Property name="otel.x.log_sending.disabled" value="${env:OTEL_X_LOG_SENDING_DISABLED}"/>
<Property name="otel.x.log_sending.level" value="${env:OTEL_X_LOG_SENDING_LEVEL:-all}"/>
</Properties>
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="log4j2: %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<Select>
<SystemPropertyArbiter propertyName="otel.x.log_sending.disabled" propertyValue="true">
<!-- noop -->
</SystemPropertyArbiter>
<DefaultArbiter>
<OpenTelemetry name="OpenTelemetryAppender" captureMapMessageAttributes="true" captureExperimentalAttributes="true"/>
</DefaultArbiter>
</Select>
</Appenders>
<Loggers>
<Root level="info">
<Root level="${logLevel}">
<AppenderRef ref="ConsoleAppender" />
<Select>
<SystemPropertyArbiter propertyName="otel.x.log_sending.disabled" propertyValue="true">
<!-- noop -->
</SystemPropertyArbiter>
<DefaultArbiter>
<AppenderRef ref="OpenTelemetryAppender" level="${otel.x.log_sending.level}" />
</DefaultArbiter>
</Select>
</Root>
</Loggers>
</Configuration>