diff --git a/README.md b/README.md index 1203349d87..88be719bd3 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ of them assume you have docker running on your local machine. - This module demonstrates using the OpenTelemetry Java Agent with a simple spring boot application. Traces, metrics, and logs are exported to a collector via OTLP. +- [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 the Logging Exporters](logging) - This module contains a fully-functional example of configuring the OpenTelemetry SDK to use a logging exporter. @@ -43,18 +46,21 @@ of them assume you have docker running on your local machine. with spring boot actuator) configured to bridge metrics to OpenTelemetry with the micrometer shim. - Note: the micrometer shim is still experimental at this time. -- [Setting up the Prometheus exporter](prometheus) - - The module shows how to configure the OpenTelemetry SDK to expose an - endpoint that can be scraped by Prometheus. - - Note: the prometheus metric reader is still experimental at this time. - [Setting up OTLP exporters](otlp) - OTLP is the OpenTelemetry Protocol. This module will demonstrate how to configure the OTLP exporters, and send data to the OpenTelemetry collector using them. - Note: this example requires having docker installed to run the example. +- [Setting up the Prometheus exporter](prometheus) + - The module shows how to configure the OpenTelemetry SDK to expose an + endpoint that can be scraped by Prometheus. + - Note: the prometheus metric reader is still experimental at this time. - [Manually Configuring the SDK](sdk-usage) - This module shows some concrete examples of manually configuring the Java OpenTelemetry SDK for Tracing. +- [Telemetry Testing](sdk-usage) + - This module demonstrates how to test OpenTelemetry instrumentation with a + MockServer. - [Setting up the Zipkin exporter](zipkin) - This module contains a fully-functional example of configuring the OpenTelemetry SDK to use a Zipkin exporter, and send some spans to a diff --git a/javaagent/docker-compose.yml b/javaagent/docker-compose.yml index 3fc524ef66..d40a48b7a5 100644 --- a/javaagent/docker-compose.yml +++ b/javaagent/docker-compose.yml @@ -12,7 +12,7 @@ services: depends_on: - collector collector: - image: otel/opentelemetry-collector-contrib:0.51.0 + image: otel/opentelemetry-collector-contrib:0.72.0 volumes: - ./otel-config.yaml:/otel-config.yaml command: ["--config=/otel-config.yaml"] diff --git a/javaagent/otel-config.yaml b/javaagent/otel-config.yaml index 24a64da579..58d79532e8 100644 --- a/javaagent/otel-config.yaml +++ b/javaagent/otel-config.yaml @@ -4,7 +4,7 @@ receivers: grpc: exporters: logging: - logLevel: DEBUG + verbosity: detailed service: pipelines: metrics: diff --git a/log-appender/README.md b/log-appender/README.md new file mode 100644 index 0000000000..f37389a616 --- /dev/null +++ b/log-appender/README.md @@ -0,0 +1,43 @@ +# OpenTelemetry Log Appender Example + +This example demonstrates an application configured to use the OpenTelemetry Log +Appenders 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 Log4J Appender](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/log4j/log4j-appender-2.17/library/README.md). + * SLF4J API [configured with Logback](./src/main/resources/logback.xml) to + print logs to the console and + the [OpenTelemetry Logback Appender](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/logback/logback-appender-1.0/library/README.md). + * [JUL to SLF4J](./build.gradle), 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 +../gradlew run +``` + +Watch the collector logs to see exported log records diff --git a/log-appender/build.gradle b/log-appender/build.gradle new file mode 100644 index 0000000000..665f89f7de --- /dev/null +++ b/log-appender/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'java' + id 'application' +} + +description = 'OpenTelemetry Log Appender Example' +ext.moduleName = "io.opentelemetry.examples.log-appender" + +dependencies { + // Slf4J / logback + implementation("org.slf4j:slf4j-api:2.0.6") + implementation("ch.qos.logback:logback-core:1.4.5") + implementation("ch.qos.logback:logback-classic:1.4.5") + + // JUL to SLF4J bridge + implementation("org.slf4j:jul-to-slf4j:2.0.6") + + // Log4j + implementation(platform("org.apache.logging.log4j:log4j-bom:2.20.0")) + implementation("org.apache.logging.log4j:log4j-api") + implementation("org.apache.logging.log4j:log4j-core") + + // OpenTelemetry core + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-sdk-logs") + implementation("io.opentelemetry:opentelemetry-exporter-otlp-logs") + implementation("io.opentelemetry:opentelemetry-semconv") + + // OpenTelemetry log4j / logback appenders + implementation 'io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17' + implementation 'io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0' +} + +application { + mainClass = 'io.opentelemetry.example.logappender.Application' +} diff --git a/log-appender/docker-compose.yml b/log-appender/docker-compose.yml new file mode 100644 index 0000000000..1de7e8d240 --- /dev/null +++ b/log-appender/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3' +services: + collector: + image: otel/opentelemetry-collector-contrib:0.72.0 + volumes: + - ./otel-config.yaml:/otel-config.yaml + command: ["--config=/otel-config.yaml"] + ports: + - "4317:4317" diff --git a/log-appender/otel-config.yaml b/log-appender/otel-config.yaml new file mode 100644 index 0000000000..0bf3c8800f --- /dev/null +++ b/log-appender/otel-config.yaml @@ -0,0 +1,12 @@ +receivers: + otlp: + protocols: + grpc: +exporters: + logging: + verbosity: detailed +service: + pipelines: + logs: + receivers: [otlp] + exporters: [logging] diff --git a/log-appender/src/main/java/io/opentelemetry/example/logappender/Application.java b/log-appender/src/main/java/io/opentelemetry/example/logappender/Application.java new file mode 100644 index 0000000000..7c31b9c689 --- /dev/null +++ b/log-appender/src/main/java/io/opentelemetry/example/logappender/Application.java @@ -0,0 +1,123 @@ +package io.opentelemetry.example.logappender; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.logs.GlobalLoggerProvider; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +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 io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +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; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +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) { + // Initialize OpenTelemetry as early as possible + initializeOpenTelemetry(); + + // 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(); + + // 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); + + // 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); + + // Log using OpenTelemetry Log Bridge API + // WARNING: This illustrates how to write appenders which bridge logs from + // existing frameworks into the OpenTelemetry Log Brdige 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 = + GlobalLoggerProvider.get().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 initializeOpenTelemetry() { + OpenTelemetrySdk sdk = + OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder() + .setSampler(Sampler.alwaysOn()).build()) + .setLoggerProvider( + SdkLoggerProvider.builder() + .setResource( + Resource.getDefault().toBuilder() + .put(ResourceAttributes.SERVICE_NAME, "log4j-example") + .build()) + .addLogRecordProcessor( + BatchLogRecordProcessor.builder( + OtlpGrpcLogRecordExporter.builder() + .setEndpoint("http://localhost:4317") + .build()) + .build()) + .build()) + .build(); + GlobalOpenTelemetry.set(sdk); + GlobalLoggerProvider.set(sdk.getSdkLoggerProvider()); + + // Add hook to close SDK, which flushes logs + Runtime.getRuntime().addShutdownHook(new Thread(sdk::close)); + } + + 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/log-appender/src/main/resources/log4j2.xml b/log-appender/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..f97cbab34c --- /dev/null +++ b/log-appender/src/main/resources/log4j2.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log-appender/src/main/resources/logback.xml b/log-appender/src/main/resources/logback.xml new file mode 100644 index 0000000000..a3c5df5634 --- /dev/null +++ b/log-appender/src/main/resources/logback.xml @@ -0,0 +1,18 @@ + + + + + + logback: %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %kvp{DOUBLE}%n + + + + + + + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index b72c5a9b27..29f528dba1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,21 +7,17 @@ pluginManagement { } rootProject.name = "opentelemetry-java-examples" -include ":opentelemetry-examples-javaagent", - ":opentelemetry-examples-autoconfigure", - ":opentelemetry-examples-grpc", - ":opentelemetry-examples-http", - ":opentelemetry-examples-jaeger", - ":opentelemetry-examples-metrics", - ":opentelemetry-examples-micrometer-shim", - ":opentelemetry-examples-prometheus", - ":opentelemetry-examples-otlp", - ":opentelemetry-examples-sdk-usage", - ":opentelemetry-examples-zipkin", - ":opentelemetry-examples-logging", - ":opentelemetry-examples-telemetry-testing" - -rootProject.children.each { - it.projectDir = "$rootDir/" + it.name - .replace("opentelemetry-examples-", "") as File -} +include ":autoconfigure", + ":grpc", + ":http", + ":jaeger", + ":javaagent", + ":log-appender", + ":logging", + ":metrics", + ":micrometer-shim", + ":otlp", + ":prometheus", + ":sdk-usage", + ":telemetry-testing", + ":zipkin"