Skip to content

Commit

Permalink
add stdout log record exporter (#6675)
Browse files Browse the repository at this point in the history
Co-authored-by: Jack Berg <[email protected]>
Co-authored-by: jack-berg <[email protected]>
  • Loading branch information
3 people authored Sep 18, 2024
1 parent 82b9e9b commit d899702
Show file tree
Hide file tree
Showing 27 changed files with 1,152 additions and 221 deletions.
1 change: 0 additions & 1 deletion buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ tasks {
"-Xlint:-processing",
// We suppress the "options" warning because it prevents compilation on modern JDKs
"-Xlint:-options",

// Fail build on any warning
"-Werror",
),
Expand Down
1 change: 1 addition & 0 deletions exporters/logging-otlp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ dependencies {

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

testImplementation("com.google.guava:guava")
testImplementation("org.skyscreamer:jsonassert")
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,12 @@

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.internal.otlp.logs.ResourceLogsMarshaler;
import io.opentelemetry.exporter.logging.otlp.internal.logs.OtlpStdoutLogRecordExporter;
import io.opentelemetry.exporter.logging.otlp.internal.logs.OtlpStdoutLogRecordExporterBuilder;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
Expand All @@ -30,49 +24,31 @@ public final class OtlpJsonLoggingLogRecordExporter implements LogRecordExporter
private static final Logger logger =
Logger.getLogger(OtlpJsonLoggingLogRecordExporter.class.getName());

private final AtomicBoolean isShutdown = new AtomicBoolean();
private final OtlpStdoutLogRecordExporter delegate;

/** Returns a new {@link OtlpJsonLoggingLogRecordExporter}. */
public static LogRecordExporter create() {
return new OtlpJsonLoggingLogRecordExporter();
OtlpStdoutLogRecordExporter delegate =
new OtlpStdoutLogRecordExporterBuilder(logger).setWrapperJsonObject(false).build();
return new OtlpJsonLoggingLogRecordExporter(delegate);
}

private OtlpJsonLoggingLogRecordExporter() {}
OtlpJsonLoggingLogRecordExporter(OtlpStdoutLogRecordExporter delegate) {
this.delegate = delegate;
}

@Override
public CompletableResultCode export(Collection<LogRecordData> logs) {
if (isShutdown.get()) {
return CompletableResultCode.ofFailure();
}

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;
}
try {
logger.log(Level.INFO, sw.getAndClear());
} catch (IOException e) {
logger.log(Level.WARNING, "Unable to read OTLP JSON log records", e);
}
}
return CompletableResultCode.ofSuccess();
return delegate.export(logs);
}

@Override
public CompletableResultCode flush() {
return CompletableResultCode.ofSuccess();
return delegate.flush();
}

