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 Performance Tracking #97

Open
jankrikava opened this issue Sep 13, 2022 · 3 comments
Open

Add Performance Tracking #97

jankrikava opened this issue Sep 13, 2022 · 3 comments

Comments

@jankrikava
Copy link

First off thanks for nestjs-sentry which recently helped us a lot. We would appreciate if we can add Performance Tracking (https://docs.sentry.io/product/performance/getting-started/) to the npm package. Anyone else here who would appreciate this feature?

@mpx2m
Copy link

mpx2m commented Sep 20, 2022

here is some code, maybe help you

import { Plugin } from '@nestjs/apollo'
import { ApolloServerPlugin, GraphQLRequestListener } from 'apollo-server-plugin-base'
import { InjectSentry, SentryService } from '@ntegral/nestjs-sentry'
import { GraphQLRequestContext } from 'apollo-server-types'
import '@sentry/tracing'

/**
 * @document: https://develop.sentry.dev/sdk/event-payloads/request/
 * @document: https://github.com/ntegral/nestjs-sentry/issues/63
 * @document: https://www.apollographql.com/docs/apollo-server/integrations/plugins/
 * @document: https://blog.sentry.io/2021/08/31/guest-post-performance-monitoring-in-graphql
 * @document: https://github.com/getsentry/sentry-javascript/issues/4731
 */

@Plugin()
export class SentryPlugin implements ApolloServerPlugin {
  constructor(@InjectSentry() private readonly sentry: SentryService) {}

  async requestDidStart({ request, context }: GraphQLRequestContext): Promise<GraphQLRequestListener> {
    const transaction = this.sentry.instance().startTransaction({
      op: 'gql',
      name: request.operationName ? `graphql: ${request.operationName}` : 'GraphQLTransaction'
    })

    this.sentry
      .instance()
      .getCurrentHub()
      .configureScope(scope => {
        const { headers, body: data, method, baseUrl: url } = context.req
        scope.addEventProcessor(event => {
          event.request = { method, url, headers, data }
          return event
        })
      })

    this.sentry.instance().configureScope(scope => {
      scope.setSpan(transaction)
    })

    return {
      // hook for transaction finished
      async willSendResponse() {
        transaction.finish()
      },
      async executionDidStart() {
        return {
          // hook for each new resolver
          willResolveField({ info }) {
            const span = transaction.startChild({
              op: 'resolver',
              description: `${info.parentType.name}.${info.fieldName}`
            })
            // this will execute once the resolver is finished
            return () => {
              span.finish()
            }
          }
        }
      }
    }
  }
}

I update code to below to solve some problems of tracing overflow

@Plugin()
export class SentryPlugin implements ApolloServerPlugin {
  constructor(@InjectSentry() private readonly sentry: SentryService) {}

  async requestDidStart({ request, context }: GraphQLRequestContext): Promise<GraphQLRequestListener> {
    context.transaction = this.sentry.instance().startTransaction({
      op: 'gql',
      name: request.operationName ? `graphql: ${request.operationName}` : 'graphql: withoutOperationName'
    })

    this.sentry
      .instance()
      .getCurrentHub()
      .configureScope(scope => {
        scope.addEventProcessor(event => {
          const { headers, body: data, method, baseUrl: url } = context.req
          // eslint-disable-next-line no-param-reassign
          event.request = { method, url, headers, data }
          return event
        })
      })

    this.sentry.instance().configureScope(scope => {
      scope.setSpan(context.transaction)
    })

    return {
      // hook for transaction finished
      async willSendResponse({ context }) {
        context.transaction.finish()
      },
      async executionDidStart() {
        return {
          // hook for each new resolver
          willResolveField({ context, info }) {
            const span = context.transaction.startChild({
              op: 'resolver',
              description: `${info.parentType.name}.${info.fieldName}`
            })
            // this will execute once the resolver is finished
            return () => {
              span.finish()
            }
          }
        }
      }
    }
  }
}

@CalMlynarczyk
Copy link

I was able to get performance monitoring, including tracing, working on Sentry v7.31.1 using the below module configuration (thanks to @mahendradambe and their own Sentry/NestJS integration for pointing me in the right direction on how to inject the Express application object into the tracing integration).

Root Module (ex. AppModule)

SentryModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService, HttpAdapterHost],
  useFactory: async (
    config: ConfigService,
    adapterHost: HttpAdapterHost,
  ) => ({
    dsn: "...",
    // ...
    integrations: [
      // ...,
      new Sentry.Integrations.Http({ tracing: true }),
      new Tracing.Integrations.Express({
        app: adapterHost.httpAdapter.getInstance(),
      }),
    ] as Integration[],
    tracesSampleRate: 1.0,
  }),
})

App Init

const app = await NestFactory.create<NestExpressApplication>(AppModule);

app.use(Sentry.Handlers.tracingHandler());

await app.listen(8080);

@AdamGerthel
Copy link

AdamGerthel commented Sep 14, 2023

@CalMlynarczyk it seems your example has become outdated. Tracing.Integrations.Express is deprecated in @sentry/tracing. But even without that, it seems that only my first request to the server works after adding app.use(Sentry.Handlers.tracingHandler());. After that, the server stops responding to requests (but is still running). This is with @sentry/node running on ^6.13.3.

Are you using this sample still?

Update:

I got it working on @sentry/node 7.69.0 without using Tracing.Integrations.Express:

    SentryModule.forRoot({
      dsn: process.env.SENTRY_DSN,
      // ...
      integrations: [
        new Sentry.Integrations.Http({ tracing: true })
      ] as unknown as Integration[],
    }),

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

No branches or pull requests

4 participants