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