diff --git a/README.md b/README.md index 289b6cff..fe242b64 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # datadog-lambda-js + ![build](https://github.com/DataDog/datadog-lambda-js/workflows/build/badge.svg) [![Code Coverage](https://img.shields.io/codecov/c/github/DataDog/datadog-lambda-js)](https://codecov.io/gh/DataDog/datadog-lambda-js) [![NPM](https://img.shields.io/npm/v/datadog-lambda-js)](https://www.npmjs.com/package/datadog-lambda-js) @@ -35,15 +36,15 @@ By default, the Datadog Lambda library automatically initializes the tracer. How 1. Set enviornment variable `DD_TRACE_ENABLED` to `false`, so the Datadog Lambda library does not initialize the tracer. 1. Add the following snippet to the function code to manually initialize the tracer with your desired settings. - ```js - const tracer = require('dd-trace').init({ - enabled: true, - tags: { - "_dd.origin": "lambda", - }, - sampleRate: 0.1 // e.g., keep 10% of traces - }); - ``` + ```js + const tracer = require("dd-trace").init({ + enabled: true, + tags: { + "_dd.origin": "lambda", + }, + sampleRate: 0.1, // e.g., keep 10% of traces + }); + ``` ### Trace & Log Correlation @@ -59,7 +60,7 @@ If this method doesn't work for you, instead of overriding the handler and setti ```js const { datadog } = require("datadog-lambda-js"); -const tracer = require('dd-trace').init({}); +const tracer = require("dd-trace").init({}); module.exports.myHandler = datadog(myHandler, { // my function code @@ -112,7 +113,7 @@ If `DD_FLUSH_TO_LOG` is set to `false` (not recommended), the Datadog API Key mu ### DD_SITE -If `DD_FLUSH_TO_LOG` is set to `false` (not recommended), you must set `DD_SITE`. Possible values are `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`, `us5.datadoghq.com`, and `ddog-gov.com`. The default is `datadoghq.com`. +If `DD_FLUSH_TO_LOG` is set to `false` (not recommended), you must set `DD_SITE`. Possible values are `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`, `us5.datadoghq.com`, and `ddog-gov.com`. The default is `datadoghq.com`. ### DD_LOG_LEVEL @@ -138,6 +139,10 @@ Inject Datadog trace id into logs for correlation. Defaults to `true`. Set to `true` to merge the X-Ray trace and the Datadog trace, when using both the X-Ray and Datadog tracing. Defaults to `false`. +### DD_TRACE_MANAGED_SERVICES + +Create inferred spans for managed services. Defaults to `true`. + ## Opening Issues If you encounter a bug with this package, we want to hear about it. Before opening a new issue, search the existing issues to avoid duplicates. diff --git a/event_samples/api-gateway-wss.json b/event_samples/api-gateway-wss.json new file mode 100644 index 00000000..3f738ed5 --- /dev/null +++ b/event_samples/api-gateway-wss.json @@ -0,0 +1,55 @@ +{ + "headers": { + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-US,en;q=0.9", + "Cache-Control": "no-cache", + "Host": "08se3mvh28.execute-api.sa-east-1.amazonaws.com", + "Origin": "https://www.piesocket.com", + "Pragma": "no-cache", + "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits", + "Sec-WebSocket-Key": "NjZTRCyloLbGD/YPeURxZA==", + "Sec-WebSocket-Version": "13", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36", + "X-Amzn-Trace-Id": "Root=1-61e834a7-7b918e142a94fd8d0ca64b5b", + "X-Forwarded-For": "71.195.30.42", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept-Encoding": ["gzip, deflate, br"], + "Accept-Language": ["en-US,en;q=0.9"], + "Cache-Control": ["no-cache"], + "Host": ["08se3mvh28.execute-api.sa-east-1.amazonaws.com"], + "Origin": ["https://www.piesocket.com"], + "Pragma": ["no-cache"], + "Sec-WebSocket-Extensions": ["permessage-deflate; client_max_window_bits"], + "Sec-WebSocket-Key": ["NjZTRCyloLbGD/YPeURxZA=="], + "Sec-WebSocket-Version": ["13"], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36" + ], + "X-Amzn-Trace-Id": ["Root=1-61e834a7-7b918e142a94fd8d0ca64b5b"], + "X-Forwarded-For": ["71.195.30.42"], + "X-Forwarded-Port": ["443"], + "X-Forwarded-Proto": ["https"] + }, + "requestContext": { + "routeKey": "$connect", + "eventType": "CONNECT", + "extendedRequestId": "MM0qRGu4GjQFfbQ=", + "requestTime": "19/Jan/2022:15:56:23 +0000", + "messageDirection": "IN", + "stage": "dev", + "connectedAt": 1642607783912, + "requestTimeEpoch": 1642607783913, + "identity": { + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36", + "sourceIp": "71.195.30.42" + }, + "requestId": "MM0qRGu4GjQFfbQ=", + "domainName": "08se3mvh28.execute-api.sa-east-1.amazonaws.com", + "connectionId": "MM0qReAFGjQCE-w=", + "apiId": "08se3mvh28" + }, + "isBase64Encoded": false +} diff --git a/event_samples/eventbridge.json b/event_samples/eventbridge.json new file mode 100644 index 00000000..3509d7ea --- /dev/null +++ b/event_samples/eventbridge.json @@ -0,0 +1,19 @@ +{ + "version": "0", + "id": "bd3c8258-8d30-007c-2562-64715b2d0ea8", + "detail-type": "UserSignUp", + "source": "my.event", + "account": "601427279990", + "time": "2022-01-24T16:00:10Z", + "region": "sa-east-1", + "resources": [], + "detail": { + "hello": "there", + "_datadog": { + "x-datadog-trace-id": "5827606813695714842", + "x-datadog-parent-id": "4726693487091824375", + "x-datadog-sampled": "1", + "x-datadog-sampling-priority": "1" + } + } +} diff --git a/event_samples/kinesis.json b/event_samples/kinesis.json index 2d7f8887..ad7dd782 100644 --- a/event_samples/kinesis.json +++ b/event_samples/kinesis.json @@ -1,20 +1,20 @@ { - "Records": [ - { - "kinesis": { - "partitionKey": "partitionKey-03", - "kinesisSchemaVersion": "1.0", - "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=", - "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", - "approximateArrivalTimestamp": 1428537600 - }, - "eventSource": "aws:kinesis", - "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", - "invokeIdentityArn": "arn:aws:iam::EXAMPLE", - "eventVersion": "1.0", - "eventName": "aws:kinesis:record", - "eventSourceARN": "arn:aws:kinesis:EXAMPLE", - "awsRegion": "us-east-1" - } - ] + "Records": [ + { + "kinesis": { + "kinesisSchemaVersion": "1.0", + "partitionKey": "cdbfd750-cec0-4f0f-a4b0-82ae6152c7fb", + "sequenceNumber": "49625698045709644136382874226371117765484751339579768834", + "data": "eyJJJ20gbWFkZSBvZiB3YXgsIExhcnJ5IjoiV2hhdCBhcmUgeW91IG1hZGUgb2Y/IiwiX2RhdGFkb2ciOnsieC1kYXRhZG9nLXRyYWNlLWlkIjoiNjY3MzA5NTE0MjIxMDM1NTM4IiwieC1kYXRhZG9nLXBhcmVudC1pZCI6IjEzNTA3MzUwMzU0OTc4MTE4MjgiLCJ4LWRhdGFkb2ctc2FtcGxlZCI6IjEiLCJ4LWRhdGFkb2ctc2FtcGxpbmctcHJpb3JpdHkiOiIxIn19", + "approximateArrivalTimestamp": 1642518727.248 + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "us-east-1" + } + ] } diff --git a/event_samples/sns.json b/event_samples/sns.json index 06c6062d..42d33733 100644 --- a/event_samples/sns.json +++ b/event_samples/sns.json @@ -1,31 +1,35 @@ { - "Records": [ - { - "EventSource": "aws:sns", - "EventVersion": "1.0", - "EventSubscriptionArn": "arn:aws:sns:us-east-1:{{{accountId}}}:ExampleTopic", - "Sns": { - "Type": "Notification", - "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", - "TopicArn": "arn:aws:sns:us-east-1:123456789012:ExampleTopic", - "Subject": "example subject", - "Message": "example message", - "Timestamp": "1970-01-01T00:00:00.000Z", - "SignatureVersion": "1", - "Signature": "EXAMPLE", - "SigningCertUrl": "EXAMPLE", - "UnsubscribeUrl": "EXAMPLE", - "MessageAttributes": { - "Test": { - "Type": "String", - "Value": "TestString" - }, - "TestBinary": { - "Type": "Binary", - "Value": "TestBinary" - } - } - } + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:us-east-1:123456789012:ExampleTopic", + "Sns": { + "Type": "Notification", + "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + "TopicArn": "arn:aws:sns:us-east-1:123456789012:ExampleTopic", + "Subject": "example subject", + "Message": "{\"hello\":\"there\",\"ajTimestamp\":1643039127879}", + "Timestamp": "2022-01-24T15:45:27.968Z", + "SignatureVersion": "1", + "Signature": "mzp2Ou0fASw4LYRxY6SSww7qFfofn4luCJBRaTjLpQ5uhwhsAUKdyLz9VPD+/dlRbi1ImsWtIZ7A+wxj1oV7Z2Gyu/N4RpGalae37+jTluDS7AhjgcD7Bs4bgQtFkCfMFEwbhICQfukLLzbwbgczZ4NTPn6zj5o28c5NBKSJMYSnLz82ohw77GgnZ/m26E32ZQNW4+VCEMINg9Ne2rHstwPWRXPr5xGTrx8jH8CNUZnVpFVfhU8o+OSeAdpzm2l99grHIo7qPhekERxANz6QHynMlhdzD3UNSgc3oZkamZban/NEKd4MKJzgNQdNOYVj3Kw6eF2ZweEoBQ5sSFK5fQ==", + "SigningCertUrl": "https://sns.sa-east-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem", + "UnsubscribeUrl": "https://sns.sa-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:sa-east-1:601427279990:aj-js-library-test-dev-solo-topic:1bd19208-a99a-46d9-8398-f90f8699c641", + "MessageAttributes": { + "_datadog": { + "Type": "String", + "Value": "{\"x-datadog-trace-id\":\"6966585609680374559\",\"x-datadog-parent-id\":\"4297634551783724228\",\"x-datadog-sampled\":\"1\",\"x-datadog-sampling-priority\":\"1\"}" + }, + "Test": { + "Type": "String", + "Value": "TestString" + }, + "TestBinary": { + "Type": "Binary", + "Value": "TestBinary" + } } - ] + } + } + ] } diff --git a/event_samples/snssqs.json b/event_samples/snssqs.json new file mode 100644 index 00000000..1fd0f159 --- /dev/null +++ b/event_samples/snssqs.json @@ -0,0 +1,20 @@ +{ + "Records": [ + { + "messageId": "64812b68-4d9b-4dca-b3fb-9b18f255ee51", + "receiptHandle": "AQEBER6aRkfG8092GvkL7FRwCwbQ7LLDW9Tlk/CembqHe+suS2kfFxXiukomvaIN61QoyQMoRgWuV52SDkiQno2u+5hP64BDbmw+e/KR9ayvIfHJ3M6RfyQLaWNWm3hDFBCKTnBMVIxtdx0N9epZZewyokjKcrNYtmCghFgTCvZzsQkowi5rnoHAVHJ3je1c3bDnQ1KLrZFgajDnootYXDwEPuMq5FIxrf4EzTe0S7S+rnRm+GaQfeBLBVAY6dASL9usV3/AFRqDtaI7GKI+0F2NCgLlqj49VlPRz4ldhkGknYlKTZTluAqALWLJS62/J1GQo53Cs3nneJcmu5ajB2zzmhhRXoXINEkLhCD5ujZfcsw9H4xqW69Or4ECvlqx14bUU2rtMIW0QM2p7pEeXnyocymQv6m1te113eYWTVmaJ4I=", + "body": "{\n \"Type\" : \"Notification\",\n \"MessageId\" : \"0a0ab23e-4861-5447-82b7-e8094ff3e332\",\n \"TopicArn\" : \"arn:aws:sns:sa-east-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA\",\n \"Message\" : \"{\\\"hello\\\":\\\"harv\\\",\\\"nice of you to join us\\\":\\\"david\\\",\\\"anotherThing\\\":{\\\"foo\\\":\\\"bar\\\",\\\"blah\\\":null,\\\"harv\\\":123},\\\"vals\\\":[{\\\"thingOne\\\":1},{\\\"thingTwo\\\":2}],\\\"ajTimestamp\\\":1639777617957}\",\n \"Timestamp\" : \"2021-12-17T21:46:58.040Z\",\n \"SignatureVersion\" : \"1\",\n \"Signature\" : \"FR35/7E8C3LHEVk/rC4XxXlXwV/5mNkFNPgDhHSnJ2I6hIoSrTROAm7h5xm1PuBkAeFDvq0zofw91ouk9zZyvhdrMLFIIgrjEyNayRmEffmoEAkzLFUsgtQX7MmTl644r4NuWiM0Oiz7jueRvIcKXcZr7Nc6GJcWV1ymec8oOmuHNMisnPMxI07LIQVYSyAfv6P9r2jEWMVIukRoCzwTnRk4bUUYhPSGHI7OC3AsxxXBbv8snqTrLM/4z2rXCf6jHCKNxWeLlm9/45PphCkEyx5BWS4/71KaoMWUWy8+6CCsy+uF3XTCVmvSEYLyEwTSzOY+vCUjazrRW93498i70g==\",\n \"SigningCertURL\" : \"https://sns.sa-east-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem\",\n \"UnsubscribeURL\" : \"https://sns.sa-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:sa-east-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA:1290f550-9a8a-4e8f-a900-8f5f96dcddda\",\n \"MessageAttributes\" : {\n \"_datadog\" : {\"Type\":\"String\",\"Value\":\"{\\\"x-datadog-trace-id\\\":\\\"2776434475358637757\\\",\\\"x-datadog-parent-id\\\":\\\"4493917105238181843\\\",\\\"x-datadog-sampled\\\":\\\"1\\\",\\\"x-datadog-sampling-priority\\\":\\\"1\\\"}\"}\n }\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1639777618130", + "SenderId": "AIDAIOA2GYWSHW4E2VXIO", + "ApproximateFirstReceiveTimestamp": "1639777618132" + }, + "messageAttributes": {}, + "md5OfBody": "ee19d8b1377919239ad3fd5dabc33739", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:sa-east-1:601427279990:aj-js-library-test-dev-demo-queue", + "awsRegion": "sa-east-1" + } + ] +} diff --git a/integration_tests/snapshots/logs/async-metrics_node12.log b/integration_tests/snapshots/logs/async-metrics_node12.log index e99c7942..b9039227 100644 --- a/integration_tests/snapshots/logs/async-metrics_node12.log +++ b/integration_tests/snapshots/logs/async-metrics_node12.log @@ -1,23 +1,19 @@ -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-async-metrics_node12","resource:integration-tests-js-XXXX-async-metrics_node12","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} {"e":XXXX,"m":"serverless.integration_test.execution","t":["tagkey:tagvalue","eventsource:APIGateway","dd_lambda_layer:datadog-nodev12.XX.X"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed APIGateway request -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed APIGateway request +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms -START RequestId: XXXX Version: $LATEST -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-async-metrics_node12","resource:integration-tests-js-XXXX-async-metrics_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} +START {"e":XXXX,"m":"serverless.integration_test.records_processed","t":["tagkey:tagvalue","eventsource:SNS","dd_lambda_layer:datadog-nodev12.XX.X"],"v":1} {"e":XXXX,"m":"serverless.integration_test.execution","t":["tagkey:tagvalue","eventsource:SNS","dd_lambda_layer:datadog-nodev12.XX.X"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SNS request -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SNS request +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-async-metrics_node12","resource:integration-tests-js-XXXX-async-metrics_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} {"e":XXXX,"m":"serverless.integration_test.records_processed","t":["tagkey:tagvalue","eventsource:SQS","dd_lambda_layer:datadog-nodev12.XX.X"],"v":1} {"e":XXXX,"m":"serverless.integration_test.records_processed","t":["tagkey:tagvalue","eventsource:SQS","dd_lambda_layer:datadog-nodev12.XX.X"],"v":1} {"e":XXXX,"m":"serverless.integration_test.execution","t":["tagkey:tagvalue","eventsource:SQS","dd_lambda_layer:datadog-nodev12.XX.X"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SQS request -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SQS request +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB diff --git a/integration_tests/snapshots/logs/async-metrics_node14.log b/integration_tests/snapshots/logs/async-metrics_node14.log index 8370efd1..16e5263d 100644 --- a/integration_tests/snapshots/logs/async-metrics_node14.log +++ b/integration_tests/snapshots/logs/async-metrics_node14.log @@ -1,23 +1,19 @@ -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-async-metrics_node14","resource:integration-tests-js-XXXX-async-metrics_node14","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} {"e":XXXX,"m":"serverless.integration_test.execution","t":["tagkey:tagvalue","eventsource:APIGateway","dd_lambda_layer:datadog-nodev14.XX.X"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed APIGateway request -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed APIGateway request +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms -START RequestId: XXXX Version: $LATEST -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-async-metrics_node14","resource:integration-tests-js-XXXX-async-metrics_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} +START {"e":XXXX,"m":"serverless.integration_test.records_processed","t":["tagkey:tagvalue","eventsource:SNS","dd_lambda_layer:datadog-nodev14.XX.X"],"v":1} {"e":XXXX,"m":"serverless.integration_test.execution","t":["tagkey:tagvalue","eventsource:SNS","dd_lambda_layer:datadog-nodev14.XX.X"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SNS request -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SNS request +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-async-metrics_node14","resource:integration-tests-js-XXXX-async-metrics_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} {"e":XXXX,"m":"serverless.integration_test.records_processed","t":["tagkey:tagvalue","eventsource:SQS","dd_lambda_layer:datadog-nodev14.XX.X"],"v":1} {"e":XXXX,"m":"serverless.integration_test.records_processed","t":["tagkey:tagvalue","eventsource:SQS","dd_lambda_layer:datadog-nodev14.XX.X"],"v":1} {"e":XXXX,"m":"serverless.integration_test.execution","t":["tagkey:tagvalue","eventsource:SQS","dd_lambda_layer:datadog-nodev14.XX.X"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SQS request -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SQS request +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB diff --git a/integration_tests/snapshots/logs/http-requests_node12.log b/integration_tests/snapshots/logs/http-requests_node12.log index a09afdcb..967e2c54 100644 --- a/integration_tests/snapshots/logs/http-requests_node12.log +++ b/integration_tests/snapshots/logs/http-requests_node12.log @@ -1,26 +1,22 @@ -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-http-requests_node12","resource:integration-tests-js-XXXX-http-requests_node12","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} HTTP GET https://ip-ranges.datadoghq.com/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] HTTP GET https://ip-ranges.datadoghq.eu/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms -START RequestId: XXXX Version: $LATEST -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-http-requests_node12","resource:integration-tests-js-XXXX-http-requests_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} +START +XXXX-XX-XX XX:XX:XX.XXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] {"status":"error","message":"datadog:can't send metrics, datadog lambda handler not set up."} HTTP GET https://ip-ranges.datadoghq.com/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] HTTP GET https://ip-ranges.datadoghq.eu/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu -HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-http-requests_node12","resource:integration-tests-js-XXXX-http-requests_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} HTTP GET https://ip-ranges.datadoghq.com/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] HTTP GET https://ip-ranges.datadoghq.eu/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB diff --git a/integration_tests/snapshots/logs/http-requests_node14.log b/integration_tests/snapshots/logs/http-requests_node14.log index 5b7ab71d..94e1d3fd 100644 --- a/integration_tests/snapshots/logs/http-requests_node14.log +++ b/integration_tests/snapshots/logs/http-requests_node14.log @@ -1,26 +1,22 @@ -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-http-requests_node14","resource:integration-tests-js-XXXX-http-requests_node14","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} HTTP GET https://ip-ranges.datadoghq.com/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] HTTP GET https://ip-ranges.datadoghq.eu/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms -START RequestId: XXXX Version: $LATEST -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-http-requests_node14","resource:integration-tests-js-XXXX-http-requests_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} +START +XXXX-XX-XX XX:XX:XX.XXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] {"status":"error","message":"datadog:can't send metrics, datadog lambda handler not set up."} HTTP GET https://ip-ranges.datadoghq.com/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] HTTP GET https://ip-ranges.datadoghq.eu/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu -HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-http-requests_node14","resource:integration-tests-js-XXXX-http-requests_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} HTTP GET https://ip-ranges.datadoghq.com/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] HTTP GET https://ip-ranges.datadoghq.eu/ Headers: ["Accept:application/json, text/plain, */*","User-Agent:axios/X.X.X","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Snapshot test http requests successfully made to URLs: https://ip-ranges.datadoghq.com,https://ip-ranges.datadoghq.eu HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB diff --git a/integration_tests/snapshots/logs/process-input-traced_node12.log b/integration_tests/snapshots/logs/process-input-traced_node12.log index af2015ce..c0279d3b 100644 --- a/integration_tests/snapshots/logs/process-input-traced_node12.log +++ b/integration_tests/snapshots/logs/process-input-traced_node12.log @@ -1,20 +1,16 @@ -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-process-input-traced_node12","resource:integration-tests-js-XXXX-process-input-traced_node12","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} {"e":XXXX,"m":"serverless.integration_test.execution","t":["function:process-input-traced","dd_lambda_layer:datadog-nodev12.XX.X"],"v":1} -{"traces":[[{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getRecordIds","resource":"getRecordIds","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node12"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getAPIGatewayRequestId","resource":"getAPIGatewayRequestId","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node12"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.lambda","resource":"integration-tests-js-XXXX-process-input-traced_node12","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"aws.lambda","type":"serverless"}]]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms +{"traces":[[{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getRecordIds","resource":"getRecordIds","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node12"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getAPIGatewayRequestId","resource":"getAPIGatewayRequestId","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node12"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.lambda","resource":"integration-tests-js-XXXX-process-input-traced_node12","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"aws.lambda","type":"serverless"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.apigateway","resource":" ","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"type":"http"}]]} +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms -START RequestId: XXXX Version: $LATEST -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-process-input-traced_node12","resource:integration-tests-js-XXXX-process-input-traced_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} +START {"e":XXXX,"m":"serverless.integration_test.execution","t":["function:process-input-traced","dd_lambda_layer:datadog-nodev12.XX.X"],"v":1} {"traces":[[{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getRecordIds","resource":"getRecordIds","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node12"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.lambda","resource":"integration-tests-js-XXXX-process-input-traced_node12","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"aws.lambda","type":"serverless"}]]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-process-input-traced_node12","resource:integration-tests-js-XXXX-process-input-traced_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} {"e":XXXX,"m":"serverless.integration_test.execution","t":["function:process-input-traced","dd_lambda_layer:datadog-nodev12.XX.X"],"v":1} -{"traces":[[{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getRecordIds","resource":"getRecordIds","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node12"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.lambda","resource":"integration-tests-js-XXXX-process-input-traced_node12","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"aws.lambda","type":"serverless"}]]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +{"traces":[[{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getRecordIds","resource":"getRecordIds","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node12"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.lambda","resource":"integration-tests-js-XXXX-process-input-traced_node12","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"aws.lambda","type":"serverless"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.sqs","resource":"my-queue","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"my-queue","type":"web"}]]} +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB diff --git a/integration_tests/snapshots/logs/process-input-traced_node14.log b/integration_tests/snapshots/logs/process-input-traced_node14.log index b57428e1..98bb1b42 100644 --- a/integration_tests/snapshots/logs/process-input-traced_node14.log +++ b/integration_tests/snapshots/logs/process-input-traced_node14.log @@ -1,20 +1,16 @@ -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-process-input-traced_node14","resource:integration-tests-js-XXXX-process-input-traced_node14","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} {"e":XXXX,"m":"serverless.integration_test.execution","t":["function:process-input-traced","dd_lambda_layer:datadog-nodev14.XX.X"],"v":1} -{"traces":[[{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getRecordIds","resource":"getRecordIds","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node14"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getAPIGatewayRequestId","resource":"getAPIGatewayRequestId","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node14"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.lambda","resource":"integration-tests-js-XXXX-process-input-traced_node14","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"aws.lambda","type":"serverless"}]]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms +{"traces":[[{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getRecordIds","resource":"getRecordIds","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node14"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getAPIGatewayRequestId","resource":"getAPIGatewayRequestId","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node14"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.lambda","resource":"integration-tests-js-XXXX-process-input-traced_node14","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"aws.lambda","type":"serverless"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.apigateway","resource":" ","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"type":"http"}]]} +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms -START RequestId: XXXX Version: $LATEST -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-process-input-traced_node14","resource:integration-tests-js-XXXX-process-input-traced_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} +START {"e":XXXX,"m":"serverless.integration_test.execution","t":["function:process-input-traced","dd_lambda_layer:datadog-nodev14.XX.X"],"v":1} {"traces":[[{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getRecordIds","resource":"getRecordIds","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node14"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.lambda","resource":"integration-tests-js-XXXX-process-input-traced_node14","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"aws.lambda","type":"serverless"}]]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-process-input-traced_node14","resource:integration-tests-js-XXXX-process-input-traced_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} {"e":XXXX,"m":"serverless.integration_test.execution","t":["function:process-input-traced","dd_lambda_layer:datadog-nodev14.XX.X"],"v":1} -{"traces":[[{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getRecordIds","resource":"getRecordIds","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node14"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.lambda","resource":"integration-tests-js-XXXX-process-input-traced_node14","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"aws.lambda","type":"serverless"}]]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +{"traces":[[{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"getRecordIds","resource":"getRecordIds","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"integration-tests-js-XXXX-process-input-traced_node14"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.lambda","resource":"integration-tests-js-XXXX-process-input-traced_node14","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"aws.lambda","type":"serverless"},{"trace_id":"XXXX","span_id":"XXXX","parent_id":"XXXX","name":"aws.sqs","resource":"my-queue","error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"start":XXXX,"duration":XXXX,"service":"my-queue","type":"web"}]]} +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB diff --git a/integration_tests/snapshots/logs/sync-metrics_node12.log b/integration_tests/snapshots/logs/sync-metrics_node12.log index 90c930ac..a14b38e2 100644 --- a/integration_tests/snapshots/logs/sync-metrics_node12.log +++ b/integration_tests/snapshots/logs/sync-metrics_node12.log @@ -1,20 +1,17 @@ -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-sync-metrics_node12","resource:integration-tests-js-XXXX-sync-metrics_node12","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed APIGateway request +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed APIGateway request HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms -START RequestId: XXXX Version: $LATEST -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-sync-metrics_node12","resource:integration-tests-js-XXXX-sync-metrics_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SNS request -HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +START +XXXX-XX-XX XX:XX:XX.XXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] {"status":"error","message":"datadog:can't send metrics, datadog lambda handler not set up."} +XXXX-XX-XX XX:XX:XX.XXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] {"status":"error","message":"datadog:can't send metrics, datadog lambda handler not set up."} +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SNS request +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-sync-metrics_node12","resource:integration-tests-js-XXXX-sync-metrics_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SQS request +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SQS request HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB diff --git a/integration_tests/snapshots/logs/sync-metrics_node14.log b/integration_tests/snapshots/logs/sync-metrics_node14.log index 7f4f532b..de3faedc 100644 --- a/integration_tests/snapshots/logs/sync-metrics_node14.log +++ b/integration_tests/snapshots/logs/sync-metrics_node14.log @@ -1,20 +1,17 @@ -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-sync-metrics_node14","resource:integration-tests-js-XXXX-sync-metrics_node14","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed APIGateway request +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed APIGateway request HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms -START RequestId: XXXX Version: $LATEST -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-sync-metrics_node14","resource:integration-tests-js-XXXX-sync-metrics_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SNS request -HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +START +XXXX-XX-XX XX:XX:XX.XXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] {"status":"error","message":"datadog:can't send metrics, datadog lambda handler not set up."} +XXXX-XX-XX XX:XX:XX.XXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] {"status":"error","message":"datadog:can't send metrics, datadog lambda handler not set up."} +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SNS request +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-sync-metrics_node14","resource:integration-tests-js-XXXX-sync-metrics_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SQS request +XXXX-XX-XX XX:XX:XX.XXX INFO [dd.trace_id=XXXX dd.span_id=XXXX] Processed SQS request HTTP POST https://api.datadoghq.com/api/v1/distribution_points?api_key=XXXX Headers: ["content-type:application/json","x-datadog-parent-id:XXXX","x-datadog-sampling-priority:-1","x-datadog-trace-id:XXXX"] -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB diff --git a/integration_tests/snapshots/logs/throw-error-traced_node12.log b/integration_tests/snapshots/logs/throw-error-traced_node12.log index 20eab504..c193e369 100644 --- a/integration_tests/snapshots/logs/throw-error-traced_node12.log +++ b/integration_tests/snapshots/logs/throw-error-traced_node12.log @@ -1,20 +1,15 @@ -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node12","resource:integration-tests-js-XXXX-throw-error-traced_node12","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} {"e":XXXX,"m":"aws.lambda.enhanced.errors","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node12","resource:integration-tests-js-XXXX-throw-error-traced_node12","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] Invoke Error {"errorType":"Error","errorMessage":"Hello","stack":["Error: Hello"," at handle (/var/task/throw-error-traced.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/utils/handler.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at step (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at Object.next (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at new Promise ()"," at __awaiter (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms +XXXX-XX-XX XX:XX:XX.XXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] Invoke Error {"errorType":"Error","errorMessage":"Hello","stack":["Error: Hello"," at handle (/var/task/throw-error-traced.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/utils/handler.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at step (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at Object.next (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at new Promise ()"," at __awaiter (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"]} +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms -START RequestId: XXXX Version: $LATEST -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node12","resource:integration-tests-js-XXXX-throw-error-traced_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} +START {"e":XXXX,"m":"aws.lambda.enhanced.errors","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node12","resource:integration-tests-js-XXXX-throw-error-traced_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] Invoke Error {"errorType":"Error","errorMessage":"Hello","stack":["Error: Hello"," at handle (/var/task/throw-error-traced.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/utils/handler.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at step (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at Object.next (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at new Promise ()"," at __awaiter (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node12","resource:integration-tests-js-XXXX-throw-error-traced_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} {"e":XXXX,"m":"aws.lambda.enhanced.errors","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node12","resource:integration-tests-js-XXXX-throw-error-traced_node12","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs12.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] Invoke Error {"errorType":"Error","errorMessage":"Hello","stack":["Error: Hello"," at handle (/var/task/throw-error-traced.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/utils/handler.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at step (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at Object.next (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at new Promise ()"," at __awaiter (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +XXXX-XX-XX XX:XX:XX.XXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] Invoke Error {"errorType":"Error","errorMessage":"Hello","stack":["Error: Hello"," at handle (/var/task/throw-error-traced.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/utils/handler.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at step (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at Object.next (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at new Promise ()"," at __awaiter (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"]} +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB diff --git a/integration_tests/snapshots/logs/throw-error-traced_node14.log b/integration_tests/snapshots/logs/throw-error-traced_node14.log index 00eaeabc..f5a4c540 100644 --- a/integration_tests/snapshots/logs/throw-error-traced_node14.log +++ b/integration_tests/snapshots/logs/throw-error-traced_node14.log @@ -1,20 +1,15 @@ -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node14","resource:integration-tests-js-XXXX-throw-error-traced_node14","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} {"e":XXXX,"m":"aws.lambda.enhanced.errors","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node14","resource:integration-tests-js-XXXX-throw-error-traced_node14","cold_start:true","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] Invoke Error {"errorType":"Error","errorMessage":"Hello","stack":["Error: Hello"," at handle (/var/task/throw-error-traced.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/utils/handler.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at step (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at Object.next (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at new Promise ()"," at __awaiter (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms +XXXX-XX-XX XX:XX:XX.XXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] Invoke Error {"errorType":"Error","errorMessage":"Hello","stack":["Error: Hello"," at handle (/var/task/throw-error-traced.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/utils/handler.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at step (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at Object.next (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at new Promise ()"," at __awaiter (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"]} +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms -START RequestId: XXXX Version: $LATEST -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node14","resource:integration-tests-js-XXXX-throw-error-traced_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} +START {"e":XXXX,"m":"aws.lambda.enhanced.errors","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node14","resource:integration-tests-js-XXXX-throw-error-traced_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] Invoke Error {"errorType":"Error","errorMessage":"Hello","stack":["Error: Hello"," at handle (/var/task/throw-error-traced.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/utils/handler.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at step (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at Object.next (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at new Promise ()"," at __awaiter (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB -START RequestId: XXXX Version: $LATEST +START {"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node14","resource:integration-tests-js-XXXX-throw-error-traced_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} {"e":XXXX,"m":"aws.lambda.enhanced.errors","t":["region:sa-east-1","account_id:XXXX","functionname:integration-tests-js-XXXX-throw-error-traced_node14","resource:integration-tests-js-XXXX-throw-error-traced_node14","cold_start:false","memorysize:1024","datadog_lambda:vX.X.X","runtime:nodejs14.x"],"v":1} -XXXX-XX-XX XX:XX:XX.XXX XXXX-XXXX-XXXX-XXXX-XXXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] Invoke Error {"errorType":"Error","errorMessage":"Hello","stack":["Error: Hello"," at handle (/var/task/throw-error-traced.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/utils/handler.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at step (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at Object.next (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at new Promise ()"," at __awaiter (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"]} -END RequestId: XXXX -REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB +XXXX-XX-XX XX:XX:XX.XXX ERROR [dd.trace_id=XXXX dd.span_id=XXXX] Invoke Error {"errorType":"Error","errorMessage":"Hello","stack":["Error: Hello"," at handle (/var/task/throw-error-traced.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/utils/handler.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at step (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at Object.next (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at new Promise ()"," at __awaiter (/opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX)"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"," at /opt/nodejs/node_modules/datadog-lambda-js/index.js:XXX:XXX"]} +END Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB diff --git a/integration_tests/snapshots/return_values/async-metrics_node12_sns.json b/integration_tests/snapshots/return_values/async-metrics_node12_sns.json index 50064b8d..19765bd5 100644 --- a/integration_tests/snapshots/return_values/async-metrics_node12_sns.json +++ b/integration_tests/snapshots/return_values/async-metrics_node12_sns.json @@ -1,7 +1 @@ -{ - "message": "hello, dog!", - "recordIds": [ - "95df01b4-ee98-5cb9-9903-4c221d41eb5e" - ], - "eventType": "SNS" -} +null diff --git a/integration_tests/snapshots/return_values/async-metrics_node14_sns.json b/integration_tests/snapshots/return_values/async-metrics_node14_sns.json index 50064b8d..19765bd5 100644 --- a/integration_tests/snapshots/return_values/async-metrics_node14_sns.json +++ b/integration_tests/snapshots/return_values/async-metrics_node14_sns.json @@ -1,7 +1 @@ -{ - "message": "hello, dog!", - "recordIds": [ - "95df01b4-ee98-5cb9-9903-4c221d41eb5e" - ], - "eventType": "SNS" -} +null diff --git a/integration_tests/snapshots/return_values/http-requests_node12_sns.json b/integration_tests/snapshots/return_values/http-requests_node12_sns.json index eb1e9d31..19765bd5 100644 --- a/integration_tests/snapshots/return_values/http-requests_node12_sns.json +++ b/integration_tests/snapshots/return_values/http-requests_node12_sns.json @@ -1,3 +1 @@ -{ - "message": "hello, dog!" -} +null diff --git a/integration_tests/snapshots/return_values/http-requests_node14_sns.json b/integration_tests/snapshots/return_values/http-requests_node14_sns.json index eb1e9d31..19765bd5 100644 --- a/integration_tests/snapshots/return_values/http-requests_node14_sns.json +++ b/integration_tests/snapshots/return_values/http-requests_node14_sns.json @@ -1,3 +1 @@ -{ - "message": "hello, dog!" -} +null diff --git a/integration_tests/snapshots/return_values/process-input-traced_node12_sns.json b/integration_tests/snapshots/return_values/process-input-traced_node12_sns.json index 50064b8d..19765bd5 100644 --- a/integration_tests/snapshots/return_values/process-input-traced_node12_sns.json +++ b/integration_tests/snapshots/return_values/process-input-traced_node12_sns.json @@ -1,7 +1 @@ -{ - "message": "hello, dog!", - "recordIds": [ - "95df01b4-ee98-5cb9-9903-4c221d41eb5e" - ], - "eventType": "SNS" -} +null diff --git a/integration_tests/snapshots/return_values/process-input-traced_node14_sns.json b/integration_tests/snapshots/return_values/process-input-traced_node14_sns.json index 50064b8d..19765bd5 100644 --- a/integration_tests/snapshots/return_values/process-input-traced_node14_sns.json +++ b/integration_tests/snapshots/return_values/process-input-traced_node14_sns.json @@ -1,7 +1 @@ -{ - "message": "hello, dog!", - "recordIds": [ - "95df01b4-ee98-5cb9-9903-4c221d41eb5e" - ], - "eventType": "SNS" -} +null diff --git a/integration_tests/snapshots/return_values/sync-metrics_node12_sns.json b/integration_tests/snapshots/return_values/sync-metrics_node12_sns.json index 50064b8d..19765bd5 100644 --- a/integration_tests/snapshots/return_values/sync-metrics_node12_sns.json +++ b/integration_tests/snapshots/return_values/sync-metrics_node12_sns.json @@ -1,7 +1 @@ -{ - "message": "hello, dog!", - "recordIds": [ - "95df01b4-ee98-5cb9-9903-4c221d41eb5e" - ], - "eventType": "SNS" -} +null diff --git a/integration_tests/snapshots/return_values/sync-metrics_node14_sns.json b/integration_tests/snapshots/return_values/sync-metrics_node14_sns.json index 50064b8d..19765bd5 100644 --- a/integration_tests/snapshots/return_values/sync-metrics_node14_sns.json +++ b/integration_tests/snapshots/return_values/sync-metrics_node14_sns.json @@ -1,7 +1 @@ -{ - "message": "hello, dog!", - "recordIds": [ - "95df01b4-ee98-5cb9-9903-4c221d41eb5e" - ], - "eventType": "SNS" -} +null diff --git a/integration_tests/snapshots/return_values/throw-error-traced_node12_sns.json b/integration_tests/snapshots/return_values/throw-error-traced_node12_sns.json index 7147cafa..19765bd5 100644 --- a/integration_tests/snapshots/return_values/throw-error-traced_node12_sns.json +++ b/integration_tests/snapshots/return_values/throw-error-traced_node12_sns.json @@ -1 +1 @@ -Invocation failed +null diff --git a/integration_tests/snapshots/return_values/throw-error-traced_node14_sns.json b/integration_tests/snapshots/return_values/throw-error-traced_node14_sns.json index 7147cafa..19765bd5 100644 --- a/integration_tests/snapshots/return_values/throw-error-traced_node14_sns.json +++ b/integration_tests/snapshots/return_values/throw-error-traced_node14_sns.json @@ -1 +1 @@ -Invocation failed +null diff --git a/src/index.ts b/src/index.ts index 0f9195f8..0d2c858b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,6 @@ import { MetricsListener, } from "./metrics"; import { TraceConfig, TraceHeaders, TraceListener } from "./trace"; -import { extractHTTPStatusCodeTag } from "./trace/trigger"; import { logDebug, logError, @@ -17,7 +16,6 @@ import { setColdStart, setLogger, setLogLevel, - tagObject, } from "./utils"; export { TraceHeaders } from "./trace"; @@ -25,6 +23,7 @@ export { TraceHeaders } from "./trace"; export const apiKeyEnvVar = "DD_API_KEY"; export const apiKeyKMSEnvVar = "DD_KMS_API_KEY"; export const captureLambdaPayloadEnvVar = "DD_CAPTURE_LAMBDA_PAYLOAD"; +export const traceManagedServicesEnvVar = "DD_TRACE_MANAGED_SERVICES"; export const siteURLEnvVar = "DD_SITE"; export const logLevelEnvVar = "DD_LOG_LEVEL"; export const logForwardingEnvVar = "DD_FLUSH_TO_LOG"; @@ -63,6 +62,7 @@ export const defaultConfig: Config = { apiKeyKMS: "", autoPatchHTTP: true, captureLambdaPayload: false, + createInferredSpan: true, debugLogging: false, enhancedMetrics: true, forceWrap: false, @@ -94,9 +94,8 @@ export function datadog( ): Handler { const finalConfig = getConfig(config); const metricsListener = new MetricsListener(new KMSService(), finalConfig); - const handlerName = getEnvValue(datadogHandlerEnvVar, getEnvValue("_HANDLER", "handler")); - const traceListener = new TraceListener(finalConfig, handlerName); + const traceListener = new TraceListener(finalConfig); // Only wrap the handler once unless forced const _ddWrappedKey = "_ddWrapped"; @@ -132,26 +131,12 @@ export function datadog( let localResult: TResult | undefined; let error: any; let didThrow = false; - try { result = await traceListener.onWrap(async (localEvent: TEvent, localContext: Context) => { try { localResult = await promHandler(localEvent, localContext); } finally { - if (traceListener.currentSpan && finalConfig.captureLambdaPayload) { - tagObject(traceListener.currentSpan, "function.request", localEvent); - tagObject(traceListener.currentSpan, "function.response", localResult); - } - if (traceListener.triggerTags) { - const statusCode = extractHTTPStatusCodeTag(traceListener.triggerTags, localResult); - if (statusCode) { - // Store the status tag in the listener to send to Xray on invocation completion - traceListener.triggerTags["http.status_code"] = statusCode; - if (traceListener.currentSpan) { - traceListener.currentSpan.setTag("http.status_code", statusCode); - } - } - } + traceListener.onEndingInvocation(localEvent, localResult, finalConfig.captureLambdaPayload); } return localResult; })(event, context); @@ -272,10 +257,15 @@ function getConfig(userConfig?: Partial): Config { } if (userConfig === undefined || userConfig.captureLambdaPayload === undefined) { - const result = getEnvValue(captureLambdaPayloadEnvVar, "false").toLocaleLowerCase(); + const result = getEnvValue(captureLambdaPayloadEnvVar, "false").toLowerCase(); config.captureLambdaPayload = result === "true"; } + if (userConfig === undefined || userConfig.createInferredSpan === undefined) { + const result = getEnvValue(traceManagedServicesEnvVar, "true").toLowerCase(); + config.createInferredSpan = result === "true"; + } + return config; } diff --git a/src/trace/context.spec.ts b/src/trace/context.spec.ts index 7f800367..13ddc650 100644 --- a/src/trace/context.spec.ts +++ b/src/trace/context.spec.ts @@ -15,7 +15,6 @@ import { convertToAPMParentID, convertToAPMTraceID, convertToSampleMode, - convertTraceContext, extractTraceContext, readTraceContextFromXray, readTraceFromEvent, @@ -120,38 +119,6 @@ describe("convertToSampleMode", () => { }); }); -describe("convertTraceContext", () => { - it("converts a valid xray trace header", () => { - const result = convertTraceContext({ - parentID: "0b11cc4230d3e09e", - sampled: 1, - traceID: "1-5ce31dc2-ac779014b90ce44db5e03875", - }); - expect(result).toEqual({ - parentID: "797643193680388254", - sampleMode: SampleMode.USER_KEEP, - traceID: "4110911582297405557", - source: Source.Xray, - }); - }); - it("returns undefined if traceID is invalid", () => { - const result = convertTraceContext({ - parentID: "0b11cc4230d3e09e", - sampled: 1, - traceID: "1-5ce31dc2", - }); - expect(result).toBeUndefined(); - }); - it("returns undefined if parentID is invalid", () => { - const result = convertTraceContext({ - parentID: "0b11cc4230d;09e", - sampled: 1, - traceID: "1-5ce31dc2-ac779014b90ce44db5e03875", - }); - expect(result).toBeUndefined(); - }); -}); - describe("readTraceContextFromXray", () => { afterEach(() => { process.env["_X_AMZN_TRACE_ID"] = undefined; @@ -253,6 +220,129 @@ describe("readTraceFromEvent", () => { source: Source.Event, }); }); + + it("can parse an SNS message source", () => { + const result = readTraceFromEvent({ + Records: [ + { + EventSource: "aws:sns", + EventVersion: "1.0", + EventSubscriptionArn: + "arn:aws:sns:sa-east-1:601427279990:aj-js-library-test-dev-solo-topic:1bd19208-a99a-46d9-8398-f90f8699c641", + Sns: { + Type: "Notification", + MessageId: "f19d39fa-8c61-5df9-8f49-639247b6cece", + TopicArn: "arn:aws:sns:sa-east-1:601427279990:aj-js-library-test-dev-solo-topic", + Subject: null, + Message: '{"hello":"there","ajTimestamp":1643039127879}', + Timestamp: "2022-01-24T15:45:27.968Z", + SignatureVersion: "1", + Signature: + "mzp2Ou0fASw4LYRxY6SSww7qFfofn4luCJBRaTjLpQ5uhwhsAUKdyLz9VPD+/dlRbi1ImsWtIZ7A+wxj1oV7Z2Gyu/N4RpGalae37+jTluDS7AhjgcD7Bs4bgQtFkCfMFEwbhICQfukLLzbwbgczZ4NTPn6zj5o28c5NBKSJMYSnLz82ohw77GgnZ/m26E32ZQNW4+VCEMINg9Ne2rHstwPWRXPr5xGTrx8jH8CNUZnVpFVfhU8o+OSeAdpzm2l99grHIo7qPhekERxANz6QHynMlhdzD3UNSgc3oZkamZban/NEKd4MKJzgNQdNOYVj3Kw6eF2ZweEoBQ5sSFK5fQ==", + SigningCertUrl: + "https://sns.sa-east-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem", + UnsubscribeUrl: + "https://sns.sa-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:sa-east-1:601427279990:aj-js-library-test-dev-solo-topic:1bd19208-a99a-46d9-8398-f90f8699c641", + MessageAttributes: { + _datadog: { + Type: "String", + Value: + '{"x-datadog-trace-id":"6966585609680374559","x-datadog-parent-id":"4297634551783724228","x-datadog-sampled":"1","x-datadog-sampling-priority":"1"}', + }, + }, + }, + }, + ], + }); + expect(result).toEqual({ + parentID: "4297634551783724228", + sampleMode: 1, + source: "event", + traceID: "6966585609680374559", + }); + }); + it("can read from SNS message delivered to SQS queue source", () => { + const result = readTraceFromEvent({ + Records: [ + { + messageId: "64812b68-4d9b-4dca-b3fb-9b18f255ee51", + receiptHandle: + "AQEBER6aRkfG8092GvkL7FRwCwbQ7LLDW9Tlk/CembqHe+suS2kfFxXiukomvaIN61QoyQMoRgWuV52SDkiQno2u+5hP64BDbmw+e/KR9ayvIfHJ3M6RfyQLaWNWm3hDFBCKTnBMVIxtdx0N9epZZewyokjKcrNYtmCghFgTCvZzsQkowi5rnoHAVHJ3je1c3bDnQ1KLrZFgajDnootYXDwEPuMq5FIxrf4EzTe0S7S+rnRm+GaQfeBLBVAY6dASL9usV3/AFRqDtaI7GKI+0F2NCgLlqj49VlPRz4ldhkGknYlKTZTluAqALWLJS62/J1GQo53Cs3nneJcmu5ajB2zzmhhRXoXINEkLhCD5ujZfcsw9H4xqW69Or4ECvlqx14bUU2rtMIW0QM2p7pEeXnyocymQv6m1te113eYWTVmaJ4I=", + body: '{\n "Type" : "Notification",\n "MessageId" : "0a0ab23e-4861-5447-82b7-e8094ff3e332",\n "TopicArn" : "arn:aws:sns:sa-east-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA",\n "Message" : "{\\"hello\\":\\"harv\\",\\"nice of you to join us\\":\\"david\\",\\"anotherThing\\":{\\"foo\\":\\"bar\\",\\"blah\\":null,\\"harv\\":123},\\"vals\\":[{\\"thingOne\\":1},{\\"thingTwo\\":2}],\\"ajTimestamp\\":1639777617957}",\n "Timestamp" : "2021-12-17T21:46:58.040Z",\n "SignatureVersion" : "1",\n "Signature" : "FR35/7E8C3LHEVk/rC4XxXlXwV/5mNkFNPgDhHSnJ2I6hIoSrTROAm7h5xm1PuBkAeFDvq0zofw91ouk9zZyvhdrMLFIIgrjEyNayRmEffmoEAkzLFUsgtQX7MmTl644r4NuWiM0Oiz7jueRvIcKXcZr7Nc6GJcWV1ymec8oOmuHNMisnPMxI07LIQVYSyAfv6P9r2jEWMVIukRoCzwTnRk4bUUYhPSGHI7OC3AsxxXBbv8snqTrLM/4z2rXCf6jHCKNxWeLlm9/45PphCkEyx5BWS4/71KaoMWUWy8+6CCsy+uF3XTCVmvSEYLyEwTSzOY+vCUjazrRW93498i70g==",\n "SigningCertURL" : "https://sns.sa-east-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem",\n "UnsubscribeURL" : "https://sns.sa-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:sa-east-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA:1290f550-9a8a-4e8f-a900-8f5f96dcddda",\n "MessageAttributes" : {\n "_datadog" : {"Type":"String","Value":"{\\"x-datadog-trace-id\\":\\"2776434475358637757\\",\\"x-datadog-parent-id\\":\\"4493917105238181843\\",\\"x-datadog-sampled\\":\\"1\\",\\"x-datadog-sampling-priority\\":\\"1\\"}"}\n }\n}', + attributes: { + ApproximateReceiveCount: "1", + SentTimestamp: "1639777618130", + SenderId: "AIDAIOA2GYWSHW4E2VXIO", + ApproximateFirstReceiveTimestamp: "1639777618132", + }, + messageAttributes: {}, + md5OfBody: "ee19d8b1377919239ad3fd5dabc33739", + eventSource: "aws:sqs", + eventSourceARN: "arn:aws:sqs:sa-east-1:601427279990:aj-js-library-test-dev-demo-queue", + awsRegion: "sa-east-1", + }, + ], + }); + }); + + it("can read context from EventBridge messages", () => { + const result = readTraceFromEvent({ + version: "0", + id: "bd3c8258-8d30-007c-2562-64715b2d0ea8", + "detail-type": "UserSignUp", + source: "my.event", + account: "601427279990", + time: "2022-01-24T16:00:10Z", + region: "sa-east-1", + resources: [], + detail: { + hello: "there", + _datadog: { + "x-datadog-trace-id": "5827606813695714842", + "x-datadog-parent-id": "4726693487091824375", + "x-datadog-sampled": "1", + "x-datadog-sampling-priority": "1", + }, + }, + }); + + expect(result).toEqual({ + parentID: "4726693487091824375", + sampleMode: 1, + source: "event", + traceID: "5827606813695714842", + }); + }); + + it("can read context from Kinesis messages", () => { + const result = readTraceFromEvent({ + Records: [ + { + kinesis: { + kinesisSchemaVersion: "1.0", + partitionKey: "cdbfd750-cec0-4f0f-a4b0-82ae6152c7fb", + sequenceNumber: "49625698045709644136382874226371117765484751339579768834", + data: "eyJJJ20gbWFkZSBvZiB3YXgsIExhcnJ5IjoiV2hhdCBhcmUgeW91IG1hZGUgb2Y/IiwiX2RhdGFkb2ciOnsieC1kYXRhZG9nLXRyYWNlLWlkIjoiNjY3MzA5NTE0MjIxMDM1NTM4IiwieC1kYXRhZG9nLXBhcmVudC1pZCI6IjEzNTA3MzUwMzU0OTc4MTE4MjgiLCJ4LWRhdGFkb2ctc2FtcGxlZCI6IjEiLCJ4LWRhdGFkb2ctc2FtcGxpbmctcHJpb3JpdHkiOiIxIn19", + approximateArrivalTimestamp: 1642518727.248, + }, + eventSource: "aws:kinesis", + eventID: "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + invokeIdentityArn: "arn:aws:iam::EXAMPLE", + eventVersion: "1.0", + eventName: "aws:kinesis:record", + eventSourceARN: "arn:aws:kinesis:EXAMPLE", + awsRegion: "us-east-1", + }, + ], + }); + expect(result).toEqual({ + parentID: "1350735035497811828", + sampleMode: 1, + source: "event", + traceID: "667309514221035538", + }); + }); + it("can read well formed headers with mixed casing", () => { const result = readTraceFromEvent({ headers: { @@ -678,18 +768,6 @@ describe("extractTraceContext", () => { const extractor = (event: any, context: Context) => { throw new Error("test"); - - const traceID = event.foo[traceIDHeader]; - const parentID = event.foo[parentIDHeader]; - const sampledHeader = event.foo[samplingPriorityHeader]; - const sampleMode = parseInt(sampledHeader, 10); - - return { - parentID, - sampleMode, - source: Source.Event, - traceID, - }; }; const result = extractTraceContext( diff --git a/src/trace/context.ts b/src/trace/context.ts index 27e95d9d..60bd646d 100644 --- a/src/trace/context.ts +++ b/src/trace/context.ts @@ -1,9 +1,16 @@ -import { Context, SQSEvent } from "aws-lambda"; +import { Context, EventBridgeEvent, KinesisStreamEvent, SNSEvent, SNSMessage, SQSEvent } from "aws-lambda"; import { BigNumber } from "bignumber.js"; import { randomBytes } from "crypto"; import { createSocket, Socket } from "dgram"; import { logDebug, logError } from "../utils"; -import { isAppSyncResolverEvent, isSQSEvent } from "../utils/event-type-guards"; +import { + isAppSyncResolverEvent, + isEventBridgeEvent, + isKinesisStreamEvent, + isSNSEvent, + isSNSSQSEvent, + isSQSEvent, +} from "../utils/event-type-guards"; import { awsXrayDaemonAddressEnvVar, parentIDHeader, @@ -196,27 +203,19 @@ export function readTraceFromAppSyncEvent(event: any): TraceContext | undefined } export function readTraceFromSQSEvent(event: SQSEvent): TraceContext | undefined { - if ( - event.Records[0].messageAttributes && - event.Records[0].messageAttributes._datadog && - event.Records[0].messageAttributes._datadog.stringValue - ) { + if (event.Records?.[0]?.messageAttributes?._datadog?.stringValue) { const traceHeaders = event.Records[0].messageAttributes._datadog.stringValue; try { const traceData = JSON.parse(traceHeaders); const traceID = traceData[traceIDHeader]; - if (typeof traceID !== "string") { - return; - } const parentID = traceData[parentIDHeader]; - if (typeof parentID !== "string") { - return; - } const sampledHeader = traceData[samplingPriorityHeader]; - if (typeof sampledHeader !== "string") { + + if (typeof traceID !== "string" || typeof parentID !== "string" || typeof sampledHeader !== "string") { return; } + const sampleMode = parseInt(sampledHeader, 10); const trace = { @@ -238,6 +237,136 @@ export function readTraceFromSQSEvent(event: SQSEvent): TraceContext | undefined return; } +export function readTraceFromSNSSQSEvent(event: SQSEvent): TraceContext | undefined { + if (event?.Records?.[0]?.body) { + try { + const parsedBody = JSON.parse(event.Records[0].body) as SNSMessage; + if ( + parsedBody.MessageAttributes && + parsedBody.MessageAttributes._datadog && + parsedBody.MessageAttributes._datadog.Value + ) { + const traceData = JSON.parse(parsedBody.MessageAttributes._datadog.Value); + const traceID = traceData[traceIDHeader]; + const parentID = traceData[parentIDHeader]; + const sampledHeader = traceData[samplingPriorityHeader]; + + if (typeof traceID !== "string" || typeof parentID !== "string" || typeof sampledHeader !== "string") { + return; + } + const sampleMode = parseInt(sampledHeader, 10); + + const trace = { + parentID, + sampleMode, + source: Source.Event, + traceID, + }; + logDebug(`extracted trace context from SNS SQS event`, { trace, event }); + return trace; + } + } catch (err) { + if (err instanceof Error) { + logError("Error parsing SNS SQS message trace data", err as Error); + } + return; + } + } +} + +export function readTraceFromKinesisEvent(event: KinesisStreamEvent): TraceContext | undefined { + if (event?.Records?.[0]?.kinesis?.data) { + try { + const parsedBody = JSON.parse(Buffer.from(event.Records[0].kinesis.data, "base64").toString("ascii")) as any; + if (parsedBody && parsedBody._datadog) { + const traceData = parsedBody._datadog; + const traceID = traceData[traceIDHeader]; + const parentID = traceData[parentIDHeader]; + const sampledHeader = traceData[samplingPriorityHeader]; + + if (typeof traceID !== "string" || typeof parentID !== "string" || typeof sampledHeader !== "string") { + return; + } + const sampleMode = parseInt(sampledHeader, 10); + + const trace = { + parentID, + sampleMode, + source: Source.Event, + traceID, + }; + logDebug(`extracted trace context from Kinesis event`, { trace }); + return trace; + } + } catch (err) { + if (err instanceof Error) { + logError("Error parsing Kinesis message trace data", err as Error); + } + return; + } + } +} + +export function readTraceFromEventbridgeEvent(event: EventBridgeEvent): TraceContext | undefined { + if (event?.detail?._datadog) { + try { + const traceData = event.detail._datadog; + const traceID = traceData[traceIDHeader]; + const parentID = traceData[parentIDHeader]; + const sampledHeader = traceData[samplingPriorityHeader]; + + if (typeof traceID !== "string" || typeof parentID !== "string" || typeof sampledHeader !== "string") { + return; + } + const sampleMode = parseInt(sampledHeader, 10); + + const trace = { + parentID, + sampleMode, + source: Source.Event, + traceID, + }; + logDebug(`extracted trace context from Eventbridge event`, { trace, event }); + return trace; + } catch (err) { + if (err instanceof Error) { + logError("Error parsing Eventbridge trace data", err as Error); + } + return; + } + } +} + +export function readTraceFromSNSEvent(event: SNSEvent): TraceContext | undefined { + if (event?.Records?.[0]?.Sns?.MessageAttributes?._datadog.Value) { + try { + const traceData = JSON.parse(event.Records[0].Sns.MessageAttributes._datadog.Value); + const traceID = traceData[traceIDHeader]; + const parentID = traceData[parentIDHeader]; + const sampledHeader = traceData[samplingPriorityHeader]; + + if (typeof traceID !== "string" || typeof parentID !== "string" || typeof sampledHeader !== "string") { + return; + } + const sampleMode = parseInt(sampledHeader, 10); + + const trace = { + parentID, + sampleMode, + source: Source.Event, + traceID, + }; + logDebug(`extracted trace context from SNS event`, { trace, event }); + return trace; + } catch (err) { + if (err instanceof Error) { + logError("Error parsing SNS SQS message trace data", err as Error); + } + return; + } + } +} + export function readTraceFromLambdaContext(context: any): TraceContext | undefined { if (!context || typeof context !== "object") { return; @@ -334,6 +463,13 @@ export function readTraceFromEvent(event: any): TraceContext | undefined { return readTraceFromHTTPEvent(event); } + if (isSNSEvent(event)) { + return readTraceFromSNSEvent(event); + } + if (isSNSSQSEvent(event)) { + return readTraceFromSNSSQSEvent(event); + } + if (isAppSyncResolverEvent(event)) { return readTraceFromAppSyncEvent(event); } @@ -341,6 +477,13 @@ export function readTraceFromEvent(event: any): TraceContext | undefined { if (isSQSEvent(event)) { return readTraceFromSQSEvent(event); } + if (isKinesisStreamEvent(event)) { + return readTraceFromKinesisEvent(event); + } + + if (isEventBridgeEvent(event)) { + return readTraceFromEventbridgeEvent(event); + } return; } @@ -379,7 +522,7 @@ export function readTraceContextFromXray(): TraceContext | undefined { return trace; } -export function parseXrayTraceContextHeader(header: string) { +function parseXrayTraceContextHeader(header: string) { // Example: Root=1-5e272390-8c398be037738dc042009320;Parent=94ae789b969f1cc5;Sampled=1 logDebug(`Reading trace context from env var ${header}`); const [root, parent, sampled] = header.split(";"); @@ -387,17 +530,9 @@ export function parseXrayTraceContextHeader(header: string) { return; } const [, xrayTraceID] = root.split("="); - if (xrayTraceID === undefined) { - return; - } - const [, xrayParentID] = parent.split("="); - if (xrayParentID === undefined) { - return; - } - const [, xraySampled] = sampled.split("="); - if (xraySampled === undefined) { + if (xraySampled === undefined || xrayParentID === undefined || xrayTraceID === undefined) { return; } return { @@ -455,22 +590,6 @@ export function readStepFunctionContextFromEvent(event: any): StepFunctionContex "step_function.step_name": stepName, }; } - -export function convertTraceContext(traceHeader: XRayTraceHeader): TraceContext | undefined { - const sampleMode = convertToSampleMode(traceHeader.sampled); - const traceID = convertToAPMTraceID(traceHeader.traceID); - const parentID = convertToAPMParentID(traceHeader.parentID); - if (traceID === undefined || parentID === undefined) { - return; - } - return { - parentID, - sampleMode, - source: Source.Xray, - traceID, - }; -} - export function convertToSampleMode(xraySampled: number): SampleMode { return xraySampled === 1 ? SampleMode.USER_KEEP : SampleMode.USER_REJECT; } diff --git a/src/trace/listener.spec.ts b/src/trace/listener.spec.ts index 5552a683..34ebb684 100644 --- a/src/trace/listener.spec.ts +++ b/src/trace/listener.spec.ts @@ -1,6 +1,8 @@ -import { TraceListener } from "./listener"; +import { TraceExtractor, TraceListener } from "./listener"; import { Source, ddtraceVersion } from "./constants"; import { datadogLambdaVersion } from "../constants"; +import { Context } from "aws-lambda"; +import { TraceHeaders } from "./trace-context-service"; let mockWrap: jest.Mock; let mockExtract: jest.Mock; @@ -32,6 +34,14 @@ jest.mock("./tracer-wrapper", () => { jest.mock("./trace-context-service", () => { class MockTraceContextService { + extractHeadersFromContext( + event: any, + context: Context, + extractor?: TraceExtractor, + ): Partial | undefined { + return mockTraceHeaders; + } + get traceSource() { return mockTraceSource; } @@ -48,6 +58,7 @@ describe("TraceListener", () => { const defaultConfig = { autoPatchHTTP: true, captureLambdaPayload: false, + createInferredSpan: true, mergeDatadogXrayTraces: false, injectLogContext: false, }; @@ -72,7 +83,7 @@ describe("TraceListener", () => { }); it("wraps dd-trace span around invocation", async () => { - const listener = new TraceListener(defaultConfig, "handler.my-handler"); + const listener = new TraceListener(defaultConfig); listener.onStartInvocation({}, context as any); const unwrappedFunc = () => {}; const wrappedFunc = listener.onWrap(unwrappedFunc); @@ -101,7 +112,7 @@ describe("TraceListener", () => { }); it("wraps dd-trace span around invocation, with trace context from event", async () => { - const listener = new TraceListener(defaultConfig, "handler.my-handler"); + const listener = new TraceListener(defaultConfig); mockTraceHeaders = { "x-datadog-parent-id": "797643193680388251", "x-datadog-sampling-priority": "2", @@ -138,7 +149,7 @@ describe("TraceListener", () => { }); it("wraps dd-trace span around invocation, without trace context from xray", async () => { - const listener = new TraceListener(defaultConfig, "handler.my-handler"); + const listener = new TraceListener(defaultConfig); mockTraceHeaders = { "x-datadog-parent-id": "797643193680388251", "x-datadog-sampling-priority": "2", @@ -174,7 +185,7 @@ describe("TraceListener", () => { }); it("wraps dd-trace span around invocation, with trace context from xray when mergeDatadogXrayTraces is enabled", async () => { - const listener = new TraceListener({ ...defaultConfig, mergeDatadogXrayTraces: true }, "handler.my-handler"); + const listener = new TraceListener({ ...defaultConfig, mergeDatadogXrayTraces: true }); mockTraceHeaders = { "x-datadog-parent-id": "797643193680388251", "x-datadog-sampling-priority": "2", @@ -212,7 +223,7 @@ describe("TraceListener", () => { }); it("wraps dd-trace span around invocation, with function alias", async () => { - const listener = new TraceListener(defaultConfig, "handler.my-handler"); + const listener = new TraceListener(defaultConfig); listener.onStartInvocation({}, contextWithFunctionAlias as any); const unwrappedFunc = () => {}; const wrappedFunc = listener.onWrap(unwrappedFunc); @@ -241,7 +252,7 @@ describe("TraceListener", () => { }); it("wraps dd-trace span around invocation, with function version", async () => { - const listener = new TraceListener(defaultConfig, "handler.my-handler"); + const listener = new TraceListener(defaultConfig); listener.onStartInvocation({}, contextWithFunctionVersion as any); const unwrappedFunc = () => {}; const wrappedFunc = listener.onWrap(unwrappedFunc); diff --git a/src/trace/listener.ts b/src/trace/listener.ts index 5f1fcf22..b135321a 100644 --- a/src/trace/listener.ts +++ b/src/trace/listener.ts @@ -3,21 +3,21 @@ import { Context } from "aws-lambda"; import { addLambdaFunctionTagsToXray, TraceContext, - extractTraceContext, readStepFunctionContextFromEvent, StepFunctionContext, } from "./context"; import { patchHttp, unpatchHttp } from "./patch-http"; import { TraceContextService } from "./trace-context-service"; -import { extractTriggerTags } from "./trigger"; +import { extractTriggerTags, extractHTTPStatusCodeTag } from "./trigger"; -import { logDebug } from "../utils"; +import { logDebug, tagObject } from "../utils"; import { didFunctionColdStart } from "../utils/cold-start"; import { datadogLambdaVersion } from "../constants"; import { Source, ddtraceVersion } from "./constants"; import { patchConsole } from "./patch-console"; import { SpanContext, TraceOptions, TracerWrapper } from "./tracer-wrapper"; - +import { SpanInferrer } from "./span-inferrer"; +import { SpanWrapper } from "./span-wrapper"; export type TraceExtractor = (event: any, context: Context) => TraceContext; export interface TraceConfig { @@ -30,6 +30,10 @@ export interface TraceConfig { * Whether to capture the lambda payload and response in Datadog. */ captureLambdaPayload: boolean; + /** + * Whether to create inferred spans for managed services + */ + createInferredSpan: boolean; /** * Whether to automatically patch console.log with Datadog's tracing ids. */ @@ -50,17 +54,20 @@ export class TraceListener { private context?: Context; private stepFunctionContext?: StepFunctionContext; private tracerWrapper: TracerWrapper; + private inferrer: SpanInferrer; + private inferredSpan?: SpanWrapper; + private wrappedCurrentSpan?: SpanWrapper; + private triggerTags?: { [key: string]: string }; + private lambdaSpanParentContext?: SpanContext; - public triggerTags?: { [key: string]: string }; public get currentTraceHeaders() { return this.contextService.currentTraceHeaders; } - public get currentSpan() { - return this.tracerWrapper.currentSpan; - } - constructor(private config: TraceConfig, private handlerName: string) { + + constructor(private config: TraceConfig) { this.tracerWrapper = new TracerWrapper(); this.contextService = new TraceContextService(this.tracerWrapper); + this.inferrer = new SpanInferrer(this.tracerWrapper); } public onStartInvocation(event: any, context: Context) { @@ -79,13 +86,62 @@ export class TraceListener { } else { logDebug("Not patching HTTP libraries", { autoPatchHTTP: this.config.autoPatchHTTP, tracerInitialized }); } - + const rootTraceHeaders = this.contextService.extractHeadersFromContext(event, context, this.config.traceExtractor); + // The aws.lambda span needs to have a parented to the Datadog trace context from the + // incoming event if available or the X-Ray trace context if hybrid tracing is enabled + let parentSpanContext: SpanContext | undefined; + if (this.contextService.traceSource === Source.Event || this.config.mergeDatadogXrayTraces) { + parentSpanContext = rootTraceHeaders ? this.tracerWrapper.extract(rootTraceHeaders) ?? undefined : undefined; + logDebug("Attempting to find parent for the aws.lambda span"); + } else { + logDebug("Didn't attempt to find parent for aws.lambda span", { + mergeDatadogXrayTraces: this.config.mergeDatadogXrayTraces, + traceSource: this.contextService.traceSource, + }); + } + if (this.config.createInferredSpan) { + this.inferredSpan = this.inferrer.createInferredSpan(event, context, parentSpanContext); + } + this.lambdaSpanParentContext = this.inferredSpan?.span || parentSpanContext; this.context = context; this.triggerTags = extractTriggerTags(event, context); - this.contextService.rootTraceContext = extractTraceContext(event, context, this.config.traceExtractor); this.stepFunctionContext = readStepFunctionContextFromEvent(event); } + /** + * onEndingInvocation runs after the user function has returned + * but before the wrapped function has returned + * this is needed to apply tags to the lambda span + * before it is flushed to logs or extension + * + * @param event + * @param result + * @param shouldTagPayload + */ + public onEndingInvocation(event: any, result: any, shouldTagPayload = false) { + // Guard clause if something has gone horribly wrong + // so we won't crash user code. + if (!this.tracerWrapper.currentSpan) return; + + this.wrappedCurrentSpan = new SpanWrapper(this.tracerWrapper.currentSpan, {}); + if (shouldTagPayload) { + tagObject(this.tracerWrapper.currentSpan, "function.request", event); + tagObject(this.tracerWrapper.currentSpan, "function.response", result); + } + + if (this.triggerTags) { + const statusCode = extractHTTPStatusCodeTag(this.triggerTags, result); + // Store the status tag in the listener to send to Xray on invocation completion + this.triggerTags["http.status_code"] = statusCode!; + if (this.tracerWrapper.currentSpan) { + this.tracerWrapper.currentSpan.setTag("http.status_code", statusCode); + } + if (this.inferredSpan) { + this.inferredSpan.setTag("http.status_code", statusCode); + } + } + } + public async onCompleteInvocation() { // Create a new dummy Datadog subsegment for function trigger tags so we // can attach them to X-Ray spans when hybrid tracing is used @@ -98,23 +154,14 @@ export class TraceListener { logDebug("Unpatching HTTP libraries"); unpatchHttp(); } + if (this.inferredSpan) { + logDebug("Finishing inferred span"); + const finishTime = this.inferredSpan.isAsync() ? this.wrappedCurrentSpan?.startTime() : Date.now(); + this.inferredSpan.finish(finishTime); + } } public onWrap any>(func: T): T { - // The aws.lambda span needs to have a parented to the Datadog trace context from the - // incoming event if available or the X-Ray trace context if hybrid tracing is enabled - let parentSpanContext: SpanContext | null = null; - if (this.contextService.traceSource === Source.Event || this.config.mergeDatadogXrayTraces) { - const rootTraceHeaders = this.contextService.rootTraceHeaders; - parentSpanContext = this.tracerWrapper.extract(rootTraceHeaders); - logDebug("Attempting to find parent for the aws.lambda span"); - } else { - logDebug("Didn't attempt to find parent for aws.lambda span", { - mergeDatadogXrayTraces: this.config.mergeDatadogXrayTraces, - traceSource: this.contextService.traceSource, - }); - } - const options: TraceOptions = {}; if (this.context) { logDebug("Creating the aws.lambda span"); @@ -147,16 +194,14 @@ export class TraceListener { ...this.stepFunctionContext, }; } - - if (parentSpanContext !== null) { - options.childOf = parentSpanContext; + if (this.lambdaSpanParentContext) { + options.childOf = this.lambdaSpanParentContext; } options.type = "serverless"; options.service = "aws.lambda"; if (this.context) { options.resource = this.context.functionName; } - return this.tracerWrapper.wrap("aws.lambda", options, func); } } diff --git a/src/trace/patch-console.spec.ts b/src/trace/patch-console.spec.ts index 8254c70f..1960922e 100644 --- a/src/trace/patch-console.spec.ts +++ b/src/trace/patch-console.spec.ts @@ -28,7 +28,7 @@ describe("patchConsole", () => { trace = jest.fn(); cnsole = { log, info, debug, error, warn, trace } as any; contextService = new TraceContextService(traceWrapper as any); - contextService.rootTraceContext = { + contextService["rootTraceContext"] = { parentID: "78910", sampleMode: SampleMode.USER_KEEP, source: Source.Event, @@ -72,7 +72,7 @@ describe("patchConsole", () => { }); it("doesn't inject trace context when none is present", () => { - contextService.rootTraceContext = undefined; + contextService["rootTraceContext"] = undefined; patchConsole(cnsole as any, contextService); cnsole.log("Hello"); expect(log).toHaveBeenCalledWith("Hello"); @@ -91,7 +91,7 @@ describe("patchConsole", () => { ); }); it("leaves empty message unmodified when there is no trace context", () => { - contextService.rootTraceContext = undefined; + contextService["rootTraceContext"] = undefined; patchConsole(cnsole as any, contextService); cnsole.log(); expect(log).toHaveBeenCalledWith(); diff --git a/src/trace/patch-http.spec.ts b/src/trace/patch-http.spec.ts index ecac7a81..e4ed86f8 100644 --- a/src/trace/patch-http.spec.ts +++ b/src/trace/patch-http.spec.ts @@ -28,7 +28,7 @@ describe("patchHttp", () => { beforeEach(() => { contextService = new TraceContextService(traceWrapper as any); - contextService.rootTraceContext = { + contextService["rootTraceContext"] = { parentID: "78910", sampleMode: SampleMode.USER_KEEP, source: Source.Event, @@ -124,7 +124,7 @@ describe("patchHttp", () => { it("doesn't inject tracing headers when context is empty", () => { nock("http://www.example.com").get("/").reply(200, {}); - contextService.rootTraceContext = undefined; + contextService["rootTraceContext"] = undefined; patchHttp(contextService); const req = http.request("http://www.example.com"); const headers = req.getHeaders(); diff --git a/src/trace/span-inferrer.spec.ts b/src/trace/span-inferrer.spec.ts new file mode 100644 index 00000000..1190264e --- /dev/null +++ b/src/trace/span-inferrer.spec.ts @@ -0,0 +1,240 @@ +import { SpanInferrer } from "./span-inferrer"; +import { SpanContext, TracerWrapper } from "./tracer-wrapper"; +const snssqsEvent = require("../../event_samples/snssqs.json"); +const snsEvent = require("../../event_samples/sns.json"); +const sqsEvent = require("../../event_samples/sqs.json"); +const ddbEvent = require("../../event_samples/dynamodb.json"); +const kinesisEvent = require("../../event_samples/kinesis.json"); +const eventBridgeEvent = require("../../event_samples/eventbridge.json"); +const webSocketEvent = require("../../event_samples/api-gateway-wss.json"); +const s3Event = require("../../event_samples/s3.json"); +const mockWrapper = { + startSpan: jest.fn(), +}; + +describe("SpanInferrer", () => { + beforeEach(() => { + mockWrapper.startSpan.mockClear(); + }); + + it("creates an inferred span for sns events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(snsEvent, {} as any, {} as SpanContext); + + expect(mockWrapper.startSpan).toBeCalledWith("aws.sns", { + childOf: {}, + startTime: 1643039127968, + tags: { + _inferred_span: { synchronicity: "async", tag_source: "self" }, + event_subscription_arn: "arn:aws:sns:us-east-1:123456789012:ExampleTopic", + message_id: "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + operation_name: "aws.sns", + request_id: undefined, + "resource.name": "ExampleTopic", + resource_names: "ExampleTopic", + service: "sns", + "span.type": "sns", + subject: "example subject", + topic_arn: "arn:aws:sns:us-east-1:123456789012:ExampleTopic", + topicname: "ExampleTopic", + type: "Notification", + }, + }); + }); + + it("creates an inferred span for sqs events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(sqsEvent, {} as any, {} as SpanContext); + + expect(mockWrapper.startSpan).toBeCalledWith("aws.sqs", { + childOf: {}, + startTime: 1523232000000, + tags: { + _inferred_span: { synchronicity: "async", tag_source: "self" }, + event_source_arn: "arn:aws:sqs:us-east-1:123456789012:MyQueue", + operation_name: "aws.sqs", + queuename: "MyQueue", + receipt_handle: "MessageReceiptHandle", + request_id: undefined, + "resource.name": "MyQueue", + resource_names: "MyQueue", + retry_count: 1, + sender_id: "123456789012", + service: "sqs", + "service.name": "MyQueue", + "span.type": "web", + }, + }); + }); + + it("creates an inferred span for ddb events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(ddbEvent, {} as any, {} as SpanContext); + + expect(mockWrapper.startSpan).toBeCalledWith("aws.dynamodb", { + childOf: {}, + startTime: 1428537600000, + tags: { + _inferred_span: { synchronicity: "async", tag_source: "self" }, + tablename: "ExampleTableWithStream", + event_id: "c4ca4238a0b923820dcc509a6f75849b", + event_name: "INSERT", + event_source_arn: + "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + event_version: "1.1", + operation_name: "aws.dynamodb", + request_id: undefined, + "resource.name": "INSERT ExampleTableWithStream", + resource_names: "INSERT ExampleTableWithStream", + service: "aws.dynamodb", + size_bytes: 26, + "span.type": "web", + stream_view_type: "NEW_AND_OLD_IMAGES", + }, + }); + }); + + it("creates an inferred span for kinesis events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(kinesisEvent, {} as any, {} as SpanContext); + + expect(mockWrapper.startSpan).toBeCalledWith("aws.kinesis", { + childOf: {}, + startTime: 1642518727248, + tags: { + _inferred_span: { synchronicity: "async", tag_source: "self" }, + event_id: "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + event_name: "aws:kinesis:record", + event_source_arn: "arn:aws:kinesis:EXAMPLE", + event_version: "1.0", + operation_name: "aws.kinesis", + partition_key: "cdbfd750-cec0-4f0f-a4b0-82ae6152c7fb", + request_id: undefined, + "resource.name": "EXAMPLE", + resource_names: "EXAMPLE", + service: "kinesis", + shardid: "49545115243490985018280067714973144582180062593244200961", + "span.type": "web", + streamname: "EXAMPLE", + }, + }); + }); + + it("creates an inferred span for sns sqs events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(snssqsEvent, {} as any, {} as SpanContext); + + expect(mockWrapper.startSpan.mock.calls).toEqual([ + [ + "aws.sns", + { + childOf: {}, + startTime: 1639777618040, + tags: { + _inferred_span: { synchronicity: "async", tag_source: "self" }, + message_id: "0a0ab23e-4861-5447-82b7-e8094ff3e332", + operation_name: "aws.sns", + "resource.name": "js-library-test-dev-demoTopic-15WGUVRCBMPAA", + resource_names: "js-library-test-dev-demoTopic-15WGUVRCBMPAA", + service: "sns", + "span.type": "sns", + subject: undefined, + topic_arn: "arn:aws:sns:sa-east-1:601427279990:js-library-test-dev-demoTopic-15WGUVRCBMPAA", + topicname: "js-library-test-dev-demoTopic-15WGUVRCBMPAA", + type: "Notification", + }, + }, + ], + [ + "aws.sqs", + { + childOf: undefined, + startTime: 1639777618130, + tags: { + _inferred_span: { synchronicity: "async", tag_source: "self" }, + event_source_arn: "arn:aws:sqs:sa-east-1:601427279990:aj-js-library-test-dev-demo-queue", + operation_name: "aws.sqs", + queuename: "aj-js-library-test-dev-demo-queue", + receipt_handle: + "AQEBER6aRkfG8092GvkL7FRwCwbQ7LLDW9Tlk/CembqHe+suS2kfFxXiukomvaIN61QoyQMoRgWuV52SDkiQno2u+5hP64BDbmw+e/KR9ayvIfHJ3M6RfyQLaWNWm3hDFBCKTnBMVIxtdx0N9epZZewyokjKcrNYtmCghFgTCvZzsQkowi5rnoHAVHJ3je1c3bDnQ1KLrZFgajDnootYXDwEPuMq5FIxrf4EzTe0S7S+rnRm+GaQfeBLBVAY6dASL9usV3/AFRqDtaI7GKI+0F2NCgLlqj49VlPRz4ldhkGknYlKTZTluAqALWLJS62/J1GQo53Cs3nneJcmu5ajB2zzmhhRXoXINEkLhCD5ujZfcsw9H4xqW69Or4ECvlqx14bUU2rtMIW0QM2p7pEeXnyocymQv6m1te113eYWTVmaJ4I=", + request_id: undefined, + "resource.name": "aj-js-library-test-dev-demo-queue", + resource_names: "aj-js-library-test-dev-demo-queue", + retry_count: 1, + sender_id: "AIDAIOA2GYWSHW4E2VXIO", + service: "sqs", + "service.name": "aj-js-library-test-dev-demo-queue", + "span.type": "web", + }, + }, + ], + ]); + }); + it("creates an inferred span for eventbridge events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(eventBridgeEvent, {} as any, {} as SpanContext); + + expect(mockWrapper.startSpan).toBeCalledWith("aws.eventbridge", { + childOf: {}, + startTime: 1643040010000, + tags: { + _inferred_span: { synchronicity: "async", tag_source: "self" }, + operation_name: "aws.eventbridge", + request_id: undefined, + "resource.name": "my.event", + resource_names: "my.event", + service: "eventbridge", + "span.type": "web", + }, + }); + }); + it("creates an inferred span for websocket events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(webSocketEvent, {} as any, {} as SpanContext); + + expect(mockWrapper.startSpan).toBeCalledWith("aws.apigateway", { + childOf: {}, + startTime: undefined, + tags: { + _inferred_span: { synchronicity: undefined, tag_source: "self" }, + apiid: "08se3mvh28", + connection_id: "MM0qReAFGjQCE-w=", + endpoint: "$connect", + event_type: "CONNECT", + "http.url": "08se3mvh28.execute-api.sa-east-1.amazonaws.com$connect", + message_direction: "IN", + operation_name: "aws.apigateway", + request_id: undefined, + "resource.name": "08se3mvh28.execute-api.sa-east-1.amazonaws.com $connect", + resource_names: "08se3mvh28.execute-api.sa-east-1.amazonaws.com $connect", + service: "08se3mvh28.execute-api.sa-east-1.amazonaws.com", + "service.name": "08se3mvh28.execute-api.sa-east-1.amazonaws.com", + "span.type": "http", + }, + }); + }); + it("creates an inferred span for s3 events", () => { + const inferrer = new SpanInferrer(mockWrapper as unknown as TracerWrapper); + inferrer.createInferredSpan(s3Event, {} as any, {} as SpanContext); + + expect(mockWrapper.startSpan).toBeCalledWith("aws.s3", { + childOf: {}, + startTime: 0, + tags: { + _inferred_span: { synchronicity: "async", tag_source: "self" }, + bucket_arn: "arn:aws:s3:::example-bucket", + bucketname: "example-bucket", + event_name: "ObjectCreated:Put", + object_etag: "0123456789abcdef0123456789abcdef", + object_key: "test/key", + object_size: 1024, + operation_name: "aws.s3", + request_id: undefined, + "resource.name": "example-bucket", + resource_names: "example-bucket", + service: "s3", + "span.type": "web", + }, + }); + }); +}); diff --git a/src/trace/span-inferrer.ts b/src/trace/span-inferrer.ts new file mode 100644 index 00000000..b4649a31 --- /dev/null +++ b/src/trace/span-inferrer.ts @@ -0,0 +1,401 @@ +import { + Context, + DynamoDBStreamEvent, + EventBridgeEvent, + KinesisStreamEvent, + S3CreateEvent, + SNSEvent, + SNSMessage, + SQSEvent, +} from "aws-lambda"; +import { SpanContext, SpanOptions, TracerWrapper } from "./tracer-wrapper"; +import { eventSources, parseEventSource } from "./trigger"; +import { SpanWrapper } from "./span-wrapper"; +export class SpanInferrer { + traceWrapper: TracerWrapper; + constructor(traceWrapper: TracerWrapper) { + this.traceWrapper = traceWrapper; + } + + public createInferredSpan(event: any, context: Context | undefined, parentSpanContext: SpanContext | undefined): any { + const eventSource = parseEventSource(event); + if (eventSource === eventSources.apiGateway) { + return this.createInferredSpanForApiGateway(event, context, parentSpanContext); + } + if (eventSource === eventSources.sns) { + return this.createInferredSpanForSns(event, context, parentSpanContext); + } + if (eventSource === eventSources.dynamoDB) { + return this.createInferredSpanForDynamoDBStreamEvent(event, context, parentSpanContext); + } + if (eventSource === eventSources.sqs) { + return this.createInferredSpanForSqs(event, context, parentSpanContext); + } + if (eventSource === eventSources.kinesis) { + return this.createInferredSpanForKinesis(event, context, parentSpanContext); + } + if (eventSource === eventSources.s3) { + return this.createInferredSpanForS3(event, context, parentSpanContext); + } + if (eventSource === eventSources.eventBridge) { + return this.createInferredSpanForEventBridge(event, context, parentSpanContext); + } + } + + isApiGatewayAsync(event: any): boolean { + return ( + event.headers && event.headers["X-Amz-Invocation-Type"] && event.headers["X-Amz-Invocation-Type"] === "Event" + ); + } + + createInferredSpanForApiGateway( + event: any, + context: Context | undefined, + parentSpanContext: SpanContext | undefined, + ): SpanWrapper { + const options: SpanOptions = {}; + const domain = event.requestContext.domainName; + const path = event.rawPath || event.requestContext.routeKey; + let method; + if (event.requestContext.httpMethod) { + method = event.requestContext.httpMethod; + } else if (event.requestContext.http) { + method = event.requestContext.http.method; + } + const resourceName = [domain, path].join(" "); + options.tags = { + operation_name: "aws.apigateway", + "http.url": domain + path, + endpoint: path, + resource_names: resourceName, + request_id: context?.awsRequestId, + "span.type": "http", + "resource.name": resourceName, + "service.name": domain, + apiid: event.requestContext.apiId, + service: domain, + _inferred_span: { + tag_source: "self", + synchronicity: this.isApiGatewayAsync(event), + }, + }; + // Set APIGW v1 or v1 metadata + if (method) { + options.tags["http.method"] = method; + options.tags.stage = event.requestContext.stage; + options.tags.domain_name = domain; + } + // Set websocket metadata + if (event.requestContext.messageDirection) { + options.tags.message_direction = event.requestContext.messageDirection; + options.tags.connection_id = event.requestContext.connectionId; + options.tags.event_type = event.requestContext.eventType; + } + if (parentSpanContext) { + options.childOf = parentSpanContext; + } + options.startTime = event.requestContext.timeEpoch; + const spanWrapperOptions = { + isAsync: this.isApiGatewayAsync(event), + }; + return new SpanWrapper(this.traceWrapper.startSpan("aws.apigateway", options), spanWrapperOptions); + } + + createInferredSpanForDynamoDBStreamEvent( + event: any, + context: Context | undefined, + parentSpanContext: SpanContext | undefined, + ): SpanWrapper { + const options: SpanOptions = {}; + const { Records } = event as DynamoDBStreamEvent; + const referenceRecord = Records[0]; + const { eventSourceARN, eventName, eventVersion, eventID, dynamodb } = referenceRecord; + const [tableArn, tableName] = eventSourceARN?.split("/") || [undefined, undefined]; + const resourceName = `${eventName} ${tableName}`; + options.tags = { + operation_name: "aws.dynamodb", + tablename: tableName, + resource_names: resourceName, + request_id: context?.awsRequestId, + "span.type": "web", + "resource.name": resourceName, + service: "aws.dynamodb", + _inferred_span: { + tag_source: "self", + synchronicity: "async", + }, + event_name: eventName, + event_version: eventVersion, + event_source_arn: eventSourceARN, + event_id: eventID, + }; + if (dynamodb) { + options.tags.stream_view_type = dynamodb.StreamViewType; + options.tags.size_bytes = dynamodb.SizeBytes; + } + if (parentSpanContext) { + options.childOf = parentSpanContext; + } + options.startTime = Number(referenceRecord.dynamodb?.ApproximateCreationDateTime) * 1000; + const spanWrapperOptions = { + isAsync: true, + }; + return new SpanWrapper(this.traceWrapper.startSpan("aws.dynamodb", options), spanWrapperOptions); + } + + createInferredSpanForSns( + event: any, + context: Context | undefined, + parentSpanContext: SpanContext | undefined, + ): SpanWrapper { + const options: SpanOptions = {}; + const { Records } = event as SNSEvent; + const referenceRecord = Records[0]; + const { + EventSubscriptionArn, + Sns: { TopicArn, Timestamp, Type, Subject, MessageId }, + } = referenceRecord; + const topicName = TopicArn?.split(":").pop(); + const resourceName = topicName; + options.tags = { + operation_name: "aws.sns", + resource_names: resourceName, + request_id: context?.awsRequestId, + "span.type": "sns", + "resource.name": resourceName, + service: "sns", + _inferred_span: { + tag_source: "self", + synchronicity: "async", + }, + type: Type, + subject: Subject, + message_id: MessageId, + topicname: topicName, + topic_arn: TopicArn, + event_subscription_arn: EventSubscriptionArn, + }; + if (parentSpanContext) { + options.childOf = parentSpanContext; + } + options.startTime = Date.parse(Timestamp); + const spanWrapperOptions = { + isAsync: true, + }; + return new SpanWrapper(this.traceWrapper.startSpan("aws.sns", options), spanWrapperOptions); + } + + createInferredSpanForSqsSns( + event: SNSMessage, + context: Context | undefined, + parentSpanContext: SpanContext | undefined, + ): SpanWrapper { + const options: SpanOptions = {}; + const { TopicArn, Timestamp, Type, Subject, MessageId } = event; + const topicName = TopicArn?.split(":").pop(); + const resourceName = topicName; + options.tags = { + operation_name: "aws.sns", + resource_names: resourceName, + "span.type": "sns", + "resource.name": resourceName, + service: "sns", + _inferred_span: { + tag_source: "self", + synchronicity: "async", + }, + type: Type, + subject: Subject, + message_id: MessageId, + topicname: topicName, + topic_arn: TopicArn, + }; + if (parentSpanContext) { + options.childOf = parentSpanContext; + } + options.startTime = Date.parse(Timestamp); + const spanWrapperOptions = { + isAsync: true, + }; + return new SpanWrapper(this.traceWrapper.startSpan("aws.sns", options), spanWrapperOptions); + } + + createInferredSpanForSqs( + event: any, + context: Context | undefined, + parentSpanContext: SpanContext | undefined, + ): SpanWrapper { + const options: SpanOptions = {}; + const { Records } = event as SQSEvent; + const referenceRecord = Records[0]; + const { + attributes: { SentTimestamp, ApproximateReceiveCount, SenderId }, + eventSourceARN, + receiptHandle, + body, + } = referenceRecord; + const queueName = eventSourceARN?.split(":").pop(); + const resourceName = queueName; + options.tags = { + operation_name: "aws.sqs", + resource_names: resourceName, + request_id: context?.awsRequestId, + "span.type": "web", + "resource.name": resourceName, + "service.name": resourceName, + service: "sqs", + _inferred_span: { + tag_source: "self", + synchronicity: "async", + }, + queuename: queueName, + event_source_arn: eventSourceARN, + receipt_handle: receiptHandle, + sender_id: SenderId, + }; + if (ApproximateReceiveCount && Number(ApproximateReceiveCount) > 0) { + options.tags.retry_count = Number(ApproximateReceiveCount); + } + // Check if sqs message was from sns + // If so, unpack and look at timestamp + // create further upstream sns span and finish/attach it here + let upstreamSnsSpan: SpanWrapper | null = null; + try { + let upstreamSnsMessage: SNSMessage; + upstreamSnsMessage = JSON.parse(body); + if (upstreamSnsMessage && upstreamSnsMessage.TopicArn && upstreamSnsMessage.Timestamp) { + upstreamSnsSpan = this.createInferredSpanForSqsSns(upstreamSnsMessage, context, parentSpanContext); + upstreamSnsSpan.finish(Number(SentTimestamp)); + } + } catch (e) { + // Pass, it's a raw SQS message + } + options.childOf = upstreamSnsSpan ? upstreamSnsSpan.span : parentSpanContext; + + options.startTime = Number(SentTimestamp); + + const spanWrapperOptions = { + isAsync: true, + }; + return new SpanWrapper(this.traceWrapper.startSpan("aws.sqs", options), spanWrapperOptions); + } + + createInferredSpanForKinesis( + event: any, + context: Context | undefined, + parentSpanContext: SpanContext | undefined, + ): SpanWrapper { + const options: SpanOptions = {}; + const { Records } = event as KinesisStreamEvent; + const referenceRecord = Records[0]; + const { + kinesis: { approximateArrivalTimestamp, partitionKey }, + eventSourceARN, + eventName, + eventVersion, + eventID, + } = referenceRecord; + const streamName = eventSourceARN?.split(":").pop(); + const shardId = eventID.split(":").pop(); + options.tags = { + operation_name: "aws.kinesis", + resource_names: streamName, + request_id: context?.awsRequestId, + "span.type": "web", + "resource.name": streamName, + service: "kinesis", + _inferred_span: { + tag_source: "self", + synchronicity: "async", + }, + streamname: streamName, + event_id: eventID, + event_name: eventName, + event_source_arn: eventSourceARN, + event_version: eventVersion, + partition_key: partitionKey, + shardid: shardId, + }; + if (parentSpanContext) { + options.childOf = parentSpanContext; + } + options.startTime = Number(approximateArrivalTimestamp) * 1000; + const spanWrapperOptions = { + isAsync: true, + }; + return new SpanWrapper(this.traceWrapper.startSpan("aws.kinesis", options), spanWrapperOptions); + } + + createInferredSpanForS3( + event: any, + context: Context | undefined, + parentSpanContext: SpanContext | undefined, + ): SpanWrapper { + const options: SpanOptions = {}; + const { Records } = event as S3CreateEvent; + const referenceRecord = Records[0]; + const { + s3: { + bucket: { name: bucketName, arn }, + object: { key, size, eTag }, + }, + eventTime, + eventName, + } = referenceRecord; + options.tags = { + operation_name: "aws.s3", + resource_names: bucketName, + request_id: context?.awsRequestId, + "span.type": "web", + "resource.name": bucketName, + service: "s3", + _inferred_span: { + tag_source: "self", + synchronicity: "async", + }, + bucketname: bucketName, + bucket_arn: arn, + event_name: eventName, + object_key: key, + object_size: size, + object_etag: eTag, + }; + if (parentSpanContext) { + options.childOf = parentSpanContext; + } + options.startTime = Date.parse(eventTime); + const spanWrapperOptions = { + isAsync: true, + }; + return new SpanWrapper(this.traceWrapper.startSpan("aws.s3", options), spanWrapperOptions); + } + + createInferredSpanForEventBridge( + event: any, + context: Context | undefined, + parentSpanContext: SpanContext | undefined, + ): SpanWrapper { + const options: SpanOptions = {}; + const { time, source } = event as EventBridgeEvent; + options.tags = { + operation_name: "aws.eventbridge", + resource_names: source, + request_id: context?.awsRequestId, + "span.type": "web", + "resource.name": source, + service: "eventbridge", + _inferred_span: { + tag_source: "self", + synchronicity: "async", + }, + }; + if (parentSpanContext) { + options.childOf = parentSpanContext; + } + options.startTime = Date.parse(time); + const spanWrapperOptions = { + isAsync: true, + }; + return new SpanWrapper(this.traceWrapper.startSpan("aws.eventbridge", options), spanWrapperOptions); + } +} diff --git a/src/trace/span-wrapper.ts b/src/trace/span-wrapper.ts new file mode 100644 index 00000000..c68412a5 --- /dev/null +++ b/src/trace/span-wrapper.ts @@ -0,0 +1,33 @@ +export type SpanWrapperOptions = { + isAsync?: boolean; +}; + +export class SpanWrapper { + span: any; + public options: SpanWrapperOptions; + + constructor(span: any, options: SpanWrapperOptions) { + this.span = span; + this.options = options; + } + + public isAsync(): boolean { + return this.options.isAsync || false; + } + + public startTime(): number { + return this.span._startTime; + } + + public endTime(): number { + return this.span._endTime; + } + + public finish(timestamp = Date.now()): void { + this.span.finish(timestamp); + } + + public setTag(tagName: string, val: any): void { + this.span.setTag(tagName, val); + } +} diff --git a/src/trace/trace-context-service.spec.ts b/src/trace/trace-context-service.spec.ts index d301e6aa..64f03992 100644 --- a/src/trace/trace-context-service.spec.ts +++ b/src/trace/trace-context-service.spec.ts @@ -25,7 +25,7 @@ describe("TraceContextService", () => { sampleMode: SampleMode.AUTO_KEEP, source: Source.Event, }; - traceContextService.rootTraceContext = { + traceContextService["rootTraceContext"] = { traceID: "123456", parentID: "abcdef", sampleMode: SampleMode.AUTO_KEEP, @@ -43,7 +43,7 @@ describe("TraceContextService", () => { mockXRaySegment = { id: "0b11cc", }; - traceContextService.rootTraceContext = { + traceContextService["rootTraceContext"] = { traceID: "123456", parentID: "abcdef", sampleMode: SampleMode.AUTO_KEEP, @@ -58,7 +58,7 @@ describe("TraceContextService", () => { }); it("uses parent trace parent id when no datadog trace context is available and xray throws", () => { mockXRayShouldThrow = true; - traceContextService.rootTraceContext = { + traceContextService["rootTraceContext"] = { traceID: "123456", parentID: "abcdef", sampleMode: SampleMode.AUTO_KEEP, diff --git a/src/trace/trace-context-service.ts b/src/trace/trace-context-service.ts index e88d35a7..bdec7989 100644 --- a/src/trace/trace-context-service.ts +++ b/src/trace/trace-context-service.ts @@ -1,8 +1,9 @@ +import { Context } from "aws-lambda"; import { logDebug } from "../utils"; import { parentIDHeader, samplingPriorityHeader, traceIDHeader } from "./constants"; -import { convertToAPMParentID, TraceContext } from "./context"; +import { TraceContext, extractTraceContext } from "./context"; import { TracerWrapper } from "./tracer-wrapper"; - +import { TraceExtractor } from "./listener"; /** * Headers that can be added to a request. */ @@ -16,10 +17,19 @@ export interface TraceHeaders { * Service for retrieving the latest version of the request context from xray. */ export class TraceContextService { - public rootTraceContext?: TraceContext; + private rootTraceContext?: TraceContext; constructor(private tracerWrapper: TracerWrapper) {} + extractHeadersFromContext( + event: any, + context: Context, + extractor?: TraceExtractor, + ): Partial | undefined { + this.rootTraceContext = extractTraceContext(event, context, extractor); + return this.currentTraceHeaders; + } + get currentTraceContext(): TraceContext | undefined { if (this.rootTraceContext === undefined) { return; diff --git a/src/trace/tracer-wrapper.ts b/src/trace/tracer-wrapper.ts index 74b314d5..3022d71d 100644 --- a/src/trace/tracer-wrapper.ts +++ b/src/trace/tracer-wrapper.ts @@ -8,6 +8,14 @@ export interface SpanContext { toSpanId(): string; } +export interface SpanOptions { + childOf?: SpanContext; + tags?: { [key: string]: any }; + startTime?: number; + service?: string; + type?: string; +} + export interface TraceOptions { resource?: string; service?: string; @@ -61,6 +69,13 @@ export class TracerWrapper { return this.tracer.wrap(name, options, fn); } + public startSpan any>(name: string, options: TraceOptions): T | null { + if (!this.isTracerAvailable) { + return null; + } + return this.tracer.startSpan(name, options); + } + public traceContext(): TraceContext | undefined { if (!this.isTracerAvailable) { return; diff --git a/src/trace/trigger.spec.ts b/src/trace/trigger.spec.ts index 56ebe8f1..e3384b32 100644 --- a/src/trace/trigger.spec.ts +++ b/src/trace/trigger.spec.ts @@ -146,7 +146,14 @@ describe("parseEventSource", () => { for (let response of responses) { const statusCode = extractHTTPStatusCodeTag(triggerTags, response.responseBody); // We should always return a status code for API Gateway and ALB - if (["api-gateway-v1.json", "api-gateway-v2.json", "application-load-balancer.json"].includes(event.file)) { + if ( + [ + "api-gateway-v1.json", + "api-gateway-v2.json", + "application-load-balancer.json", + "lambda-function-urls.json", + ].includes(event.file) + ) { expect(statusCode).toEqual(response.expectedStatusCode); } else { expect(statusCode).toBeUndefined(); diff --git a/src/trace/trigger.ts b/src/trace/trigger.ts index 8714fa3d..0c55796d 100644 --- a/src/trace/trigger.ts +++ b/src/trace/trigger.ts @@ -11,13 +11,18 @@ import { S3Event, SNSEvent, SQSEvent, + EventBridgeEvent, } from "aws-lambda"; import * as eventType from "../utils/event-type-guards"; import { logError } from "../utils"; import { gunzipSync } from "zlib"; function isHTTPTriggerEvent(eventSource: string | undefined) { - return eventSource === "api-gateway" || eventSource === "application-load-balancer"; + return ( + eventSource === "api-gateway" || + eventSource === "application-load-balancer" || + eventSource === "lambda-function-url" + ); } function getAWSPartitionByRegion(region: string) { @@ -72,6 +77,25 @@ function extractSQSEventARN(event: SQSEvent) { return event.Records[0].eventSourceARN; } +function extractEventBridgeARN(event: EventBridgeEvent) { + return event.source; +} + +export enum eventSources { + apiGateway = "api-gateway", + applicationLoadBalancer = "application-load-balancer", + cloudFront = "cloudfront", + cloudWatchEvents = "cloudwatch-events", + cloudWatchLogs = "cloudwatch-logs", + cloudWatch = "cloudwatch", + dynamoDB = "dynamodb", + eventBridge = "eventbridge", + kinesis = "kinesis", + s3 = "s3", + sns = "sns", + sqs = "sqs", +} + /** * parseEventSource parses the triggering event to determine the source * Possible Returns: @@ -79,48 +103,53 @@ function extractSQSEventARN(event: SQSEvent) { * cloudwatch-events | cloudfront | dynamodb | kinesis | s3 | sns | sqs */ export function parseEventSource(event: any) { - let eventSource: string | undefined; - - if (eventType.isAPIGatewayEvent(event) || eventType.isAPIGatewayEventV2(event)) { - eventSource = "api-gateway"; + if ( + eventType.isAPIGatewayEvent(event) || + eventType.isAPIGatewayEventV2(event) || + eventType.isAPIGatewayWebsocketEvent(event) + ) { + return eventSources.apiGateway; } if (eventType.isALBEvent(event)) { - eventSource = "application-load-balancer"; + return eventSources.applicationLoadBalancer; } if (eventType.isCloudWatchLogsEvent(event)) { - eventSource = "cloudwatch-logs"; + return eventSources.cloudWatchLogs; } if (eventType.isCloudWatchEvent(event)) { - eventSource = "cloudwatch-events"; + return eventSources.cloudWatchEvents; } if (eventType.isCloudFrontRequestEvent(event)) { - eventSource = "cloudfront"; + return eventSources.cloudFront; } if (eventType.isDynamoDBStreamEvent(event)) { - eventSource = "dynamodb"; + return eventSources.dynamoDB; } if (eventType.isKinesisStreamEvent(event)) { - eventSource = "kinesis"; + return eventSources.kinesis; } if (eventType.isS3Event(event)) { - eventSource = "s3"; + return eventSources.s3; } if (eventType.isSNSEvent(event)) { - eventSource = "sns"; + return eventSources.sns; } if (eventType.isSQSEvent(event)) { - eventSource = "sqs"; + return eventSources.sqs; + } + + if (eventType.isEventBridgeEvent(event)) { + return eventSources.eventBridge; } - return eventSource; } /** @@ -186,6 +215,11 @@ export function parseEventSourceARN(source: string | undefined, event: any, cont if (source === "kinesis") { eventSourceARN = extractKinesisStreamEventARN(event); } + + if (source === "eventbridge") { + eventSourceARN = extractEventBridgeARN(event); + } + return eventSourceARN; } @@ -263,7 +297,10 @@ export function extractTriggerTags(event: any, context: Context) { * extractHTTPStatusCode extracts a status code from the response if the Lambda was triggered * by API Gateway or ALB */ -export function extractHTTPStatusCodeTag(triggerTags: { [key: string]: string }, result: any) { +export function extractHTTPStatusCodeTag( + triggerTags: { [key: string]: string } | undefined, + result: any, +): string | undefined { let eventSource: string | undefined; triggerTags ? (eventSource = triggerTags["function_trigger.event_source"]) : (eventSource = undefined); if (!isHTTPTriggerEvent(eventSource)) { diff --git a/src/utils/event-type-guards.ts b/src/utils/event-type-guards.ts index 46caeffd..9e47b8f5 100644 --- a/src/utils/event-type-guards.ts +++ b/src/utils/event-type-guards.ts @@ -11,6 +11,8 @@ import { S3Event, SNSEvent, SQSEvent, + SNSMessage, + EventBridgeEvent, } from "aws-lambda"; import { apiGatewayEventV2 } from "../trace/constants"; @@ -20,10 +22,17 @@ export function isAPIGatewayEvent(event: any): event is APIGatewayEvent { export function isAPIGatewayEventV2(event: any): event is APIGatewayProxyEventV2 { return ( - event.requestContext !== undefined && event.version === apiGatewayEventV2 && event.rawQueryString !== undefined + event.requestContext !== undefined && + event.version === apiGatewayEventV2 && + event.rawQueryString !== undefined && + !event.requestContext.domainName.includes("lambda-url") ); } +export function isAPIGatewayWebsocketEvent(event: any): event is any { + return event.requestContext !== undefined && event.requestContext.messageDirection !== undefined; +} + export function isALBEvent(event: any): event is ALBEvent { return event.requestContext !== undefined && event.requestContext.elb !== undefined; } @@ -60,6 +69,24 @@ export function isSQSEvent(event: any): event is SQSEvent { return Array.isArray(event.Records) && event.Records.length > 0 && event.Records[0].eventSource === "aws:sqs"; } +export function isSNSSQSEvent(event: any): event is SQSEvent { + if (Array.isArray(event.Records) && event.Records.length > 0 && event.Records[0].eventSource === "aws:sqs") { + try { + const body = JSON.parse(event.Records[0].body) as SNSMessage; + if (body.Type === "Notification" && body.TopicArn) { + return true; + } + } catch (e) { + return false; + } + } + return false; +} + export function isAppSyncResolverEvent(event: any): event is AppSyncResolverEvent { return event.info !== undefined && event.info.selectionSetGraphQL !== undefined; } + +export function isEventBridgeEvent(event: any): event is EventBridgeEvent { + return event["detail-type"] !== undefined; +}