Skip to content

Commit

Permalink
Add logback appender
Browse files Browse the repository at this point in the history
  • Loading branch information
jack-berg committed Jan 3, 2022
1 parent 1a9b375 commit f9484c1
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 1 deletion.
3 changes: 2 additions & 1 deletion bom-alpha/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ javaPlatform {
}

val otelVersion: String by project
val otelAlphaVersion: String by project

dependencies {
api(platform("io.opentelemetry:opentelemetry-bom:${otelVersion}"))
api(platform("io.opentelemetry:opentelemetry-bom-alpha:${otelVersion}-alpha"))
api(platform("io.opentelemetry:opentelemetry-bom-alpha:${otelAlphaVersion}"))
}

dependencies {
Expand Down
1 change: 1 addition & 0 deletions dependencyManagement/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ rootProject.extra["versions"] = dependencyVersions
val otelVersion = "1.10.0-rc.1"
val otelAlphaVersion = "1.10.0-alpha-rc.1"
rootProject.extra["otelVersion"] = otelVersion
rootProject.extra["otelAlphaVersion"] = otelAlphaVersion

// Need both BOM and -all
val groovyVersion = "3.0.9"
Expand Down
80 changes: 80 additions & 0 deletions instrumentation/logback/logback-appender-1.0/library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Logback Appender

This module provides a Logback [appender](https://logback.qos.ch/manual/appenders.html) which
forwards Logback log events to
the [OpenTelemetry Log SDK](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk/logs)

To use it, add the following modules to your application's classpath.

Replace `OPENTELEMETRY_VERSION` with the latest
stable [release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation).

**Maven**

```xml
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-appender-1.0</artifactId>
<version>OPENTELEMETRY_VERSION</version>
<scope>runtime</scope>
</dependency>
<dependency>
<!-- The SDK appender is required to configure the appender with the OpenTelemetry Log SDK -->
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-sdk-appender</artifactId>
<version>OPENTELEMETRY_VERSION</version>
</dependency>
</dependencies>
```

**Gradle**

```kotlin
dependencies {
runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0:OPENTELEMETRY_VERSION")
// The SDK appender is required to configure the appender with the OpenTelemetry Log SDK
implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-sdk-appender:OPENTELEMETRY_VERSION")
}
```

The following demonstrates how you might configure the appender in your `logback.xml` configuration:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<appender name="OpenTelemetry" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
</appender>

<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="OpenTelemetry" />
</root>

</configuration>
```

Next, associate the `OpenTelemetry` configured via `logback.xml` with a `SdkLogEmitterProvider` in
your application:

```
SdkLogEmitterProvider logEmitterProvider =
SdkLogEmitterProvider.builder()
.setResource(Resource.create(...))
.addLogProcessor(...)
.build();
GlobalLogEmitterProvider.set(DelegatingLogEmitterProvider.from(logEmitterProvider));
```

In this example Logback log events will be sent to both the console appender and
the `OpenTelemetryAppender`, which will drop the logs until `GlobalLogEmitterProvider.set(..)` is
called. Once initialized, logs will be emitted to a `LogEmitter` obtained from
the `SdkLogEmitterProvider`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.logback.appender.v1_0;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import io.opentelemetry.instrumentation.logback.appender.v1_0.internal.LoggingEventMapper;

public class OpenTelemetryAppender extends AppenderBase<ILoggingEvent> {

public OpenTelemetryAppender() {}

@Override
protected void append(ILoggingEvent event) {
LoggingEventMapper.INSTANCE.capture(event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.logback.appender.v1_0;

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.appender.GlobalLogEmitterProvider;
import io.opentelemetry.instrumentation.sdk.appender.DelegatingLogEmitterProvider;
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
import io.opentelemetry.sdk.logs.data.LogData;
import io.opentelemetry.sdk.logs.data.Severity;
import io.opentelemetry.sdk.logs.export.InMemoryLogExporter;
import io.opentelemetry.sdk.logs.export.SimpleLogProcessor;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.assertj.core.api.AssertionsForClassTypes;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

class OpenTelemetryAppenderConfigTest {

private static final Logger logger = LoggerFactory.getLogger("TestLogger");

private static InMemoryLogExporter logExporter;
private static Resource resource;
private static InstrumentationLibraryInfo instrumentationLibraryInfo;

@BeforeAll
static void setupAll() {
logExporter = InMemoryLogExporter.create();
resource = Resource.getDefault();
instrumentationLibraryInfo = InstrumentationLibraryInfo.create("TestLogger", null);

SdkLogEmitterProvider logEmitterProvider =
SdkLogEmitterProvider.builder()
.setResource(resource)
.addLogProcessor(SimpleLogProcessor.create(logExporter))
.build();

GlobalLogEmitterProvider.resetForTest();
GlobalLogEmitterProvider.set(DelegatingLogEmitterProvider.from(logEmitterProvider));
}

@BeforeEach
void setup() {
logExporter.reset();
}

@Test
void logNoSpan() {
logger.info("log message 1");

List<LogData> logDataList = logExporter.getFinishedLogItems();
assertThat(logDataList).hasSize(1);
LogData logData = logDataList.get(0);
assertThat(logData.getResource()).isEqualTo(resource);
assertThat(logData.getInstrumentationLibraryInfo()).isEqualTo(instrumentationLibraryInfo);
assertThat(logData.getBody().asString()).isEqualTo("log message 1");
assertThat(logData.getAttributes()).isEqualTo(Attributes.empty());
}

@Test
void logWithSpan() {
Span span1 = runWithSpan("span1", () -> logger.info("log message 1"));

logger.info("log message 2");

Span span2 = runWithSpan("span2", () -> logger.info("log message 3"));

List<LogData> logDataList = logExporter.getFinishedLogItems();
assertThat(logDataList).hasSize(3);
assertThat(logDataList.get(0).getSpanContext()).isEqualTo(span1.getSpanContext());
assertThat(logDataList.get(1).getSpanContext()).isEqualTo(SpanContext.getInvalid());
assertThat(logDataList.get(2).getSpanContext()).isEqualTo(span2.getSpanContext());
}

private static Span runWithSpan(String spanName, Runnable runnable) {
Span span = SdkTracerProvider.builder().build().get("tracer").spanBuilder(spanName).startSpan();
try (Scope ignored = span.makeCurrent()) {
runnable.run();
} finally {
span.end();
}
return span;
}

@Test
void logWithExtras() {
Instant start = Instant.now();
logger.info("log message 1", new IllegalStateException("Error!"));

List<LogData> logDataList = logExporter.getFinishedLogItems();
assertThat(logDataList).hasSize(1);
LogData logData = logDataList.get(0);
assertThat(logData.getResource()).isEqualTo(resource);
assertThat(logData.getInstrumentationLibraryInfo()).isEqualTo(instrumentationLibraryInfo);
assertThat(logData.getBody().asString()).isEqualTo("log message 1");
assertThat(logData.getEpochNanos())
.isGreaterThan(TimeUnit.MILLISECONDS.toNanos(start.toEpochMilli()))
.isLessThan(TimeUnit.MILLISECONDS.toNanos(Instant.now().toEpochMilli()));
assertThat(logData.getSeverity()).isEqualTo(Severity.INFO);
assertThat(logData.getSeverityText()).isEqualTo("INFO");
assertThat(logData.getAttributes().size()).isEqualTo(3);
assertThat(logData.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE))
.isEqualTo(IllegalStateException.class.getName());
assertThat(logData.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE))
.isEqualTo("Error!");
assertThat(logData.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE))
.contains("logWithExtras");
}

@Test
void logContextData() {
MDC.put("key1", "val1");
MDC.put("key2", "val2");
try {
logger.info("log message 1");
} finally {
MDC.clear();
}

List<LogData> logDataList = logExporter.getFinishedLogItems();
assertThat(logDataList).hasSize(1);
LogData logData = logDataList.get(0);
assertThat(logData.getResource()).isEqualTo(resource);
assertThat(logData.getInstrumentationLibraryInfo()).isEqualTo(instrumentationLibraryInfo);
assertThat(logData.getBody().asString()).isEqualTo("log message 1");
assertThat(logData.getAttributes().size()).isEqualTo(2);
AssertionsForClassTypes.assertThat(
logData.getAttributes().get(AttributeKey.stringKey("logback.mdc.key1")))
.isEqualTo("val1");
AssertionsForClassTypes.assertThat(
logData.getAttributes().get(AttributeKey.stringKey("logback.mdc.key2")))
.isEqualTo("val2");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<appender name="OpenTelemetry" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
</appender>

<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="OpenTelemetry" />
</root>

</configuration>

0 comments on commit f9484c1

Please sign in to comment.