Skip to content

Inferred Spans #254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Feb 7, 2022
Merged

Inferred Spans #254

merged 39 commits into from
Feb 7, 2022

Conversation

astuyve
Copy link
Contributor

@astuyve astuyve commented Dec 21, 2021

What does this PR do?

Creates inferred spans for:

  • APIGW v1
  • APIGW HTTPAPI v2
  • APIGW Websocket v2
  • DynamoDB stream
  • EventBridge events
  • S3 events
  • SNS
  • SQS
  • SNS + SQS
  • Kinesis stream events

Motivation

Testing Guidelines

Additional Notes

Types of Changes

  • Bug fix
  • New feature
  • Breaking change
  • Misc (docs, refactoring, dependency upgrade, etc.)

Check all that apply

  • This PR's description is comprehensive
  • This PR contains breaking changes that are documented in the description
  • This PR introduces new APIs or parameters that are documented and unlikely to change in the foreseeable future
  • This PR impacts documentation, and it has been updated (or a ticket has been logged)
  • This PR's changes are covered by the automated tests
  • This PR collects user input/sensitive content into Datadog
  • This PR passes the integration tests (ask a Datadog member to run the tests)

@astuyve astuyve requested a review from a team as a code owner December 21, 2021 16:40
@astuyve astuyve changed the title Aj/feat support coldstart spans Inferred Spans Dec 21, 2021
@codecov-commenter
Copy link

codecov-commenter commented Dec 21, 2021

Codecov Report

Merging #254 (df030e1) into main (0031257) will decrease coverage by 1.30%.
The diff coverage is 77.77%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #254      +/-   ##
==========================================
- Coverage   83.56%   82.26%   -1.31%     
==========================================
  Files          35       37       +2     
  Lines        1339     1618     +279     
  Branches      284      349      +65     
==========================================
+ Hits         1119     1331     +212     
- Misses        196      238      +42     
- Partials       24       49      +25     
Impacted Files Coverage Δ
src/trace/tracer-wrapper.ts 86.48% <25.00%> (-7.46%) ⬇️
src/trace/listener.ts 75.55% <45.45%> (-18.82%) ⬇️
src/trace/span-wrapper.ts 64.28% <64.28%> (ø)
src/trace/context.ts 85.03% <69.13%> (-4.74%) ⬇️
src/trace/span-inferrer.ts 85.62% <85.62%> (ø)
src/trace/trigger.ts 87.32% <90.90%> (-0.49%) ⬇️
src/index.ts 87.15% <100.00%> (+5.41%) ⬆️
src/trace/trace-context-service.ts 86.66% <100.00%> (-5.65%) ⬇️
src/utils/event-type-guards.ts 100.00% <100.00%> (ø)
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0031257...df030e1. Read the comment docs.

@@ -238,6 +238,89 @@ export function readTraceFromSQSEvent(event: SQSEvent): TraceContext | undefined
return;
}

