Skip to content

Commit c577ac8

Browse files
authored
Merge branch 'main' into sigv4_logs
2 parents 511de17 + cf04d98 commit c577ac8

File tree

7 files changed

+332
-65
lines changed

7 files changed

+332
-65
lines changed

.github/workflows/release-lambda.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ permissions:
2222

2323
jobs:
2424
build-layer:
25+
environment: Release
2526
runs-on: ubuntu-latest
2627
outputs:
2728
aws_regions_json: ${{ steps.set-matrix.outputs.aws_regions_json }}

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ RUN npm install aws-aws-distro-opentelemetry-node-autoinstrumentation-$(node -p
1818
RUN npm install
1919

2020
# Stage 2: Build the cp-utility binary
21-
FROM public.ecr.aws/docker/library/rust:1.81 as builder
21+
FROM public.ecr.aws/docker/library/rust:1.87 as builder
2222

2323
WORKDIR /usr/src/cp-utility
2424
COPY ./tools/cp-utility .

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
SERVICE_METRIC,
3434
} from './metric-attribute-generator';
3535
import { SqsUrlParser } from './sqs-url-parser';
36+
import { LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT } from './aws-opentelemetry-configurator';
3637

3738
// Does not exist in @opentelemetry/semantic-conventions
3839
const _SERVER_SOCKET_ADDRESS: string = 'server.socket.address';
@@ -50,6 +51,9 @@ const _GRAPHQL_OPERATION_TYPE: string = 'graphql.operation.type';
5051
// Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
5152
const GRAPHQL: string = 'graphql';
5253

54+
// Constants for Lambda operations
55+
const LAMBDA_INVOKE_OPERATION: string = 'Invoke';
56+
5357
// Normalized remote service names for supported AWS services
5458
const NORMALIZED_DYNAMO_DB_SERVICE_NAME: string = 'AWS::DynamoDB';
5559
const NORMALIZED_KINESIS_SERVICE_NAME: string = 'AWS::Kinesis';
@@ -109,6 +113,7 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
109113
AwsMetricAttributeGenerator.setEgressOperation(span, attributes);
110114
AwsMetricAttributeGenerator.setRemoteServiceAndOperation(span, attributes);
111115
AwsMetricAttributeGenerator.setRemoteResourceTypeAndIdentifier(span, attributes);
116+
AwsMetricAttributeGenerator.setRemoteEnvironment(span, attributes);
112117
AwsMetricAttributeGenerator.setSpanKindForDependency(span, attributes);
113118
AwsMetricAttributeGenerator.setRemoteDbUser(span, attributes);
114119

@@ -336,7 +341,18 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
336341
BedrockRuntime: NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME,
337342
SecretsManager: NORMALIZED_SECRETSMANAGER_SERVICE_NAME,
338343
SFN: NORMALIZED_STEPFUNCTIONS_SERVICE_NAME,
344+
Lambda: NORMALIZED_LAMBDA_SERVICE_NAME,
339345
};
346+
347+
// Special handling for Lambda invoke operations
348+
if (AwsMetricAttributeGenerator.isLambdaInvokeOperation(span)) {
349+
const lambdaFunctionName = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_NAME];
350+
// If Lambda name is not present, use UnknownRemoteService
351+
// This is intentional - we want to clearly indicate when the Lambda function name
352+
// is missing rather than falling back to a generic service name
353+
return lambdaFunctionName ? String(lambdaFunctionName) : AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE;
354+
}
355+
340356
return awsSdkServiceMapping[serviceName] || 'AWS::' + serviceName;
341357
}
342358
return serviceName;
@@ -410,24 +426,9 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
410426
);
411427
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(activityArn);
412428
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_NAME)) {
413-
// Handling downstream Lambda as a service vs. an AWS resource:
414-
// - If the method call is "Invoke", we treat downstream Lambda as a service.
415-
// - Otherwise, we treat it as an AWS resource.
416-
//
417-
// This addresses a Lambda topology issue in Application Signals.
418-
// More context in PR: https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319
419-
//
420-
// NOTE: The env var LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT was introduced as part of this fix.
421-
// It is optional and allow users to override the default value if needed.
422-
if (AwsMetricAttributeGenerator.getRemoteOperation(span, SEMATTRS_RPC_METHOD) === 'Invoke') {
423-
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_SERVICE] = AwsMetricAttributeGenerator.escapeDelimiters(
424-
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_NAME]
425-
);
426-
427-
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_ENVIRONMENT] = `lambda:${
428-
process.env.LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT || 'default'
429-
}`;
430-
} else {
429+
// For non-Invoke Lambda operations, treat Lambda as a resource,
430+
// see normalizeRemoteServiceName for more information.
431+
if (!AwsMetricAttributeGenerator.isLambdaInvokeOperation(span)) {
431432
remoteResourceType = NORMALIZED_LAMBDA_SERVICE_NAME + '::Function';
432433
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
433434
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_NAME]
@@ -491,17 +492,33 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
491492
remoteResourceIdentifier = AwsMetricAttributeGenerator.getDbConnection(span);
492493
}
493494

495+
if (cloudFormationIdentifier === undefined) {
496+
cloudFormationIdentifier = remoteResourceIdentifier;
497+
}
498+
494499
if (remoteResourceType !== undefined && remoteResourceIdentifier !== undefined) {
495500
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_TYPE] = remoteResourceType;
496501
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_IDENTIFIER] = remoteResourceIdentifier;
502+
attributes[AWS_ATTRIBUTE_KEYS.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER] = cloudFormationIdentifier;
503+
}
504+
}
497505

498-
if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) {
499-
if (cloudFormationIdentifier === undefined) {
500-
cloudFormationIdentifier = remoteResourceIdentifier;
501-
}
502-
503-
attributes[AWS_ATTRIBUTE_KEYS.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER] = cloudFormationIdentifier;
506+
/**
507+
* Remote environment is used to identify the environment of downstream services. Currently only
508+
* set to "lambda:default" for Lambda Invoke operations when aws-api system is detected.
509+
*/
510+
private static setRemoteEnvironment(span: ReadableSpan, attributes: Attributes): void {
511+
// We want to treat downstream Lambdas as a service rather than a resource because
512+
// Application Signals topology map gets disconnected due to conflicting Lambda Entity
513+
// definitions
514+
// Additional context can be found in
515+
// https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319
516+
if (AwsMetricAttributeGenerator.isLambdaInvokeOperation(span)) {
517+
let remoteEnvironment = process.env[LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT]?.trim();
518+
if (!remoteEnvironment) {
519+
remoteEnvironment = 'default';
504520
}
521+
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_ENVIRONMENT] = `lambda:${remoteEnvironment}`;
505522
}
506523
}
507524

@@ -620,6 +637,18 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
620637
return input.split('^').join('^^').split('|').join('^|');
621638
}
622639

640+
/**
641+
* Check if the span represents a Lambda Invoke operation.
642+
*/
643+
private static isLambdaInvokeOperation(span: ReadableSpan): boolean {
644+
if (!AwsSpanProcessingUtil.isAwsSDKSpan(span)) {
645+
return false;
646+
}
647+
648+
const rpcService = AwsMetricAttributeGenerator.getRemoteService(span, SEMATTRS_RPC_SERVICE);
649+
return rpcService === 'Lambda' && span.attributes[SEMATTRS_RPC_METHOD] === LAMBDA_INVOKE_OPERATION;
650+
}
651+
623652
// Extracts the name of the resource from an arn
624653
private static extractResourceNameFromArn(attribute: AttributeValue | undefined): string | undefined {
625654
if (typeof attribute === 'string' && attribute.startsWith('arn:aws:')) {

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ const FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX = 'T1U';
9292
// Follow Python SDK Impl to set the max span batch size
9393
// which will reduce the chance of UDP package size is larger than 64KB
9494
const LAMBDA_SPAN_EXPORT_BATCH_SIZE = 10;
95+
export const LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT: string = 'LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT';
9596

9697
/**
9798
* Aws Application Signals Config Provider creates a configuration object that can be provided to

aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import { AwsLambdaInstrumentation } from '@opentelemetry/instrumentation-aws-lam
3838
import type { Command as AwsV3Command } from '@aws-sdk/types';
3939

4040
export const traceContextEnvironmentKey = '_X_AMZN_TRACE_ID';
41+
export const AWSXRAY_TRACE_ID_HEADER_CAPITALIZED = 'X-Amzn-Trace-Id';
42+
4143
const awsPropagator = new AWSXRayPropagator();
4244
export const headerGetter: TextMapGetter<APIGatewayProxyEventHeaders> = {
4345
keys(carrier: any): string[] {
@@ -294,7 +296,6 @@ function patchAwsLambdaInstrumentation(instrumentation: Instrumentation): void {
294296
// Override the upstream private _getV3SmithyClientSendPatch method to add middleware to inject X-Ray Trace Context into HTTP Headers
295297
// https://github.com/open-telemetry/opentelemetry-js-contrib/blob/instrumentation-aws-sdk-v0.48.0/plugins/node/opentelemetry-instrumentation-aws-sdk/src/aws-sdk.ts#L373-L384
296298
const awsXrayPropagator = new AWSXRayPropagator();
297-
const AWSXRAY_TRACE_ID_HEADER_CAPITALIZED = 'X-Amzn-Trace-Id';
298299
const V3_CLIENT_CONFIG_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.client.config');
299300
type V3PluginCommand = AwsV3Command<any, any, any, any, any> & {
300301
[V3_CLIENT_CONFIG_KEY]?: any;
@@ -311,9 +312,12 @@ function patchAwsSdkInstrumentation(instrumentation: Instrumentation): void {
311312
// Need to set capitalized version of the trace id to ensure that the Recursion Detection Middleware
312313
// of aws-sdk-js-v3 will detect the propagated X-Ray Context
313314
// See: https://github.com/aws/aws-sdk-js-v3/blob/v3.768.0/packages/middleware-recursion-detection/src/index.ts#L13
314-
middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER_CAPITALIZED] =
315-
middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
316-
delete middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
315+
const xrayTraceId = middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
316+
317+
if (xrayTraceId) {
318+
middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER_CAPITALIZED] = xrayTraceId;
319+
delete middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
320+
}
317321
const result = await next(middlewareArgs);
318322
return result;
319323
},

0 commit comments

Comments
 (0)