Git Product home page Git Product logo

Comments (6)

foxted avatar foxted commented on July 30, 2024 15

I used a workaround in the meantime. Install @sentry/tracing and setup the module like so:

SentryModule.forRoot({
   enabled: true,
   dsn: process.env.SENTRY_DSN,
   debug: false,
   environment: 'production',
   integrations: [
      new Sentry.Integrations.Http({ tracing: true })
   ],
   tracesSampleRate: 1.0,
})

Then if you want to trace requests, create a middleware, like trace.middleware.ts like this:

import {
  Injectable,
  NestMiddleware,
} from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
import { InjectSentry, SentryService } from '@ntegral/nestjs-sentry';

@Injectable()
export class TraceMiddleware implements NestMiddleware {
  constructor(@InjectSentry() private readonly sentry: SentryService) {}
  use(req: Request, res: Response, next: NextFunction): void {
    const transaction = this.sentry.instance().startTransaction({
      op: 'request',
      name: req.url,
    });

    this.sentry.instance().getCurrentHub().configureScope(scope => {
      scope.addEventProcessor(event => {
        event.request = {
          method: req.method,
          url: req.url,
        };
        return event;
      });
    });

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

    req.on('close', () => {
      transaction.setHttpStatus(res.statusCode);
      transaction.finish();
    });

    next();
  }
}

Then you can setup the middleware in app.module.ts (or any module you want):

export class AppModule {
    configure(consumer: MiddlewareConsumer): void {
        consumer.apply(TraceMiddleware).forRoutes('*');
    }
}

from nestjs-sentry.

moshenskyDV avatar moshenskyDV commented on July 30, 2024 5

Additional to answer above, if you have an error
TypeError: Cannot read property 'setHttpStatus' of undefined

You need to add

import * as SentryTracing from '@sentry/tracing';
SentryTracing.addExtensionMethods();

somewhere in main.ts (before app.listen).

from nestjs-sentry.

mustafa-barakat avatar mustafa-barakat commented on July 30, 2024 2

I think the name should be req.baseUrl , for some req.url is empty in my case

from nestjs-sentry.

SonnyAD avatar SonnyAD commented on July 30, 2024

That worked for me too. Thanks @foxted and @moshenskyDV

from nestjs-sentry.

JaLe29 avatar JaLe29 commented on July 30, 2024

Hey, having issue with @foxted code. I am using same code but my Performance tab is empty :-( Any hints?

https://i.imgur.com/y0kN5Ec.png

from nestjs-sentry.

sikhlana avatar sikhlana commented on July 30, 2024

For anyone else wondering how to add support for traces:

I created these two middlewares for handling transactions:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { InjectSentry, SentryService } from '@ntegral/nestjs-sentry';
import { Handlers } from '@sentry/node';
import { NextFunction, Request, Response } from 'express';

@Injectable()
export class RequestMiddleware implements NestMiddleware {
  private readonly handler = Handlers.requestHandler({
    include: {
      ip: true,
      user: true,
      request: true,
      transaction: 'methodPath',
    },
  });

  constructor(@InjectSentry() private readonly sentry: SentryService) {}
  use(req: Request, res: Response, next: NextFunction): void {
    this.handler(req, res, next);
  }
}
import { Injectable, NestMiddleware } from '@nestjs/common';
import { InjectSentry, SentryService } from '@ntegral/nestjs-sentry';
import { Handlers } from '@sentry/node';
import { NextFunction, Request, Response } from 'express';

@Injectable()
export class TraceMiddleware implements NestMiddleware {
  private readonly handler = Handlers.tracingHandler();

  constructor(@InjectSentry() private readonly sentry: SentryService) {}
  use(req: Request, res: Response, next: NextFunction): void {
    this.handler(req, res, next);
  }
}

Then added them to a module:

export class WebModule {
  configure(consumer: MiddlewareConsumer): void {
    consumer.apply(TraceMiddleware).forRoutes('*');
    consumer.apply(RequestMiddleware).forRoutes('*');
  }
}

I have also overridden the interceptor with (note that we only use HTTP, didn't consider WS and RPC):

import { CallHandler, ExecutionContext, HttpException } from '@nestjs/common';
import {
  SentryInterceptor as BaseInterceptor,
  InjectSentry,
  SentryService,
} from '@ntegral/nestjs-sentry';
import { Handlers } from '@sentry/node';
import { Observable, catchError, throwError } from 'rxjs';
import { EntityNotFoundError } from 'typeorm';

export class SentryInterceptor extends BaseInterceptor {
  private readonly handler = Handlers.errorHandler();

  constructor(@InjectSentry() private readonly sentry: SentryService) {
    super({
      filters: [
        {
          type: HttpException,
          filter: (e: HttpException) => e.getStatus() < 500,
        },
        {
          type: EntityNotFoundError,
        },
      ],
    });
  }

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      catchError((error) => {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        if (this.shouldReport(error)) {
          const http = context.switchToHttp();
          this.handler(error, http.getRequest(), http.getResponse(), () => {});
        }

        return throwError(() => error);
      }),
    );
  }
}

