diff --git a/build.gradle.kts b/build.gradle.kts index b7ef88b38..d608ddf83 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,12 +13,13 @@ subprojects { version = "0.1.0-SNAPSHOT" repositories { + mavenLocal() mavenCentral() } dependencies { // using the bom ensures that all of your opentelemetry dependency versions are aligned - implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:2.22.0-alpha")) + implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:2.23.0-alpha-SNAPSHOT")) } spotless { diff --git a/log-appender-log4j2/README.md b/log-appender-log4j2/README.md new file mode 100644 index 000000000..e0b9128d1 --- /dev/null +++ b/log-appender-log4j2/README.md @@ -0,0 +1,34 @@ +# OpenTelemetry with Log4j2 + +This sample app demonstrates an application emitting logs through a variety of logging APIs (OpenTelemetry API, log4j1, log4j2, JUL, slf4j) and configuring those logs to be sent an OTLP receiver via the OpenTelemetry SDK, and to the console via log4j2. + +* Install and run a OTLP receiver listening at `localhost:4318`. [otel-tui](https://github.com/ymtdzzz/otel-tui) is a good choice for local development. + +* Run the application: + +```shell +../gradlew run + +> Task :opentelemetry-examples-log-appender-log4j2:run +log4j2: 08:49:54.249 [main] INFO log4j2-logger - A log4j2 log message. +log4j2: 08:49:54.263 [main] INFO log4j1-logger - A log4j1 log message. +log4j2: 08:49:54.263 [main] INFO slf4j-logger - A slf4j log message. +log4j2: 08:49:54.263 [main] INFO jul-logger - A JUL log message. +log4j2: 08:49:54.265 [main] INFO otel-logger - An OTEL log message. +``` + +* Or run the application with the javaagent: + +```shell +../gradlew run -PrunWithAgent + +> Task :opentelemetry-examples-log-appender-log4j2:run +[otel.javaagent 2025-12-08 08:54:48:330 -0600] [main] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 2.23.0-SNAPSHOT +log4j2: 08:54:55.599 [main] INFO log4j2-logger - A log4j2 log message. +log4j2: 08:54:55.625 [main] INFO log4j1-logger - A log4j1 log message. +log4j2: 08:54:55.626 [main] INFO slf4j-logger - A slf4j log message. +log4j2: 08:54:55.626 [main] INFO jul-logger - A JUL log message. +log4j2: 08:54:55.629 [main] INFO otel-logger - An OTEL log message. +``` + +* A log message from each logging API appears exactly once in the console and OTLP receiver diff --git a/log-appender-log4j2/build.gradle.kts b/log-appender-log4j2/build.gradle.kts new file mode 100644 index 000000000..3f75c0f73 --- /dev/null +++ b/log-appender-log4j2/build.gradle.kts @@ -0,0 +1,81 @@ +plugins { + id("java") + id("application") +} + +description = "OpenTelemetry Log4j2 SDK Example" +val moduleName by extra { "io.opentelemetry.examples.log-appender" } + +java { + toolchain { + // logback 1.4.x+ requires java 11 + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +val agent = configurations.create("agent") + +dependencies { + implementation(platform("org.apache.logging.log4j:log4j-bom:2.25.2")) + + // Slf4J API + implementation("org.slf4j:slf4j-api:2.0.17") + // Log4j2 API + implementation("org.apache.logging.log4j:log4j-api") + // Log4j1 API (and bridge to SLF4J) + implementation("org.slf4j:log4j-over-slf4j:2.0.17") + // OTEL API + implementation("io.opentelemetry:opentelemetry-api") + + // JUL to SLF4J bridge + implementation("org.slf4j:jul-to-slf4j:2.0.17") + // Log4j1 to SLF4J bridge + implementation("org.slf4j:slf4j-api:2.0.17") + + // SLF4J binding for Log4j2 + implementation("org.apache.logging.log4j:log4j-slf4j2-impl") + + // Log4j2 SDK + implementation("org.apache.logging.log4j:log4j-core") + implementation("io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17") + + // OTEL SDK + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-exporter-otlp") + implementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") + + agent("io.opentelemetry.javaagent:opentelemetry-javaagent:2.23.0-SNAPSHOT") +} + +val copyAgent = tasks.register("copyAgent") { + from(agent.singleFile) + into(layout.buildDirectory.dir("agent")) + rename("opentelemetry-javaagent-.*\\.jar", "opentelemetry-javaagent.jar") +} + +tasks.getByName("run") { + dependsOn(copyAgent) +} + +application { + mainClass = "io.opentelemetry.example.logappender.Application" + + if (project.hasProperty("runWithAgent")) { + applicationDefaultJvmArgs = listOf("-javaagent:build/agent/opentelemetry-javaagent.jar") + } +} + +tasks.register("runWithoutAgent") { + group = ApplicationPlugin.APPLICATION_GROUP + classpath = sourceSets.main.get().runtimeClasspath + mainClass = "io.opentelemetry.example.logappender.Application" +} + +tasks.register("runWithAgent") { + dependsOn(copyAgent) + + group = ApplicationPlugin.APPLICATION_GROUP + classpath = sourceSets.main.get().runtimeClasspath + mainClass = "io.opentelemetry.example.logappender.Application" + jvmArgs = listOf("-javaagent:build/agent/opentelemetry-javaagent.jar") +} diff --git a/log-appender-log4j2/src/main/java/io/opentelemetry/example/logappender/Application.java b/log-appender-log4j2/src/main/java/io/opentelemetry/example/logappender/Application.java new file mode 100644 index 000000000..cc66d8a96 --- /dev/null +++ b/log-appender-log4j2/src/main/java/io/opentelemetry/example/logappender/Application.java @@ -0,0 +1,84 @@ +package io.opentelemetry.example.logappender; + +import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.extension.incubator.slf4j.Slf4jBridgeProcessor; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import java.util.logging.Logger; +import org.apache.logging.log4j.LogManager; +import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; + +public class Application { + + public static void main(String[] args) { + // If javaagent isn't installed, initialize opentelemetry SDK and install it in the log4j2 + // OpenTelemetryAppender + OpenTelemetry openTelemetry; + if (!GlobalOpenTelemetry.isSet()) { + openTelemetry = initializeOpenTelemetry(); + OpenTelemetryAppender.install(openTelemetry); + } else { + openTelemetry = GlobalOpenTelemetry.get(); + } + + // Install JUL to SLF4J bridging + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + + // Initialize loggers and log from all APIs + + // log4j2 + org.apache.logging.log4j.Logger log4j2Logger = LogManager.getLogger("log4j2-logger"); + log4j2Logger.info("A log4j2 log message."); + + // log4j1 + org.apache.log4j.Logger log4j1Logger = org.apache.log4j.Logger.getLogger("log4j1-logger"); + log4j1Logger.info("A log4j1 log message."); + + // slf4j + org.slf4j.Logger slf4jLogger = LoggerFactory.getLogger("slf4j-logger"); + slf4jLogger.info("A slf4j log message."); + + // jul + java.util.logging.Logger julLogger = Logger.getLogger("jul-logger"); + julLogger.info("A JUL log message."); + + // otel + io.opentelemetry.api.logs.Logger otelLogger = openTelemetry.getLogsBridge().get("otel-logger"); + otelLogger.logRecordBuilder().setSeverity(Severity.INFO).setBody("An OTEL log message.").emit(); + } + + private static OpenTelemetry initializeOpenTelemetry() { + OpenTelemetrySdk sdk = + OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder().setSampler(Sampler.alwaysOn()).build()) + .setLoggerProvider( + SdkLoggerProvider.builder() + .setResource( + Resource.getDefault().toBuilder() + .put(SERVICE_NAME, "log4j-example") + .build()) + .addLogRecordProcessor(Slf4jBridgeProcessor.create()) + .addLogRecordProcessor( + BatchLogRecordProcessor.builder(OtlpHttpLogRecordExporter.builder().build()) + .build()) + .build()) + .build(); + + // Add hook to close SDK, which flushes logs + Runtime.getRuntime().addShutdownHook(new Thread(sdk::close)); + + return sdk; + } +} diff --git a/log-appender-log4j2/src/main/resources/log4j2.xml b/log-appender-log4j2/src/main/resources/log4j2.xml new file mode 100644 index 000000000..505b966fe --- /dev/null +++ b/log-appender-log4j2/src/main/resources/log4j2.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log-appender-logback/README.md b/log-appender-logback/README.md new file mode 100644 index 000000000..e3f567489 --- /dev/null +++ b/log-appender-logback/README.md @@ -0,0 +1,34 @@ +# OpenTelemetry with Logback + +This sample app demonstrates an application emitting logs through a variety of logging APIs (OpenTelemetry API, log4j1, log4j2, JUL, slf4j) and configuring those logs to be sent an OTLP receiver via the OpenTelemetry SDK, and to the console via logback. + +* Install and run a OTLP receiver listening at `localhost:4318`. [otel-tui](https://github.com/ymtdzzz/otel-tui) is a good choice for local development. + +* Run the application: + +```shell +../gradlew run + +> Task :opentelemetry-examples-log-appender-logback:run +logback: 08:53:12.625 [main] INFO log4j2-logger - A log4j2 log message. +logback: 08:53:12.633 [main] INFO log4j1-logger - A log4j1 log message. +logback: 08:53:12.633 [main] INFO slf4j-logger - A slf4j log message. +logback: 08:53:12.633 [main] INFO jul-logger - A JUL log message. +logback: 08:53:12.634 [main] INFO otel-logger - An OTEL log message. +``` + +* Or run the application with the javaagent: + +```shell +../gradlew run -PrunWithAgent + +> Task :opentelemetry-examples-log-appender-logback:run +logback: 08:47:33.317 [main] INFO i.o.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 2.23.0-SNAPSHOT +logback: 08:47:33.337 [main] INFO log4j2-logger - A log4j2 log message. +logback: 08:47:33.340 [main] INFO log4j1-logger - A log4j1 log message. +logback: 08:47:33.340 [main] INFO slf4j-logger - A slf4j log message. +logback: 08:47:33.340 [main] INFO jul-logger - A JUL log message. +logback: 08:47:33.343 [main] INFO otel-logger - An OTEL log message. +``` + +* A log message from each logging API appears exactly once in the console and OTLP receiver diff --git a/log-appender-logback/build.gradle.kts b/log-appender-logback/build.gradle.kts new file mode 100644 index 000000000..12f74a395 --- /dev/null +++ b/log-appender-logback/build.gradle.kts @@ -0,0 +1,78 @@ +plugins { + id("java") + id("application") +} + +description = "OpenTelemetry Logback SDK Example" +val moduleName by extra { "io.opentelemetry.examples.log-appender" } + +java { + toolchain { + // logback 1.4.x+ requires java 11 + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +val agent = configurations.create("agent") + +dependencies { + implementation(platform("org.apache.logging.log4j:log4j-bom:2.25.2")) + + // Slf4J API + implementation("org.slf4j:slf4j-api:2.0.17") + // Log4j2 API + implementation("org.apache.logging.log4j:log4j-api") + // Log4j1 API (and bridge to SLF4J) + implementation("org.slf4j:log4j-over-slf4j:2.0.17") + // OTEL API + implementation("io.opentelemetry:opentelemetry-api") + + // JUL to SLF4J bridge + implementation("org.slf4j:jul-to-slf4j:2.0.17") + // Log4j2 to SLF4J bridge + implementation("org.apache.logging.log4j:log4j-to-slf4j") + + // Logback SDK and SLF4J binding for logback + implementation("ch.qos.logback:logback-classic:1.5.21") + implementation("io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0") + + // OTEL SDK + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-exporter-otlp") + implementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") + + agent("io.opentelemetry.javaagent:opentelemetry-javaagent:2.23.0-SNAPSHOT") +} + +val copyAgent = tasks.register("copyAgent") { + from(agent.singleFile) + into(layout.buildDirectory.dir("agent")) + rename("opentelemetry-javaagent-.*\\.jar", "opentelemetry-javaagent.jar") +} + +tasks.getByName("run") { + dependsOn(copyAgent) +} + +application { + mainClass = "io.opentelemetry.example.logappender.Application" + + if (project.hasProperty("runWithAgent")) { + applicationDefaultJvmArgs = listOf("-javaagent:build/agent/opentelemetry-javaagent.jar") + } +} + +tasks.register("runWithoutAgent") { + group = ApplicationPlugin.APPLICATION_GROUP + classpath = sourceSets.main.get().runtimeClasspath + mainClass = "io.opentelemetry.example.logappender.Application" +} + +tasks.register("runWithAgent") { + dependsOn(copyAgent) + + group = ApplicationPlugin.APPLICATION_GROUP + classpath = sourceSets.main.get().runtimeClasspath + mainClass = "io.opentelemetry.example.logappender.Application" + jvmArgs = listOf("-javaagent:build/agent/opentelemetry-javaagent.jar") +} diff --git a/log-appender-logback/src/main/java/io/opentelemetry/example/logappender/Application.java b/log-appender-logback/src/main/java/io/opentelemetry/example/logappender/Application.java new file mode 100644 index 000000000..dbf308afe --- /dev/null +++ b/log-appender-logback/src/main/java/io/opentelemetry/example/logappender/Application.java @@ -0,0 +1,84 @@ +package io.opentelemetry.example.logappender; + +import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.extension.incubator.slf4j.Slf4jBridgeProcessor; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import java.util.logging.Logger; +import org.apache.logging.log4j.LogManager; +import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; + +public class Application { + + public static void main(String[] args) { + // If javaagent isn't installed, initialize opentelemetry SDK and install it in the logback + // OpenTelemetryAppender + OpenTelemetry openTelemetry; + if (!GlobalOpenTelemetry.isSet()) { + openTelemetry = initializeOpenTelemetry(); + OpenTelemetryAppender.install(openTelemetry); + } else { + openTelemetry = GlobalOpenTelemetry.get(); + } + + // Install JUL to SLF4J bridging + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + + // Initialize loggers and log from all APIs + + // log4j2 + org.apache.logging.log4j.Logger log4j2Logger = LogManager.getLogger("log4j2-logger"); + log4j2Logger.info("A log4j2 log message."); + + // log4j1 + org.apache.log4j.Logger log4j1Logger = org.apache.log4j.Logger.getLogger("log4j1-logger"); + log4j1Logger.info("A log4j1 log message."); + + // slf4j + org.slf4j.Logger slf4jLogger = LoggerFactory.getLogger("slf4j-logger"); + slf4jLogger.info("A slf4j log message."); + + // jul + java.util.logging.Logger julLogger = Logger.getLogger("jul-logger"); + julLogger.info("A JUL log message."); + + // otel + io.opentelemetry.api.logs.Logger otelLogger = openTelemetry.getLogsBridge().get("otel-logger"); + otelLogger.logRecordBuilder().setSeverity(Severity.INFO).setBody("An OTEL log message.").emit(); + } + + private static OpenTelemetry initializeOpenTelemetry() { + OpenTelemetrySdk sdk = + OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder().setSampler(Sampler.alwaysOn()).build()) + .setLoggerProvider( + SdkLoggerProvider.builder() + .setResource( + Resource.getDefault().toBuilder() + .put(SERVICE_NAME, "logback-example") + .build()) + .addLogRecordProcessor(Slf4jBridgeProcessor.create()) + .addLogRecordProcessor( + BatchLogRecordProcessor.builder(OtlpHttpLogRecordExporter.builder().build()) + .build()) + .build()) + .build(); + + // Add hook to close SDK, which flushes logs + Runtime.getRuntime().addShutdownHook(new Thread(sdk::close)); + + return sdk; + } +} diff --git a/log-appender-logback/src/main/resources/logback.xml b/log-appender-logback/src/main/resources/logback.xml new file mode 100644 index 000000000..a14aeb7fa --- /dev/null +++ b/log-appender-logback/src/main/resources/logback.xml @@ -0,0 +1,19 @@ + + + + + + logback: %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %kvp{DOUBLE}%n + + + + + true + true + + + + + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index cd6cd2eca..daa468553 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -60,6 +60,8 @@ include( ":opentelemetry-examples-jaeger", ":opentelemetry-examples-javaagent", ":opentelemetry-examples-log-appender", + ":opentelemetry-examples-log-appender-log4j2", + ":opentelemetry-examples-log-appender-logback", ":opentelemetry-examples-logging", ":opentelemetry-examples-logging-k8s-stdout-otlp-json", ":opentelemetry-examples-manual-tracing",