Skip to content

Commit ae10f71

Browse files
committed
add compact log record exporter for lambda environment
1 parent 746e989 commit ae10f71

File tree

7 files changed

+174
-32
lines changed

7 files changed

+174
-32
lines changed

awsagentprovider/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ dependencies {
4646
implementation("com.amazonaws:aws-java-sdk-core:1.12.773")
4747
// Export configuration
4848
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp")
49+
// For logging exporter
50+
compileOnly("io.opentelemetry:opentelemetry-exporter-logging")
4951
// For Udp emitter
5052
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp-common")
5153

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@
6565
import java.util.logging.Level;
6666
import java.util.logging.Logger;
6767
import javax.annotation.concurrent.Immutable;
68+
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.logs.CompactConsoleLogRecordExporter;
6869
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.AwsCloudWatchEmfExporter;
69-
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogsExporterBuilder;
70+
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogRecordExporterBuilder;
7071
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.traces.OtlpAwsSpanExporterBuilder;
7172

7273
/**
@@ -514,7 +515,7 @@ LogRecordExporter customizeLogsExporter(
514515
configProps.getString(OTEL_EXPORTER_OTLP_COMPRESSION_CONFIG, "none"));
515516

516517
try {
517-
return OtlpAwsLogsExporterBuilder.create(
518+
return OtlpAwsLogRecordExporterBuilder.create(
518519
(OtlpHttpLogRecordExporter) logsExporter,
519520
configProps.getString(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT))
520521
.setCompression(compression)
@@ -527,6 +528,13 @@ LogRecordExporter customizeLogsExporter(
527528
e);
528529
}
529530
}
531+
String logsExporterConfig = configProps.getString(OTEL_LOGS_EXPORTER);
532+
533+
if (isLambdaEnvironment()
534+
&& logsExporterConfig != null
535+
&& logsExporterConfig.equals("console")) {
536+
return new CompactConsoleLogRecordExporter();
537+
}
530538

531539
return logsExporter;
532540
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.opentelemetry.javaagent.providers.exporter.aws.logs;
17+
18+
/*
19+
* Copyright The OpenTelemetry Authors
20+
* SPDX-License-Identifier: Apache-2.0
21+
*
22+
* Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
23+
*/
24+
25+
import io.opentelemetry.api.common.Value;
26+
import io.opentelemetry.exporter.logging.SystemOutLogRecordExporter;
27+
import io.opentelemetry.sdk.common.CompletableResultCode;
28+
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
29+
import io.opentelemetry.sdk.logs.data.LogRecordData;
30+
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
31+
import java.time.Instant;
32+
import java.time.ZoneOffset;
33+
import java.time.format.DateTimeFormatter;
34+
import java.util.Collection;
35+
import java.util.concurrent.TimeUnit;
36+
import java.util.concurrent.atomic.AtomicBoolean;
37+
38+
/**
39+
* A compact console log exporter that changes the functionality of OpenTelemetry's {@link
40+
* SystemOutLogRecordExporter} by removing whitespace around JSON delimiters in the printed log
41+
* output.
42+
*
43+
* <p>This exporter uses the same formatting logic as {@code SystemOutLogRecordExporter} but applies
44+
* compact formatting by removing spaces around characters like {@code {}[]:,} to produce more
45+
* condensed log output.
46+
*/
47+
public class CompactConsoleLogRecordExporter implements LogRecordExporter {
48+
private static final DateTimeFormatter ISO_FORMAT = DateTimeFormatter.ISO_DATE_TIME;
49+
private final AtomicBoolean isShutdown = new AtomicBoolean();
50+
51+
private final LogRecordExporter parentExporter;
52+
53+
public CompactConsoleLogRecordExporter() {
54+
this.parentExporter = SystemOutLogRecordExporter.create();
55+
}
56+
57+
@Override
58+
public CompletableResultCode export(Collection<LogRecordData> logs) {
59+
if (this.isShutdown.get()) {
60+
return CompletableResultCode.ofFailure();
61+
} else {
62+
StringBuilder stringBuilder = new StringBuilder(60);
63+
64+
for (LogRecordData logRecord : logs) {
65+
stringBuilder.setLength(0);
66+
formatLog(stringBuilder, logRecord);
67+
System.out.println(stringBuilder);
68+
}
69+
70+
return CompletableResultCode.ofSuccess();
71+
}
72+
}
73+
74+
@Override
75+
public CompletableResultCode flush() {
76+
return this.parentExporter.flush();
77+
}
78+
79+
/**
80+
* Shuts down the exporter. This method is copied and modified from
81+
* SystemOutLogRecordExporter.shutdown().
82+
*
83+
* <p>See: <a
84+
* href="https://github.com/open-telemetry/opentelemetry-java/blob/5ab0a65675e5a06d13b293a758ef495d797e6d04/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/SystemOutLogRecordExporter.java#L93">...</a>
85+
*/
86+
@Override
87+
public CompletableResultCode shutdown() {
88+
if (!this.isShutdown.compareAndSet(false, true)) {
89+
System.out.println("Calling shutdown() multiple times.");
90+
}
91+
return CompletableResultCode.ofSuccess();
92+
}
93+
94+
/**
95+
* Formats log record data into a compact string representation. This method is copied from
96+
* SystemOutLogRecordExporter.formatLog() and modified to apply compact formatting by removing
97+
* whitespace around JSON delimiters.
98+
*
99+
* <p>See: <a
100+
* href="https://github.com/open-telemetry/opentelemetry-java/blob/5ab0a65675e5a06d13b293a758ef495d797e6d04/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/SystemOutLogRecordExporter.java#L66">...</a>
101+
*/
102+
static void formatLog(StringBuilder stringBuilder, LogRecordData log) {
103+
InstrumentationScopeInfo instrumentationScopeInfo = log.getInstrumentationScopeInfo();
104+
Value<?> body = log.getBodyValue();
105+
stringBuilder
106+
.append(
107+
ISO_FORMAT.format(
108+
Instant.ofEpochMilli(TimeUnit.NANOSECONDS.toMillis(log.getTimestampEpochNanos()))
109+
.atZone(ZoneOffset.UTC)))
110+
.append(" ")
111+
.append(log.getSeverity())
112+
.append(" '")
113+
.append(body == null ? "" : body.asString())
114+
.append("' : ")
115+
.append(log.getSpanContext().getTraceId())
116+
.append(" ")
117+
.append(log.getSpanContext().getSpanId())
118+
.append(" [scopeInfo: ")
119+
.append(instrumentationScopeInfo.getName())
120+
.append(":")
121+
.append(
122+
instrumentationScopeInfo.getVersion() == null
123+
? ""
124+
: instrumentationScopeInfo.getVersion())
125+
.append("] ")
126+
.append(log.getAttributes());
127+
128+
String compact = stringBuilder.toString().replaceAll("\\s*([{}\\[\\]:,])\\s*", "$1");
129+
stringBuilder.setLength(0);
130+
stringBuilder.append(compact);
131+
}
132+
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,21 @@
3838
* documentation: "<a
3939
* href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLPEndpoint.html">...</a>">
4040
*/
41-
public final class OtlpAwsLogsExporter extends BaseOtlpAwsExporter implements LogRecordExporter {
41+
public final class OtlpAwsLogRecordExporter extends BaseOtlpAwsExporter implements LogRecordExporter {
4242
private final OtlpHttpLogRecordExporterBuilder parentExporterBuilder;
4343
private final OtlpHttpLogRecordExporter parentExporter;
4444

45-
static OtlpAwsLogsExporter getDefault(String endpoint) {
46-
return new OtlpAwsLogsExporter(
45+
static OtlpAwsLogRecordExporter getDefault(String endpoint) {
46+
return new OtlpAwsLogRecordExporter(
4747
OtlpHttpLogRecordExporter.getDefault(), endpoint, CompressionMethod.NONE);
4848
}
4949

50-
static OtlpAwsLogsExporter create(
50+
static OtlpAwsLogRecordExporter create(
5151
OtlpHttpLogRecordExporter parent, String endpoint, CompressionMethod compression) {
52-
return new OtlpAwsLogsExporter(parent, endpoint, compression);
52+
return new OtlpAwsLogRecordExporter(parent, endpoint, compression);
5353
}
5454

55-
private OtlpAwsLogsExporter(
55+
private OtlpAwsLogRecordExporter(
5656
OtlpHttpLogRecordExporter parentExporter, String endpoint, CompressionMethod compression) {
5757
super(endpoint, compression);
5858

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,35 @@
2020
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
2121
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.CompressionMethod;
2222

23-
public class OtlpAwsLogsExporterBuilder {
23+
public class OtlpAwsLogRecordExporterBuilder {
2424
private final OtlpHttpLogRecordExporter parentExporter;
2525
private final String endpoint;
2626
private String compression;
2727

28-
public static OtlpAwsLogsExporterBuilder create(
28+
public static OtlpAwsLogRecordExporterBuilder create(
2929
OtlpHttpLogRecordExporter parentExporter, String endpoint) {
30-
return new OtlpAwsLogsExporterBuilder(parentExporter, endpoint);
30+
return new OtlpAwsLogRecordExporterBuilder(parentExporter, endpoint);
3131
}
3232

33-
public static OtlpAwsLogsExporter getDefault(String endpoint) {
34-
return OtlpAwsLogsExporter.getDefault(endpoint);
33+
public static OtlpAwsLogRecordExporter getDefault(String endpoint) {
34+
return OtlpAwsLogRecordExporter.getDefault(endpoint);
3535
}
3636

37-
public OtlpAwsLogsExporterBuilder setCompression(String compression) {
37+
public OtlpAwsLogRecordExporterBuilder setCompression(String compression) {
3838
this.compression = compression;
3939
return this;
4040
}
4141

42-
public OtlpAwsLogsExporter build() {
42+
public OtlpAwsLogRecordExporter build() {
4343
CompressionMethod compression = CompressionMethod.NONE;
4444
if (this.compression != null && "gzip".equalsIgnoreCase(this.compression)) {
4545
compression = CompressionMethod.GZIP;
4646
}
4747

48-
return OtlpAwsLogsExporter.create(this.parentExporter, this.endpoint, compression);
48+
return OtlpAwsLogRecordExporter.create(this.parentExporter, this.endpoint, compression);
4949
}
5050

51-
private OtlpAwsLogsExporterBuilder(OtlpHttpLogRecordExporter parentExporter, String endpoint) {
51+
private OtlpAwsLogRecordExporterBuilder(OtlpHttpLogRecordExporter parentExporter, String endpoint) {
5252
this.parentExporter = requireNonNull(parentExporter, "Must set a parentExporter");
5353
this.endpoint = requireNonNull(endpoint, "Must set an endpoint");
5454
}

awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProviderTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
import org.junit.jupiter.params.provider.MethodSource;
5454
import org.mockito.MockedStatic;
5555
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.AwsCloudWatchEmfExporter;
56-
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogsExporter;
56+
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogRecordExporter;
5757
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.traces.OtlpAwsSpanExporter;
5858

5959
class AwsApplicationSignalsCustomizerProviderTest {
@@ -74,7 +74,7 @@ void testShouldEnableSigV4LogsExporterIfConfigIsCorrect(Map<String, String> vali
7474
validSigv4Config,
7575
defaultHttpLogsExporter,
7676
this.provider::customizeLogsExporter,
77-
OtlpAwsLogsExporter.class);
77+
OtlpAwsLogRecordExporter.class);
7878
}
7979

8080
@ParameterizedTest

awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/OtlpAwsExporterTest.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@
5151
import software.amazon.awssdk.http.auth.spi.signer.SignedRequest;
5252
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
5353
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.CompressionMethod;
54-
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogsExporter;
55-
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogsExporterBuilder;
54+
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogRecordExporter;
55+
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogRecordExporterBuilder;
5656
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.traces.OtlpAwsSpanExporter;
5757
import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.traces.OtlpAwsSpanExporterBuilder;
5858

@@ -282,7 +282,7 @@ public void export() {
282282
}
283283
}
284284

285-
static class OtlpAwsLogsExporterTest extends AbstractOtlpAwsExporterTest {
285+
static class OtlpAwsLogRecordExporterTest extends AbstractOtlpAwsExporterTest {
286286
private static final String LOGS_OTLP_ENDPOINT = "https://logs.us-east-1.amazonaws.com/v1/logs";
287287

288288
@Mock private OtlpHttpLogRecordExporterBuilder mockBuilder;
@@ -306,33 +306,33 @@ void setup() {
306306

307307
@Test
308308
void testLogsExporterCompressionDefaultsToNone() {
309-
OtlpAwsLogsExporter exporter =
310-
OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT).build();
309+
OtlpAwsLogRecordExporter exporter =
310+
OtlpAwsLogRecordExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT).build();
311311
assertEquals(CompressionMethod.NONE, exporter.getCompression());
312312
}
313313

314314
@Test
315315
void testLogsExporterCompressionCanBeSetToGzip() {
316-
OtlpAwsLogsExporter exporter =
317-
OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT)
316+
OtlpAwsLogRecordExporter exporter =
317+
OtlpAwsLogRecordExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT)
318318
.setCompression("gzip")
319319
.build();
320320
assertEquals(CompressionMethod.GZIP, exporter.getCompression());
321321
}
322322

323323
@Test
324324
void testLogsExporterCompressionIgnoresCaseForGzip() {
325-
OtlpAwsLogsExporter exporter =
326-
OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT)
325+
OtlpAwsLogRecordExporter exporter =
326+
OtlpAwsLogRecordExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT)
327327
.setCompression("GZIP")
328328
.build();
329329
assertEquals(CompressionMethod.GZIP, exporter.getCompression());
330330
}
331331

332332
@Test
333333
void testLogsExporterCompressionDefaultsToNoneForUnknownValue() {
334-
OtlpAwsLogsExporter exporter =
335-
OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT)
334+
OtlpAwsLogRecordExporter exporter =
335+
OtlpAwsLogRecordExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT)
336336
.setCompression("unknown")
337337
.build();
338338
assertEquals(CompressionMethod.NONE, exporter.getCompression());
@@ -343,8 +343,8 @@ private static final class MockOtlpAwsLogsExporterWrapper implements OtlpAwsExpo
343343

344344
private MockOtlpAwsLogsExporterWrapper(OtlpHttpLogRecordExporter mockExporter) {
345345
this.exporter =
346-
OtlpAwsLogsExporterBuilder.create(
347-
mockExporter, OtlpAwsLogsExporterTest.LOGS_OTLP_ENDPOINT)
346+
OtlpAwsLogRecordExporterBuilder.create(
347+
mockExporter, OtlpAwsLogRecordExporterTest.LOGS_OTLP_ENDPOINT)
348348
.build();
349349
}
350350

0 commit comments

Comments
 (0)