And this is how I setup the Sentry module:

    SentryCoreModule.forRootAsync({
      inject: [ConfigService, VersionService, HttpAdapterHost],
      useFactory: async (
        config: ConfigService,
        version: VersionService,
        host: HttpAdapterHost,
      ) => ({
        dsn: config.get('SENTRY_DSN'),
        release: await version.release(),
        environment: config.get('NODE_ENV'),
        tracesSampleRate: 1.0,
        integrations: [
          new Sentry.Integrations.Http({ tracing: true }),
          new Mysql(),
          host?.httpAdapter
            ? new Tracing.Integrations.Express({
                app: host.httpAdapter.getInstance(),
              })
            : null,
        ].filter((i) => !!i),
      }),
    }),

Note: I created my own version of Mysql integration to support the mysql2 package:

import type { Hub } from '@sentry/core';
import type { EventProcessor, Integration } from '@sentry/types';
import { fill, loadModule, logger } from '@sentry/utils';

interface MysqlConnection {
  createQuery: () => void;
}

/** Tracing integration for node-mysql package */
export class Mysql implements Integration {
  /**
   * @inheritDoc
   */
  public static id = 'Mysql';

  /**
   * @inheritDoc
   */
  public name: string = Mysql.id;

  /**
   * @inheritDoc
   */
  public setupOnce(
    _: (callback: EventProcessor) => void,
    getCurrentHub: () => Hub,
  ): void {
    const pkg = loadModule<MysqlConnection>('mysql2/lib/connection.js');

    if (!pkg) {
      __DEBUG_BUILD__ &&
        logger.error(
          'Mysql Integration was unable to require `mysql2` package.',
        );
      return;
    }

    fill(pkg, 'createQuery', function (orig: () => void) {
      return function (
        this: unknown,
        sql: unknown,
        values: unknown,
        cb: Function,
        config: any,
      ) {
        let options: Record<string, any> = {
          rowsAsArray: config?.rowsAsArray,
        };

        if (typeof sql === 'object') {
          // query(options, cb)
          options = sql;
          if (typeof values === 'function') {
            cb = values;
          } else if (values !== undefined) {
            options.values = values;
          }
        } else if (typeof values === 'function') {
          // query(sql, cb)
          cb = values;
          options.sql = sql;
          options.values = undefined;
        } else {
          // query(sql, values, cb)
          options.sql = sql;
          options.values = values;
        }

        const scope = getCurrentHub().getScope();
        const parentSpan = scope?.getSpan();
        const span = parentSpan?.startChild({
          description: options.sql,
          op: 'db',
        });

        const ret = orig.call(this, ...arguments); // eslint-disable-line prefer-rest-params

        ret.on('end', () => {
          span?.finish();
        });

        return ret;
      };
    });
  }
}

from nestjs-sentry.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.