-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Spans in continueTrace callback are not being recorded #13146
Comments
Hey @kparksalku, thanks for reaching out. So ideally in v8 these spans should be automatically connected, or is there a specific reason why you use |
Just to add to this: Have you tried using our Also, can you confirm that the headers are actually present when Lambda B is invoked? |
I'm not sure how it's possible that they would be automatically connected, since these are distributed systems. To answer your question, though, the reason I'm using |
Yes, I tried adding I believe the fact that the |
The idea is that it works automatically in the following situations:
Can you confirm that you run your lambda in ESM (using Both should pick up our
Hmm this is interesting and gave me an idea. Maybe there's a detail I'm not aware of for AWS lambda functions. Our handler extracts the tracing headers from the
Technically yes. I just didn't see this in the trace you linked (In fact, I'm not sure if this trace is the one from Lambda A or B or what should be in there vs. what isn't. I can only see that there's just one transaction, so it's not linked). To debug and fix this we need to reproduce this properly. Can you show us how you invoke Lambda B from Lambda A? |
Ah, yeah, the headers could be the problem. I've been digging through some of the code in this repo, and it appears the
So, if I manually pass the baggage and trace data through the ClientContext, I can retrieve it and (potentially) continue the trace, but I don't think the automatic instrumentation would support that. I'm still confused as to why And yes, I'm using ESM. |
My suspicion is that You can try to manually flush after export const handler = async (event, context) => {
const { baggage, sentryTrace } = extractFromLambdaClientContextHeaders(context);
const result = await Sentry.continueTrace({ baggage, sentryTrace }, async () => {
console.log(Sentry.getActiveSpan());
const queryString = "select count(*) from myTable;";
return await Sentry.startSpan({
name: queryString,
op: "db.sql.execute"
}, async (span) => {
console.log(span);
return await executeQuery();
});
});
await Sentry.flush(2000)
return result;
}); or, another idea would be to actually use const _handler = async (event, context) => {
const { baggage, sentryTrace } = extractFromLambdaClientContextHeaders(context);
return await Sentry.continueTrace({ baggage, sentryTrace }, async () => {
console.log(Sentry.getActiveSpan());
const queryString = "select count(*) from myTable;";
return await Sentry.startSpan({
name: queryString,
op: "db.sql.execute"
}, async (span) => {
console.log(span);
return await executeQuery();
});
});
});
export const handler = Sentry.wrapHandler(_handler, {startTrace: false}) Ideally, we find a way (and document this properly) how traces across AWS SDK invoked lambda functions can be continued. Gonna cc @andreiborza for some input here as well. |
I tried both of these solutions, and neither worked. I think the issue is less about the timings, and more that |
hi @kparksalku, I just took a stab at reproducing this and found that you can pass the headers on the event via the
That way our sdk picks them up correctly and you should have connected traces. Let me know if this doesn't work for you. |
Hi @andreiborza, unfortunately, this doesn't work either. I don't believe this approach actually attaches the headers to the HTTP request; it just adds an arbitrary "headers" property to the payload object. I can retrieve these values by parsing the payload in the lambda, but the Sentry SDK doesn't pick up on it. |
@kparksalku we pick the headers from the event (first argument to the handler) in the sdk, see https://github.com/getsentry/sentry-javascript/blob/develop/packages/aws-serverless/src/sdk.ts#L337-L343 |
@andreiborza
It does not result in the span showing up in Sentry. |
@kparksalku right, I think I see my mistake. I specifically start a span here. Is your For the time being, I think if you wrap your |
That activeSpan is defined, before invoking Lambda B, and I can see it in Sentry |
Thanks for confirming, I'll take a closer look at reproducing this. |
@kparksalku we had another look through our aws-sdk integration and found that the underlying otel aws-sdk integration propagates the context to the lambda context object. As you correctly identified, our SDK expects the tracing data to come from the event tho. We're going to fix this. For the time being, I found a way to get connected traces this way:
For reference, here are my two Lambda functions: Lambda A: import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";
import * as Sentry from "@sentry/aws-serverless";
export const handler = Sentry.wrapHandler(async (event, context) => {
const span = Sentry.getActiveSpan()
const headers = {
baggage: Sentry.spanToBaggageHeader(span),
'sentry-trace': Sentry.spanToTraceHeader(span)
}
const client = new LambdaClient();
const command = new InvokeCommand({
FunctionName: `lambda-B-13146`,
InvocationType: "RequestResponse",
Payload: Buffer.from(JSON.stringify({
headers
}).toString('base64'))
})
return client.send(command);
}); Lambda B: import * as Sentry from "@sentry/aws-serverless";
Sentry.addIntegration(Sentry.postgresIntegration())
export const handler = Sentry.wrapHandler(async (event) => {
const queryString = "select count(*) from myTable;";
return await Sentry.startSpan({
name: queryString,
op: "db.sql.execute"
}, async (span) => {
console.log('executing query', queryString);
})
}) This should give you a trace looking like this: Now, this isn't perfect. As you can see, the |
Unfortunately, this still doesn't work. I've tried loading the instrumentation file, both from the the Also, the solution provided doesn't work, as AWS doesn't expect the Payload to be base64 encoded. I think you're referring to ClientContext. Base64 encoding the Payload results in an error. |
Could you please explain your whole setup in more detail? How are you getting your Sentry package, how are you initing Sentry, how are you executing your lambdas (from start to Lambda A to Lambda B). I feel like we're missing something here. Additionally, please enable For my setup above, I have Lambda A and B as pasted and I invoke Lambda A from the AWS UI with an empty event json. I also invoked Lambda A locally via the aws-sdk using a node script that uses |
I've tried several different approaches, in an effort to get it to work, so I'll do my best to describe everything I've tried. First, I'm getting this Sentry package from NPM: First, I used the Second, I tried removing the The second approach has given me more consistent results; I can see all of the traces from Lambda A in Sentry.io. Just to clarify, I can see the invocation of Lambda A as the root span, and I can see the subsequent HTTP/RPC call to invoke Lambda B, but both approaches still did not capture the traces from within Lambda B. I've already enabled debug in my Sentry config (posted above), but I'm not seeing any additional logs. Where would I find that? I couldn't find that information in the documentation. |
… over `event` (#13266) Currently, the AWS otel integration (and our `wrapHandler` fallback) try to extract sentry trace data from the `event` object passed to a Lambda call. The aws-sdk integration, however, places tracing data onto `context.clientContext.Custom`. This PR adds a custom `eventContextExtractor` that attempts extracting sentry trace data from the `context`, with a fallback to `event` to enable distributed tracing among Lambda invocations. Traces are now connected. Here an example: `Lambda-A` calling `Lambda-B`: ``` import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda"; import * as Sentry from "@sentry/aws-serverless"; export const handler = Sentry.wrapHandler(async (event, context) => { const client = new LambdaClient(); const command = new InvokeCommand({ FunctionName: `Lambda-B`, InvocationType: "RequestResponse", Payload: new Uint16Array(), }) return client.send(command); }); ``` `Lambda-B`: ``` import * as Sentry from "@sentry/aws-serverless"; Sentry.addIntegration(Sentry.postgresIntegration()) export const handler = Sentry.wrapHandler(async (event) => { const queryString = "select count(*) from myTable;"; return await Sentry.startSpan({ name: queryString, op: "db.sql.execute" }, async (span) => { console.log('executing query', queryString); }) }) ``` ![CleanShot 2024-08-07 at 16 34 51@2x](https://github.com/user-attachments/assets/43f5dd9e-e5af-4667-9551-05fac90f03a6) Closes: #13146
@kparksalku thanks. We just merged in a PR that hopefully fixes all of this for you.We're probably going to cut a release today or so. As for your setup, after we release the new fix, I recommend following the four steps in https://docs.sentry.io/platforms/javascript/guides/aws-lambda/install/esm-npm with setting Then you need to make sure to wrap your handler with Here's what my Lambda A looks like: import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";
import * as Sentry from "@sentry/aws-serverless";
export const handler = Sentry.wrapHandler(async (event, context) => {
const client = new LambdaClient();
const command = new InvokeCommand({
FunctionName: `lambda-B-13146`,
InvocationType: "RequestResponse",
Payload: new Uint16Array(),
})
return client.send(command);
}); and here's what my Lambda B looks like: import * as Sentry from "@sentry/aws-serverless";
Sentry.addIntegration(Sentry.postgresIntegration())
export const handler = Sentry.wrapHandler(async (event) => {
const queryString = "select count(*) from myTable;";
return await Sentry.startSpan({
name: queryString,
op: "db.sql.execute"
}, async (span) => {
console.log('executing query', queryString);
})
}) Both Lambdas have a layer that just uses a |
Thanks. I'll keep an eye out for the new release. One concern though with this approach, using |
@kparksalku that confusion is understandable, but basically our top-level apis (e.g. |
@kparksalku this has bene released, please give it a try when you have some time. |
@andreiborza, unfortunately, this still doesn't work. After upgrading to version I then tried exporting the Sentry object from the layer, and importing it in the Lambdas. That allows me to get the traces from Lambda A, but still nothing from Lambda B. I logged out the active spans for both Lambdas and (to my surprise), the active spans in Lambda B appear to be normal spans, and they have the correct trace IDs and parent span IDs, and they're no longer nonRecording spans. So, I think that's an improvement, but I don't know why they wouldn't be forwarded to Sentry. I'm pretty much out of ideas of other things to try. |
I just realized why I think it's not working for me. Lambda B is in a VPC, and doesn't have internet access. So, as I understand it, the Sentry SDK tries to send the traces back from where the spans are created. If this is true, then the Lambda would have no way to get the traces back to Sentry. That would also explain why I'm seeing regular (recording) spans that are not making it back to Sentry. Are there any suggested ways to handle this situation, besides re-architecting our solution? Is there a way to send the trace data back to Lambda A in the response, instead of sending it back to Sentry directly? |
Ahh, yeah, that makes sense then! |
Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/aws-serverless
SDK Version
8.18.0
Framework Version
No response
Link to Sentry event
https://alku.sentry.io/performance/trace/d7e316d5f796b8b046d0c0cdbf5ca61c/?pageEnd&pageStart&project=4507617300054016&source=traces&statsPeriod=1h×tamp=1722461032.632
Reproduction Example/SDK Setup
Initialization code for both Lambda functions:
Lambda A:
Lambda B:
Steps to Reproduce
Expected Result
The trace should record the "db.sql.execute" span as a child span in the trace.
Actual Result
There is no record of the "db.sql.execute" span at all.
In the logs, I can see that
continueTrace
creates anonRecordingSpan
, which is not sent to Sentry. I believe this is the root of the problem. The call tostartSpan
appears to create a normal (recording) span, but since it is a child of the non-recording span, it is not sent along to Sentry either.The text was updated successfully, but these errors were encountered: