Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();

/**
Expand All @@ -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<String, String> environmentVariables) {
return buildResource(environmentVariables, Paths.get(ACCOUNT_ID_SYMLINK_PATH));
}

// Visible for testing
static Resource buildResource(Map<String, String> 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", "");
Expand All @@ -60,6 +72,15 @@ static Resource buildResource(Map<String, String> 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading