こんにちは、インフラチーム テックリードの櫻井です。
今回はアプリケーションモニタリングのために導入しているNew RelicにLogs in ContextとInfinite Tracingとカスタム属性を導入して、システムのObservabilityを向上させたことについて紹介したいと思います。
Observability(可観測性)とは
まずObservability(可観測性)とは処理時間やエラーなどシステム内部の状態がどれだけ可視化されているかを示す指標です。
Observabilityが高ければボトルネック解消や障害発生時の迅速な対応が可能になり、より安定してサービスを運用することが可能になります。
New Relicについて
New Relicは様々な機能を備えたObservability Platformです。
APMやBrowserでバックエンドやフロントエンドのパフォーマンスやエラーを監視できるほか、NRQLを使ったアラートやダッシュボード作成など非常に多くの機能が提供されています。
DatadogやSentryなど同様のサービスは色々ありますが、PHPのエージェントが安定していることやNRQLがとても便利などの理由から当社ではNew Relicを採用しています。
Logs in Contextを有効化する
まず初めにNew Relicの機能の一つであるLogs in Contextについて紹介します。
Logs in Contextでできること
Logs in ContextはNew Relicの機能の一つです。
これを有効化するとErrorやDistributed Tracingとログを紐付けることができるようになり、エラーから関係するログを調査したり、反対にログから対応するTraceを調査したりできるようになります。
Logs in Contextを有効化する方法
Logs in Contextを有効化するためには大きく分けて以下の2通りの方法があります。
- APMエージェントのログ転送機能を使う
- Fluentdなどのサードパーティツールを使う
APMエージェントのログ転送機能を使う
1の方法はAPMのエージェントがサーバーにインストールされており、MonologなどのLoggerを使用しているなら使い始めることができます。
詳しいインストール手順については NewRelicの公式ドキュメント を参照ください。
この方法は条件を満たしているならすぐに使い始めることができますが、実際に試してみたところ 引数に指定した $context が削除されてしまう という問題が発生しました。
Monologでは
$logger->info('this is log message', ['user_id' => 1, 'some key' => 'some value']);
のようにメソッドの第二引数 $context
に配列形式でログのコンテキストを追加することができます。
しかしNew RelicのPHPエージェントを使ってログを転送すると、この $context
が出力されなくなってしまいます。
確認したところ、どうやら以下のPRからPHPエージェントでMonologの $context
を削除してしまっているようです。
エージェントをコンパイルすればこの挙動を変更することもできるようですが、このためにコンパイルするのは面倒なので今回は別の方法で転送することにしました。
Fluentdなどのサードパーティツールを使う
2の方法はFluentdやLogstashなどサードパーティのログ転送ツールやNew Relic Infrastructureのエージェントのログ転送機能を使用している場合にLogs in Contextを有効化するための方法です。
これらのログ転送ツールを使用する場合は、Logs in Contextのために必要なデータをログに付与して転送する必要があります。
例えばPHPなら newrelic_get_linking_metadata() 関数を実行することで以下のようなデータを取得することができます。
<?php
if (function_exists('newrelic_get_linking_metadata')) {
echo json_encode(newrelic_get_linking_metadata());
}
{
"entity.name": "PR TIMES",
"entity.type": "SERVICE",
"entity.guid": "xxxxxxxxxxxxxxxxxx",
"hostname": "xxxxxxxxxxxxx",
"trace.id": "xxxxxxxxxxxxx",
"span.id": "xxxxxxxxxxxxx"
}
ログをJSON形式で出力している場合は以下のようにnewrelic_get_linking_metadata()の結果をログの連想配列にマージするだけで良いです。
<?php
$record = [
'line' => 1,
'file' => '/path/to/file',
'message' => 'this is log message.'
];
if (function_exists('newrelic_get_linking_metadata')) {
$record = array_merge($record, newrelic_get_linking_metadata());
}
ログがJSON形式でない場合はログメッセージの末尾に NR-LINKING
という文字列に続けて |
区切りで必要なデータを付与することでNew Relic側で解析させることができます。
<?php
$message = 'this is log message. ';
if (function_exists('newrelic_get_linking_metadata')) {
$metadata = newrelic_get_linking_metadata();
$message .= sprintf(
'NR-LINKING|%s|%s|%s|%s|%s|',
$metadata['entity.guid'] ?? '',
$metadata['hostname'] ?? '',
$metadata['trace.id'] ?? '',
$metadata['span.id'] ?? '',
$metadata['entity.name'] ?? ''
);
}
また、このようにAPMではなくサードパーティのログ転送ツールなどを使う場合は、ログを重複して転送することを防ぐために以下のように newrelic.ini
ファイルに追記しておくことをおすすめします。
newrelic.application_logging.forwarding.enabled = false
LogsからTracesを確認する
アプリケーション側の設定が完了したら、New Relicで実際にLogs in Contextが有効化されているか確認します。
まずNew Relicを開いたら左側のメニューからLogsを選択し、対象のアプリケーションログをクリックします。
Logs in Contextの設定が成功している場合はログに span.id
や trace.id
などの項目が追加されていることが確認できます。
またそのログに対応するTraceが取得できている場合は上の方にDistributed traceという項目が表示され、クリックするとログに対応するTraceの詳細を確認することができます。

