JavaScriptNestjs

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もざっと出せるように。

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

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

Related Category posts