Skip to content

Commit d899702

Browse files
add stdout log record exporter (#6675)
Co-authored-by: Jack Berg <[email protected]> Co-authored-by: jack-berg <[email protected]>
1 parent 82b9e9b commit d899702

27 files changed

+1152
-221
lines changed

buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ tasks {
8787
"-Xlint:-processing",
8888
// We suppress the "options" warning because it prevents compilation on modern JDKs
8989
"-Xlint:-options",
90-
9190
// Fail build on any warning
9291
"-Werror",
9392
),

exporters/logging-otlp/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ dependencies {
2020

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

23+
testImplementation("com.google.guava:guava")
2324
testImplementation("org.skyscreamer:jsonassert")
2425
}

exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingLogRecordExporter.java

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,12 @@
55

66
package io.opentelemetry.exporter.logging.otlp;
77

8-
import static io.opentelemetry.exporter.logging.otlp.JsonUtil.JSON_FACTORY;
9-
10-
import com.fasterxml.jackson.core.JsonGenerator;
11-
import com.fasterxml.jackson.core.io.SegmentedStringWriter;
12-
import io.opentelemetry.exporter.internal.otlp.logs.ResourceLogsMarshaler;
8+
import io.opentelemetry.exporter.logging.otlp.internal.logs.OtlpStdoutLogRecordExporter;
9+
import io.opentelemetry.exporter.logging.otlp.internal.logs.OtlpStdoutLogRecordExporterBuilder;
1310
import io.opentelemetry.sdk.common.CompletableResultCode;
1411
import io.opentelemetry.sdk.logs.data.LogRecordData;
1512
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
16-
import java.io.IOException;
1713
import java.util.Collection;
18-
import java.util.concurrent.atomic.AtomicBoolean;
19-
import java.util.logging.Level;
2014
import java.util.logging.Logger;
2115

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

33-
private final AtomicBoolean isShutdown = new AtomicBoolean();
27+
private final OtlpStdoutLogRecordExporter delegate;
3428

3529
/** Returns a new {@link OtlpJsonLoggingLogRecordExporter}. */
3630
public static LogRecordExporter create() {
37-
return new OtlpJsonLoggingLogRecordExporter();
31+
OtlpStdoutLogRecordExporter delegate =
32+
new OtlpStdoutLogRecordExporterBuilder(logger).setWrapperJsonObject(false).build();
33+
return new OtlpJsonLoggingLogRecordExporter(delegate);
3834
}
3935

40-
private OtlpJsonLoggingLogRecordExporter() {}
36+
OtlpJsonLoggingLogRecordExporter(OtlpStdoutLogRecordExporter delegate) {
37+
this.delegate = delegate;
38+
}
4139

4240
@Override
4341
public CompletableResultCode export(Collection<LogRecordData> logs) {
44-
if (isShutdown.get()) {
45-
return CompletableResultCode.ofFailure();
46-
}
47-
48-
ResourceLogsMarshaler[] allResourceLogs = ResourceLogsMarshaler.create(logs);
49-
for (ResourceLogsMarshaler resourceLogs : allResourceLogs) {
50-
SegmentedStringWriter sw = new SegmentedStringWriter(JSON_FACTORY._getBufferRecycler());
51-
try (JsonGenerator gen = JsonUtil.create(sw)) {
52-
resourceLogs.writeJsonTo(gen);
53-
} catch (IOException e) {
54-
// Shouldn't happen in practice, just skip it.
55-
continue;
56-
}
57-
try {
58-
logger.log(Level.INFO, sw.getAndClear());
59-
} catch (IOException e) {
60-
logger.log(Level.WARNING, "Unable to read OTLP JSON log records", e);
61-
}
62-
}
63-
return CompletableResultCode.ofSuccess();
42+
return delegate.export(logs);
6443
}
6544

6645
@Override
6746
public CompletableResultCode flush() {
68-
return CompletableResultCode.ofSuccess();
47+
return delegate.flush();
6948
}
7049

7150
@Override
7251
public CompletableResultCode shutdown() {
73-
if (!isShutdown.compareAndSet(false, true)) {
74-
logger.log(Level.INFO, "Calling shutdown() multiple times.");
75-
}
76-
return CompletableResultCode.ofSuccess();
52+
return delegate.shutdown();
7753
}
7854
}

exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingMetricExporter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55

66
package io.opentelemetry.exporter.logging.otlp;
77

8-
import static io.opentelemetry.exporter.logging.otlp.JsonUtil.JSON_FACTORY;
8+
import static io.opentelemetry.exporter.logging.otlp.internal.writer.JsonUtil.JSON_FACTORY;
99

1010
import com.fasterxml.jackson.core.JsonGenerator;
1111
import com.fasterxml.jackson.core.io.SegmentedStringWriter;
1212
import io.opentelemetry.exporter.internal.otlp.metrics.ResourceMetricsMarshaler;
13+
import io.opentelemetry.exporter.logging.otlp.internal.writer.JsonUtil;
1314
import io.opentelemetry.sdk.common.CompletableResultCode;
1415
import io.opentelemetry.sdk.metrics.InstrumentType;
1516
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;

exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingSpanExporter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55

66
package io.opentelemetry.exporter.logging.otlp;
77

8+
import static io.opentelemetry.exporter.logging.otlp.internal.writer.JsonUtil.JSON_FACTORY;
9+
810
import com.fasterxml.jackson.core.JsonGenerator;
911
import com.fasterxml.jackson.core.io.SegmentedStringWriter;
1012
import io.opentelemetry.exporter.internal.otlp.traces.ResourceSpansMarshaler;
13+
import io.opentelemetry.exporter.logging.otlp.internal.writer.JsonUtil;
1114
import io.opentelemetry.sdk.common.CompletableResultCode;
1215
import io.opentelemetry.sdk.trace.data.SpanData;
1316
import io.opentelemetry.sdk.trace.export.SpanExporter;
@@ -43,8 +46,7 @@ public CompletableResultCode export(Collection<SpanData> spans) {
4346

4447
ResourceSpansMarshaler[] allResourceSpans = ResourceSpansMarshaler.create(spans);
4548
for (ResourceSpansMarshaler resourceSpans : allResourceSpans) {
46-
SegmentedStringWriter sw =
47-
new SegmentedStringWriter(JsonUtil.JSON_FACTORY._getBufferRecycler());
49+
SegmentedStringWriter sw = new SegmentedStringWriter(JSON_FACTORY._getBufferRecycler());
4850
try (JsonGenerator gen = JsonUtil.create(sw)) {
4951
resourceSpans.writeJsonTo(gen);
5052
} catch (IOException e) {

exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/LoggingLogRecordExporterProvider.java renamed to exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/logs/LoggingLogRecordExporterProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
package io.opentelemetry.exporter.logging.otlp.internal;
6+
package io.opentelemetry.exporter.logging.otlp.internal.logs;
77

88
import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingLogRecordExporter;
99
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
@@ -17,6 +17,7 @@
1717
* at any time.
1818
*/
1919
public class LoggingLogRecordExporterProvider implements ConfigurableLogRecordExporterProvider {
20+
2021
@Override
2122
public LogRecordExporter createExporter(ConfigProperties config) {
2223
return OtlpJsonLoggingLogRecordExporter.create();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.logging.otlp.internal.logs;
7+
8+
import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler;
9+
import io.opentelemetry.exporter.internal.otlp.logs.ResourceLogsMarshaler;
10+
import io.opentelemetry.exporter.logging.otlp.internal.writer.JsonWriter;
11+
import io.opentelemetry.sdk.common.CompletableResultCode;
12+
import io.opentelemetry.sdk.logs.data.LogRecordData;
13+
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
14+
import java.util.Collection;
15+
import java.util.StringJoiner;
16+
import java.util.concurrent.atomic.AtomicBoolean;
17+
import java.util.logging.Level;
18+
import java.util.logging.Logger;
19+
20+
/**
21+
* Exporter for sending OTLP log records to stdout.
22+
*
23+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
24+
* at any time.
25+
*/
26+
public class OtlpStdoutLogRecordExporter implements LogRecordExporter {
27+
28+
private static final Logger LOGGER =
29+
Logger.getLogger(OtlpStdoutLogRecordExporter.class.getName());
30+
31+
private final AtomicBoolean isShutdown = new AtomicBoolean();
32+
33+
private final Logger logger;
34+
private final JsonWriter jsonWriter;
35+
private final boolean wrapperJsonObject;
36+
37+
OtlpStdoutLogRecordExporter(Logger logger, JsonWriter jsonWriter, boolean wrapperJsonObject) {
38+
this.logger = logger;
39+
this.jsonWriter = jsonWriter;
40+
this.wrapperJsonObject = wrapperJsonObject;
41+
}
42+
43+
/** Returns a new {@link OtlpStdoutLogRecordExporterBuilder}. */
44+
@SuppressWarnings("SystemOut")
45+
public static OtlpStdoutLogRecordExporterBuilder builder() {
46+
return new OtlpStdoutLogRecordExporterBuilder(LOGGER).setOutput(System.out);
47+
}
48+
49+
@Override
50+
public CompletableResultCode export(Collection<LogRecordData> logs) {
51+
if (isShutdown.get()) {
52+
return CompletableResultCode.ofFailure();
53+
}
54+
55+
if (wrapperJsonObject) {
56+
LogsRequestMarshaler request = LogsRequestMarshaler.create(logs);
57+
return jsonWriter.write(request);
58+
} else {
59+
for (ResourceLogsMarshaler resourceLogs : ResourceLogsMarshaler.create(logs)) {
60+
CompletableResultCode resultCode = jsonWriter.write(resourceLogs);
61+
if (!resultCode.isSuccess()) {
62+
// already logged
63+
return resultCode;
64+
}
65+
}
66+
return CompletableResultCode.ofSuccess();
67+
}
68+
}
69+
70+
@Override
71+
public CompletableResultCode flush() {
72+
return jsonWriter.flush();
73+
}
74+
75+
@Override
76+
public CompletableResultCode shutdown() {
77+
if (!isShutdown.compareAndSet(false, true)) {
78+
logger.log(Level.INFO, "Calling shutdown() multiple times.");
79+
} else {
80+
jsonWriter.close();
81+
}
82+
return CompletableResultCode.ofSuccess();
83+
}
84+
85+
@Override
86+
public String toString() {
87+
StringJoiner joiner = new StringJoiner(", ", "OtlpStdoutLogRecordExporter{", "}");
88+
joiner.add("jsonWriter=" + jsonWriter);
89+
joiner.add("wrapperJsonObject=" + wrapperJsonObject);
90+
return joiner.toString();
91+
}
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.logging.otlp.internal.logs;
7+
8+
import static java.util.Objects.requireNonNull;
9+
10+
import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingLogRecordExporter;
11+
import io.opentelemetry.exporter.logging.otlp.internal.writer.JsonWriter;
12+
import io.opentelemetry.exporter.logging.otlp.internal.writer.LoggerJsonWriter;
13+
import io.opentelemetry.exporter.logging.otlp.internal.writer.StreamJsonWriter;
14+
import java.io.OutputStream;
15+
import java.util.logging.Logger;
16+
17+
/**
18+
* Builder for {@link OtlpJsonLoggingLogRecordExporter}.
19+
*
20+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
21+
* at any time.
22+
*/
23+
public final class OtlpStdoutLogRecordExporterBuilder {
24+
25+
private static final String TYPE = "log records";
26+
27+
private final Logger logger;
28+
private JsonWriter jsonWriter;
29+
private boolean wrapperJsonObject = true;
30+
31+
public OtlpStdoutLogRecordExporterBuilder(Logger logger) {
32+
this.logger = logger;
33+
this.jsonWriter = new LoggerJsonWriter(logger, TYPE);
34+
}
35+
36+
/**
37+
* Sets the exporter to use the specified JSON object wrapper.
38+
*
39+
* @param wrapperJsonObject whether to wrap the JSON object in an outer JSON "resourceLogs"
40+
* object.
41+
*/
42+
public OtlpStdoutLogRecordExporterBuilder setWrapperJsonObject(boolean wrapperJsonObject) {
43+
this.wrapperJsonObject = wrapperJsonObject;
44+
return this;
45+
}
46+
47+
/**
48+
* Sets the exporter to use the specified output stream.
49+
*
50+
* <p>The output stream will be closed when {@link OtlpStdoutLogRecordExporter#shutdown()} is
51+
* called unless it's {@link System#out} or {@link System#err}.
52+
*
53+
* @param outputStream the output stream to use.
54+
*/
55+
public OtlpStdoutLogRecordExporterBuilder setOutput(OutputStream outputStream) {
56+
requireNonNull(outputStream, "outputStream");
57+
this.jsonWriter = new StreamJsonWriter(outputStream, TYPE);
58+
return this;
59+
}
60+
61+
/** Sets the exporter to use the specified logger. */
62+
public OtlpStdoutLogRecordExporterBuilder setOutput(Logger logger) {
63+
requireNonNull(logger, "logger");
64+
this.jsonWriter = new LoggerJsonWriter(logger, TYPE);
65+
return this;
66+
}
67+
68+
/**
69+
* Constructs a new instance of the exporter based on the builder's values.
70+
*
71+
* @return a new exporter's instance
72+
*/
73+
public OtlpStdoutLogRecordExporter build() {
74+
return new OtlpStdoutLogRecordExporter(logger, jsonWriter, wrapperJsonObject);
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.logging.otlp.internal.logs;
7+
8+
import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider;
9+
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
10+
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
11+
12+
/**
13+
* File configuration SPI implementation for {@link OtlpStdoutLogRecordExporter}.
14+
*
15+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
16+
* at any time.
17+
*/
18+
public class OtlpStdoutLogRecordExporterComponentProvider
19+
implements ComponentProvider<LogRecordExporter> {
20+
21+
@Override
22+
public Class<LogRecordExporter> getType() {
23+
return LogRecordExporter.class;
24+
}
25+
26+
@Override
27+
public String getName() {
28+
return "experimental-otlp/stdout";
29+
}
30+
31+
@Override
32+
public LogRecordExporter create(StructuredConfigProperties config) {
33+
OtlpStdoutLogRecordExporterBuilder builder = OtlpStdoutLogRecordExporter.builder();
34+
return builder.build();
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.logging.otlp.internal.logs;
7+
8+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
9+
import io.opentelemetry.sdk.autoconfigure.spi.logs.ConfigurableLogRecordExporterProvider;
10+
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
11+
12+
/**
13+
* {@link LogRecordExporter} SPI implementation for {@link OtlpStdoutLogRecordExporter}.
14+
*
15+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
16+
* at any time.
17+
*/
18+
public class OtlpStdoutLogRecordExporterProvider implements ConfigurableLogRecordExporterProvider {
19+
@Override
20+
public LogRecordExporter createExporter(ConfigProperties config) {
21+
OtlpStdoutLogRecordExporterBuilder builder = OtlpStdoutLogRecordExporter.builder();
22+
return builder.build();
23+
}
24+
25+
@Override
26+
public String getName() {
27+
return "experimental-otlp/stdout";
28+
}
29+
}

0 commit comments

Comments
 (0)