@Override
public CompletableResultCode shutdown() {
if (!isShutdown.compareAndSet(false, true)) {
logger.log(Level.INFO, "Calling shutdown() multiple times.");
}
return CompletableResultCode.ofSuccess();
return delegate.shutdown();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

package io.opentelemetry.exporter.logging.otlp;

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

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.io.SegmentedStringWriter;
import io.opentelemetry.exporter.internal.otlp.metrics.ResourceMetricsMarshaler;
import io.opentelemetry.exporter.logging.otlp.internal.writer.JsonUtil;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

package io.opentelemetry.exporter.logging.otlp;

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

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.io.SegmentedStringWriter;
import io.opentelemetry.exporter.internal.otlp.traces.ResourceSpansMarshaler;
import io.opentelemetry.exporter.logging.otlp.internal.writer.JsonUtil;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SpanExporter;
Expand Down Expand Up @@ -43,8 +46,7 @@ public CompletableResultCode export(Collection<SpanData> spans) {

ResourceSpansMarshaler[] allResourceSpans = ResourceSpansMarshaler.create(spans);
for (ResourceSpansMarshaler resourceSpans : allResourceSpans) {
SegmentedStringWriter sw =
new SegmentedStringWriter(JsonUtil.JSON_FACTORY._getBufferRecycler());
SegmentedStringWriter sw = new SegmentedStringWriter(JSON_FACTORY._getBufferRecycler());
try (JsonGenerator gen = JsonUtil.create(sw)) {
resourceSpans.writeJsonTo(gen);
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.logging.otlp.internal;
package io.opentelemetry.exporter.logging.otlp.internal.logs;

import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingLogRecordExporter;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
Expand All @@ -17,6 +17,7 @@
* at any time.
*/
public class LoggingLogRecordExporterProvider implements ConfigurableLogRecordExporterProvider {

@Override
public LogRecordExporter createExporter(ConfigProperties config) {
return OtlpJsonLoggingLogRecordExporter.create();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.logging.otlp.internal.logs;

import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler;
import io.opentelemetry.exporter.internal.otlp.logs.ResourceLogsMarshaler;
import io.opentelemetry.exporter.logging.otlp.internal.writer.JsonWriter;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import java.util.Collection;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Exporter for sending OTLP log records to stdout.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public class OtlpStdoutLogRecordExporter implements LogRecordExporter {

private static final Logger LOGGER =
Logger.getLogger(OtlpStdoutLogRecordExporter.class.getName());

private final AtomicBoolean isShutdown = new AtomicBoolean();

private final Logger logger;
private final JsonWriter jsonWriter;
private final boolean wrapperJsonObject;

OtlpStdoutLogRecordExporter(Logger logger, JsonWriter jsonWriter, boolean wrapperJsonObject) {
this.logger = logger;
this.jsonWriter = jsonWriter;
this.wrapperJsonObject = wrapperJsonObject;
}

/** Returns a new {@link OtlpStdoutLogRecordExporterBuilder}. */
@SuppressWarnings("SystemOut")
public static OtlpStdoutLogRecordExporterBuilder builder() {
return new OtlpStdoutLogRecordExporterBuilder(LOGGER).setOutput(System.out);
}

@Override
public CompletableResultCode export(Collection<LogRecordData> logs) {
if (isShutdown.get()) {
return CompletableResultCode.ofFailure();
}

if (wrapperJsonObject) {
LogsRequestMarshaler request = LogsRequestMarshaler.create(logs);
return jsonWriter.write(request);
} else {
for (ResourceLogsMarshaler resourceLogs : ResourceLogsMarshaler.create(logs)) {
CompletableResultCode resultCode = jsonWriter.write(resourceLogs);
if (!resultCode.isSuccess()) {
// already logged
return resultCode;
}
}
return CompletableResultCode.ofSuccess();
}
}

@Override
public CompletableResultCode flush() {
return jsonWriter.flush();
}

@Override
public CompletableResultCode shutdown() {
if (!isShutdown.compareAndSet(false, true)) {
logger.log(Level.INFO, "Calling shutdown() multiple times.");
} else {
jsonWriter.close();
}
return CompletableResultCode.ofSuccess();
}

@Override
public String toString() {
StringJoiner joiner = new StringJoiner(", ", "OtlpStdoutLogRecordExporter{", "}");
joiner.add("jsonWriter=" + jsonWriter);
joiner.add("wrapperJsonObject=" + wrapperJsonObject);
return joiner.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.logging.otlp.internal.logs;

import static java.util.Objects.requireNonNull;

import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingLogRecordExporter;
import io.opentelemetry.exporter.logging.otlp.internal.writer.JsonWriter;
import io.opentelemetry.exporter.logging.otlp.internal.writer.LoggerJsonWriter;
import io.opentelemetry.exporter.logging.otlp.internal.writer.StreamJsonWriter;
import java.io.OutputStream;
import java.util.logging.Logger;

/**
* Builder for {@link OtlpJsonLoggingLogRecordExporter}.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public final class OtlpStdoutLogRecordExporterBuilder {

private static final String TYPE = "log records";

private final Logger logger;
private JsonWriter jsonWriter;
private boolean wrapperJsonObject = true;

public OtlpStdoutLogRecordExporterBuilder(Logger logger) {
this.logger = logger;
this.jsonWriter = new LoggerJsonWriter(logger, TYPE);
}

/**
* Sets the exporter to use the specified JSON object wrapper.
*
* @param wrapperJsonObject whether to wrap the JSON object in an outer JSON "resourceLogs"
* object.
*/
public OtlpStdoutLogRecordExporterBuilder setWrapperJsonObject(boolean wrapperJsonObject) {
this.wrapperJsonObject = wrapperJsonObject;
return this;
}

/**
* Sets the exporter to use the specified output stream.
*
* <p>The output stream will be closed when {@link OtlpStdoutLogRecordExporter#shutdown()} is
* called unless it's {@link System#out} or {@link System#err}.
*
* @param outputStream the output stream to use.
*/
public OtlpStdoutLogRecordExporterBuilder setOutput(OutputStream outputStream) {
requireNonNull(outputStream, "outputStream");
this.jsonWriter = new StreamJsonWriter(outputStream, TYPE);
return this;
}

/** Sets the exporter to use the specified logger. */
public OtlpStdoutLogRecordExporterBuilder setOutput(Logger logger) {
requireNonNull(logger, "logger");
this.jsonWriter = new LoggerJsonWriter(logger, TYPE);
return this;
}

/**
* Constructs a new instance of the exporter based on the builder's values.
*
* @return a new exporter's instance
*/
public OtlpStdoutLogRecordExporter build() {
return new OtlpStdoutLogRecordExporter(logger, jsonWriter, wrapperJsonObject);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.logging.otlp.internal.logs;

import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider;
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;

/**
* File configuration SPI implementation for {@link OtlpStdoutLogRecordExporter}.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public class OtlpStdoutLogRecordExporterComponentProvider
implements ComponentProvider<LogRecordExporter> {

@Override
public Class<LogRecordExporter> getType() {
return LogRecordExporter.class;
}

@Override
public String getName() {
return "experimental-otlp/stdout";
}

@Override
public LogRecordExporter create(StructuredConfigProperties config) {
OtlpStdoutLogRecordExporterBuilder builder = OtlpStdoutLogRecordExporter.builder();
return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.logging.otlp.internal.logs;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.logs.ConfigurableLogRecordExporterProvider;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;

/**
* {@link LogRecordExporter} SPI implementation for {@link OtlpStdoutLogRecordExporter}.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public class OtlpStdoutLogRecordExporterProvider implements ConfigurableLogRecordExporterProvider {
@Override
public LogRecordExporter createExporter(ConfigProperties config) {
OtlpStdoutLogRecordExporterBuilder builder = OtlpStdoutLogRecordExporter.builder();
return builder.build();
}

@Override
public String getName() {
return "experimental-otlp/stdout";
}
}
Loading

0 comments on commit d899702

Please sign in to comment.