NestJSのHttpExceptionをRollbarに通知するためのラッパーエラークラス

Yak Shavingもいいところですが、覚書として。 やりたかったこと NestJSのエラーをRollbarに投げる NestJSはHttpException投げてくるので、interceptorで拾ってなげる (ne […]

広告ここから
広告ここまで

目次

    Yak Shavingもいいところですが、覚書として。

    やりたかったこと

    • NestJSのエラーをRollbarに投げる
    • NestJSはHttpException投げてくるので、interceptorで拾ってなげる (nest-ravenを真似する)
    • stackTraceとかメッセージはわかりやすく

    困ったところ

    rollbar.jsが想定しているErrorオブジェクトと、NestJSが投げるHttpExceptionオブジェクトが違うので、Error [object Object]で記録される。

    • Rollbar.jsが思うExpception -> error.messageにメッセージがある
    • NestJSが投げるHttpException -> error.messageはオブジェクト(statusCodeやmessageなどが入っている)
    • string想定の場所にObjectがくるから[object Object]になる
    • とはいえnew ErrorとかするとstackTraceが変なことになる

    やったこと

    Qiitaのカスタム例外を作る記事のコードが、stackTraceを継承できる様子だったので、これを使うことに。

    ただし以下の理由からnpmのライブラリではなくコードをforkして使用。

    • 元記事はカスタム例外を作って投げることを想定している
    • 今回は「すでにあるカスタム例外」をラップしたい
    • TypeScriptに対応していない

    /**
     * NestJSのHttpExceptionをRollbarで記録できるようにラップするやつ
     * 参考: https://qiita.com/taharah/items/ef69f2b722844cc249f6
     */
    export class HttpExceptionForRollbar extends Error {
        constructor(message: string | Error, arg?: Error) {
          let errorToWrap: Error;
      
          if (message instanceof Error) {
            errorToWrap = message;
          } else if (arg instanceof Error) {
            errorToWrap = arg;
          }
      
          super(message instanceof Error ? message.message : message);
      
          // Align with Object.getOwnPropertyDescriptor(Error.prototype, 'name')
          Object.defineProperty(this, 'name', {
            configurable: true,
            enumerable: false,
            value: errorToWrap.constructor.name,
            writable: true,
          });
      
          // Helper function to merge stack traces
          const mergeStackTrace = (stackTraceToMerge, baseStackTrace) => {
            if (!baseStackTrace) {
              return stackTraceToMerge;
            }
      
            const entriesToMerge = stackTraceToMerge.split('\n');
            const baseEntries = baseStackTrace.split('\n');
      
            /**
             * 1行目のメッセージだけ差し替えておく。
             * 2行目以降はwrapした時点のtraceなのでいらない。
             */
            const newEntries = [
              entriesToMerge[0],
              ...baseEntries.filter((entry, i) => i > 0)
            ];
            return newEntries.join('\n')
          };
      
          const stackTraceSoFar = errorToWrap ? errorToWrap.stack : undefined;
      
          if (Error.hasOwnProperty('captureStackTrace')) {
            Error.captureStackTrace(this, this.constructor);
            this.stack = mergeStackTrace(this.stack, stackTraceSoFar);
            return;
          }
      
          // This class is supposed to be extended, so the first two lines from
          // the second line are about error object constructors.
          const stackTraceEntries = new Error(message instanceof Error ? message.message : message).stack.split('\n');
          const stackTraceWithoutConstructors =
            [stackTraceEntries[0], ...stackTraceEntries.slice(3)].join('\n');
      
          this.stack = mergeStackTrace(stackTraceWithoutConstructors, stackTraceSoFar);
        }
      }
      

    Interceptorを作る

    雑ですがこんな感じで、httpかつHttpExceptionの時だけ先ほど作ったエラークラスでラップしてやることにしました。

    export class RollbarInterceptor implements NestInterceptor {
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        const reporter = new RollbarService();
        return next.handle().pipe(
          tap(null, (exception: Error) => {
            try {
              const type = context.getType();
              if (type === 'http') {
                const http = context.switchToHttp();
                const request = getLoggingRequest(http.getRequest<Request>());
                if (exception instanceof HttpException) {
                  const wrappedError = new HttpExceptionForRollbar(exception.message.message, exception)
                  reporter.error(wrappedError, { request });
                } else {
                  reporter.error(exception, { request, exception });
                }
              } else {
                reporter.error(exception);
              }
            } catch (e) {
              console.log(e);
            }
          }),
        );
      }
    }
    

    結果

    一覧もstackTraceもざっと出せるように。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark