diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java index c0fcbdf981..301552e956 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java @@ -65,9 +65,10 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.concurrent.Immutable; +import software.amazon.opentelemetry.javaagent.providers.exporter.aws.logs.CompactConsoleLogRecordExporter; import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.AwsCloudWatchEmfExporter; import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.ConsoleEmfExporter; -import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogsExporterBuilder; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogRecordExporterBuilder; import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.traces.OtlpAwsSpanExporterBuilder; /** @@ -91,7 +92,10 @@ public final class AwsApplicationSignalsCustomizerProvider // https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-envvars.html static final String AWS_REGION = "aws.region"; static final String AWS_DEFAULT_REGION = "aws.default.region"; + // TODO: We should clean up and get rid of using AWS_LAMBDA_FUNCTION_NAME and default to + // upstream config property implementation. static final String AWS_LAMBDA_FUNCTION_NAME_CONFIG = "AWS_LAMBDA_FUNCTION_NAME"; + static final String AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG = "aws.lambda.function.name"; static final String LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT = "LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT"; @@ -189,6 +193,10 @@ private static Optional getAwsRegionFromConfig(ConfigProperties configPr return Optional.ofNullable(configProps.getString(AWS_DEFAULT_REGION)); } + static boolean isLambdaEnvironment(ConfigProperties props) { + return props.getString(AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG) != null; + } + static boolean isLambdaEnvironment() { return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG) != null; } @@ -515,7 +523,7 @@ LogRecordExporter customizeLogsExporter( configProps.getString(OTEL_EXPORTER_OTLP_COMPRESSION_CONFIG, "none")); try { - return OtlpAwsLogsExporterBuilder.create( + return OtlpAwsLogRecordExporterBuilder.create( (OtlpHttpLogRecordExporter) logsExporter, configProps.getString(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT)) .setCompression(compression) @@ -528,34 +536,41 @@ LogRecordExporter customizeLogsExporter( e); } } + String logsExporterConfig = configProps.getString(OTEL_LOGS_EXPORTER); + + if (isLambdaEnvironment(configProps) + && logsExporterConfig != null + && logsExporterConfig.equals("console")) { + return new CompactConsoleLogRecordExporter(); + } return logsExporter; } MetricExporter customizeMetricExporter( MetricExporter metricExporter, ConfigProperties configProps) { + if (isEmfExporterEnabled) { Map headers = AwsApplicationSignalsConfigUtils.parseOtlpHeaders( configProps.getString(OTEL_EXPORTER_OTLP_LOGS_HEADERS)); Optional awsRegion = getAwsRegionFromConfig(configProps); + String namespace = headers.get(AWS_EMF_METRICS_NAMESPACE); if (awsRegion.isPresent()) { - String namespace = headers.get(AWS_EMF_METRICS_NAMESPACE); - if (headers.containsKey(AWS_OTLP_LOGS_GROUP_HEADER) && headers.containsKey(AWS_OTLP_LOGS_STREAM_HEADER)) { String logGroup = headers.get(AWS_OTLP_LOGS_GROUP_HEADER); String logStream = headers.get(AWS_OTLP_LOGS_STREAM_HEADER); return new AwsCloudWatchEmfExporter(namespace, logGroup, logStream, awsRegion.get()); } - if (isLambdaEnvironment()) { + + if (isLambdaEnvironment(configProps)) { return new ConsoleEmfExporter(namespace); } logger.warning( String.format( - "Improper EMF Exporter configuration: Please configure the environment variable %s to have values for %s, %s, and %s", - OTEL_EXPORTER_OTLP_LOGS_HEADERS, + "Improper EMF Exporter configuration: Please configure the environment variable OTEL_EXPORTER_OTLP_LOGS_HEADERS to have values for %s, %s, and %s", AWS_OTLP_LOGS_GROUP_HEADER, AWS_OTLP_LOGS_STREAM_HEADER, AWS_EMF_METRICS_NAMESPACE)); diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/aws/logs/CompactConsoleLogRecordExporter.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/aws/logs/CompactConsoleLogRecordExporter.java new file mode 100644 index 0000000000..8a0ddf42fa --- /dev/null +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/aws/logs/CompactConsoleLogRecordExporter.java @@ -0,0 +1,267 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.opentelemetry.javaagent.providers.exporter.aws.logs; + +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + * + * Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import io.opentelemetry.exporter.internal.otlp.IncubatingUtil; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import io.opentelemetry.sdk.resources.Resource; +import java.io.PrintStream; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A {@link LogRecordExporter} that prints {@link LogRecordData} to standard out based on upstream's + * implementation of SystemOutLogRecordExporter, see: ... + */ +@SuppressWarnings("SystemOut") +public class CompactConsoleLogRecordExporter implements LogRecordExporter { + private static final DateTimeFormatter ISO_FORMAT = DateTimeFormatter.ISO_INSTANT; + private static final ObjectMapper MAPPER = + new ObjectMapper().disable(SerializationFeature.INDENT_OUTPUT); + private final AtomicBoolean isShutdown = new AtomicBoolean(); + private final PrintStream printStream; + + public CompactConsoleLogRecordExporter() { + this(System.out); + } + + public CompactConsoleLogRecordExporter(PrintStream printStream) { + this.printStream = printStream; + } + + @Override + public CompletableResultCode export(Collection logs) { + if (isShutdown.get()) { + return CompletableResultCode.ofFailure(); + } + + for (LogRecordData log : logs) { + this.printStream.println(this.toCompactJson(log)); + this.printStream.flush(); + } + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + this.printStream.flush(); + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + if (!this.isShutdown.compareAndSet(false, true)) { + this.printStream.println("Calling shutdown() multiple times."); + } + return CompletableResultCode.ofSuccess(); + } + + @Override + public String toString() { + return "CompactConsoleLogRecordExporter{}"; + } + + /** + * Converts OpenTelemetry log data to compact JSON format. OTel Java's SystemOutLogRecordExporter + * uses a concise text format, this implementation outputs a compact JSON representation based on + * OTel JavaScript's _exportInfo: ... + * + *

Example output: + * + *

+   *     {"body":"This is a test log","severityNumber":9,"severityText":"INFO","attributes":{},"droppedAttributes":0,"timestamp":"2025-09-30T22:37:56.724Z","observedTimestamp":"2025-09-30T22:37:56.724Z","traceId":"","spanId":"","traceFlags":0,"resource":{}}
+   * 
+ * + * @param log the log record data to convert + * @return compact JSON string representation of the log record + */ + private String toCompactJson(LogRecordData log) { + LogRecordDataTemplate template = LogRecordDataTemplate.parse(log); + + try { + return MAPPER.writeValueAsString(template); + } catch (Exception e) { + this.printStream.println("Error serializing log record: " + e.getMessage()); + return "{}"; + } + } + + /** Data object that structures OTel log record data for JSON serialization. */ + @SuppressWarnings("unused") + private static final class LogRecordDataTemplate { + @JsonProperty("resource") + private final ResourceTemplate resourceTemplate; + + @JsonProperty("body") + private final String body; + + @JsonProperty("severityNumber") + private final int severityNumber; + + @JsonProperty("severityText") + private final String severityText; + + @JsonProperty("attributes") + private final Map attributes; + + @JsonProperty("droppedAttributes") + private final int droppedAttributes; + + @JsonProperty("timestamp") + private final String timestamp; + + @JsonProperty("observedTimestamp") + private final String observedTimestamp; + + @JsonProperty("traceId") + private final String traceId; + + @JsonProperty("spanId") + private final String spanId; + + @JsonProperty("traceFlags") + private final int traceFlags; + + @JsonProperty("instrumentationScope") + private final InstrumentationScopeTemplate instrumentationScope; + + private LogRecordDataTemplate( + String body, + int severityNumber, + String severityText, + Map attributes, + int droppedAttributes, + String timestamp, + String observedTimestamp, + String traceId, + String spanId, + int traceFlags, + ResourceTemplate resourceTemplate, + InstrumentationScopeTemplate instrumentationScope) { + this.resourceTemplate = resourceTemplate; + this.body = body; + this.severityNumber = severityNumber; + this.severityText = severityText; + this.attributes = attributes; + this.droppedAttributes = droppedAttributes; + this.timestamp = timestamp; + this.observedTimestamp = observedTimestamp; + this.traceId = traceId; + this.spanId = spanId; + this.traceFlags = traceFlags; + this.instrumentationScope = instrumentationScope; + } + + private static LogRecordDataTemplate parse(LogRecordData log) { + // https://github.com/open-telemetry/opentelemetry-java/blob/48684d6d33048030b133b4f6479d45afddcdc313/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java#L59 + Map attributes = new HashMap<>(); + log.getAttributes() + .forEach((key, value) -> attributes.put(key.getKey(), String.valueOf(value))); + + int attributeSize = + IncubatingUtil.isExtendedLogRecordData(log) + ? IncubatingUtil.extendedAttributesSize(log) + : log.getAttributes().size(); + + return new LogRecordDataTemplate( + log.getBodyValue() != null ? log.getBodyValue().asString() : null, + log.getSeverity().getSeverityNumber(), + log.getSeverity().name(), + attributes, + log.getTotalAttributeCount() - attributeSize, + formatTimestamp(log.getTimestampEpochNanos()), + formatTimestamp(log.getObservedTimestampEpochNanos()), + log.getSpanContext().isValid() ? log.getSpanContext().getTraceId() : "", + log.getSpanContext().isValid() ? log.getSpanContext().getSpanId() : "", + log.getSpanContext().getTraceFlags().asByte(), + ResourceTemplate.parse(log.getResource()), + InstrumentationScopeTemplate.parse(log.getInstrumentationScopeInfo())); + } + + private static String formatTimestamp(long nanos) { + return nanos != 0 + ? ISO_FORMAT.format( + Instant.ofEpochMilli(TimeUnit.NANOSECONDS.toMillis(nanos)).atZone(ZoneOffset.UTC)) + : null; + } + } + + @SuppressWarnings("unused") + private static final class ResourceTemplate { + @JsonProperty("attributes") + private final Map attributes; + + @JsonProperty("schemaUrl") + private final String schemaUrl; + + private ResourceTemplate(Map attributes, String schemaUrl) { + this.attributes = attributes; + this.schemaUrl = schemaUrl != null ? schemaUrl : ""; + } + + private static ResourceTemplate parse(Resource resource) { + Map attributes = new HashMap<>(); + resource + .getAttributes() + .forEach((key, value) -> attributes.put(key.getKey(), String.valueOf(value))); + return new ResourceTemplate(attributes, resource.getSchemaUrl()); + } + } + + @SuppressWarnings("unused") + private static final class InstrumentationScopeTemplate { + @JsonProperty("name") + private final String name; + + @JsonProperty("version") + private final String version; + + @JsonProperty("schemaUrl") + private final String schemaUrl; + + private InstrumentationScopeTemplate(String name, String version, String schemaUrl) { + this.name = name != null ? name : ""; + this.version = version != null ? version : ""; + this.schemaUrl = schemaUrl != null ? schemaUrl : ""; + } + + private static InstrumentationScopeTemplate parse(InstrumentationScopeInfo scope) { + return new InstrumentationScopeTemplate( + scope.getName(), scope.getVersion(), scope.getSchemaUrl()); + } + } +} diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporter.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogRecordExporter.java similarity index 91% rename from awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporter.java rename to awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogRecordExporter.java index f93b1f1c9a..0a74968813 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporter.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogRecordExporter.java @@ -38,21 +38,22 @@ * documentation: "..."> */ -public final class OtlpAwsLogsExporter extends BaseOtlpAwsExporter implements LogRecordExporter { +public final class OtlpAwsLogRecordExporter extends BaseOtlpAwsExporter + implements LogRecordExporter { private final OtlpHttpLogRecordExporterBuilder parentExporterBuilder; private final OtlpHttpLogRecordExporter parentExporter; - static OtlpAwsLogsExporter getDefault(String endpoint) { - return new OtlpAwsLogsExporter( + static OtlpAwsLogRecordExporter getDefault(String endpoint) { + return new OtlpAwsLogRecordExporter( OtlpHttpLogRecordExporter.getDefault(), endpoint, CompressionMethod.NONE); } - static OtlpAwsLogsExporter create( + static OtlpAwsLogRecordExporter create( OtlpHttpLogRecordExporter parent, String endpoint, CompressionMethod compression) { - return new OtlpAwsLogsExporter(parent, endpoint, compression); + return new OtlpAwsLogRecordExporter(parent, endpoint, compression); } - private OtlpAwsLogsExporter( + private OtlpAwsLogRecordExporter( OtlpHttpLogRecordExporter parentExporter, String endpoint, CompressionMethod compression) { super(endpoint, compression); diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporterBuilder.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogRecordExporterBuilder.java similarity index 70% rename from awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporterBuilder.java rename to awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogRecordExporterBuilder.java index bf91bd6d4e..53be35931a 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporterBuilder.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogRecordExporterBuilder.java @@ -20,35 +20,36 @@ import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.CompressionMethod; -public class OtlpAwsLogsExporterBuilder { +public class OtlpAwsLogRecordExporterBuilder { private final OtlpHttpLogRecordExporter parentExporter; private final String endpoint; private String compression; - public static OtlpAwsLogsExporterBuilder create( + public static OtlpAwsLogRecordExporterBuilder create( OtlpHttpLogRecordExporter parentExporter, String endpoint) { - return new OtlpAwsLogsExporterBuilder(parentExporter, endpoint); + return new OtlpAwsLogRecordExporterBuilder(parentExporter, endpoint); } - public static OtlpAwsLogsExporter getDefault(String endpoint) { - return OtlpAwsLogsExporter.getDefault(endpoint); + public static OtlpAwsLogRecordExporter getDefault(String endpoint) { + return OtlpAwsLogRecordExporter.getDefault(endpoint); } - public OtlpAwsLogsExporterBuilder setCompression(String compression) { + public OtlpAwsLogRecordExporterBuilder setCompression(String compression) { this.compression = compression; return this; } - public OtlpAwsLogsExporter build() { + public OtlpAwsLogRecordExporter build() { CompressionMethod compression = CompressionMethod.NONE; if (this.compression != null && "gzip".equalsIgnoreCase(this.compression)) { compression = CompressionMethod.GZIP; } - return OtlpAwsLogsExporter.create(this.parentExporter, this.endpoint, compression); + return OtlpAwsLogRecordExporter.create(this.parentExporter, this.endpoint, compression); } - private OtlpAwsLogsExporterBuilder(OtlpHttpLogRecordExporter parentExporter, String endpoint) { + private OtlpAwsLogRecordExporterBuilder( + OtlpHttpLogRecordExporter parentExporter, String endpoint) { this.parentExporter = requireNonNull(parentExporter, "Must set a parentExporter"); this.endpoint = requireNonNull(endpoint, "Must set an endpoint"); } diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProviderTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProviderTest.java index 624c4a521c..bcc3fed6fc 100644 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProviderTest.java +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProviderTest.java @@ -40,6 +40,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.BiFunction; @@ -52,8 +53,10 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.MockedStatic; +import software.amazon.opentelemetry.javaagent.providers.exporter.aws.logs.CompactConsoleLogRecordExporter; import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.AwsCloudWatchEmfExporter; -import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogsExporter; +import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.ConsoleEmfExporter; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogRecordExporter; import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.traces.OtlpAwsSpanExporter; class AwsApplicationSignalsCustomizerProviderTest { @@ -74,7 +77,7 @@ void testShouldEnableSigV4LogsExporterIfConfigIsCorrect(Map vali validSigv4Config, defaultHttpLogsExporter, this.provider::customizeLogsExporter, - OtlpAwsLogsExporter.class); + OtlpAwsLogRecordExporter.class); } @ParameterizedTest @@ -108,6 +111,31 @@ void testShouldNotUseSigv4LogsExporterIfValidatorThrows() { } } + @Test + void testLambdaShouldEnableCompactLogsExporterIfConfigIsCorrect() { + Map lambdaConfig = + Map.of( + OTEL_LOGS_EXPORTER, "console", AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG, "test-function"); + DefaultConfigProperties configProps = DefaultConfigProperties.createFromMap(lambdaConfig); + this.provider.customizeProperties(configProps); + + customizeExporterTest( + lambdaConfig, + defaultHttpLogsExporter, + this.provider::customizeLogsExporter, + CompactConsoleLogRecordExporter.class); + } + + @ParameterizedTest + @MethodSource("invalidCompactLogsConfigProvider") + void testShouldNotUseCompactLogsExporterIfConfigIsIncorrect(Map invalidConfig) { + customizeExporterTest( + invalidConfig, + defaultHttpLogsExporter, + this.provider::customizeLogsExporter, + OtlpHttpLogRecordExporter.class); + } + @ParameterizedTest @MethodSource("validSigv4TracesConfigProvider") void testShouldEnableSigV4SpanExporterIfConfigIsCorrect(Map validSigv4Config) { @@ -223,8 +251,8 @@ void testSigv4ShouldNotDisableApplicationSignalsSpanExporter() { } @ParameterizedTest - @MethodSource("validEmfConfigProvider") - void testShouldEnableEmfExporterIfConfigIsCorrect(Map validEmfConfig) { + @MethodSource("validCloudWatchEmfConfigProvider") + void testShouldEnableCloudWatchEmfExporterIfConfigIsCorrect(Map validEmfConfig) { DefaultConfigProperties configProps = DefaultConfigProperties.createFromMap(validEmfConfig); this.provider.customizeProperties(configProps); @@ -236,8 +264,41 @@ void testShouldEnableEmfExporterIfConfigIsCorrect(Map validEmfCo } @ParameterizedTest - @MethodSource("invalidEmfConfigProvider") - void testShouldNotUseEmfExporterIfConfigIsIncorrect(Map invalidEmfConfig) { + @MethodSource("validCloudWatchEmfConfigProvider") + void testLambdaShouldEnableCloudWatchEmfExporterIfConfigIsCorrect( + Map validEmfConfig) { + Map lambdaCloudWatchEmfConfig = new HashMap<>(validEmfConfig); + lambdaCloudWatchEmfConfig.put(AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG, "test-function"); + DefaultConfigProperties configProps = + DefaultConfigProperties.createFromMap(lambdaCloudWatchEmfConfig); + this.provider.customizeProperties(configProps); + + customizeExporterTest( + lambdaCloudWatchEmfConfig, + defaultHttpMetricsExporter, + this.provider::customizeMetricExporter, + AwsCloudWatchEmfExporter.class); + } + + @ParameterizedTest + @MethodSource("validConsoleEmfConfigProvider") + void testLambdaShouldEnableConsoleEmfExporterIfConfigIsCorrect( + Map lambdaConsoleEmfConfig) { + DefaultConfigProperties configProps = + DefaultConfigProperties.createFromMap(lambdaConsoleEmfConfig); + this.provider.customizeProperties(configProps); + + customizeExporterTest( + lambdaConsoleEmfConfig, + defaultHttpMetricsExporter, + this.provider::customizeMetricExporter, + ConsoleEmfExporter.class); + } + + @ParameterizedTest + @MethodSource("invalidCloudWatchEmfConfigProvider") + void testShouldNotUseCloudWatchEmfExporterIfConfigIsIncorrect( + Map invalidEmfConfig) { DefaultConfigProperties configProps = DefaultConfigProperties.createFromMap(invalidEmfConfig); this.provider.customizeProperties(configProps); @@ -248,6 +309,39 @@ void testShouldNotUseEmfExporterIfConfigIsIncorrect(Map invalidE OtlpHttpMetricExporter.class); } + @ParameterizedTest + @MethodSource("invalidConsoleEmfConfigProvider") + void testShouldNotUseConsoleEmfExporterIfConfigIsIncorrect( + Map invalidConsoleEmfConfig) { + DefaultConfigProperties configProps = + DefaultConfigProperties.createFromMap(invalidConsoleEmfConfig); + this.provider.customizeProperties(configProps); + + customizeExporterTest( + invalidConsoleEmfConfig, + defaultHttpMetricsExporter, + this.provider::customizeMetricExporter, + OtlpHttpMetricExporter.class); + } + + @ParameterizedTest + @MethodSource("invalidLambdaCloudWatchEmfConfigProvider") + void testLambdaShouldNotUseCloudWatchEmfExporterIfConfigIsIncorrect( + Map invalidEmfConfig) { + Map lambdaCloudWatchEmfConfig = new HashMap<>(invalidEmfConfig); + lambdaCloudWatchEmfConfig.put(AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG, "test-function"); + + DefaultConfigProperties configProps = + DefaultConfigProperties.createFromMap(lambdaCloudWatchEmfConfig); + this.provider.customizeProperties(configProps); + + customizeExporterTest( + lambdaCloudWatchEmfConfig, + defaultHttpMetricsExporter, + this.provider::customizeMetricExporter, + OtlpHttpMetricExporter.class); + } + @Test void setAdaptiveSamplingConfigFromString_validConfig() throws JsonProcessingException { assertThat(AwsApplicationSignalsCustomizerProvider.parseConfigString("version: 1").getVersion()) @@ -562,7 +656,7 @@ static Stream invalidSigv4LogsConfigProvider() { return args.stream().map(Arguments::of); } - static Stream invalidEmfConfigProvider() { + static Stream invalidCloudWatchEmfConfigProvider() { List> args = new ArrayList<>(); Map wrongExporter = @@ -610,7 +704,7 @@ static Stream invalidEmfConfigProvider() { return args.stream().map(Arguments::of); } - static Stream validEmfConfigProvider() { + static Stream validCloudWatchEmfConfigProvider() { List> args = new ArrayList<>(); Map awsRegionConfig = @@ -636,4 +730,64 @@ static Stream validEmfConfigProvider() { return args.stream().map(Arguments::of); } + + static Stream invalidCompactLogsConfigProvider() { + return Stream.of( + Arguments.of(Map.of(OTEL_LOGS_EXPORTER, "console")), + Arguments.of( + Map.of( + OTEL_LOGS_EXPORTER, "otlp", AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG, "test-function")), + Arguments.of( + Map.of( + OTEL_LOGS_EXPORTER, "none", AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG, "test-function")), + Arguments.of(Map.of(AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG, "test-function")), + Arguments.of(Map.of())); + } + + static Stream validConsoleEmfConfigProvider() { + return Stream.of( + Arguments.of( + Map.of( + OTEL_METRICS_EXPORTER, "awsemf", + OTEL_EXPORTER_OTLP_LOGS_HEADERS, "x-aws-metric-namespace=test-namespace", + AWS_REGION, "us-east-1", + AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG, "test-function")), + Arguments.of( + Map.of( + OTEL_METRICS_EXPORTER, "awsemf", + OTEL_EXPORTER_OTLP_LOGS_HEADERS, "x-aws-metric-namespace=another-namespace", + AWS_DEFAULT_REGION, "us-west-2", + AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG, "another-function"))); + } + + static Stream invalidConsoleEmfConfigProvider() { + return Stream.of( + Arguments.of( + Map.of( + OTEL_METRICS_EXPORTER, "otlp", + OTEL_EXPORTER_OTLP_LOGS_HEADERS, "x-aws-metric-namespace=test-namespace", + AWS_REGION, "us-east-1", + AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG, "test-function")), + Arguments.of( + Map.of( + OTEL_METRICS_EXPORTER, "awsemf", + AWS_REGION, "us-east-1")), + Arguments.of( + Map.of( + OTEL_METRICS_EXPORTER, "awsemf", + OTEL_EXPORTER_OTLP_LOGS_HEADERS, "x-aws-metric-namespace=test-namespace"))); + } + + static Stream invalidLambdaCloudWatchEmfConfigProvider() { + return Stream.of( + Arguments.of( + Map.of( + OTEL_METRICS_EXPORTER, "otlp", + OTEL_EXPORTER_OTLP_LOGS_HEADERS, "x-aws-metric-namespace=test-namespace", + AWS_REGION, "us-east-1")), + Arguments.of( + Map.of( + OTEL_METRICS_EXPORTER, "awsemf", + OTEL_EXPORTER_OTLP_LOGS_HEADERS, "x-aws-metric-namespace=test-namespace"))); + } } diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/exporter/aws/logs/CompactConsoleLogRecordExporterTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/exporter/aws/logs/CompactConsoleLogRecordExporterTest.java new file mode 100644 index 0000000000..f77967d4bf --- /dev/null +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/exporter/aws/logs/CompactConsoleLogRecordExporterTest.java @@ -0,0 +1,339 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.opentelemetry.javaagent.providers.exporter.aws.logs; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanId; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceId; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.testing.logs.TestLogRecordData; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.time.Instant; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class CompactConsoleLogRecordExporterTest { + + private static final String TRACE_ID_KEY = "traceId"; + private static final String SPAN_ID_KEY = "spanId"; + private static final String TRACE_FLAGS_KEY = "traceFlags"; + private static final String BODY_KEY = "body"; + private static final String SEVERITY_NUMBER_KEY = "severityNumber"; + private static final String SEVERITY_TEXT_KEY = "severityText"; + private static final String TIMESTAMP_KEY = "timestamp"; + private static final String OBSERVED_TIMESTAMP_KEY = "observedTimestamp"; + private static final String INSTRUMENTATION_SCOPE_KEY = "instrumentationScope"; + private static final String RESOURCE_KEY = "resource"; + private static final String ATTRIBUTES_KEY = "attributes"; + private static final String DROPPED_ATTRIBUTES_KEY = "droppedAttributes"; + private static final String SCOPE_NAME_KEY = "name"; + private static final String SCOPE_VERSION_KEY = "version"; + private static final String SCOPE_SCHEMA_URL_KEY = "schemaUrl"; + private static final String RESOURCE_SCHEMA_URL_KEY = "schemaUrl"; + + private OutputStream outputStream; + private PrintStream printStream; + private CompactConsoleLogRecordExporter exporter; + + @BeforeEach + void setUp() { + this.outputStream = new ByteArrayOutputStream(); + this.printStream = new PrintStream(this.outputStream); + this.exporter = new CompactConsoleLogRecordExporter(this.printStream); + } + + @Test + void testExportWithAllFieldsSet() { + SpanContext spanContext = + SpanContext.create( + "12345678901234567890123456789012", + "1234567890123456", + TraceFlags.getSampled(), + TraceState.getDefault()); + Resource resource = + Resource.empty().toBuilder() + .put("service.name", "test-service") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .build(); + InstrumentationScopeInfo scope = + InstrumentationScopeInfo.builder("test-scope") + .setVersion("1.0.0") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .build(); + + LogRecordData logRecord = + TestLogRecordData.builder() + .setResource(resource) + .setInstrumentationScopeInfo(scope) + .setBody("Test log message") + .setSeverity(Severity.INFO) + .setAttributes(Attributes.builder().put("key", "value").build()) + .setTotalAttributeCount(3) + .setTimestamp(Instant.ofEpochSecond(1000000000L)) + .setObservedTimestamp(Instant.ofEpochSecond(1000000000L)) + .setSpanContext(spanContext) + .build(); + + this.exporter.export(Collections.singletonList(logRecord)); + + validateJsonOutput(this.outputStream.toString().trim(), logRecord); + } + + @Test + void testSpanContextValidation() { + SpanContext spanContext = + SpanContext.create( + "12345678901234567890123456789012", + "1234567890123456", + TraceFlags.getSampled(), + TraceState.getDefault()); + + LogRecordData logRecord = + TestLogRecordData.builder() + .setResource(Resource.empty()) + .setInstrumentationScopeInfo(InstrumentationScopeInfo.builder("test-scope").build()) + .setBody("Test message") + .setSeverity(Severity.INFO) + .setTimestamp(Instant.ofEpochSecond(1000000000L)) + .setObservedTimestamp(Instant.ofEpochSecond(1000000000L)) + .setSpanContext(spanContext) + .build(); + + this.exporter.export(Collections.singletonList(logRecord)); + + validateJsonOutput(this.outputStream.toString().trim(), logRecord); + } + + @ParameterizedTest + @MethodSource("nullFieldsTestCases") + void testExportWithNullFields(LogRecordData logRecord) { + this.exporter.export(Collections.singletonList(logRecord)); + validateJsonOutput(this.outputStream.toString().trim(), logRecord); + } + + @Test + void testFlushBehavior() { + assertTrue(this.exporter.flush().isSuccess()); + } + + @Test + void testShutdownBehavior() { + assertTrue(this.exporter.shutdown().isSuccess()); + } + + @ParameterizedTest + @MethodSource("invalidSpanContextTestCases") + void testInvalidSpanContextExportsEmpty(String traceId, String spanId) { + SpanContext spanContext = + SpanContext.create(traceId, spanId, TraceFlags.getSampled(), TraceState.getDefault()); + + LogRecordData logRecord = + TestLogRecordData.builder() + .setResource(Resource.empty()) + .setInstrumentationScopeInfo(InstrumentationScopeInfo.builder("test-scope").build()) + .setBody("Test message") + .setSeverity(Severity.INFO) + .setTimestamp(Instant.ofEpochSecond(1000000000L)) + .setObservedTimestamp(Instant.ofEpochSecond(1000000000L)) + .setSpanContext(spanContext) + .build(); + + this.exporter.export(Collections.singletonList(logRecord)); + validateJsonOutput(this.outputStream.toString().trim(), logRecord); + } + + private static void validateJsonOutput( + String actualJsonString, LogRecordData expectedLogRecordData) { + ObjectMapper mapper = new ObjectMapper(); + Map actualParsedJson = + assertDoesNotThrow( + () -> mapper.readValue(actualJsonString, new TypeReference>() {})); + + // Validate nested objects exist + assertTrue(actualParsedJson.containsKey(INSTRUMENTATION_SCOPE_KEY)); + assertTrue(actualParsedJson.containsKey(RESOURCE_KEY)); + assertTrue(actualParsedJson.containsKey(ATTRIBUTES_KEY)); + + // Validate body field and value + assertTrue(actualParsedJson.containsKey(BODY_KEY)); + String expectedBody = + expectedLogRecordData.getBodyValue() != null + ? expectedLogRecordData.getBodyValue().asString() + : null; + assertEquals(expectedBody, actualParsedJson.get(BODY_KEY)); + + // Validate instrumentationScope structure and values + assertInstanceOf(Map.class, actualParsedJson.get(INSTRUMENTATION_SCOPE_KEY)); + Map instrumentationScope = + (Map) actualParsedJson.get(INSTRUMENTATION_SCOPE_KEY); + assertTrue(instrumentationScope.containsKey(SCOPE_NAME_KEY)); + assertTrue(instrumentationScope.containsKey(SCOPE_VERSION_KEY)); + assertTrue(instrumentationScope.containsKey(SCOPE_SCHEMA_URL_KEY)); + assertEquals( + expectedLogRecordData.getInstrumentationScopeInfo().getName(), + instrumentationScope.get(SCOPE_NAME_KEY)); + assertEquals( + expectedLogRecordData.getInstrumentationScopeInfo().getVersion() != null + ? expectedLogRecordData.getInstrumentationScopeInfo().getVersion() + : "", + instrumentationScope.get(SCOPE_VERSION_KEY)); + assertEquals( + expectedLogRecordData.getInstrumentationScopeInfo().getSchemaUrl() != null + ? expectedLogRecordData.getInstrumentationScopeInfo().getSchemaUrl() + : "", + instrumentationScope.get(SCOPE_SCHEMA_URL_KEY)); + + // Validate resource structure and values + assertInstanceOf(Map.class, actualParsedJson.get(RESOURCE_KEY)); + Map resource = (Map) actualParsedJson.get(RESOURCE_KEY); + assertTrue(resource.containsKey(ATTRIBUTES_KEY)); + assertTrue(resource.containsKey(RESOURCE_SCHEMA_URL_KEY)); + assertInstanceOf(Map.class, resource.get(ATTRIBUTES_KEY)); + assertEquals( + expectedLogRecordData.getResource().getSchemaUrl() != null + ? expectedLogRecordData.getResource().getSchemaUrl() + : "", + resource.get(RESOURCE_SCHEMA_URL_KEY)); + + // Validate attributes match expected + assertInstanceOf(Map.class, actualParsedJson.get(ATTRIBUTES_KEY)); + Map actualAttributes = + (Map) actualParsedJson.get(ATTRIBUTES_KEY); + expectedLogRecordData + .getAttributes() + .forEach( + (key, value) -> { + assertTrue(actualAttributes.containsKey(key.getKey())); + assertEquals( + String.valueOf(value), String.valueOf(actualAttributes.get(key.getKey()))); + }); + + // Validate timestamp fields and values + assertTrue(actualParsedJson.containsKey(TIMESTAMP_KEY)); + assertTrue(actualParsedJson.containsKey(OBSERVED_TIMESTAMP_KEY)); + assertEquals( + expectedLogRecordData.getTimestampEpochNanos(), + Instant.parse((String) actualParsedJson.get(TIMESTAMP_KEY)).toEpochMilli() * 1_000_000L); + assertEquals( + expectedLogRecordData.getObservedTimestampEpochNanos(), + Instant.parse((String) actualParsedJson.get(OBSERVED_TIMESTAMP_KEY)).toEpochMilli() + * 1_000_000L); + + // Validate droppedAttributes field and value + assertTrue(actualParsedJson.containsKey(DROPPED_ATTRIBUTES_KEY)); + int expectedDroppedAttributes = + expectedLogRecordData.getTotalAttributeCount() + - expectedLogRecordData.getAttributes().size(); + assertEquals(expectedDroppedAttributes, actualParsedJson.get(DROPPED_ATTRIBUTES_KEY)); + + // Validate traceId, spanId, and traceFlags fields + assertTrue(actualParsedJson.containsKey(TRACE_ID_KEY)); + assertTrue(actualParsedJson.containsKey(SPAN_ID_KEY)); + assertTrue(actualParsedJson.containsKey(TRACE_FLAGS_KEY)); + + SpanContext spanContext = expectedLogRecordData.getSpanContext(); + if (spanContext != null) { + if (TraceId.isValid(spanContext.getTraceId()) && SpanId.isValid(spanContext.getSpanId())) { + assertEquals(spanContext.getTraceId(), actualParsedJson.get(TRACE_ID_KEY)); + assertEquals(spanContext.getSpanId(), actualParsedJson.get(SPAN_ID_KEY)); + } else { + assertEquals("", actualParsedJson.get(TRACE_ID_KEY)); + assertEquals("", actualParsedJson.get(SPAN_ID_KEY)); + } + assertEquals( + (int) spanContext.getTraceFlags().asByte(), actualParsedJson.get(TRACE_FLAGS_KEY)); + } + + // Validate severity fields + assertTrue(actualParsedJson.containsKey(SEVERITY_NUMBER_KEY)); + assertTrue(actualParsedJson.containsKey(SEVERITY_TEXT_KEY)); + assertEquals( + expectedLogRecordData.getSeverity().getSeverityNumber(), + actualParsedJson.get(SEVERITY_NUMBER_KEY)); + assertEquals( + expectedLogRecordData.getSeverity().name(), actualParsedJson.get(SEVERITY_TEXT_KEY)); + } + + static Stream invalidSpanContextTestCases() { + return Stream.of( + Arguments.of( + "00000000000000000000000000000000", + "1234567890123456"), // invalid traceId, valid spanId + Arguments.of( + "12345678901234567890123456789012", + "0000000000000000"), // valid traceId, invalid spanId + Arguments.of("00000000000000000000000000000000", "0000000000000000"), // both invalid + Arguments.of( + "1234567890123456789012345678901g", "1234567890123456"), // invalid hex in traceId + Arguments.of( + "12345678901234567890123456789012", "123456789012345g") // invalid hex in spanId + ); + } + + static Stream nullFieldsTestCases() { + return Stream.of( + Arguments.of( + TestLogRecordData.builder() + .setResource(Resource.empty()) + .setInstrumentationScopeInfo(InstrumentationScopeInfo.builder("test-scope").build()) + .setBody("Test message") + .setSeverity(Severity.INFO) + .setTimestamp(Instant.ofEpochSecond(1000000000L)) + .setObservedTimestamp(Instant.ofEpochSecond(1000000000L)) + .build()), + Arguments.of( + TestLogRecordData.builder() + .setResource(Resource.empty()) + .setInstrumentationScopeInfo(InstrumentationScopeInfo.builder("test-scope").build()) + .setSeverity(Severity.INFO) + .setTimestamp(Instant.ofEpochSecond(1000000000L)) + .setObservedTimestamp(Instant.ofEpochSecond(1000000000L)) + .build()), + Arguments.of( + TestLogRecordData.builder() + .setResource(Resource.empty()) + .setInstrumentationScopeInfo(InstrumentationScopeInfo.builder("test-scope").build()) + .setBody("Test message") + .setSeverity(Severity.INFO) + .setTimestamp(Instant.ofEpochSecond(1000000000L)) + .setObservedTimestamp(Instant.ofEpochSecond(1000000000L)) + .setSpanContext( + SpanContext.create( + "abcdef1234567890abcdef1234567890", + "abcdef1234567890", + TraceFlags.getSampled(), + TraceState.getDefault())) + .build())); + } +} diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/OtlpAwsExporterTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/OtlpAwsExporterTest.java index 4c809e8fec..dd8696bc04 100644 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/OtlpAwsExporterTest.java +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/OtlpAwsExporterTest.java @@ -51,8 +51,8 @@ import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.CompressionMethod; -import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogsExporter; -import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogsExporterBuilder; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogRecordExporter; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogRecordExporterBuilder; import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.traces.OtlpAwsSpanExporter; import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.traces.OtlpAwsSpanExporterBuilder; @@ -282,7 +282,7 @@ public void export() { } } - static class OtlpAwsLogsExporterTest extends AbstractOtlpAwsExporterTest { + static class OtlpAwsLogRecordExporterTest extends AbstractOtlpAwsExporterTest { private static final String LOGS_OTLP_ENDPOINT = "https://logs.us-east-1.amazonaws.com/v1/logs"; @Mock private OtlpHttpLogRecordExporterBuilder mockBuilder; @@ -306,15 +306,15 @@ void setup() { @Test void testLogsExporterCompressionDefaultsToNone() { - OtlpAwsLogsExporter exporter = - OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT).build(); + OtlpAwsLogRecordExporter exporter = + OtlpAwsLogRecordExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT).build(); assertEquals(CompressionMethod.NONE, exporter.getCompression()); } @Test void testLogsExporterCompressionCanBeSetToGzip() { - OtlpAwsLogsExporter exporter = - OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT) + OtlpAwsLogRecordExporter exporter = + OtlpAwsLogRecordExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT) .setCompression("gzip") .build(); assertEquals(CompressionMethod.GZIP, exporter.getCompression()); @@ -322,8 +322,8 @@ void testLogsExporterCompressionCanBeSetToGzip() { @Test void testLogsExporterCompressionIgnoresCaseForGzip() { - OtlpAwsLogsExporter exporter = - OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT) + OtlpAwsLogRecordExporter exporter = + OtlpAwsLogRecordExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT) .setCompression("GZIP") .build(); assertEquals(CompressionMethod.GZIP, exporter.getCompression()); @@ -331,8 +331,8 @@ void testLogsExporterCompressionIgnoresCaseForGzip() { @Test void testLogsExporterCompressionDefaultsToNoneForUnknownValue() { - OtlpAwsLogsExporter exporter = - OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT) + OtlpAwsLogRecordExporter exporter = + OtlpAwsLogRecordExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT) .setCompression("unknown") .build(); assertEquals(CompressionMethod.NONE, exporter.getCompression()); @@ -343,8 +343,8 @@ private static final class MockOtlpAwsLogsExporterWrapper implements OtlpAwsExpo private MockOtlpAwsLogsExporterWrapper(OtlpHttpLogRecordExporter mockExporter) { this.exporter = - OtlpAwsLogsExporterBuilder.create( - mockExporter, OtlpAwsLogsExporterTest.LOGS_OTLP_ENDPOINT) + OtlpAwsLogRecordExporterBuilder.create( + mockExporter, OtlpAwsLogRecordExporterTest.LOGS_OTLP_ENDPOINT) .build(); } diff --git a/sample-apps/apigateway-lambda/src/main/java/com/amazon/sampleapp/LambdaHandler.java b/sample-apps/apigateway-lambda/src/main/java/com/amazon/sampleapp/LambdaHandler.java index bc8a7543ac..4a06081311 100644 --- a/sample-apps/apigateway-lambda/src/main/java/com/amazon/sampleapp/LambdaHandler.java +++ b/sample-apps/apigateway-lambda/src/main/java/com/amazon/sampleapp/LambdaHandler.java @@ -9,6 +9,7 @@ import java.net.http.HttpResponse; import java.util.HashMap; import java.util.Map; +import java.util.logging.Logger; import org.json.JSONObject; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.ListBucketsResponse; @@ -16,12 +17,13 @@ public class LambdaHandler implements RequestHandler> { + private static final Logger logger = Logger.getLogger(LambdaHandler.class.getName()); HttpClient client = HttpClient.newHttpClient(); private final S3Client s3Client = S3Client.create(); @Override public Map handleRequest(Object input, Context context) { - System.out.println("Executing LambdaHandler"); + logger.info("Executing LambdaHandler"); // https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime // try and get the trace id from environment variable _X_AMZN_TRACE_ID. If it's not present @@ -32,13 +34,13 @@ public Map handleRequest(Object input, Context context) { ? System.getenv("_X_AMZN_TRACE_ID") : System.getProperty("com.amazonaws.xray.traceHeader"); - System.out.println("Trace ID: " + traceId); + logger.info("Trace ID: " + traceId); JSONObject responseBody = new JSONObject(); responseBody.put("traceId", traceId); // Make a remote call using OkHttp - System.out.println("Making a remote call using Java HttpClient"); + logger.info("Making a remote call using Java HttpClient"); String url = "https://aws.amazon.com/"; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) @@ -46,24 +48,26 @@ public Map handleRequest(Object input, Context context) { .build(); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - System.out.println("Response status code: " + response.statusCode()); + logger.info("Response status code: " + response.statusCode()); responseBody.put("httpRequest", "Request successful"); } catch (Exception e) { - System.err.println("Error: " + e.getMessage()); + logger.severe("Error: " + e.getMessage()); responseBody.put("httpRequest", "Request failed"); } - System.out.println("Remote call done"); + logger.info("Remote call done"); // Make a S3 ListBuckets call to list the S3 buckets in the account - System.out.println("Making a S3 ListBuckets call"); + logger.info("Making a S3 ListBuckets call"); try { ListBucketsResponse listBucketsResponse = s3Client.listBuckets(); responseBody.put("s3Request", "ListBuckets successful"); } catch (S3Exception e) { - System.err.println("Error listing buckets: " + e.awsErrorDetails().errorMessage()); + logger.severe("Error listing buckets: " + e.awsErrorDetails().errorMessage()); responseBody.put("s3Request", "Error listing buckets: " + e.awsErrorDetails().errorMessage()); } - System.out.println("S3 HeadBucket call done"); + logger.info("S3 ListBuckets call done"); + + logger.info("Lambda function completed processing request successfully"); // return a response in the ApiGateway proxy format return Map.of(