Skip to content
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

Add log appender example #108

Merged
merged 3 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
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");
Comment on lines +28 to +31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😱 (to seem them used all in one file 😂)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, originally I had separate examples for each situation, but that just created a bunch of bloat and increased the maintenance burden. Let's show it all in one place instead!


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 Brdige API. These APIs
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
// 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
}