-
Notifications
You must be signed in to change notification settings - Fork 576
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
Add support to record errors from lambda handlers #4677
base: main
Are you sure you want to change the base?
Add support to record errors from lambda handlers #4677
Conversation
0257125
to
01f0f99
Compare
Why would someone explicitly not want to record errors? (do we need the option? How about recording the error all the time?) |
Great question. I think there are two parts to the answer:
|
Does this comment applies to this PR ? How about simply adding I think this is also inline with #4677 (comment). |
I've had some real-world use cases where we wanted to record that an error occurred without setting the span status to error (or returning an error for that matter). If you'll allow a super contrived example, imagine you're (for whatever reason) hand-rolling HTTP backoff-and-retry code wherein you may want to record the individual errors that occurred caused each of the eventual retries. I think the value proposition is clearer in languages where the SDK opts to include stack traces by default, but for golang, it looks like we have to explicitly opt-in each time: span.RecordError(err, trace.WithStackTrace(true)) which now makes me think we should just expose this at the same time, e.g., func WithRecordError(recordError bool, recordStackTrace bool) Option {
return optionFunc(func(c *config) {
c.RecordError = recordError
c.RecordStackTrace = recordStackTrace
})
} response, err := h.handler.Invoke(ctx, payload)
if err != nil {
if h.instrumentor.configuration.RecordError {
- span.RecordError(err)
+ span.RecordError(err, span.WithStackTrace(h.instrumentation.RecordStackTrace))
}
if h.instrumentor.configuration.SetError {
span.SetStatus(codes.Error, err.Error())
}
return nil, err
}
I'm definitely in favor of just calling |
// error returned by the wrapped lambda on the instrumentation-provided span. | ||
// | ||
// By default, returned errors are not recorded. | ||
func WithRecordError(recordError bool, recordStackTrace bool) Option { |
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.
Should this be two separate options? We don't typically accept multiple distinct configuration values in options and I don't think there's any need to check whether c.RecordError
is set when setting c.RecordStackTrace
as the stack trace config is only ever used when the instrumentation is configured to record errors.
if h.instrumentor.configuration.RecordError { | ||
span.RecordError(err, trace.WithStackTrace(h.instrumentor.configuration.RecordStackTrace)) | ||
} | ||
span.SetStatus(codes.Error, err.Error()) |
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 this is correct. There are situations where an error may be returned from the handler but that should not result in the span status being set to error. For instance, if the handler is supporting an HTTP-based service and an error would indicate a 4xx
status code, the span status MUST be left unset. Similarly, the status message shouldn't be set when it can be inferred from other data such as the HTTP status code.
Given that the instrumented handler has access to this span and can set the status as appropriate I would omit setting it here.
Currently,
otellambda
doesn't update the instrumentation-provided span based on the result of the wrapped handler.I was surprised by this, as most of my OTel instrumentation work involves open-telemetry/opentelemetry-dotnet, so this may well be a sensible default for golang, but given the breadth and scale of the OTel projects, I figured this might also just be an oversight, so wanted to open a PR and contribute some code.
This PR adds two config methods:
WithRecordError(bool)
which instructs thewrappedHandler
to callspan.RecordError(...)
ifwrappedHandler.Handler.Invoke(...)
returns an error.WithSetStatus(bool)
which instructs thewrappedHandler
to callspan.SetStatus(...)
ifwrappedHandler.Handler.Invoke(...)
returns an error.Off-topic, but in the dotnet OTel libraries, it's also common to provide enrichment delegates which can be used to filter/manipulate the instrumentation-provided span in other ways (e.g. setting custom attributes/tags based on the exception being thrown). I considered adding similar functionality here, but decided to hold off on doing anything more until the code owners have had an opportunity to weigh in.
Background
based on
go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda v0.46.1
Sample code based on version
0.0.112
in the above screencapSample code based on version
0.0.113
in the above screencapNote that our handler is no longer creating its own internal span. We can work around this by defining our own
wrappingHandler
, but the path of least resistance is to just work with the instrumentation-provided span.Sample code based on this patch
Now we can create an internal span in our handler and choose whether errors recorded in this layer should also be passed back up to the instrumentation-provided span to be recorded there as well.
(Example here might be overly simplified, but underlying all of the required obfuscation there is a real-world business case for this. I can provide more details upon request if required.)