Skip to content

Commit

Permalink
Add log appender example (#108)
Browse files Browse the repository at this point in the history
Adding functional examples demonstrating the log4j and logback
appenders.

Partially motivated by the goal of [stabilizing log bridge API and
SDK](open-telemetry/opentelemetry-specification#2911),
partially to make it abundantly how to use our log solutions.

---------

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
  • Loading branch information
jack-berg and trask authored Mar 2, 2023
1 parent 9a421c3 commit 65220a4
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 13 deletions.
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion javaagent/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
2 changes: 1 addition & 1 deletion javaagent/otel-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ receivers:
grpc:
exporters:
logging:
logLevel: DEBUG
verbosity: detailed
service:
pipelines:
metrics:
Expand Down
43 changes: 43 additions & 0 deletions log-appender/README.md
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions log-appender/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
}
9 changes: 9 additions & 0 deletions log-appender/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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"
12 changes: 12 additions & 0 deletions log-appender/otel-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
receivers:
otlp:
protocols:
grpc:
exporters:
logging:
verbosity: detailed
service:
pipelines:
logs:
receivers: [otlp]
exporters: [logging]
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
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 java.util.HashMap;
import java.util.Map;
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) {
// 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<String, Object> 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 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 =
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();
}
}
}
15 changes: 15 additions & 0 deletions log-appender/src/main/resources/log4j2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" packages="io.opentelemetry.instrumentation.log4j.appender.v2_17">
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="log4j2: %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<OpenTelemetry name="OpenTelemetryAppender" captureMapMessageAttributes="true"/>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="OpenTelemetryAppender" />
<AppenderRef ref="ConsoleAppender" />
</Root>
</Loggers>
</Configuration>
18 changes: 18 additions & 0 deletions log-appender/src/main/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
logback: %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %kvp{DOUBLE}%n
</pattern>
</encoder>
</appender>
<appender name="OpenTelemetry"
class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"
captureMdcAttributes="true" >
</appender>
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="OpenTelemetry"/>
</root>
</configuration>
15 changes: 8 additions & 7 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ pluginManagement {
}

rootProject.name = "opentelemetry-java-examples"
include ":opentelemetry-examples-javaagent",
":opentelemetry-examples-autoconfigure",
include ":opentelemetry-examples-autoconfigure",
":opentelemetry-examples-grpc",
":opentelemetry-examples-http",
":opentelemetry-examples-jaeger",
":opentelemetry-examples-javaagent",
":opentelemetry-examples-log-appender",
":opentelemetry-examples-logging",
":opentelemetry-examples-metrics",
":opentelemetry-examples-micrometer-shim",
":opentelemetry-examples-prometheus",
":opentelemetry-examples-otlp",
":opentelemetry-examples-prometheus",
":opentelemetry-examples-sdk-usage",
":opentelemetry-examples-zipkin",
":opentelemetry-examples-logging",
":opentelemetry-examples-telemetry-testing"
":opentelemetry-examples-telemetry-testing",
":opentelemetry-examples-zipkin"

rootProject.children.each {
it.projectDir = "$rootDir/" + it.name
.replace("opentelemetry-examples-", "") as File
.replace("opentelemetry-examples-", "") as File
}

0 comments on commit 65220a4

Please sign in to comment.