export function readTraceFromSNSSQSEvent(event: SQSEvent): TraceContext | undefined {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, in dd-lambda-js do we have multiple readTraceFrom...Event functions that are specifically tailored for each aws managed service?
If so then why do we have a readTraceFromSQSEvent function and readTraceFromSNSSQSEvent (line 241)?

Is an SQS event and SNSSQS event different? Therefore needing two separate functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah they are a bit different, basically the SNSSQS event is not batched, but a typical SNS event has an array of Records with each SNS message inside it. I think we could probably refactor this though, going to take a closer look

@astuyve astuyve requested review from agocs and hghotra January 20, 2022 18:28
export function readTraceFromEventbridgeEvent(event: EventBridgeEvent<any, any>): TraceContext | undefined {
if (event.detail && event.detail._datadog) {
try {
const traceData = event.detail._datadog;
Copy link
Contributor

@agocs agocs Jan 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we anticipate having a _datadog key in the event.detail. The way it was implemented in dd-trace-py, we just have x-datadog-trace-id, etc. We could change it either way, but it's important we're consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, I'd been following the convention for sns/sqs where we stick a _datadog string map in the message attributes, but I can change it to this. But yes, we need stay consistent here.

if (isSNSEvent(event)) {
return readTraceFromSNSEvent(event);
}
// TODO [astuyve] SNSSQS must be before SQS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to move isSNSSQSEvent in this case

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if tests hit this section of code? It might be important to ensure we get it right and keep getting it right in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add better tests for this to ensure it's hit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove this TODO since it looks like isSNSSQSEvent is being hit before isSQSEvent and just leave the comment.

@astuyve astuyve requested a review from zARODz11z February 1, 2022 18:43
} from "./utils";

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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update the README with some information about this env var.

Comment on lines +253 to +255
parsedBody.MessageAttributes &&
parsedBody.MessageAttributes._datadog &&
parsedBody.MessageAttributes._datadog.Value
Copy link
Contributor

@hghotra hghotra Feb 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: conditions like these can be simplified to: parsedBody?.MessageAttributes?._datadog?.Value

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/event-bridge.json");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can we rename this to eventbridge, the - prevents it from showing up in search.

Comment on lines 259 to 269
if (typeof traceID !== "string") {
return;
}
const parentID = traceData[parentIDHeader];
if (typeof parentID !== "string") {
return;
}
const sampledHeader = traceData[samplingPriorityHeader];
if (typeof sampledHeader !== "string") {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can we do a single type check for all three variables instead of three separate conditions?

Comment on lines 94 to 95
// TODO [astuyve] discuss with team RE nulls or undefined, or a coalescing strategy
parentSpanContext = rootTraceHeaders ? this.tracerWrapper.extract(rootTraceHeaders) || undefined : undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// TODO [astuyve] discuss with team RE nulls or undefined, or a coalescing strategy
parentSpanContext = rootTraceHeaders ? this.tracerWrapper.extract(rootTraceHeaders) || undefined : undefined;
parentSpanContext = rootTraceHeaders ? this.tracerWrapper.extract(rootTraceHeaders) ?? undefined : undefined;

"span.type": "http",
"resource.name": resourceName,
"service.name": domain,
api_id: event.requestContext.apiId,
Copy link
Contributor

@hghotra hghotra Feb 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this to apiid? This is done since the metric dimension is apiid and we want to have an attribute that's the same name as the metric tag. This ensure that the attribute is there when pivoting from a metric to a trace query.

Suggested change
api_id: event.requestContext.apiId,
apiid: event.requestContext.apiId,
apiname: event.requestContext.apiId,

const resourceName = `${eventName} ${tableName}`;
options.tags = {
operation_name: "aws.dynamodb",
"aws.dynamodb.table_name": tableName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"aws.dynamodb.table_name": tableName,
"tablename": tableName,

type: Type,
subject: Subject,
message_id: MessageId,
topic_name: topicName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
topic_name: topicName,
topicname: topicName,

type: Type,
subject: Subject,
message_id: MessageId,
topic_name: topicName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
topic_name: topicName,
topicname: topicName,

… span attribute names for topicname, tablename, etc.
@@ -238,6 +237,136 @@ export function readTraceFromSQSEvent(event: SQSEvent): TraceContext | undefined
return;
}

export function readTraceFromSNSSQSEvent(event: SQSEvent): TraceContext | undefined {
if (event?.Records[0]?.body) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's very unlikely that Records here will be null, but the proper syntax is the weird looking event?.Records?.[0]?.body.

const testEvent1 = {Records: [{body: "Hello"}]}
console.log("testEvent1?.Records?.[0]?.body:", testEvent1?.Records?.[0]?.body)
console.log("testEvent1?.Records[0]?.body:", testEvent1?.Records[0]?.body)

const testEvent2 = {Records: []}
console.log("testEvent2?.Records?.[0]?.body:", testEvent2?.Records?.[0]?.body)
console.log("testEvent2?.Records[0]?.body:", testEvent2?.Records[0]?.body)

const testEvent3 = {Records: null}
console.log("testEvent3?.Records?.[0]?.body:", testEvent3?.Records?.[0]?.body)
console.log("testEvent3?.Records[0]?.body:", testEvent3?.Records[0]?.body)
/usr/local/lib/node_modules/ts-node-fm/src/index.ts:226
testEvent1?.Records?.[0]?.body: Hello
testEvent1?.Records[0]?.body: Hello
testEvent2?.Records?.[0]?.body: undefined
testEvent2?.Records[0]?.body: undefined
testEvent3?.Records?.[0]?.body: undefined
TypeError: Cannot read property '0' of null
    at index.ts:10:126
    at Script.runInThisContext (vm.js:122:20)
    at startRepl (/usr/local/lib/node_modules/ts-node-fm/src/bin.ts:157:12)
    at Object.<anonymous> (/usr/local/lib/node_modules/ts-node-fm/src/bin.ts:66:1)
    at Module._compile (internal/modules/cjs/loader.js:778:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... But it's ugly. I'll use it :)

Copy link
Contributor

@hghotra hghotra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

@astuyve astuyve merged commit a44b34a into main Feb 7, 2022
@astuyve astuyve deleted the aj/feat-support-coldstart-spans branch February 7, 2022 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants