🐾

node.js/express.jsにSentryをいれる。一気通関にログを抽出するためのトレースIDを仕込む。

2024/06/13に公開

はじめに

node.jsにSetnryを導入し、ちょっと凝ったことをしようとしたとき、あまり記事がヒットしなかったので自分の実装を備忘録として残しておきます。
やりたいことはフロントとバックエンドを通して関連するログを一気通貫で抽出することです。

Source

今回使用したソースコードはこちらです。「sentry-nodejs-react」となっているのは、当初はフロント側も作ろうと思っていたからです、、、コードはnodejs部分だけです。

https://github.com/ishiyama0530/sentry-nodejs-react

そして、ほぼこのsrc/index.tsが全容になります。

https://github.com/ishiyama0530/sentry-nodejs-react/blob/main/src/index.ts

セットアップ

npm install --save @sentry/node
Sentry.init({
  dsn: "your sentry dsn",
  // debug: true, #Integrationの内訳などを確認したいときなど
  // environment: process.env.NODE_ENV #そもそも環境はdsnを別にしたい
});

トレースIDを仕込む

Sentryが自動で採番してくれるトレースIDもありますが、私の現場ではSentry以外にもシステムログを格納している場所があり、Sentryに依存しない形で関連するログを抽出できるのが理想でした。
なので、関連するログを芋づる式に抽出するための目印であるトレースIDは、アプリケーション側で作成します。

import httpContext from "express-http-context";
~
app.use(httpContext.middleware);
~
app.use((req, res, next) => {
  const traceId = crypto.randomUUID();
  httpContext.set("traceId", traceId);
  res.setHeader("x-traceId", traceId); // 後で使う
  next();
});

express-http-contextを使用するとリクエストごとに独立したコンテキストを保持することができ、それがリクエスト全体にわたってデータを共有が可能になります。
つまり、リクエストコンテキストにトレースIDを格納しておき、そのリクエスト中のログにそのトレースIDを仕込むことで、そのリクエストに関連するログはトレースIDから芋づる式に抽出することが可能になります。

https://www.npmjs.com/package/express-http-context

Sentryにログを送る

app.use((err: any, req: Request, res: Response, next: NextFunction) => {
  res.status(500).json({
    status: "error",
    message: "Internal Server Error",
  });

  Sentry.captureException(err, (s) => {
    s.setLevel("fatal");
    s.setTag("app-traceId", httpContext.get("traceId"));
    return s;
  });
});

上記のように定義することでExpress.jsでは包括的な例外処理を行うことが可能です(グローバルエラーハンドラ)
Sentry.captureExceptionは明示的にSentryにエラーログを送信するための関数です。
第二引数で送信するログの内容をカスタマイズでき、express-http-contextのgetで現在のリクエスト中のトレースIDを取り出し、タグにセットしています。

Sentry.setupExpressErrorHandler(app);

公式のExpressのページにはSentry.setupExpressErrorHandler(app);を定義すれば良いよとあったのですが、これを定義してしまうと、グローバルエラーハンドラに入った時点でSentryにログが送信されてしまい、ここの部分がログに反映されませんでした。

  Sentry.captureException(err, (s) => {
--ここ
    s.setLevel("fatal");
    s.setTag("app-traceId", httpContext.get("traceId"));
--
    return s;
  });

なので、独自のトレースIDを書き込みたい私はこれを使わず、グローバルエラーハンドラ内でSentry.captureExceptionを定義するようにしています。
(ここあたり、間違えてたら優しく教えて下さい🥹)

Sentryの管理画面で

こんな感じでトレースIDが格納されており、

こんな感じでトレースIDでログを検索することができます。

クライアントサイド

res.setHeader("x-traceId", traceId); // 後で使う

のおかげで、クライアント側でもリクエストに紐づくトレースIDが取得できました。
これをクライアント側のログに仕込むことで、クライアントとバックエンドのログがトレースIDを通して一気通貫で抽出できそうです。

バックエンドのバックエンド

フロントエンド -(api1)- BFF -(api2)- 共通バックエンド

など、システムが3層以上に分かれている場合、api2のリクエストヘッダーにx-traceIdを仕込みます。
共通バックエンドのログにx-traceIdを記録しておいてもらうと、共通バックエンドとのエラー解決に役立ちそうです。

さいごに

今回Sentryを使ってみてSplunkなどと比べて自由度が低い反面、SDKが豊富だったりSlackを使ったアラート設定がとても簡単にできるなど、導入コストの低さに魅力を感じました!
(Splunkと比べるのはちょっと…ってのはわかります😇)
また何かアップデートあったら更新またはサイレント手直しさせてください。

Discussion