-
-
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
ref(nextjs): Use integration to add request data to transaction events #5703
ref(nextjs): Use integration to add request data to transaction events #5703
Conversation
a806f00
to
11b1fbd
Compare
size-limit report 📦
|
Why not make |
My thinking was that it should live in I wonder if there might not be other isomorphic situations in other frameworks (current or future) which would be more like the Any 🦆 thoughts here? UPDATE: As of our DACI meeting earlier today (about recording failed http requests as errors), this question just got a bit more complicated, because we'll need request data there, too. Let's discuss this at the next standup. FURTHER UPDATE: We decided to move it to |
a2801d8
to
e593f70
Compare
e593f70
to
e0c4edc
Compare
e0c4edc
to
96d9a6c
Compare
packages/nextjs/src/index.server.ts
Outdated
integrations = addOrUpdateIntegration(defaultRequestDataIntegration, integrations, { | ||
// Specify the `@sentry/node` version of `addRequestDataToEvent`, so we get the injected dependencies | ||
'_options._addReqDataCallback': addRequestDataToEvent, | ||
}); |
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.
Rant alert. Just want to get the following out for my own sanity - no ill intentions:
I don't have any actionable feedback here but these lines of code make me question whether it is a good idea for us to internally use integrations at all. This whole addOrUpdateBlah
api + passing down integrations and having to deal with them competing with eachother is so weird to me. I feel like we're more often fighting integrations than using them. In my perfect world, anything that is default should just be init options. Only extra non-default functionality should be done via integrations.
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, this addOrUpdate
business is a little weird, I'll admit, though it predates me. I actually have most of a solution to at least make that part cleaner, but it would mean making core code have more bytes just to accommodate something which only the nextjs SDK needs, so I gave up on it.
As for the rest, I wasn't there when the unified SDK API was specced out, but I think the reasoning is something along the lines of "keeping SDK-specific default behavior in integrations allows the init options to be more standardized across SDKs" (which is the ultimate goal of the the unified API).
req = { headers: {}, url: 'http://dogs.are.great/tricks/kangaroo' } as IncomingMessage; | ||
res = {} as ServerResponse; |
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.
Any reason why we can't define those variables for each test individually? That would keep side-effects between tests at a minimum.
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.
Tests run serially within a given test file I believe, though, right? So resetting them before each test should in theory prevent any side effects. (And doing it this way saves having the boilerplate at the beginning of every test. On that topic, I actually tried doing this as a test.each
but IIRC I couldn't get the types to work correctly.)
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 but why depend on jest behavior when you can simply make something be scoped to a single test? It is also easier to read and debug imo since you don't have to jump around to a globally defined variable and check whether it's overwritten at some point by some other test or if it is reset on some afterEach
hook.
I don't want to turn these tests into test.each
, I just want to remove the mutability of test inputs. I am not so worried about this particular file since it is very small but I'd like to avoid this pattern in other places. That's why I am bringing it up here.
This is a change which comes out of a code review[1] of #5703, moving the `CrossPlatformType` from `@sentry/utils` to `@sentry/types` and renaming it `PolymorphicRequest` (to match the existing `PolymorphicEvent`). Because it affects a number of files, I decided to pull it into its own PR, just to not confuse things in that one. [1] #5703 (comment)
96d9a6c
to
e4a9dd6
Compare
@@ -1,7 +1,9 @@ | |||
import { DynamicSamplingContext } from './envelope'; | |||
import { MeasurementUnit } from './measurement'; | |||
import { ExtractedNodeRequestData, Primitive, WorkerLocation } from './misc'; | |||
import { PolymorphicRequest } from './polymorphics'; |
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 looked at this type and something didn't feel right to me about PolymorphicRequest
being an intersection instead of a union so I opened an issue to discuss it: #5768
Of course that doesn't block this PR but just wanted to note it down here for posterity.
|
||
// For some reason TS can't figure out that after the constructor runs, `_addReqDataCallback` will always be defined | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
return addRequestData!(event, req, { include: formatIncludeOption(include) }); |
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.
Wdyt about removing this non-null-assertion and making turning the type of _options
into Required<RequestDataOptions>
? The constructor options are still optional that way. Just want to get rid of as many places where we disable the type checker as we can.
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 decided to go the other way (make everything required and have the constructor input be a partial) because it seems every-so-slightly more correct to me, but it still gets rid of the non-null assertion. That said, if we're worried about turning off the linter in this situation, maybe we should talk about whether we actually want to lint against !
at all. After all, we don't lint against as
typecasts (because we assume that if you're doing it, you're doing it for a good reason), and they're just a different way of saying the same thing.
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.
No need to change it now but I would have intentionally made it Required
since it's pretty unusual to have user-facing APIs that are Optional
. Doing it the other way would have kept the user-facing type simple to parse mentally.
2d8a11c
to
f84a690
Compare
f84a690
to
124fd6d
Compare
In #5703, a new integration, `RequestData`, was added to take the place of the request-specific event processors we've been using to add request data to transaction events in the nextjs SDK. This builds on that work by making the same switch for error events. Notes: - Because of #5718, it's hard to reason about the potential side effects of making major changes to the logic in `@sentry/utils/requestData`. Therefore, the majority of the new logic in this PR has been added to the integration, and just overwrites the `transaction` value added by the functions in `requestData`. Once we've cleaned up the request data code, we can consolidate the logic.
In most of our Node-based SDKs, we use domains to prevent scope bleed between requests, running the entire wrapped event-handling process inside a single domain. This works because in those cases, there is only one request-handling entry point for us to wrap, so we can stick the entire thing in a single
domain.run()
call and (more or less) rely on the fact that we've got a single, consistent, unique-to-that-requestScope
object that we're dealing with. We've followed this pattern in the nextjs SDK as well, both ininstrumentServer
andwithSentry
. The new auto-wrapping of data-fetching functions presents a challenge, though, because a single request may involve a number of our wrapped functions running at different points in the request handling process, with no shared (little S) scope between them. While we still use domains when wrapping the data fetchers, a single request may pass through multiple domains during its lifecycle, preventing us from using any given domain as a universalScope
-carrier for a singleScope
instance.One place where this becomes is problem is our use of
addRequestDataToEvent
, which up until now we've just been calling inside a single-request event processor added to the currentScope
. In this system, each event processor holds a reference to its particularreq
object. In the case of the data-fetchers, however, theScope
instance to which we might add such an event processor isn't the same as the one which will be active when the event is being processed, so the current way doesn't work. But given that the only way in which our current, single-request-specific event processors differ is by the request to which they hold a reference, they can be replaced by a single, and universal, event processor, as long as we can accessreq
a different way besides keeping it in a closure as we do now.This PR does just that (that is, switches to using a single event processor) for transaction events. First, a reference to
req
is stored in the transaction's metadata (which is then available to event processors assdkProcessingMetadata
). Then a new default integration,RequestData
, pullsreq
out of the metadata and uses it to add arequest
property to the event.Notes:
The options API for the new integration is inspired by, but different from, the options API for our Express request handler. (When we work on cleaning up the request data utility functions as part of fixing Clean up code relating to scope/event
transaction
value #5718, we might consider bringing those options in line with these.) The primary differences are:request
key holding a subset of the options.) Now everything is at the same level and only takes a boolean. The one exception isuser
, which can still take a boolean or a list of attributes.transaction
can either be a boolean or aTransactionNamingScheme
. In the integration, it can no longer be a boolean - events are going to have transactions, one way or another, so we shouldn't let people set it tofalse
. Further, since it's now not about whethertransaction
is or isn't included, it's been moved out ofinclude
into its owntransactionNamingScheme
option. (Note that the new option is not yet used, but will be in future PRs.)method
has also been moved out of include, and has been hardcoded totrue
, since it's an integral part of naming the request. We currently include it in the transaction name, regardless of the setting, so again here, letting people set it tofalse
makes no sense.Though
req
has been added to transaction metadata everywhere we start transactions, the existing domain/Scope
-based event processors haven't yet been removed, because this new method only works for transactions, not errors. (Solving that will be the subject of a future PR.) The existing processors have been modified to not apply to transaction events, however.Though we may at some point use the
RequestData
integration added here in browser-based SDKs as well, both in this PR and in near-term future PRs it will only be used in a Node context. It's therefore been added to@sentry/node
, to prevent a repeat of the dependency injection mess we just undid.No integration tests were added specifically for this change, because the existing integration tests already test that transaction events include a
request
property, so as long as they continue to pass, we know that using the integration instead of an event processor is working.Ref: #5505
TODO: