diff --git a/README.md b/README.md index 61fede0489..22ff5585af 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,9 @@ of them assume you have docker running on your local machine. - [Configuring Log Appenders](log-appender) - This module demonstrates how to configure the Log4j and Logback appenders to bridge logs into the OpenTelemetry Log SDK. +- [Configuring Log Appenders when using JavaAgent](javaagent-log-appender) + - This module demonstrates how to configure the Log4j and Logback appenders to + bridge logs into the OpenTelemetry Log SDK when using JavaAgent. - [Configuring the Logging Exporters](logging) - This module contains a fully-functional example of configuring the OpenTelemetry SDK to use a logging exporter. diff --git a/javaagent-log-appender/README.md b/javaagent-log-appender/README.md new file mode 100644 index 0000000000..c3b7b2f105 --- /dev/null +++ b/javaagent-log-appender/README.md @@ -0,0 +1,56 @@ +# OpenTelemetry Log Appender built-in OpenTelemetry Java Agent Example + +This example demonstrates an application configured to use the OpenTelemetry Log +Appenders built-in OpenTelemetry Java Agent to bridge logs into the OpenTelemetry Log SDK, and export +via [OTLP](https://opentelemetry.io/docs/reference/specification/protocol/otlp/). + +Details about the example: + +* The OpenTelemetry Log SDK is configured to export data to + the [collector](https://opentelemetry.io/docs/collector/), which prints the + logs to the console. +* The application is configured with a variety of log solutions: + * Log4j API [configured](./src/main/resources/log4j2.xml) to print logs to the + console and + the OpenTelemetry Java Agent brings built-in + [OpenTelemetry Log4J Appender](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/log4j/log4j-appender-2.17/javaagent/README.md). + * SLF4J API [configured with Logback](./src/main/resources/logback.xml) to + print logs to the console and + the OpenTelemetry Java Agent brings built-in + [OpenTelemetry Logback Appender](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/logback/logback-appender-1.0/javaagent/README.md). + * [JUL to SLF4J](./build.gradle.kts), which bridges JUL logs to the SLF4J API, and + ultimately to Logback. +* Demonstrates how trace context is propagated to logs when recorded within a + span. + +## Prerequisites + +* Java 1.8 +* Docker compose + +# How to run + +Run the collector via docker + +```shell +docker-compose up +``` + +In a separate shell, run the application + +```shell +export \ +OTEL_SERVICE_NAME=log4j-example \ +OTEL_METRICS_EXPORTER=none \ +OTEL_TRACES_EXPORTER=none \ +OTEL_LOGS_EXPORTER=otlp + +../gradlew run +``` + +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 +```shell +export OTEL_EXPORTER_OTLP_PROTOCOL=grpc +``` diff --git a/javaagent-log-appender/build.gradle.kts b/javaagent-log-appender/build.gradle.kts new file mode 100644 index 0000000000..59b7c32741 --- /dev/null +++ b/javaagent-log-appender/build.gradle.kts @@ -0,0 +1,58 @@ +plugins { + id("java") + id("application") +} + +description = "OpenTelemetry Log Appender Example using JavaAgent" +val moduleName by extra { "io.opentelemetry.examples.javaagent-log-appender" } + +java { + toolchain { + // logback 1.4.x+ requires java 11 + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +val agent = configurations.create("agent") + +dependencies { + // Slf4J / logback + implementation("org.slf4j:slf4j-api:2.0.16") + implementation("ch.qos.logback:logback-core:1.5.9") + implementation("ch.qos.logback:logback-classic:1.5.9") + + // JUL to SLF4J bridge + implementation("org.slf4j:jul-to-slf4j:2.0.16") + + // Log4j + implementation(platform("org.apache.logging.log4j:log4j-bom:2.24.1")) + implementation("org.apache.logging.log4j:log4j-api") + implementation("org.apache.logging.log4j:log4j-core") + + // OpenTelemetry core + implementation("io.opentelemetry:opentelemetry-api") + implementation("io.opentelemetry.semconv:opentelemetry-semconv") + + // OpenTelemetry Java Agent, this brings its own standalone log4j / logback appenders + agent("io.opentelemetry.javaagent:opentelemetry-javaagent:2.8.0") +} + +application { + mainClass = "io.opentelemetry.example.logappender.Application" +} + +tasks.named("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" + ) + } +} diff --git a/javaagent-log-appender/docker-compose.yml b/javaagent-log-appender/docker-compose.yml new file mode 100644 index 0000000000..bf50c06a92 --- /dev/null +++ b/javaagent-log-appender/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' +services: + collector: + image: otel/opentelemetry-collector-contrib:0.111.0 + volumes: + - ./otel-config.yaml:/otel-config.yaml + command: ["--config=/otel-config.yaml"] + ports: + - "4317:4317" + - "4318:4318" diff --git a/javaagent-log-appender/otel-config.yaml b/javaagent-log-appender/otel-config.yaml new file mode 100644 index 0000000000..7a645b2d72 --- /dev/null +++ b/javaagent-log-appender/otel-config.yaml @@ -0,0 +1,22 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: collector:4317 + http: + endpoint: "0.0.0.0:4318" +exporters: + debug: + verbosity: detailed + nop: +service: + pipelines: + metrics: + receivers: [otlp] + exporters: [debug] + traces: + receivers: [otlp] + exporters: [debug] + logs: + receivers: [otlp] + exporters: [debug] diff --git a/javaagent-log-appender/src/main/java/io/opentelemetry/example/logappender/Application.java b/javaagent-log-appender/src/main/java/io/opentelemetry/example/logappender/Application.java new file mode 100644 index 0000000000..b81d5c5d3d --- /dev/null +++ b/javaagent-log-appender/src/main/java/io/opentelemetry/example/logappender/Application.java @@ -0,0 +1,104 @@ +package io.opentelemetry.example.logappender; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.message.MapMessage; +import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; + +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"); + + public static void main(String[] args) { + // Route JUL logs to slf4j + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + + // 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); + Map mapMessage = new HashMap<>(); + mapMessage.put("key", "value"); + mapMessage.put("message", "A log4j structured message"); + maybeRunWithSpan(() -> log4jLogger.info(new MapMessage<>(mapMessage)), false); + ThreadContext.clearAll(); + maybeRunWithSpan( + () -> 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); + } + + private static void maybeRunWithSpan(Runnable runnable, boolean withSpan) { + if (!withSpan) { + runnable.run(); + return; + } + Span span = GlobalOpenTelemetry.getTracer("my-tracer").spanBuilder("my-span").startSpan(); + try (Scope unused = span.makeCurrent()) { + runnable.run(); + } finally { + span.end(); + } + } +} diff --git a/javaagent-log-appender/src/main/resources/log4j2.xml b/javaagent-log-appender/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..8c91e02f01 --- /dev/null +++ b/javaagent-log-appender/src/main/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/javaagent-log-appender/src/main/resources/logback.xml b/javaagent-log-appender/src/main/resources/logback.xml new file mode 100644 index 0000000000..348cb1f1af --- /dev/null +++ b/javaagent-log-appender/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + + logback: %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %kvp{DOUBLE}%n + + + + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts index fa1274b7fb..015c5581c4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -49,6 +49,7 @@ include( ":opentelemetry-examples-http", ":opentelemetry-examples-jaeger", ":opentelemetry-examples-javaagent", + ":opentelemetry-examples-javaagent-log-appender", ":opentelemetry-examples-log-appender", ":opentelemetry-examples-logging", ":opentelemetry-examples-manual-tracing",