From 4a7f154c548ebb9fa1430aaeebddcb9d9e7cf2d7 Mon Sep 17 00:00:00 2001 From: Raphael Manke Date: Mon, 9 Feb 2026 20:48:37 +0100 Subject: [PATCH 1/3] feat(aws-resources): read cloud.account.id from symlink in Lambda detector Read /tmp/.otel-account-id via Files.readSymbolicLink in LambdaResource and set cloud.account.id resource attribute. Silently skips if the symlink does not exist or readlink is unsupported. Preserves leading zeros by treating the value as a string. --- .../contrib/aws/resource/LambdaResource.java | 22 +++++++- .../aws/resource/LambdaResourceTest.java | 51 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) 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..1f8d7085a 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,17 @@ 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.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 +40,16 @@ public static Resource get() { } private static Resource buildResource() { - return buildResource(System.getenv()); + return buildResource(System.getenv(), Path.of(ACCOUNT_ID_SYMLINK_PATH)); } // Visible for testing static Resource buildResource(Map environmentVariables) { + return buildResource(environmentVariables, Path.of(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 +71,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..141909857 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,13 @@ 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.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 +69,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, Path.of("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, Path.of("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 From 0163695e58fa6105d367ce8c6993fa5f1d5913c8 Mon Sep 17 00:00:00 2001 From: Raphael Manke Date: Mon, 9 Feb 2026 21:51:04 +0100 Subject: [PATCH 2/3] fix: replace Path.of() with Paths.get() for Java 8 compatibility --- .../opentelemetry/contrib/aws/resource/LambdaResource.java | 5 +++-- .../contrib/aws/resource/LambdaResourceTest.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) 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 1f8d7085a..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 @@ -21,6 +21,7 @@ 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; @@ -40,12 +41,12 @@ public static Resource get() { } private static Resource buildResource() { - return buildResource(System.getenv(), Path.of(ACCOUNT_ID_SYMLINK_PATH)); + return buildResource(System.getenv(), Paths.get(ACCOUNT_ID_SYMLINK_PATH)); } // Visible for testing static Resource buildResource(Map environmentVariables) { - return buildResource(environmentVariables, Path.of(ACCOUNT_ID_SYMLINK_PATH)); + return buildResource(environmentVariables, Paths.get(ACCOUNT_ID_SYMLINK_PATH)); } // Visible for testing 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 141909857..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 @@ -22,6 +22,7 @@ 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; @@ -72,7 +73,7 @@ void shouldAddAllAttributes() { @Test void shouldReadCloudAccountIdFromSymlink(@TempDir Path tempDir) throws Exception { Path symlink = tempDir.resolve(".otel-account-id"); - Files.createSymbolicLink(symlink, Path.of("123456789012")); + Files.createSymbolicLink(symlink, Paths.get("123456789012")); Resource resource = LambdaResource.buildResource( @@ -106,7 +107,7 @@ void shouldSkipCloudAccountIdWhenSymlinkMissing(@TempDir Path tempDir) { @Test void shouldPreserveLeadingZerosInAccountId(@TempDir Path tempDir) throws Exception { Path symlink = tempDir.resolve(".otel-account-id"); - Files.createSymbolicLink(symlink, Path.of("012345678901")); + Files.createSymbolicLink(symlink, Paths.get("012345678901")); Resource resource = LambdaResource.buildResource( From 429ed6ab74f37cfcd73e9911d46a78c4cdf71ca8 Mon Sep 17 00:00:00 2001 From: Raphael Manke Date: Wed, 11 Feb 2026 08:25:09 +0100 Subject: [PATCH 3/3] docs: add CHANGELOG entry for cloud.account.id symlink feature --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) 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