Skip to content

Commit

Permalink
Propagate Lambda ResourceId or FaasId for the child spans (#104)
Browse files Browse the repository at this point in the history
*Description of changes:*
For Lambda case, the Lambda resource attribute only exists on Handler
span, it needs to be propagated to its child spans. This PR includes the
changes,
1. if parent span contains `cloud.resource_id` or `faas.i` but not in
child span, child span will be propagated with one of these attribute
from parent.
2. if both exist `cloud.resource_id` takes priority
3. if none of two Lambda resource attrs exists, do nothing.

*Test:*

**Hander Span**
```
2024-10-16T17:53:20.216Z | 'faas.id': 'arn:aws:lambda:us-west-1:889414516288:function:aws-opentelemetry-distro-nodejs',
-- | --
  | 2024-10-16T17:53:20.216Z | 'cloud.account.id': '889414516288',
  | 2024-10-16T17:53:20.216Z | 'aws.is.local.root': true,
  | 2024-10-16T17:53:20.216Z | 'aws.local.service': 'aws-opentelemetry-distro-nodejs',
  | 2024-10-16T17:53:20.216Z | 'aws.local.operation': 'aws-opentelemetry-distro-nodejs/Handler',
```
**Child Span**
```
  | 2024-10-16T17:53:20.194Z | attributes: {
-- | -- | --
  | 2024-10-16T17:53:20.194Z | 'rpc.system': 'aws-api',
  | 2024-10-16T17:53:20.194Z | 'rpc.method': 'ListBuckets',
  | 2024-10-16T17:53:20.194Z | 'rpc.service': 'S3',
  | 2024-10-16T17:53:20.194Z | 'aws.is.local.root': false,
  | 2024-10-16T17:53:20.194Z | 'faas.id': 'arn:aws:lambda:us-west-1:889414516288:function:aws-opentelemetry-distro-nodejs',
  | 2024-10-16T17:53:20.194Z | 'aws.local.operation': 'aws-opentelemetry-distro-nodejs/Handler',
```
By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice.
  • Loading branch information
mxiamxia authored Oct 19, 2024
1 parent 2907367 commit 7ee5904
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Span as APISpan, AttributeValue, Context, SpanKind, trace } from '@open
import { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';
import { AWS_ATTRIBUTE_KEYS } from './aws-attribute-keys';
import { AwsSpanProcessingUtil } from './aws-span-processing-util';
import { SEMRESATTRS_FAAS_ID } from '@opentelemetry/semantic-conventions';

/**
* AttributePropagatingSpanProcessor handles the propagation of attributes from parent spans to
Expand Down Expand Up @@ -85,6 +86,18 @@ export class AttributePropagatingSpanProcessor implements SpanProcessor {
if (this.isConsumerKind(span) && this.isConsumerKind(parentReadableSpan)) {
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_CONSUMER_PARENT_SPAN_KIND, SpanKind[parentReadableSpan.kind]);
}

// If parent span contains "cloud.resource_id" or "faas.id" but not in child span, child span will be
// propagated with one of these attribute from parent. "cloud.resource_id" takes priority if it exists
const parentResourceId = AwsSpanProcessingUtil.getResourceId(parentSpan);
const resourceId = AwsSpanProcessingUtil.getResourceId(span);
if (!resourceId && parentResourceId) {
if (AwsSpanProcessingUtil.isKeyPresent(parentSpan, AwsSpanProcessingUtil.CLOUD_RESOURCE_ID)) {
span.setAttribute(AwsSpanProcessingUtil.CLOUD_RESOURCE_ID, parentResourceId);
} else {
span.setAttribute(SEMRESATTRS_FAAS_ID, parentResourceId);
}
}
}

let propagationData: AttributeValue | undefined = undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
SEMATTRS_HTTP_URL,
SEMATTRS_MESSAGING_OPERATION,
SEMATTRS_RPC_SYSTEM,
SEMRESATTRS_FAAS_ID,
} from '@opentelemetry/semantic-conventions';
import { AWS_ATTRIBUTE_KEYS } from './aws-attribute-keys';
import { AWS_LAMBDA_FUNCTION_NAME_CONFIG, isLambdaEnvironment } from './aws-opentelemetry-configurator';
Expand All @@ -33,6 +34,9 @@ export class AwsSpanProcessingUtil {
static LOCAL_ROOT: string = 'LOCAL_ROOT';
static SQS_RECEIVE_MESSAGE_SPAN_NAME: string = 'Sqs.ReceiveMessage';
static AWS_SDK_INSTRUMENTATION_SCOPE_PREFIX: string = '@opentelemetry/instrumentation-aws-sdk';
// "cloud.resource_id" is defined in semconv which has not yet picked up by OTel JS
// https://opentelemetry.io/docs/specs/semconv/attributes-registry/cloud/
static CLOUD_RESOURCE_ID: string = 'cloud.resource_id';

// Max keyword length supported by parsing into remote_operation from DB_STATEMENT.
// The current longest command word is DATETIME_INTERVAL_PRECISION at 27 characters.
Expand Down Expand Up @@ -273,4 +277,15 @@ export class AwsSpanProcessingUtil {
const isLocalRoot: boolean = span.parentSpanId === undefined || !isParentSpanContextValid || isParentSpanRemote;
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT, isLocalRoot);
}

static getResourceId(span: ReadableSpan): string | undefined {
let resourceId: AttributeValue | undefined = undefined;
if (AwsSpanProcessingUtil.isKeyPresent(span, AwsSpanProcessingUtil.CLOUD_RESOURCE_ID)) {
resourceId = span.attributes[AwsSpanProcessingUtil.CLOUD_RESOURCE_ID];
} else if (AwsSpanProcessingUtil.isKeyPresent(span, SEMRESATTRS_FAAS_ID)) {
resourceId = span.attributes[SEMRESATTRS_FAAS_ID];
}

return typeof resourceId === 'string' ? resourceId : undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,44 @@ describe('AttributePropagatingSpanProcessorTest', () => {
);
});

it('testLambdaResourceIdAttributeExist', () => {
const parentSpan: APISpan = tracer.startSpan('parent', { kind: SpanKind.SERVER });

parentSpan.setAttribute(AwsSpanProcessingUtil.CLOUD_RESOURCE_ID, 'resource-123');

const childSpan: APISpan = createNestedSpan(parentSpan, 1);
expect((childSpan as any).attributes[AwsSpanProcessingUtil.CLOUD_RESOURCE_ID]).not.toBeUndefined();
expect((childSpan as any).attributes[AwsSpanProcessingUtil.CLOUD_RESOURCE_ID]).toEqual('resource-123');
});

it('testLambdaFaasIdAttributeExist', () => {
const parentSpan: APISpan = tracer.startSpan('parent', { kind: SpanKind.SERVER });

parentSpan.setAttribute('faas.id', 'faas-123');

const childSpan: APISpan = createNestedSpan(parentSpan, 1);
expect((childSpan as any).attributes['faas.id']).not.toBeUndefined();
expect((childSpan as any).attributes['faas.id']).toEqual('faas-123');
});

it('testBothLambdaFaasIdAndResourceIdAttributesExist', () => {
const parentSpan: APISpan = tracer.startSpan('parent', { kind: SpanKind.SERVER });

parentSpan.setAttribute('faas.id', 'faas-123');
parentSpan.setAttribute(AwsSpanProcessingUtil.CLOUD_RESOURCE_ID, 'resource-123');

const childSpan: APISpan = createNestedSpan(parentSpan, 1);
expect((childSpan as any).attributes[AwsSpanProcessingUtil.CLOUD_RESOURCE_ID]).not.toBeUndefined();
expect((childSpan as any).attributes[AwsSpanProcessingUtil.CLOUD_RESOURCE_ID]).toEqual('resource-123');
});

it('testLambdaNoneResourceAttributesExist', () => {
const parentSpan: APISpan = tracer.startSpan('parent', { kind: SpanKind.SERVER });

const childSpan: APISpan = createNestedSpan(parentSpan, 1);
expect((childSpan as any).attributes[AwsSpanProcessingUtil.CLOUD_RESOURCE_ID]).toBeUndefined();
});

function createNestedSpan(parentSpan: APISpan, depth: number): APISpan {
if (depth === 0) {
return parentSpan;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,36 @@ describe('AwsSpanProcessingUtilTest', () => {
const actualOperation: string = AwsSpanProcessingUtil.getIngressOperation(spanDataMock);
expect(actualOperation).toEqual('TestFunction/Handler');
});

it('should return cloud.resource_id when present', () => {
spanDataMock.attributes[AwsSpanProcessingUtil.CLOUD_RESOURCE_ID] = 'cloud-123';
const result = AwsSpanProcessingUtil.getResourceId(spanDataMock);
expect(result).toBe('cloud-123');
});

it('should return faas.id when cloud.resource_id is not present', () => {
spanDataMock.attributes['faas.id'] = 'faas-123';
const result = AwsSpanProcessingUtil.getResourceId(spanDataMock);
expect(result).toBe('faas-123');
});

it('should return cloud.resource_id when both cloud.resource_id and faas.id are present', () => {
spanDataMock.attributes[AwsSpanProcessingUtil.CLOUD_RESOURCE_ID] = 'cloud-123';
spanDataMock.attributes['faas.id'] = 'faas-123';
const result = AwsSpanProcessingUtil.getResourceId(spanDataMock);
expect(result).toBe('cloud-123');
});

it('should return undefined when neither cloud.resource_id nor faas.id are present', () => {
const result = AwsSpanProcessingUtil.getResourceId(spanDataMock);
expect(result).toBeUndefined();
});

it('should return undefined if cloud.resource_id is not a string', () => {
spanDataMock.attributes[AwsSpanProcessingUtil.CLOUD_RESOURCE_ID] = 123; // Incorrect type
const result = AwsSpanProcessingUtil.getResourceId(spanDataMock);
expect(result).toBeUndefined();
});
});

function createMockSpanContext(): SpanContext {
Expand Down

0 comments on commit 7ee5904

Please sign in to comment.