diff --git a/CHANGELOG.md b/CHANGELOG.md index 7efba704e..8fb36de6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### AWS Lambda resource detector + +- Read `cloud.account.id` from symlink created by the OTel Lambda Extension + ([#2619](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2619)) + ## Version 1.53.0 (2026-01-21) ### AWS X-Ray propagator diff --git a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/LambdaResource.java b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/LambdaResource.java index 4108ef786..59da45783 100644 --- a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/LambdaResource.java +++ b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/LambdaResource.java @@ -5,6 +5,7 @@ package io.opentelemetry.contrib.aws.resource; +import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.CLOUD_ACCOUNT_ID; import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.CLOUD_PLATFORM; import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.CLOUD_PROVIDER; import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.CLOUD_REGION; @@ -17,12 +18,18 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.semconv.SchemaUrls; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Map; import java.util.stream.Stream; /** A factory for a {@link Resource} which provides information about the AWS Lambda function. */ public final class LambdaResource { + private static final String ACCOUNT_ID_SYMLINK_PATH = "/tmp/.otel-account-id"; + private static final Resource INSTANCE = buildResource(); /** @@ -34,11 +41,16 @@ public static Resource get() { } private static Resource buildResource() { - return buildResource(System.getenv()); + return buildResource(System.getenv(), Paths.get(ACCOUNT_ID_SYMLINK_PATH)); } // Visible for testing static Resource buildResource(Map environmentVariables) { + return buildResource(environmentVariables, Paths.get(ACCOUNT_ID_SYMLINK_PATH)); + } + + // Visible for testing + static Resource buildResource(Map environmentVariables, Path accountIdSymlink) { String region = environmentVariables.getOrDefault("AWS_REGION", ""); String functionName = environmentVariables.getOrDefault("AWS_LAMBDA_FUNCTION_NAME", ""); String functionVersion = environmentVariables.getOrDefault("AWS_LAMBDA_FUNCTION_VERSION", ""); @@ -60,6 +72,15 @@ static Resource buildResource(Map environmentVariables) { builder.put(FAAS_VERSION, functionVersion); } + try { + String accountId = Files.readSymbolicLink(accountIdSymlink).toString(); + if (!accountId.isEmpty()) { + builder.put(CLOUD_ACCOUNT_ID, accountId); + } + } catch (IOException | UnsupportedOperationException e) { + // Symlink doesn't exist or readlink not supported — silently skip + } + return Resource.create(builder.build(), SchemaUrls.V1_25_0); } diff --git a/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/LambdaResourceTest.java b/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/LambdaResourceTest.java index efbac180f..dfc894165 100644 --- a/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/LambdaResourceTest.java +++ b/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/LambdaResourceTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.contrib.aws.resource; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_ACCOUNT_ID; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_PLATFORM; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_PROVIDER; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_REGION; @@ -19,10 +20,14 @@ import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.semconv.SchemaUrls; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.ServiceLoader; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; class LambdaResourceTest { @Test @@ -65,6 +70,53 @@ void shouldAddAllAttributes() { entry(FAAS_VERSION, "1.2.3")); } + @Test + void shouldReadCloudAccountIdFromSymlink(@TempDir Path tempDir) throws Exception { + Path symlink = tempDir.resolve(".otel-account-id"); + Files.createSymbolicLink(symlink, Paths.get("123456789012")); + + Resource resource = + LambdaResource.buildResource( + singletonMap("AWS_LAMBDA_FUNCTION_NAME", "my-function"), symlink); + Attributes attributes = resource.getAttributes(); + + assertThat(attributes) + .containsOnly( + entry(CLOUD_PROVIDER, "aws"), + entry(CLOUD_PLATFORM, "aws_lambda"), + entry(FAAS_NAME, "my-function"), + entry(CLOUD_ACCOUNT_ID, "123456789012")); + } + + @Test + void shouldSkipCloudAccountIdWhenSymlinkMissing(@TempDir Path tempDir) { + Path symlink = tempDir.resolve(".otel-account-id"); + + Resource resource = + LambdaResource.buildResource( + singletonMap("AWS_LAMBDA_FUNCTION_NAME", "my-function"), symlink); + Attributes attributes = resource.getAttributes(); + + assertThat(attributes) + .containsOnly( + entry(CLOUD_PROVIDER, "aws"), + entry(CLOUD_PLATFORM, "aws_lambda"), + entry(FAAS_NAME, "my-function")); + } + + @Test + void shouldPreserveLeadingZerosInAccountId(@TempDir Path tempDir) throws Exception { + Path symlink = tempDir.resolve(".otel-account-id"); + Files.createSymbolicLink(symlink, Paths.get("012345678901")); + + Resource resource = + LambdaResource.buildResource( + singletonMap("AWS_LAMBDA_FUNCTION_NAME", "my-function"), symlink); + Attributes attributes = resource.getAttributes(); + + assertThat(attributes).containsEntry(CLOUD_ACCOUNT_ID, "012345678901"); + } + @Test void inServiceLoader() { // No practical way to test the attributes themselves so at least check the service loader picks