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
Bring beforeSend
-like capability to transactions
#19
Conversation
4d4e5fd
to
3d8def9
Compare
text/0019-beforeSendTransaction.md
Outdated
# Unresolved questions | ||
|
||
- Assuming we go with the `beforeSendTransaction` option, what all should go in the hint? | ||
- Once we do this, should we deprecate `addGlobalEventProcessor` as a public API method? |
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.
People still use event processors for different reasons on Mobile SDKs, not sure if removing them is the right thing to do, I find it useful in some use cases otherwise your beforeSend
just gets bloated with so many things.
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 also think event processors can be useful. I included this question because long ago there was talk of deprecating the method (which is why it's undocumented). We backed off when it became necessary to use it for transaction processing, but our solution here would solve that. @mitsuhiko, I think you had thoughts on this at the time?
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 recall event processors being for internal usage only, and never a public API, but they turned out to be very useful in some cases.
text/0019-beforeSendTransaction.md
Outdated
|
||
- Assuming we go with the `beforeSendTransaction` option, what all should go in the hint? | ||
- Once we do this, should we deprecate `addGlobalEventProcessor` as a public API method? | ||
- Are we happy with the `beforeSendTransaction` name, or are there better alternatives? |
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.
Fine for me, but I still have the feeling that this leads to the same problem as sampleRate
for errors and tracesSampleRate
for transactions.
beforeSend
for errors, beforeSendTransaction
for transactions.
If that's not a concern, all good.
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 also somewhat have a feeling this will add confusion, having more functions on the sdk is just more surface area for people to miss.
text/0019-beforeSendTransaction.md
Outdated
|
||
*Comparison* | ||
|
||
The main advantage the proposed `beforeSendTransaction` option has over the everything-goes-through-`beforeSend` option is that it's not a breaking change, and therefore doesn't need to wait for a major release to be introduced. (Non-breaking changes are also always less friction for the user, at least in the short run.) |
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.
IIRC, there are more SDKs that run beforeSend for transactions.
Cocoa is one of them getsentry/sentry-cocoa#1730
For this, I have a slightly alternative proposal, where in I would like us to adopt more granular span processing hooks, specifically To accomplish this, we can adopt a new top level option users can configure, a interface SentryOptions: {
// ...
tracer: Tracer
// ...
}
interface Tracer {
onSpanStart?: (span: Span) => Span | null;
onSpanFinish?: (span: Span) => Span | null;
// did not include on transaction start since that's handled by sampling options
onTransactionFinish?: (transaction: Transaction) => Transaction | null;
} I'm not totally attached to the tracer, but I think it would be a nice way to encapsulate the logic. This is also similar to the open telemetry API. In languages where possible the tracer can just be a plain dictionary, otherwise it's a class you must construct. |
We have something similar for a few integrations, similar to |
@AbhiPrasad I like the fine-grained lifecycle hooks as well, but they don't solve the problem of 'run some user code at the very end of all other sentry processing'. In WSGI, we use We should definitely expand on lifecycle events / hooks throughout the SDK, but this |
Great point. Perhaps then we chose to somehow introduce a new type EventType = 'error' | 'transaction' | 'profile' | 'replay' | 'whatever-comes-next';
// name pending - hardest problem in computer science...
beforeWhatever: (type: EventType, event: Event, hint?: Hint) => Event | null; Then we have something that'll affect every event type, |
Yeah, I also prefer a unified method, and that seems to be a fairly general sentiment. Just that the ideal name is already taken so we'll have to be creative. :) |
Will I agree we should move in this direction, until we have an exact use / need for span hooks I think that can be a separate discussion / rfc.
Are you proposing just a single function which we can split based on a passed prop? I think that works better 🤔 |
The problem with this approach is that some SDKs auto instrument and users don't have a chance to pass a prop unless they do it directly in the SDK init via options. |
Just for TypeScript type inference this particular API is problematic because the Ideally, we have a |
We do have, https://develop.sentry.dev/sdk/event-payloads/types/#typedef-EventType, but this doesn't cover profiling, which just use envelope item headers to determine the type of the payload (doesn't follow the event format). Replay does use the headers, but it's a different format, different schema. Perhaps we need to standardize this better on ingestion? |
Actually after writing out the above, maybe there is no world where we can standardize this, and we should just have the explicit methods. Overloaded methods is always a pain, better to be explicit! |
Just throwing an idea: |
This sounds like a different implementation of the hooks Abhi was talking about. Here I agree with Neel and Kevan - not a bad idea at all, but probably a separate conversation from this one, which is specifically "how do we give users the same swiss army knife for transactions which they have for errors?" |
So far it seems like people are favoring the single-method option. I actually prefer that option as well, for many of the reasons outlined, so maybe the question is actually a slightly different one: Assuming we go with a single method, should we have it be I will update the RFC a bit to reflect the conversation so far. |
beforeSendTransaction
beforeSend
-like capability to transactions
beforeSend
-like capability to transactionsbeforeSend
-like capability to transactions
5471e06
to
e92b3a3
Compare
My vote goes to expand the usage of I want to echo what I already said in a meeting yesterday:
I would continue using |
@vladanpaunovic one problem with this solution is that some languages don't have Inheritance so you can't have easily 2 types going thru the same callback if strongly typed. |
So after reading this are we back on square one? The one Do we need to get our data structures in order first, before we can deal with all the different kind of events in a unified way? |
I guess also just piping all events/transactions/whatever into So I guess we have to create a After 12 months we remove |
- It forces users to write code in two separate places, which leads to potential redundancy if they want to act on any properties shared by error and transaction events. | ||
|
||
- Lack of ultimate control over transaction events | ||
- Event processors happen in an unspecified order, and there's no guarantee a processor added by a user will run last. This means that an integration, for example, might change event data after the user's last chance to intervene. |
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.
To "Event processors happen in an unspecified order": This was also one thing we observed in Python. The user wanted to add an event processor to filter transactions based on something in the request. But our internal integration added this request to the event also with an event processor.
The users event processor ran before the integrations event processor so there was no request in the event (or the hint? cant remember) so it was impossible to filter the transactions in an event processor.
Proposal:
We could add special user_event_processors
(or whatever the name) and those event processors are run AFTER all the currently available event processors. To make sure all the SDK internal processing has been done and now the user can do stuff with the event.
(One down side: "all the SDK internal processing" also means the event could have been dropped/sampled already)
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.
That is a good point, @antonpirker, but I think that is a different discussion.
We could have a simple The question to me is: do we want to keep this
@marandaneto Can you give an example of an sdk in a language without inheritance? We could check other ways, like composition or delegation, but if it's not possible then we'd have to separate the methods. |
Using |
I'm not sure what you mean, @vladanpaunovic, because those sound like the same thing to me. |
No, you got it right. All good. My explanation wasn't great. |
@lobsterkatie option 2 sounds good, an alternative for statically typed languages:
Would that be ok? @philipphofmann @brustolin would that work on iOS? |
- `beforeSend` has a seemingly-universally-applicable name, so the most intuitive thing for users is to have it fulfill its implied role and apply universally. | ||
- Future payload types could be added without introducing new init options/versions of the method. | ||
|
||
Cons: |
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 Java we have SentryBaseEvent
which is a super class of SentryEvent
and SentryTransaction
.
At the moment the signature of beforeSend
in Java is:
SentryEvent execute(@NotNull SentryEvent event, @NotNull Hint hint);
Assuming we change the parameter to type SentryBaseEvent
it would cause people to have to cast it to SentryEvent
to access things like setLevel
or isCrashed
.
An alternative would be to have two parameters that are @Nullable
.
I assume this also applies to other SDKs (.NET).
- It forces users to write code in two separate places, which leads to potential redundancy if they want to act on any properties shared by error and transaction events. | ||
|
||
- Lack of ultimate control over transaction events | ||
- Event processors happen in an unspecified order, and there's no guarantee a processor added by a user will run last. This means that an integration, for example, might change event data after the user's last chance to intervene. |
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.
That is a good point, @antonpirker, but I think that is a different discussion.
|
||
# Background | ||
|
||
When transactions were introduced, the decision was made not to run them through `beforeSend` because they follow a slightly different schema, and therefore had the potential to break any existing `beforeSend` which relies on its input being a certain shape. It's unclear whether a transaction-specific `beforeSend`-type hook was discussed at the time. |
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.
If I remember correctly, there we good reasons for not calling beforeSend
for transactions, but I can't recall them. @mitsuhiko or @jan-auer could know.
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.
If I remember correctly, there we good reasons for not calling beforeSend for transactions
Yes - from what I recall, it was exactly the reason I listed there, that the schema mis-match had the potential to break people's beforeSend
functions. Or do you mean there was a reason for not having a transaction-specific beforeSend
-like 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.
It's unclear whether a transaction-specific
beforeSend
-type hook was discussed at the time.
I think this sentence is inaccurate. We discussed it and most likely the outcome was what you just pointed out: the schema mis-match had the potential to break people's beforeSend functions
Or do you mean there was a reason for not having a transaction-specific beforeSend-like option
Could also be, but I can't recall that.
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.
Sorry, I think we're miscommunicating here, because I believe we actually agree on the history. Let me try to rephrase:
I know that back in the day, when tracing was brand new, we discussed sending transactions through beforeSend
itself, and decided against it because of the schema issue potentially creating a breaking change. What I don't know, and you're saying you can't recall either, is if a separate beforeSend
-like option (beforeSendTransaction
or similar) was considered, and if it was, why it wasn't implemented. With regard to that question schemas/breaking changes are irrelevant, because no one would have had any existing code to break.
Does that make sense?
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.
if a separate beforeSend-like option (beforeSendTransaction or similar) was considered, and if it was, why it wasn't implemented. With regard to that question schemas/breaking changes are irrelevant, because no one would have had any existing code to break.
Yep, that makes sense 👍
- Future payload types could be added without introducing new init options/versions of the method. | ||
|
||
Cons: | ||
- Potentially breaking change for everyone, even people not using tracing, because old assumptions about `beforeSend` input schema would no longer hold. (We could maybe work around this by just surrounding our `beforeSend` call with a `try-catch` and returning the event untouched if things go sideways.) |
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.
IMO another con is that users might end up checking the type of the payload and then casting it to different payload types.
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.
If they did this, would it break downstream processing in the SDK?
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 don't think it would, but IMO this is an unappealing API.
- What would we call it? The most logical name given other option names (`beforeBreadcrumb`, `beforeNavigate`, etc) is already taken. | ||
- What would happen to `beforeSend`? Either we'd eventually either: 1) deprecate and remove it, which just kicks the breaking-change can down the road, or 2) keep it around in perpetutity, which isn't great either, because it would probably continue to confuse people (both by providing a less-functional way to do what the new method does and by _still_ not being named `beforeSendError`). |
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 these are not cons; these are problems to solve. I think having one method called before sending anything makes sense. Alongside adding the universal method, we could also have fine-grained methods. That would be Option 5, then.
Option 5
Add a new universal beforeSend
method, deprecate beforeSend
, and add fine-grained beforeSend
for events and transactions.
beforeSend
deprecatedbeforeSendAll
(name can be discussed)beforeSendEvent
beforeSendTransaction
Option 6
Make beforeSend
the new universal beforeSend
method, deprecate, and add fine-grained beforeSend
for events and transactions.
beforeSend
beforeSendEvent
beforeSendTransaction
@marandaneto, on Cocoa we don't use an interface, we use a typedef. So what you pointed out wouldn't work. /**
* Block can be used to mutate event before its send.
* To avoid sending the event altogether, return nil instead.
*/
typedef SentryEvent *_Nullable (^SentryBeforeSendEventCallback)(SentryEvent *_Nonnull event); |
I am obviously late to the party, just want to add on a potential downside of option 2, which is that it could potentially break user code if they don't write defensive hooks. E.g. say a user has a hook like this: beforeSend(type, event) {
event.user.email = 'my@email.com';
return event;
} And this works currently because all events that can, as of now, be passed in there have/support this shape. Obviously ideally users would write this defensively with e.g. |
@mydea, I believe what you're talking about is exactly what's covered in the first sentence of the
That's why we're talking about this as a change which would ship with the next major of each SDK, precisely because it would be a breaking change. |
Update after IRL meeting: Many of the points touched on above were discussed, but in the end there wasn't broad consensus on whether to go with a I'm therefore going to close this for now. Once it's clear whether hooks are going to happen, and/or when this comes up again for other events types (profiles, replays), we can revisit the topic. |
This adds a new `beforeSendTransaction` option to the SDK. As one would expect from the name, it works just like `beforeSend` (in other words, it's a guaranteed-to-be-last event processor), except that it acts upon transaction events, which the original `beforeSend` doesn't. Docs PR will be linked in the PR once it's done. Ref: getsentry/rfcs#19 (comment)
Proposal to modify SDKs to allow transactions to run through
beforeSend
or equivalent method(Originally specifically a proposal for adding
beforeSendTransaction
, but revised after initial discussion)Rendered RFC