All files / lib/services sentry.interceptor.ts

23.4% Statements 11/47
0% Branches 0/30
20% Functions 2/10
22.5% Lines 9/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 1101x   1x 1x 1x     1x       1x     3x 3x       1x                                                                                                                                                                                    
import { CallHandler, ExecutionContext, Injectable, NestInterceptor, Inject } from '@nestjs/common';
import { Observable, TimeoutError } from 'rxjs';
import { tap } from 'rxjs/operators';
import { SENTRY_TOKEN, InjectSentry } from '../common';
import { SentryService } from './sentry.service';
import { Scope } from '@sentry/hub';
import { HttpArgumentsHost, ContextType, WsArgumentsHost, RpcArgumentsHost } from '@nestjs/common/interfaces';
import { Handlers } from '@sentry/node';
import { SentryInterceptorOptions, SentryFilterFunction, SentryInterceptorOptionsFilter } from '../interfaces/sentry-interceptor.interface';
 
@Injectable()
export class SentryInterceptor implements NestInterceptor {
 
  constructor(
    @InjectSentry() private readonly client: SentryService,
    private readonly options?: SentryInterceptorOptions) {}
 
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // first param would be for events, second is for errors
    return next.handle().pipe(
      tap(null, (exception) => {
        if (this.shouldReport(exception)) {
          this.client.instance().withScope((scope) => {
            switch (context.getType<ContextType>()) {
              case 'http':
                return this.captureHttpException(
                  scope, 
                  context.switchToHttp(), 
                  exception
                );
              case 'rpc':
                return this.captureRpcException(
                  scope,
                  context.switchToRpc(),
                  exception,
                );
              case 'ws':
                return this.captureWsException(
                  scope,
                  context.switchToWs(),
                  exception,
                );
            }
          })
        }
      })
    );
  }
 
  private captureHttpException(scope: Scope, http: HttpArgumentsHost, exception: any): void {
    const data = Handlers.parseRequest(<any>{},http.getRequest(), this.options);
 
    scope.setExtra('req', data.request);
    data.extra && scope.setExtras(data.extra);
    if (data.user) scope.setUser(data.user);
 
    this.captureException(scope, exception);
  }
 
  private captureRpcException(
    scope: Scope,
    rpc: RpcArgumentsHost,
    exception: any,
  ): void {
    scope.setExtra('rpc_data', rpc.getData());
 
    this.captureException(scope, exception);
  }
 
  private captureWsException(
    scope: Scope,
    ws: WsArgumentsHost,
    exception: any,
  ): void {
    scope.setExtra('ws_client', ws.getClient());
    scope.setExtra('ws_data', ws.getData());
 
    this.captureException(scope, exception);
  }
 
  private captureException(scope: Scope, exception: any): void {
    if (this.options) {
      if (this.options.level) scope.setLevel(this.options.level);
      if (this.options.fingerprint)
        scope.setFingerprint(this.options.fingerprint);
      if (this.options.extra) scope.setExtras(this.options.extra);
      if (this.options.tags) scope.setTags(this.options.tags);
    }
 
    this.client.instance().captureException(exception);
  }
 
  private shouldReport(exception: any) {
    if (this.options && !this.options.filters) return true;
 
    // If all filters pass, then we do not report
    if (this.options) {
      const opts: SentryInterceptorOptions = this.options as {}
      if (opts.filters) {
        let filters: SentryInterceptorOptionsFilter[] = opts.filters
        return filters.every(({ type, filter }) => {
          return !(exception instanceof type && (!filter || filter(exception)));
        });
      }
    } else {
      return true;
    }
  }
}