Skip to content

Commit

Permalink
implement json log exporter (#3744)
Browse files Browse the repository at this point in the history
  • Loading branch information
ohadza authored Oct 14, 2021
1 parent 7f56fd2 commit 9b9dcf9
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 0 deletions.
2 changes: 2 additions & 0 deletions exporters/logging-otlp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ otelJava.moduleName.set("io.opentelemetry.exporter.logging.otlp")
dependencies {
compileOnly(project(":sdk:trace"))
compileOnly(project(":sdk:metrics"))
compileOnly(project(":sdk:logs"))

implementation(project(":exporters:otlp:common"))

implementation("com.fasterxml.jackson.core:jackson-core")

testImplementation(project(":sdk:testing"))
testImplementation(project(":sdk:logs"))

testImplementation("org.skyscreamer:jsonassert")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.logging.otlp;

import static io.opentelemetry.exporter.logging.otlp.JsonUtil.JSON_FACTORY;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.io.SegmentedStringWriter;
import io.opentelemetry.exporter.otlp.internal.logs.ResourceLogsMarshaler;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.data.LogData;
import io.opentelemetry.sdk.logs.export.LogExporter;
import java.io.IOException;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* A {@link LogExporter} which writes {@linkplain LogData logs} to a {@link Logger} in OTLP JSON
* format. Each log line will include a single {@code ResourceLogs}.
*/
public final class OtlpJsonLoggingLogExporter implements LogExporter {

private static final Logger logger = Logger.getLogger(OtlpJsonLoggingLogExporter.class.getName());

/** Returns a new {@link OtlpJsonLoggingLogExporter}. */
public static LogExporter create() {
return new OtlpJsonLoggingLogExporter();
}

private OtlpJsonLoggingLogExporter() {}

@Override
public CompletableResultCode export(Collection<LogData> logs) {
ResourceLogsMarshaler[] allResourceLogs = ResourceLogsMarshaler.create(logs);
for (ResourceLogsMarshaler resourceLogs : allResourceLogs) {
SegmentedStringWriter sw = new SegmentedStringWriter(JSON_FACTORY._getBufferRecycler());
try (JsonGenerator gen = JsonUtil.create(sw)) {
resourceLogs.writeJsonTo(gen);
} catch (IOException e) {
// Shouldn't happen in practice, just skip it.
continue;
}
logger.log(Level.INFO, sw.getAndClear());
}
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode shutdown() {
return CompletableResultCode.ofSuccess();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.logging.otlp;

import static io.opentelemetry.api.common.AttributeKey.booleanKey;
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static org.assertj.core.api.Assertions.assertThat;

import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
import io.opentelemetry.sdk.logs.data.LogData;
import io.opentelemetry.sdk.logs.data.LogRecord;
import io.opentelemetry.sdk.logs.data.Severity;
import io.opentelemetry.sdk.logs.export.LogExporter;
import io.opentelemetry.sdk.resources.Resource;
import java.util.Arrays;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.skyscreamer.jsonassert.JSONAssert;
import org.slf4j.event.Level;

class OtlpJsonLoggingLogExporterTest {

private static final Resource RESOURCE =
Resource.create(Attributes.builder().put("key", "value").build());

private static final LogData LOG1 =
LogRecord.builder(RESOURCE, InstrumentationLibraryInfo.create("instrumentation", "1"))
.setName("testLog1")
.setBody("body1")
.setFlags(0)
.setSeverity(Severity.INFO)
.setSeverityText("INFO")
.setSpanId("8765432112345876")
.setTraceId("12345678876543211234567887654322")
.setEpochMillis(1631533710L)
.setAttributes(Attributes.of(stringKey("animal"), "cat", longKey("lives"), 9L))
.build();

private static final LogData LOG2 =
LogRecord.builder(RESOURCE, InstrumentationLibraryInfo.create("instrumentation2", "2"))
.setName("testLog2")
.setBody("body2")
.setFlags(0)
.setSeverity(Severity.INFO)
.setSeverityText("INFO")
.setSpanId("8765432112345875")
.setTraceId("12345678876543211234567887654322")
.setEpochMillis(1631533710L)
.setAttributes(Attributes.of(booleanKey("important"), true))
.build();

@RegisterExtension
LogCapturer logs = LogCapturer.create().captureForType(OtlpJsonLoggingLogExporter.class);

LogExporter exporter;

@BeforeEach
void setUp() {
exporter = OtlpJsonLoggingLogExporter.create();
}

@Test
void log() throws Exception {
exporter.export(Arrays.asList(LOG1, LOG2));

assertThat(logs.getEvents())
.hasSize(1)
.allSatisfy(log -> assertThat(log.getLevel()).isEqualTo(Level.INFO));
JSONAssert.assertEquals(
"{\n"
+ " \"resource\":{\n"
+ " \"attributes\":[\n"
+ " {\n"
+ " \"key\":\"key\",\n"
+ " \"value\":{\n"
+ " \"stringValue\":\"value\"\n"
+ " }\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ " \"instrumentationLibraryLogs\":[\n"
+ " {\n"
+ " \"instrumentationLibrary\":{\n"
+ " \"name\":\"instrumentation2\",\n"
+ " \"version\":\"2\"\n"
+ " },\n"
+ " \"logs\":[\n"
+ " {\n"
+ " \"timeUnixNano\":\"1631533710000000\",\n"
+ " \"severityNumber\":\"SEVERITY_NUMBER_INFO\",\n"
+ " \"severityText\":\"INFO\",\n"
+ " \"name\":\"testLog2\",\n"
+ " \"body\":{\n"
+ " \"stringValue\":\"body2\"\n"
+ " },\n"
+ " \"attributes\":[\n"
+ " {\n"
+ " \"key\":\"important\",\n"
+ " \"value\":{\n"
+ " \"boolValue\":true\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"traceId\":\"12345678876543211234567887654322\",\n"
+ " \"spanId\":\"8765432112345875\"\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " \"instrumentationLibrary\":{\n"
+ " \"name\":\"instrumentation\",\n"
+ " \"version\":\"1\"\n"
+ " },\n"
+ " \"logs\":[\n"
+ " {\n"
+ " \"timeUnixNano\":\"1631533710000000\",\n"
+ " \"severityNumber\":\"SEVERITY_NUMBER_INFO\",\n"
+ " \"severityText\":\"INFO\",\n"
+ " \"name\":\"testLog1\",\n"
+ " \"body\":{\n"
+ " \"stringValue\":\"body1\"\n"
+ " },\n"
+ " \"attributes\":[\n"
+ " {\n"
+ " \"key\":\"animal\",\n"
+ " \"value\":{\n"
+ " \"stringValue\":\"cat\"\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"key\":\"lives\",\n"
+ " \"value\":{\n"
+ " \"intValue\":\"9\"\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"traceId\":\"12345678876543211234567887654322\",\n"
+ " \"spanId\":\"8765432112345876\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}",
logs.getEvents().get(0).getMessage(),
/* strict= */ false);
assertThat(logs.getEvents().get(0).getMessage()).doesNotContain("\n");
}

@Test
void shutdown() {
assertThat(exporter.shutdown().isSuccess()).isTrue();
}
}

0 comments on commit 9b9dcf9

Please sign in to comment.