これらが確認できない場合は Logs in Contextのために必要なデータが不足しているか、対象のTraceがサンプリングされていないことが原因として考えられます。
対象のTraceがサンプリングされていない場合は、後述のInfinite Tracingを活用してTraceのサンプリングをコントロールすることで解決できる可能性があります。
ErrorsからLogsを確認する
反対にAPMのErrorsから対応するログの一覧を確認することもできます。
左側のメニューからAPM & Servicesを選択し、対象のサービスをクリックした後でErrors(errors inbox)をクリックします。
そこからログを確認したいエラーをクリックし、そのエラーに関連するログが出力されている場合はLogsの部分からログの一覧を確認することができます。

ただしErrorsからLogsを確認する場合についても対象のTraceが取得されていることが前提となるため、後述のInfinite Tracingを有効化してエラー時には必ずTraceを取得するようにしておくことをおすすめします。
Infinite Tracingを有効化する
次にNew RelicのDistributed Tracingのサンプリング精度を高めるためのInfinite Tracingについて紹介します。
Infinite Tracingは前述のLogs in Contextと相性がとても良いので、合わせて有効化しておくことをおすすめします。
head-based samplingとtail-based samplingについて
まずDistributed Tracingのサンプリング手法のhead-based samplingとtail-based samplingについて紹介します。
head-based samplingは処理を実行する前にTraceを送信するかどうかを一定の割合に基づいてランダムに決定します。
実装がシンプルかつパフォーマンスに与える影響も小さく済みますが、エラーが発生した処理や時間のかかっている処理など本当に必要なTraceが取得できていないということも頻繁に発生します。
一方でtail-based samplingは処理を実行した後でTraceを送信するかどうかを決定します。
処理の実行後に送信するかどうかを決めるため、エラーが発生している場合や時間のかかっている処理など条件に合ったTraceを確実に取得することができるようになります。
通常のNew RelicのDistributed Tracingでは前者のhead-based samplingが行われていますが、Infinite Tracingを有効化することで後者のtail-based samplingができるようになり、必要なTraceを漏らさず取得できるようになります。
参考
Web UIからTrace Observerを作成する
ではInfinite Tracingを有効化するための手順を説明します。
まずNew Relicを開いたら左側のメニューから”All Capabilities”を選択し、”Infinite Tracing Settings”を検索してクリックします。

Infinite Tracing Settingsを開いたら右上の “New trace observer“ をクリックしてTrace Observerを作成します。
このときリージョンを選択しますが、残念ながら本記事の作成時点では東京リージョンはなさそうなので適当に近そうなリージョンを選択します。
Trace Observerの設定を変更する
Trace Observerを作成したら“Span attributes”の右にある歯車マークをクリックしてTrace Observerの設定を開きます。
デフォルトだと以下のような設定になっていると思います。

“Random sampler”というのはhead-based samplingと同様のランダムなサンプリングの割合で、ここを100%にすると全てのTraceを取得できるようになります。
ステージング環境などアクセスの少ない環境なら問題ないと思いますが、本番環境で100%にしてしまうとデータ転送量が跳ね上がるのでコストと相談になると思います。
真ん中の”Active span attribute filter rules”というのは現在有効になっているTrace取得ルールの一覧です。
デフォルトだとエラーや警告が発生した場合は必ずTraceを取得するようになっています。
下の”Create new rule”からルールを追加することで特定の処理のTraceを取得したり、反対に除外したりできるようになります。
APMにTrace Observerのhost名を登録する
Trace Observerの設定が完了したら次にAPMにTrace Observerのhost名を登録します。
Trace Observerのhost名はInfinite Tracing Settingsの画面からFor language agentsの右のボタンをクリックすることでコピーできます。

