Skip to content
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 exception stack traces to trace/tail events. #1910

Merged
merged 1 commit into from
Mar 27, 2024
Merged

Conversation

kentonv
Copy link
Member

@kentonv kentonv commented Mar 27, 2024

A new property, stack, contains the stack trace.

To support this in the case of async exceptions (which have already been converted to KJ), exception tunneling now adds a serialized copy of the V8 error to the exception itself, as a "detail". For now, this is only used by logUncaughtException(). In the future, though, when the exception propagates back into JavaScript, we could de-tunnel it by deserializing this detail, rather than based on the description string. However, there are several complexities that need to be solved first, so I have not tried to do that in this PR.

Depends on: capnproto/capnproto#1978

src/workerd/io/trace.h Outdated Show resolved Hide resolved
src/workerd/jsg/util.c++ Outdated Show resolved Hide resolved
void addExceptionDetail(Lock& js, kj::Exception& exception, v8::Local<v8::Value> handle) {
js.tryCatch([&]() {
Serializer ser(js);
ser.write(js, JsValue(handle));
Copy link
Contributor

@jclee jclee Mar 27, 2024

Choose a reason for hiding this comment

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

Should we be doing anything to bound the amount of data added to an exception?

Edit: Oh, I guess the existing trace size bound would help limit negative effects, and I suppose V8 might have some internal bound on stack trace generation.

"Should always be possible to unwrap error interface from an object.");
Worker::Api::ErrorInterface error;

if (exception.isObject()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the isObject() check is needed here because the deserialized result isn't necessarily an Object? (But would typically be an Object?)

Copy link
Member Author

Choose a reason for hiding this comment

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

Annoyingly, JavaScript permits you to throw any value, not just objects. I think there was a bug here that caused us to fail to log non-object exceptions, but it wasn't noticed before because our exception tunneling would turn non-objects into objects. (Exception tunneling today always produces Error values even if the original wasn't.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, OK... that would explain why the current test cases for throw "string" were working without the Object check.

@kentonv
Copy link
Member Author

kentonv commented Mar 27, 2024

// want to log the exception even if the isolate has been terminated.
}

KJ_IF_SOME(d, deserialized) {
Copy link
Contributor

@jclee jclee Mar 27, 2024

Choose a reason for hiding this comment

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

Looks like exceptionToJsValue() does some additional property population, filtering, and logging of internal tunneled exceptions that would be skipped when deserialization is successful. I guess the assumption here is that if a valid serialized value is present, it didn't come from an internal tunneled exception? (Or that the other populated properties are not currently consumed by logUncaughtException()?)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, logUncaughtException() doesn't inspect any of those extra properties today. Only type, message, and now stack.

A new property, `stack`, contains the stack trace.

To support this in the case of async exceptions (which have already been converted to KJ), exception tunneling now adds a serialized copy of the V8 error to the exception itself, as a "detail". For now, this is _only_ used by `logUncaughtException()`. In the future, though, when the exception propagates back into JavaScript, we could de-tunnel it by deserializing this detail, rather than based on the description string. However, there are several complexities that need to be solved first, so I have not tried to do that in this PR.

Depends on: capnproto/capnproto#1978
@kentonv kentonv merged commit ec4a6df into main Mar 27, 2024
10 checks passed
@kentonv kentonv deleted the kenton/trace-stacks branch March 27, 2024 21:21
KJ_IF_SOME(s, error.stack) {
kj::StringPtr slice = s;

// Normally `error.stack` repeats the error type and message first. We don't want send two
Copy link
Collaborator

Choose a reason for hiding this comment

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

@kentonv — see internal thread on this, I think by stripping out here to be efficient in what gets sent across, we ended up making it such that the stack property in the Tail Worker ends up not including full stack trace?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't know which internal thread you mean, and I don't see how the code here could possibly be stripping any part of the actual stack trace.

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.

None yet

3 participants