Skip to content

Commit 32e3803

Browse files
authored
DD_SERVICE_MAPPING for inferred spans (#392)
* initial implementation for DD_SERVICE_MAPPING * format * change function to static * format * change dd_service_mapping logic to support specific and general remapping * add lambda prefix * format and lint * use resource name instead of arn, fix tests * format * refactor logic and use easier to understand tests for remapping * update snapshots * add extra test cases where env var is not happy path, remove yargs import * add more test cases where we check the unhappy paths for DD_SERVICE_MAPPING * format * ignores same key and value pairs and adds more bad input tests
1 parent 364d862 commit 32e3803

File tree

8 files changed

+406
-16
lines changed

8 files changed

+406
-16
lines changed

integration_tests/snapshots/logs/process-input-traced_node14.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ START
3838
"_dd.p.dm": "-0",
3939
"runtime-id":"XXXX",
4040
"operation_name": "aws.apigateway",
41+
"http.url": "undefined",
4142
"resource_names": "GET /{proxy+}",
4243
"request_id":"XXXX",
4344
"apiid":"XXXX",

integration_tests/snapshots/logs/process-input-traced_node16.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ START
3939
"version": "1.0.0",
4040
"runtime-id":"XXXX",
4141
"operation_name": "aws.apigateway",
42+
"http.url": "undefined",
4243
"resource_names": "GET /{proxy+}",
4344
"request_id":"XXXX",
4445
"apiid":"XXXX",

integration_tests/snapshots/logs/process-input-traced_node18.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ START
3939
"version": "1.0.0",
4040
"runtime-id":"XXXX",
4141
"operation_name": "aws.apigateway",
42+
"http.url": "undefined",
4243
"resource_names": "GET /{proxy+}",
4344
"request_id":"XXXX",
4445
"apiid":"XXXX",

integration_tests/snapshots/logs/status-code-500s_node14.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ START
4444
"_dd.p.dm": "-0",
4545
"runtime-id":"XXXX",
4646
"operation_name": "aws.apigateway",
47+
"http.url": "undefined",
4748
"resource_names": "GET /{proxy+}",
4849
"request_id":"XXXX",
4950
"apiid":"XXXX",

integration_tests/snapshots/logs/status-code-500s_node16.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ START
4545
"version": "1.0.0",
4646
"runtime-id":"XXXX",
4747
"operation_name": "aws.apigateway",
48+
"http.url": "undefined",
4849
"resource_names": "GET /{proxy+}",
4950
"request_id":"XXXX",
5051
"apiid":"XXXX",

integration_tests/snapshots/logs/status-code-500s_node18.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ START
4545
"version": "1.0.0",
4646
"runtime-id":"XXXX",
4747
"operation_name": "aws.apigateway",
48+
"http.url": "undefined",
4849
"resource_names": "GET /{proxy+}",
4950
"request_id":"XXXX",
5051
"apiid":"XXXX",

src/trace/span-inferrer.spec.ts

Lines changed: 349 additions & 0 deletions
Large diffs are not rendered by default.

src/trace/span-inferrer.ts

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,26 @@ import { getInjectedAuthorizerData } from "./context";
1717
import { decodeAuthorizerContextEnvVar } from "../index";
1818

1919
export class SpanInferrer {
20+
private static serviceMapping: Record<string, string> = {};
2021
traceWrapper: TracerWrapper;
2122
constructor(traceWrapper: TracerWrapper) {
2223
this.traceWrapper = traceWrapper;
24+
SpanInferrer.initServiceMapping();
25+
}
26+
27+
private static initServiceMapping() {
28+
const serviceMappingStr = process.env.DD_SERVICE_MAPPING || "";
29+
30+
serviceMappingStr.split(",").forEach((entry) => {
31+
const parts = entry.split(":").map((part) => part.trim());
32+
if (parts.length === 2 && parts[0] && parts[1] && parts[0] !== parts[1]) {
33+
this.serviceMapping[parts[0]] = parts[1];
34+
}
35+
});
36+
}
37+
38+
static getServiceMapping(serviceName: string): string | undefined {
39+
return this.serviceMapping[serviceName];
2340
}
2441

2542
public createInferredSpan(
@@ -62,14 +79,18 @@ export class SpanInferrer {
6279
return "sync";
6380
}
6481

82+
static determineServiceName(specificKey: string, genericKey: string, fallback: string): string {
83+
return this.serviceMapping[specificKey] || this.serviceMapping[genericKey] || fallback;
84+
}
85+
6586
createInferredSpanForApiGateway(
6687
event: any,
6788
context: Context | undefined,
6889
parentSpanContext: SpanContext | undefined,
6990
decodeAuthorizerContext: boolean = true,
7091
): SpanWrapper {
7192
const options: SpanOptions = {};
72-
const domain = event.requestContext.domainName;
93+
const domain = event.requestContext.domainName || "";
7394
const path = event.rawPath || event.requestContext.path || event.requestContext.routeKey;
7495
const resourcePath = event.rawPath || event.requestContext.resourcePath || event.requestContext.routeKey;
7596

@@ -80,6 +101,9 @@ export class SpanInferrer {
80101
method = event.requestContext.http.method;
81102
}
82103
const resourceName = [method || domain, resourcePath].join(" ");
104+
const apiId = event.requestContext.apiId || "";
105+
const serviceName = SpanInferrer.determineServiceName(apiId, "lambda_api_gateway", domain);
106+
83107
options.tags = {
84108
operation_name: "aws.apigateway",
85109
"http.url": domain + path,
@@ -89,8 +113,8 @@ export class SpanInferrer {
89113
"span.type": "http",
90114
"resource.name": resourceName,
91115
"service.name": domain,
92-
apiid: event.requestContext.apiId,
93-
service: domain,
116+
apiid: apiId,
117+
service: serviceName,
94118
_inferred_span: {
95119
tag_source: "self",
96120
synchronicity: this.isApiGatewayAsync(event),
@@ -164,7 +188,7 @@ export class SpanInferrer {
164188
parentSpanContext: SpanContext | undefined,
165189
): any {
166190
const options: SpanOptions = {};
167-
const domain = event.requestContext.domainName;
191+
const domain: string = event.requestContext.domainName || "";
168192
const path = event.rawPath;
169193
let method;
170194
if (event.requestContext.httpMethod) {
@@ -173,6 +197,9 @@ export class SpanInferrer {
173197
method = event.requestContext.http.method;
174198
}
175199
const resourceName = [method || domain, path].join(" ");
200+
const apiId: string = event.requestContext.apiId || "";
201+
const serviceName: string = SpanInferrer.determineServiceName(apiId, "lambda_url", domain);
202+
176203
options.tags = {
177204
operation_name: "aws.lambda.url",
178205
"http.url": domain + path,
@@ -183,6 +210,7 @@ export class SpanInferrer {
183210
"span.type": "http",
184211
"resource.name": resourceName,
185212
"service.name": domain,
213+
service: serviceName,
186214
_inferred_span: {
187215
tag_source: "self",
188216
synchronicity: "sync",
@@ -207,16 +235,17 @@ export class SpanInferrer {
207235
const { Records } = event as DynamoDBStreamEvent;
208236
const referenceRecord = Records[0];
209237
const { eventSourceARN, eventName, eventVersion, eventID, dynamodb } = referenceRecord;
210-
const [tableArn, tableName] = eventSourceARN?.split("/") || [undefined, undefined];
238+
const [tableArn, tableName] = eventSourceARN?.split("/") || ["", ""];
211239
const resourceName = `${eventName} ${tableName}`;
240+
const serviceName = SpanInferrer.determineServiceName(tableName, "lambda_dynamodb", "aws.dynamodb");
212241
options.tags = {
213242
operation_name: "aws.dynamodb",
214243
tablename: tableName,
215244
resource_names: resourceName,
216245
request_id: context?.awsRequestId,
217246
"span.type": "web",
218247
"resource.name": resourceName,
219-
service: "aws.dynamodb",
248+
service: serviceName,
220249
_inferred_span: {
221250
tag_source: "self",
222251
synchronicity: "async",
@@ -252,15 +281,16 @@ export class SpanInferrer {
252281
EventSubscriptionArn,
253282
Sns: { TopicArn, Timestamp, Type, Subject, MessageId },
254283
} = referenceRecord;
255-
const topicName = TopicArn?.split(":").pop();
284+
const topicName = TopicArn?.split(":").pop() || "";
256285
const resourceName = topicName;
286+
const serviceName = SpanInferrer.determineServiceName(topicName, "lambda_sns", "sns");
257287
options.tags = {
258288
operation_name: "aws.sns",
259289
resource_names: resourceName,
260290
request_id: context?.awsRequestId,
261291
"span.type": "sns",
262292
"resource.name": resourceName,
263-
service: "sns",
293+
service: serviceName,
264294
_inferred_span: {
265295
tag_source: "self",
266296
synchronicity: "async",
@@ -289,14 +319,15 @@ export class SpanInferrer {
289319
): SpanWrapper {
290320
const options: SpanOptions = {};
291321
const { TopicArn, Timestamp, Type, Subject, MessageId } = event;
292-
const topicName = TopicArn?.split(":").pop();
322+
const topicName = TopicArn?.split(":").pop() || "";
293323
const resourceName = topicName;
324+
const serviceName = SpanInferrer.determineServiceName(topicName, "lambda_sns", "sns");
294325
options.tags = {
295326
operation_name: "aws.sns",
296327
resource_names: resourceName,
297328
"span.type": "sns",
298329
"resource.name": resourceName,
299-
service: "sns",
330+
service: serviceName,
300331
_inferred_span: {
301332
tag_source: "self",
302333
synchronicity: "async",
@@ -331,16 +362,17 @@ export class SpanInferrer {
331362
receiptHandle,
332363
body,
333364
} = referenceRecord;
334-
const queueName = eventSourceARN?.split(":").pop();
365+
const queueName = eventSourceARN?.split(":").pop() || "";
335366
const resourceName = queueName;
367+
const serviceName = SpanInferrer.determineServiceName(queueName, "lambda_sqs", "sqs");
336368
options.tags = {
337369
operation_name: "aws.sqs",
338370
resource_names: resourceName,
339371
request_id: context?.awsRequestId,
340372
"span.type": "web",
341373
"resource.name": resourceName,
342374
"service.name": resourceName,
343-
service: "sqs",
375+
service: serviceName,
344376
_inferred_span: {
345377
tag_source: "self",
346378
synchronicity: "async",
@@ -392,15 +424,16 @@ export class SpanInferrer {
392424
eventVersion,
393425
eventID,
394426
} = referenceRecord;
395-
const streamName = eventSourceARN?.split(":").pop();
427+
const streamName = eventSourceARN?.split(":").pop() || "";
396428
const shardId = eventID.split(":").pop();
429+
const serviceName = SpanInferrer.determineServiceName(streamName, "lambda_kinesis", "kinesis");
397430
options.tags = {
398431
operation_name: "aws.kinesis",
399432
resource_names: streamName,
400433
request_id: context?.awsRequestId,
401434
"span.type": "web",
402435
"resource.name": streamName,
403-
service: "kinesis",
436+
service: serviceName,
404437
_inferred_span: {
405438
tag_source: "self",
406439
synchronicity: "async",
@@ -439,13 +472,14 @@ export class SpanInferrer {
439472
eventTime,
440473
eventName,
441474
} = referenceRecord;
475+
const serviceName = SpanInferrer.determineServiceName(bucketName, "lambda_s3", "s3");
442476
options.tags = {
443477
operation_name: "aws.s3",
444478
resource_names: bucketName,
445479
request_id: context?.awsRequestId,
446480
"span.type": "web",
447481
"resource.name": bucketName,
448-
service: "s3",
482+
service: serviceName,
449483
_inferred_span: {
450484
tag_source: "self",
451485
synchronicity: "async",
@@ -474,13 +508,14 @@ export class SpanInferrer {
474508
): SpanWrapper {
475509
const options: SpanOptions = {};
476510
const { time, source } = event as EventBridgeEvent<any, any>;
511+
const serviceName = SpanInferrer.determineServiceName(source, "lambda_eventbridge", "eventbridge");
477512
options.tags = {
478513
operation_name: "aws.eventbridge",
479514
resource_names: source,
480515
request_id: context?.awsRequestId,
481516
"span.type": "web",
482517
"resource.name": source,
483-
service: "eventbridge",
518+
service: serviceName,
484519
_inferred_span: {
485520
tag_source: "self",
486521
synchronicity: "async",

0 commit comments

Comments
 (0)