コピーしたhost名はAPMがインストールされているサーバーの newrelic.ini
ファイルに
newrelic.infinite_tracing.trace_observer.host = "xxxxxx.tracing.edge.nr-data.net"
のような形式で追記します。
追記した後は設定を反映するために必要に応じてサーバーの再起動も行います。
TracesからInfinite Tracingのデータを確認する
サーバー側の設定も完了したら実際にInfinite Tracingのデータが送られてきているか確認します。
APMのDistributed tracingを見ると、Infinite Tracingのデータが送られてきている場合”Infinite tracing data only”というチェックボックスが出現していると思います。
このチェックボックスにチェックを入れて確認することで実際にInfinite Tracingで送られているTraceを確認することができます。

注意:Infinite Tracingで転送したTraceにはappIdやappNameがつかない
Infinite Tracingを利用する場合の注意点として、転送したTraceにappIdやappNameがつかないということがあります。
通常のDistributed Tracingで転送したTraceでは以下のように appId
や appName
を使ってSpanの絞り込みができます。
SELECT * FROM Span WHERE appName = 'PR TIMES'
しかし、Infinite Tracingを有効化するとこのようなクエリの結果が0件になりました。
サポートに問い合わせたところ、どうやらInfinite Tracingを有効化しているとSpanから appId
や appName
がなくなるようです。
代わりに entity.guid
や entity.name
などは使用できるので、もし appId
や appName
を使ったNRQLを使用している箇所があれば置き換える必要がありそうです。
カスタム属性を有効化する
最後にNew Relicのカスタム属性について紹介します。
カスタム属性は任意のパラメータをNew RelicのAPMやBrowserに付与することができる機能です。
例えばユーザーIDをカスタム属性として付与することでエラーがどのユーザーで発生しているのか調査しやすくなったり、特定のユーザーがどのようなリクエストを送っているのか追跡したりできるようになります。
ユーザーIDを取得してカスタム属性に設定する
実際にユーザーIDをカスタム属性に設定するには以下のように newrelic_add_custom_parameter() 関数を呼び出します。
<?php
if (extension_loaded('newrelic') && function_exists('newrelic_add_custom_parameter')) {
newrelic_add_custom_parameter('enduser.id', $userId);
}
ユーザーIDに使用するキー名について
ここでキー名を enduser.id
としているのはOpenTelemetryというテレメトリ収集のフレームワークでそのように定められているからです。
OpenTelemetryは比較的新しい技術なのでまだ不安定な部分もありますが、New Relicなどのベンダー各社も今後OpenTelemetryの標準に従っていくと思うので、なるべくOpenTelemetryの標準に合わせておくのがベターだと思います。
サーバー上で設定したカスタム属性をBrowserでも使えるようにする
サーバー上で設定したカスタム属性をサーバーサイドのAPMだけでなくフロントエンドのBrowserにも反映させるために、サーバー上の newrelic.ini
ファイルに以下のように追記しておくことをおすすめします。
newrelic.browser_monitoring.attributes.enabled = true
NRQLでカスタム属性を確認する
最後に設定したカスタム属性が実際に反映されているかどうかを確認します。
確認方法はいくつかありますが今回はNRQLを使って確認します。
左側のメニューから”Query Your Data”を選択し、上のタブでQuery builderを選択したあと以下のようなNRQLを入力し”Run”ボタンを押して実行します。
SELECT enduser.id FROM Transaction WHERE enduser.id IS NOT NULL
このNRQLでは enduser.id
が設定されているTransactionの一覧を確認することができます。
APMではなくBrowserの方にカスタム属性が反映されているか確認したい場合は以下のようにPageViewテーブルに対して実行することで確認することができます。
SELECT enduser.id FROM PageView WHERE enduser.id IS NOT NULL
まとめ
今回はNew RelicのLogs in ContextとInfinite Tracingとカスタム属性について紹介しました。
実際に導入してみて自分でもかなりNew RelicのObservabilityが向上したことを実感しているので、もしNew Relicを使っていてこれらの機能を使っていないという方は本記事を参考に導入してみることをおすすめします。