Next.js サーバーの o11y 周りで調べたことのメモ
前提として、自分のところは custom server 構成 (app router)
Metrics
Prom client を runtime metrics の収集に使っている。デフォルトだと Prometheus の推奨のメトリクス + Node.js 固有の event loop などのログなどを収集する。
There are some default metrics recommended by Prometheus itself. To collect these, call collectDefaultMetrics. In addition, some Node.js-specific metrics are included, such as event loop lag, active handles, GC and Node.js version. See lib/metrics for a list of all metrics.
Custom Metrics は Otel のライブラリを使って収集している。リクエストの回数とか。
できれば Otel に寄せたいが、runtime metrics に関する issue は open のままになっている。
現状の整理としてはこんな感じか
- Host metricsを取得するパッケージは Otel にある
- Node.js 固有のメトリクスを取得するパッケージが未成熟
- ここ最近追加されている: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/instrumentation-runtime-node
- nodejs.event_loop.utilization しか計測できてなさそうで、prom client がサポートしている GC などのメトリクスは取れてなさそう
Trace
ローカルで trace を確認する場合は jaeger の all ion one イメージを otel collector と一緒に立ち上げるのが楽だった。
# coimpose.yaml の定義
jaeger:
image: jaegertracing/all-in-one:1.56.0
ports:
- '16686:16686' # frontend
- '4317:4317' # OTLP gRPC receiver
environment:
- COLLECTOR_OTLP_ENABLED=true
Next 側で定義している span について
実装の概要としては、opentelemtry js sdk の trace API の wrapper を用意して、それを内部のいろんなところで使っているという感じ。
instrumentationHookを true にすると root の instrumentation.ts が読み込まれる。以下のようなシンプルな sdk の初期化処理が書かれているイメージ。
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'
const sdk = new NodeSDK({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: 'next-app',
}),
spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()
sdk-trace-node と trace api とかの連携の流れがよくわかってないので、もう少し掘り下げる
logs
pino で構造化しているが、request id を渡せてない気もするので、いい感じにした方がいいかもしれない。Pino の使い方は、以下のブログが参考になる。
request id を渡す方法として一番シンプルなのが http header を使う方法。以下のブログとかも参考になりそう。
ただ最近だと AsyncLocalStorage を使うのが主流だと思う。
また「いい感じにした方がいいかもしれない」と書いてるのは、Otel の node の自動計装パッケージを使うと、opentelemetry-instrumentation-pino が導入され、pino のログに trace のコンテキストが自動で挿入されるから。 trace のコンテキストがあれば、trace id などを使えば ログを引けそうな感じもある。ここら辺、server actions とかも考慮していい感じに設計したい。
4年ほど前から更新されてないが、pino が提供しているサンプルもある。custom server 構成で、フロントエンドのエラーを /logs のエンドポイントへ送信するようにしている。(pino の trasmit 機能を使っている) /logs のエンドポイントは、next.js が動くサーバーでさばいている。
/logs を定義している箇所