- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 1.7k
ref(nextjs): Use integration to add request data to transaction events #5703
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
          
     Merged
      
      
            lobsterkatie
  merged 7 commits into
  master
from
kmclb-nextjs-add-requestdata-integration
  
      
      
   
  Sep 19, 2022 
      
    
  
     Merged
                    Changes from all commits
      Commits
    
    
            Show all changes
          
          
            7 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      1bd5515
              
                add `request` to `TransactionMetadata` type
              
              
                lobsterkatie 2626356
              
                add request to transaction metadata
              
              
                lobsterkatie be7471f
              
                add `RequestData` integration
              
              
                lobsterkatie e44ed8f
              
                add `RequestData` to default server integrations
              
              
                lobsterkatie 57f2f38
              
                stop using existing request data event processors on transaction events
              
              
                lobsterkatie fed0ff3
              
                add and fix tests
              
              
                lobsterkatie 124fd6d
              
                add `transactionNamingScheme` option to `RequestData` integration
              
              
                lobsterkatie File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import * as SentryCore from '@sentry/core'; | ||
| import * as SentryTracing from '@sentry/tracing'; | ||
| import { IncomingMessage, ServerResponse } from 'http'; | ||
|  | ||
| import { | ||
| withSentryGetServerSideProps, | ||
| withSentryServerSideGetInitialProps, | ||
| // TODO: Leaving `withSentryGetStaticProps` out for now until we figure out what to do with it | ||
| // withSentryGetStaticProps, | ||
| // TODO: Leaving these out for now until we figure out pages with no data fetchers | ||
| // withSentryServerSideAppGetInitialProps, | ||
| // withSentryServerSideDocumentGetInitialProps, | ||
| // withSentryServerSideErrorGetInitialProps, | ||
| } from '../../src/config/wrappers'; | ||
|  | ||
| const startTransactionSpy = jest.spyOn(SentryCore, 'startTransaction'); | ||
| const setMetadataSpy = jest.spyOn(SentryTracing.Transaction.prototype, 'setMetadata'); | ||
|  | ||
| describe('data-fetching function wrappers', () => { | ||
| const route = '/tricks/[trickName]'; | ||
| let req: IncomingMessage; | ||
| let res: ServerResponse; | ||
|  | ||
| describe('starts a transaction and puts request in metadata if tracing enabled', () => { | ||
| beforeEach(() => { | ||
| req = { headers: {}, url: 'http://dogs.are.great/tricks/kangaroo' } as IncomingMessage; | ||
| res = {} as ServerResponse; | ||
|  | ||
| jest.spyOn(SentryTracing, 'hasTracingEnabled').mockReturnValueOnce(true); | ||
| }); | ||
|  | ||
| afterEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|  | ||
| test('withSentryGetServerSideProps', async () => { | ||
| const origFunction = jest.fn(async () => ({ props: {} })); | ||
|  | ||
| const wrappedOriginal = withSentryGetServerSideProps(origFunction, route); | ||
| await wrappedOriginal({ req, res } as any); | ||
|  | ||
| expect(startTransactionSpy).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| name: '/tricks/[trickName]', | ||
| op: 'nextjs.data.server', | ||
| metadata: expect.objectContaining({ source: 'route' }), | ||
| }), | ||
| ); | ||
|  | ||
| expect(setMetadataSpy).toHaveBeenCalledWith({ request: req }); | ||
| }); | ||
|  | ||
| test('withSentryServerSideGetInitialProps', async () => { | ||
| const origFunction = jest.fn(async () => ({})); | ||
|  | ||
| const wrappedOriginal = withSentryServerSideGetInitialProps(origFunction); | ||
| await wrappedOriginal({ req, res, pathname: route } as any); | ||
|  | ||
| expect(startTransactionSpy).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| name: '/tricks/[trickName]', | ||
| op: 'nextjs.data.server', | ||
| metadata: expect.objectContaining({ source: 'route' }), | ||
| }), | ||
| ); | ||
|  | ||
| expect(setMetadataSpy).toHaveBeenCalledWith({ request: req }); | ||
| }); | ||
| }); | ||
| }); | ||
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| // TODO (v8 or v9): Whenever this becomes a default integration for `@sentry/browser`, move this to `@sentry/core`. For | ||
| // now, we leave it in `@sentry/integrations` so that it doesn't contribute bytes to our CDN bundles. | ||
|  | ||
| import { EventProcessor, Hub, Integration } from '@sentry/types'; | ||
|  | ||
| import { | ||
| addRequestDataToEvent, | ||
| AddRequestDataToEventOptions, | ||
| DEFAULT_USER_INCLUDES, | ||
| TransactionNamingScheme, | ||
| } from '../requestdata'; | ||
|  | ||
| type RequestDataOptions = { | ||
| /** | ||
| * Controls what data is pulled from the request and added to the event | ||
| */ | ||
| include: { | ||
| cookies?: boolean; | ||
| data?: boolean; | ||
| headers?: boolean; | ||
| ip?: boolean; | ||
| query_string?: boolean; | ||
| url?: boolean; | ||
| user?: boolean | Array<typeof DEFAULT_USER_INCLUDES[number]>; | ||
| }; | ||
|  | ||
| /** Whether to identify transactions by parameterized path, parameterized path with method, or handler name */ | ||
| transactionNamingScheme: TransactionNamingScheme; | ||
|  | ||
| /** | ||
| * Function for adding request data to event. Defaults to `addRequestDataToEvent` from `@sentry/node` for now, but | ||
| * left injectable so this integration can be moved to `@sentry/core` and used in browser-based SDKs in the future. | ||
| * | ||
| * @hidden | ||
| */ | ||
| addRequestData: typeof addRequestDataToEvent; | ||
| }; | ||
|  | ||
| const DEFAULT_OPTIONS = { | ||
| addRequestData: addRequestDataToEvent, | ||
| include: { | ||
| cookies: true, | ||
| data: true, | ||
| headers: true, | ||
| ip: false, | ||
| query_string: true, | ||
| url: true, | ||
| user: DEFAULT_USER_INCLUDES, | ||
| }, | ||
| transactionNamingScheme: 'methodpath', | ||
| }; | ||
|  | ||
| /** Add data about a request to an event. Primarily for use in Node-based SDKs, but included in `@sentry/integrations` | ||
| * so it can be used in cross-platform SDKs like `@sentry/nextjs`. */ | ||
| export class RequestData implements Integration { | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| public static id: string = 'RequestData'; | ||
|  | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| public name: string = RequestData.id; | ||
|  | ||
| private _options: RequestDataOptions; | ||
|  | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| public constructor(options: Partial<RequestDataOptions> = {}) { | ||
| this._options = { | ||
| ...DEFAULT_OPTIONS, | ||
| ...options, | ||
| include: { | ||
| // @ts-ignore It's mad because `method` isn't a known `include` key. (It's only here and not set by default in | ||
| // `addRequestDataToEvent` for legacy reasons. TODO (v8): Change that.) | ||
| method: true, | ||
| ...DEFAULT_OPTIONS.include, | ||
| ...options.include, | ||
| }, | ||
| }; | ||
| } | ||
|  | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| public setupOnce(addGlobalEventProcessor: (eventProcessor: EventProcessor) => void, getCurrentHub: () => Hub): void { | ||
| const { include, addRequestData } = this._options; | ||
|  | ||
| addGlobalEventProcessor(event => { | ||
| const self = getCurrentHub().getIntegration(RequestData); | ||
| const req = event.sdkProcessingMetadata && event.sdkProcessingMetadata.request; | ||
|  | ||
| // If the globally installed instance of this integration isn't associated with the current hub, `self` will be | ||
| // undefined | ||
| if (!self || !req) { | ||
| return event; | ||
| } | ||
|  | ||
| return addRequestData(event, req, { include: formatIncludeOption(include) }); | ||
| }); | ||
| } | ||
| } | ||
|  | ||
| /** Convert `include` option to match what `addRequestDataToEvent` expects */ | ||
| /** TODO: Can possibly be deleted once https://github.com/getsentry/sentry-javascript/issues/5718 is fixed */ | ||
| function formatIncludeOption( | ||
| integrationInclude: RequestDataOptions['include'] = {}, | ||
| ): AddRequestDataToEventOptions['include'] { | ||
| const { ip, user, ...requestOptions } = integrationInclude; | ||
|  | ||
| const requestIncludeKeys: string[] = []; | ||
| for (const [key, value] of Object.entries(requestOptions)) { | ||
| if (value) { | ||
| requestIncludeKeys.push(key); | ||
| } | ||
| } | ||
|  | ||
| return { | ||
| ip, | ||
| user, | ||
| request: requestIncludeKeys.length !== 0 ? requestIncludeKeys : undefined, | ||
| }; | ||
| } | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -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 commentThe 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  Of course that doesn't block this PR but just wanted to note it down here for posterity. | ||
| import { Span, SpanContext } from './span'; | ||
|  | ||
| /** | ||
| * Interface holding Transaction-specific properties | ||
| */ | ||
|  | @@ -145,7 +147,11 @@ export interface TransactionMetadata { | |
| */ | ||
| dynamicSamplingContext?: Partial<DynamicSamplingContext>; | ||
|  | ||
| /** For transactions tracing server-side request handling, the request being tracked. */ | ||
| request?: PolymorphicRequest; | ||
|  | ||
| /** For transactions tracing server-side request handling, the path of the request being tracked. */ | ||
| /** TODO: If we rm -rf `instrumentServer`, this can go, too */ | ||
| requestPath?: string; | ||
|  | ||
| /** Information on how a transaction name was generated. */ | ||
|  | ||
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.
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.eachbut 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
afterEachhook.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.