-
Notifications
You must be signed in to change notification settings - Fork 228
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
feat: use trace-context "headers" in SNS and SQS single event message attributes for lambda transaction #2606
Conversation
… attributes for lambda transaction - If a SNS or SQS single event trigger to an instrumented Lambda function includes message attributes with the name "traceparent" (and "tracestate"), case-insensitive, then those are used to continue the trace. This was already being done for API Gateway event headers. - Also, fix a bug in the capturing of SNS and SQS message attributes as "transaction.context.message.headers". Note that only "String"-type message attributes are captured. Binary-type attributes are base64 encoded. I'm not sure of the value in capturing them. Closes: #1831 Fixes: #2605 Refs: elastic/apm#614
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing objectionable here. It looks like the thing to do and the approach seems reasonable. Happy to approve once a changelog entry gets added.
I noticed this was still in draft state -- if there's a specific part you wanted feedback on or a part you think needs more extensive testing from a second set of eyes just let me know. Otherwise 👍
@@ -9,6 +9,32 @@ const path = require('path') | |||
let isFirstRun = true | |||
let gFaasId // Set on first invocation. | |||
|
|||
// The trigger types for which we support special handling. | |||
// https://docs.aws.amazon.com/lambda/latest/dg/lambda-services.html | |||
const TRIGGER_GENERIC = 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see we've replaced the smaller functions that identify an individual payload type (ex. isApiGatewayEvent
) with this larger/more-complex function triggerTypeFromEvent
function that identifies an individual payload type, and then we call triggerTypeFromEvent
once its result value is passed around where it needs to be. The main tradeoffs here seem to be
-
the perf. of multiple calls to the older small function vs. the higher memory use of keeping the constant-ish
TRIGGER_
variables around. -
Swapping where the complexity goes :)
No objections, just noting the change out loud.
@@ -9,6 +9,32 @@ const path = require('path') | |||
let isFirstRun = true | |||
let gFaasId // Set on first invocation. | |||
|
|||
// The trigger types for which we support special handling. | |||
// https://docs.aws.amazon.com/lambda/latest/dg/lambda-services.html | |||
const TRIGGER_GENERIC = 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see we've replaced the smaller functions that identify an individual payload type (ex. isApiGatewayEvent
) with this larger/more-complex function triggerTypeFromEvent
function that identifies an individual payload type, and then we call triggerTypeFromEvent
once its result value is passed around where it needs to be. The main tradeoffs here seem to be
-
the perf. of multiple calls to the older small function vs. the higher memory use of keeping the constant-ish
TRIGGER_
variables around. -
Swapping where the complexity goes :)
No objections, just noting the change out loud.
@@ -199,7 +221,10 @@ function setSqsData (agent, trans, event, context, faasId, isColdStart) { | |||
} | |||
|
|||
if (agent._conf.captureHeaders) { | |||
messageContext.headers = Object.assign({}, record.messageAttributes) | |||
const headers = headersFromSqsMessageAttributes(record.messageAttributes) | |||
if (Object.keys(headers).length > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for Object.keys(obj)
-- I'm pretty sure that's the fastest way to do this without needing to get into for in
and hasOwnProperty
territory. Unrelated to this PR/note-to-self -- a custom eslint linting rule that ensures we're using the more efficient Object.keys() for object looping/iteration (vs. one of the other myriad ways you can loop an object in javascript) would be a nice to have.
@@ -199,7 +221,10 @@ function setSqsData (agent, trans, event, context, faasId, isColdStart) { | |||
} | |||
|
|||
if (agent._conf.captureHeaders) { | |||
messageContext.headers = Object.assign({}, record.messageAttributes) | |||
const headers = headersFromSqsMessageAttributes(record.messageAttributes) | |||
if (Object.keys(headers).length > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for Object.keys(obj)
-- I'm pretty sure that's the fastest way to do this without needing to get into for in
and hasOwnProperty
territory. Unrelated to this PR/note-to-self -- a custom eslint linting rule that ensures we're using the more efficient Object.keys() for object looping/iteration (vs. one of the other myriad ways you can loop an object in javascript) would be a nice to have.
// { <attribute-name>: { dataType: <attr-type>, stringValue: <attr-value>, ... } } | ||
// For example: | ||
// { traceparent: { dataType: 'String', stringValue: 'test-traceparent' } } | ||
function headersFromSqsMessageAttributes (messageAttributes) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I see a new headersFromSqsMessageAttributes
that simplifies the data from amazon's strong-ishly typed JSON data into something that APM Server can handle. (I also see a separate function for SNS messages)
// { <attribute-name>: { dataType: <attr-type>, stringValue: <attr-value>, ... } } | ||
// For example: | ||
// { traceparent: { dataType: 'String', stringValue: 'test-traceparent' } } | ||
function headersFromSqsMessageAttributes (messageAttributes) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I see a new headersFromSqsMessageAttributes
that simplifies the data from amazon's strong-ishly typed JSON data into something that APM Server can handle. (I also see a separate function for SNS messages)
@@ -411,7 +410,12 @@ function elasticApmAwsLambda (agent) { | |||
} | |||
|
|||
return function wrappedLambda (event, context, callback) { | |||
log.trace({ awsRequestId: context && context.awsRequestId }, 'lambda: fn start') | |||
if (!(event && context && typeof callback === 'function')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for guarding against unknown datatypes here, per previous discussions on how things can go wrong without this.
@@ -411,7 +410,12 @@ function elasticApmAwsLambda (agent) { | |||
} | |||
|
|||
return function wrappedLambda (event, context, callback) { | |||
log.trace({ awsRequestId: context && context.awsRequestId }, 'lambda: fn start') | |||
if (!(event && context && typeof callback === 'function')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for guarding against unknown datatypes here, per previous discussions on how things can go wrong without this.
if (record.Sns && record.Sns.MessageAttributes) { | ||
normedHeaders = lowerCaseObjectKeys(headersFromSnsMessageAttributes(record.Sns.MessageAttributes)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not required for this PR, but I'm genuinely unsure if we should mention the agent's automatic handling of reading the trace context headers in a Lambda environment in our message queue doc, or if that would confusing things more https://github.com/elastic/apm-agent-nodejs/blob/main/docs/message-queues.asciidoc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is worthwhile to mention this behaviour in the lambda.asciidoc. Likely a reference in the message-queues doc would be worthwhile as well, I'd have to re-read that doc.
if (record.Sns && record.Sns.MessageAttributes) { | ||
normedHeaders = lowerCaseObjectKeys(headersFromSnsMessageAttributes(record.Sns.MessageAttributes)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not required for this PR, but I'm genuinely unsure if we should mention the agent's automatic handling of reading the trace context headers in a Lambda environment in our message queue doc, or if that would confusing things more https://github.com/elastic/apm-agent-nodejs/blob/main/docs/message-queues.asciidoc
Ah, thanks. I was going to pull out of draft after the changelog entry and merge from main. |
Also there is still some discussion on the apm spec: elastic/apm#614 (comment) |
Trying that failed "Integration Tests" again: https://apm-ci.elastic.co/job/apm-integration-tests-selector-mbp/job/main/2446/ |
function includes message attributes with the name "traceparent" (and
"tracestate"), case-insensitive, then those are used to continue the
trace. This was already being done for API Gateway event headers.
"transaction.context.message.headers". Note that only "String"-type
message attributes are captured. Binary-type attributes are base64
encoded. I'm not sure of the value in capturing them.
Closes: #1831
Fixes: #2605
Refs: elastic/apm#614
Checklist