New RelicではOpenTelemetry(以下OTel)で収集したテレメトリーデータを受け取り、APMと同じようにトランザクション単位でパフォーマンスを可視化できます。しかし、PythonアプリにOTelを導入してNew Relicに送信してみたものの、トランザクションがHTTPメソッド単位でまとまってしまい、パスが表示されないことがあります。
原因の一つとして、送信メトリクスに、New Relicがパスの判定に使う属性(http.route)が含まれていないことが考えられます。
本記事では、このトランザクション名にパスが表示されないケースの原因特定から対処までの流れを確認していきます。
前提
本記事では、アプリのコードをほとんど変更せずに計装する「ゼロコード計装」で確認を行います。 ゼロコード計装の詳細については、OpenTelemetryのドキュメント「Pythonゼロコード・計装」を参照してください。
導入は、opentelemetry-distro と opentelemetry-exporter-otlp をインストールし、opentelemetry-bootstrap -a install を実行します。
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
また、New Relicへはデータを OTLP エクスポーターで送信します。設定はNew Relic公式のサンプル集 newrelic-opentelemetry-examples に沿って、環境変数で指定します。
export OTEL_SERVICE_NAME=fastapi-newrelic-app
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net
export OTEL_EXPORTER_OTLP_HEADERS="api-key=<YOUR_LICENSE_KEY>"
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_COMPRESSION=gzip
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta
export OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT=4095
export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
OTEL_EXPORTER_OTLP_HEADERS の <YOUR_LICENSE_KEY> は、ご自身のNew Relicライセンスキーに置き換えてください。
また、例ではエンドポイントにUSを指定していますが、利用しているデータセンターに応じてエンドポイントを適宜変更して下さい。
なぜパスが表示されないのか
New Relicは、OpenTelemetryのトランザクション名を、スパンではなくメトリクスの属性から組み立てています(変換ルールの詳細は New Relic ドキュメント「OpenTelemetry APM UI」を参照してください)。具体的には、メトリクスに付与されている http.routeと http.request.methodを使って、次のようなトランザクション名を生成します。
GET /users/{id}
ここで、メトリクスに http.route が含まれていないと、New Relicはルートを解決できず、メソッド単位へまとめてしまいます。
ポイントは、スパンには http.route が付いているのに、メトリクスでは欠落しているという点です。
そのため、スパンベースのレガシーなUIではルートごとに表示されます。
対処方法
対処方法として、OTEL_SEMCONV_STABILITY_OPT_IN を設定してHTTPのセマンティック規約を新しい安定版へ切り替えることで、メトリクスに http.route が含まれるようになります。値は次の2つが指定できます。
http: 新しい安定版の規約のみを使用しますhttp/dup: 新旧の規約を同時に出力します
なお、OTEL_SEMCONV_STABILITY_OPT_IN に http を指定すると、http.route が追加される一方で、一部のAttribute名も新しい規約へ変更されます(例: http.method → http.request.method、http.status_code → http.response.status_code)。詳細はOpenTelemetryのドキュメント「HTTPセマンティック規約の移行ガイド」を参照してください。
そのため、カスタムダッシュボードやアラートなどでNRQLにAttribute名を直接指定している場合、意図したとおりにデータを取得できなくなるなどの影響を受ける可能性があります。設定する際は、この点もあらかじめご留意のうえ、実施をご検討ください。
送信データをデバッグログで確認する
実際に送信データをデバッグログで確認してみましょう。
メトリクスであれば、OTEL_METRICS_EXPORTER=console を指定することで、収集したメトリクスを標準出力に表示できます。
export OTEL_METRICS_EXPORTER=console
opentelemetry-instrument python app.py
何も設定していない状態(古い規約)では、duration系メトリクスの名前は http.server.duration(単位はミリ秒)となり、データポイントの属性に http.route が含まれていないことがわかります。
{
"name": "http.server.duration",
"unit": "ms",
"data": {
"data_points": [
{
"attributes": {
"http.scheme": "http",
"http.host": "127.0.0.1:8080",
"net.host.port": 8080,
"http.flavor": "1.1",
"http.method": "GET",
"http.server_name": "localhost:8080",
"http.status_code": 200,
"http.target": "/items/{item_id}"
}
}
]
}
}
ルートの情報自体は http.target に入っていますが、New Relicがトランザクション名の組み立てに使うのは http.route です。旧規約のメトリクスにはその http.route が無いため、パスを解決できません。このようにUIが前提としている属性が、送信データに実際に含まれているかをコンソールログから確認することが可能です。
次に環境変数OTEL_SEMCONV_STABILITY_OPT_INを追加して起動します。
export OTEL_SEMCONV_STABILITY_OPT_IN=http
opentelemetry-instrument python app.py
コンソール出力を確認すると、メトリクスの名前が http.server.request.duration(単位は秒)に変わり、属性に http.route が含まれるようになります。
{
"name": "http.server.request.duration",
"unit": "s",
"data": {
"data_points": [
{
"attributes": {
"url.scheme": "http",
"network.protocol.version": "1.1",
"http.request.method": "GET",
"http.route": "/items/{item_id}",
"http.response.status_code": 200
}
}
]
}
}
http.route が送られるようになったことで、New Relic側でもパス単位のトランザクション名(例: /items/{item_id})が組み立てられるようになります。
New Relic側のデータをNRQLで確認する
New Relicに届いたデータもNRQLで確認してみましょう。次のクエリでは、旧規約のメトリクス(http.server.duration)と新規約のメトリクス(http.server.request.duration)を並べて、ルートがどの属性に入っているかを比較できます。
SELECT metricName, `http.route`, `http.target`
FROM Metric
WHERE metricName IN ('http.server.duration', 'http.server.request.duration')
AND service.name = 'fastapi-newrelic-app'
旧規約の行では http.target にルートが入り http.route は空、新規約の行では http.route にルートが入り http.target は空、というように、ルートの入る属性が入れ替わっていることがわかります。
このように「送信側(コンソール)」と「受信側(NRQL)」の両方でデータを突き合わせると、どこで属性が欠けているのかを切り分けやすくなります。
まとめ
PythonアプリのOTel計装でトランザクションにパスが表示されないときは、OTEL_SEMCONV_STABILITY_OPT_IN=http を設定して新しい規約に切り替えると、メトリクスに http.route が含まれるようになり、パス単位でトランザクションが表示されるようになります。
また、UIで期待どおりに表示されないときは、コンソール出力で送信データを、NRQLで受信データを確認し、UIが前提とする属性が過不足なく含まれているかを突き合わせると、原因を特定しやすくなります。
なお、将来のバージョンではセマンティック規約の扱いが変わる可能性があるため、計装ライブラリをアップデートする際は、トランザクション名の挙動を一度確認しておくと安心です。
本ブログに掲載されている見解は著者に所属するものであり、必ずしも New Relic 株式会社の公式見解であるわけではありません。また、本ブログには、外部サイトにアクセスするリンクが含まれる場合があります。それらリンク先の内容について、New Relic がいかなる保証も提供することはありません。