-
Notifications
You must be signed in to change notification settings - Fork 539
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: add instrumentation of AWS Lambda handlers to generate FaaS spans #399
Conversation
I found how lambda requires modules (absolute path) and the way to get it to work with the patching infrastructure open-telemetry/opentelemetry-js#2069 |
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.
@dyladan @willarmiros Updated, now the tests pass even with the HEAD opentelemetry-js code.
|
||
return [ | ||
new InstrumentationNodeModuleDefinition( | ||
// NB: The patching infrastructure seems to match names backwards, this must be the filename, while |
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 found this strange pattern that did allow patching the lambda handler.
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'm not sure what you meant by matching name backwards ? It matches absolute path (https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts#L107)
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.
Yeah, I mean that we pass the filename to a class called InstrumentationNodeModuleDefinition
, but pass a module name to a class called InstrumentationNodeModuleFile
. I'd expect file to refer to a filename :)
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.
Yeah thats weird, i'm not sure if it's because of the resolving done by lamda. With other instrumentations we do the opposite (module is a name and file is an relative path to the module): https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/opentelemetry-instrumentation-mongodb/src/mongodb.ts#L59
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.
this indeed looks strange as it should be a module name if user requires for example http
then the module name is http
so in this case it should be aws-lambda
not a file name. If you need to patch some additional files then this is what InstrumentationNodeModuleFile
is for
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.
Yeah that's what I figured. Well I'd like to leave this as is for now since it seems to be the pattern that works here :)
…trib into awslambda-faas
Codecov Report
@@ Coverage Diff @@
## main #399 +/- ##
=======================================
Coverage 94.21% 94.21%
=======================================
Files 12 12
Lines 432 432
Branches 48 48
=======================================
Hits 407 407
Misses 25 25 |
plugins/node/opentelemetry-instrumentation-awslambda/package.json
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-awslambda/package.json
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-awslambda/package.json
Outdated
Show resolved
Hide resolved
By the way I could confirm this instrumentation works to instrumentent lambda invocations by building into a lambda layer in open-telemetry/opentelemetry-lambda#25 |
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.
lgtm
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.
Please change names everywhere from awslambda
to aws-lambda
including the folder name
@@ -0,0 +1,71 @@ | |||
{ | |||
"name": "@opentelemetry/instrumentation-awslambda", |
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 the name should be
"name": "@opentelemetry/instrumentation-awslambda", | |
"name": "@opentelemetry/instrumentation-aws-lambda", |
you are patching aws-lambda
and those are 2 words anyway
() => original.apply(this, [event, context, wrappedCallback]), | ||
error => { | ||
if (error != null) { | ||
span.setStatus({ |
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.
use also span.recordException
to save the whole stack of error
errMessage = err.message; | ||
} | ||
if (errMessage) { | ||
span.setStatus({ |
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.
span.recordException
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.
recordException I don't think sets status does it? You may need to do both if that is the behavior you want.
|
||
return [ | ||
new InstrumentationNodeModuleDefinition( | ||
// NB: The patching infrastructure seems to match names backwards, this must be the filename, while |
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.
this indeed looks strange as it should be a module name if user requires for example http
then the module name is http
so in this case it should be aws-lambda
not a file name. If you need to patch some additional files then this is what InstrumentationNodeModuleFile
is for
attributes: { | ||
[FaasAttribute.FAAS_EXECUTION]: context.awsRequestId, | ||
[FaasAttribute.FAAS_ID]: context.invokedFunctionArn, | ||
// TODO(anuraaga): Add cloud.account.id when there is a JS accessor for it. |
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.
can you please create a ticket for that so it is not lost ?
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.
Can this not be extracted from the ARN?
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.
+1 to getting account from ARN
return function wrappedCallback(this: {}, err, res) { | ||
diag.debug('executing wrapped lookup callback function'); | ||
|
||
let errMessage; |
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.
this part of code is the same as in line 135. Please create a private method for that something like
this._addError(span)
please also use recordException
if you have an object error.
|
||
import { Handler } from 'aws-lambda'; | ||
|
||
export interface LambdaModule { |
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.
export interface LambdaModule { | |
export type LambdaModule = Record<string, Handler>; |
@@ -0,0 +1,5 @@ | |||
{ | |||
"name": "lambdatest", |
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.
"name": "lambdatest", | |
"name": "lambda-test", |
}); | ||
}); | ||
|
||
context('sync success handler', () => { |
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.
please add block describe('sync success handler')
and similar block to async
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.
How are you handling flushing? Are you just hoping that the lambda runtime isn't paused at an inconvenient time?
Is this a requirement that i missed for vendor components or just a better naming suggestion ? |
better naming and consistency, |
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.
LGTM in general
attributes: { | ||
[FaasAttribute.FAAS_EXECUTION]: context.awsRequestId, | ||
[FaasAttribute.FAAS_ID]: context.invokedFunctionArn, | ||
// TODO(anuraaga): Add cloud.account.id when there is a JS accessor for it. |
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.
+1 to getting account from ARN
}); | ||
|
||
// Lambda seems to pass a callback even if handler is of Promise form, so we wrap all the time before calling | ||
// the handler and see if the result is a Promise or not. In such a case, the callback is usually ignored. |
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.
In such a case, the callback is usually ignored.
What if the callback isn't ignored? Would the span be ended twice/would that matter?
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.
Yeah anything after the first end is ignored as per otel spec
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.
Thanks all applied cleanups
|
||
return [ | ||
new InstrumentationNodeModuleDefinition( | ||
// NB: The patching infrastructure seems to match names backwards, this must be the filename, while |
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.
Yeah that's what I figured. Well I'd like to leave this as is for now since it seems to be the pattern that works here :)
}); | ||
|
||
// Lambda seems to pass a callback even if handler is of Promise form, so we wrap all the time before calling | ||
// the handler and see if the result is a Promise or not. In such a case, the callback is usually ignored. |
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.
Yeah anything after the first end is ignored as per otel spec
…trib into awslambda-faas
Thanks a lot for the reminder, added a forceFlush at the end of the function invocation. |
plugins/node/opentelemetry-instrumentation-aws-lambda/src/aws-lambda.ts
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/src/aws-lambda.ts
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/src/aws-lambda.ts
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/src/aws-lambda.ts
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/src/aws-lambda.ts
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/package.json
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/package.json
Outdated
Show resolved
Hide resolved
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.
Thanks @Flarna - also, do you happen to know how to ignore a test on nodejs 8 in CI? If it's not easy I'm thinking of just going with then(() => callback(), () => callback())
Sorry, don't know. One simple solution would be to call |
@Flarna Thanks! Skipping sounds good |
plugins/node/opentelemetry-instrumentation-aws-lambda/README.md
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/README.md
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/README.md
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/README.md
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/README.md
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/package.json
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/src/aws-lambda.ts
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/README.md
Outdated
Show resolved
Hide resolved
plugins/node/opentelemetry-instrumentation-aws-lambda/README.md
Outdated
Show resolved
Hide resolved
Co-authored-by: Bartlomiej Obecny <[email protected]>
@obecny >< Thanks for the help on cleanup! |
…trib into awslambda-faas
@obecny I ended up switching from I have a couple of important followups planned, including implementing the parenting mechanism from the spec and HTTP attributes for proxy events. If this looks OK appreciate a merge :) |
…ns (open-telemetry#399) Co-authored-by: Bartlomiej Obecny <[email protected]>
Which problem is this PR solving?
Short description of the changes
This instrumentation patches the AWS Lambda handler (found via the _HANDLER env variable which is always set by Lambda) to generate spans around invocations. It is the first in a series of PRs implementing the spec defined here
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md
This currently only decorates the invocations with spans following FaaS conventions - future PRs will add reading of HTTP for proxy requests and messaging for SQS requests with appropriate parenting.
I actually am not sure if this approach works I will try it out with an actual lambda function, sending out for an initial look at the code and maybe suggestions on how to work around this problem. I noticed that the patching infrastructure, via
require-in-middle
can only patch core modules or modules innode_modules
- it literally checks the path of a required file to see if it is innode_modules
and bails if it isn't.https://github.com/watson/module-details-from-path/blob/master/index.js#L8
I have worked around it in testing here by copying handlers into
node_modules
- I need to check whether Lambda actually houses a user handler function inside a folder callednode_modules
. There's a reasonable chance it doesn't - if anyone can provide some tips on whether it's possible to work around this, that would be great.