4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NRQLマスターになろう - JSON編

Last updated at Posted at 2024-12-05

ログなどのデータに含まれるJSON文字列をNRQLを使って解析する方法を解説します。改めて構造化したログを送りたいけど手が加えられない、でもなんとかして今のログは上手く使いたい、そんなときはご参考にしてください。

はじめに

New Relicにログなどのデータを送る際にメッセージフィールドがJSONの場合は、自動的にKey/Valueに分解されて保存されるので、特に意識する必要なくログデータを簡単に分析することができます。

しかし、すでに出力しているログの中にJSON以外の情報も付与されている場合などは妥当なJSONとして認識されず自動的な解析が行われません。本来、ログを構造化して送るように変更するのが綺麗な対応方法ではありますが、諸事情で改変が行えない、影響範囲が大きすぎるという場合もあるでしょう。

その場合は、ログの取り込み後にログに含まれるJSONを抽出し、解析する方法が必要ですが、New Relicのクエリ言語であるNRQL(New Relic Query Language)を利用すればそれを実現できます。この記事ではNRQLがサポートするJSON解析のための関数を使ってどのように情報が抽出できるかみていきたいと思います。

ログからのJSONの抽出

まず始めに、単純なJSONではないログ(ログの中の一部にJSONが含まれる)からJSONテキストの部分を抽出します。

例えば以下のログをみてみましょう。前半に時刻やその他の情報があり、その後にJSON文字列が続きます。

(1) 2024/12/03 23:33:10.419116 {\"level\":\"debug\",\"msg\":\"transaction ended\",
\"context\":{\"app_connected\":true,\"duration_ms\":1.100159,\"ignored\":false,
\"name\":\"WebTransaction/Go/hipstershop.ShippingService/GetQuote\"}}

このような文字列の場合妥当なJSONとは判断される自動的に解析はされないのでNew RelicのUI上でも以下のような形で見えるので検索性が良いとは言えません。

log_1.png

この文字列から後半のJSON文字列を抽出してみます。文字列から特定の部分だけを抽出するためにはcapture関数aparse関数を利用します。それらの関数の詳細に関しては以下をご参照ください。

今回はJSON部分を抽出するのにcapture関数を使ってみます。capture関数は、ログのフォーマットを正規表現込みで指定することで欲しい部分だけを抽出できます。今回は、ログの前半が「(1) 2024/12/04 04:24:16.2222 」のような形になっているのでその部分を正規表現で指定し、後半部分を全てJSONとして抽出するため、capture(message, r'\(\d\) [\d/]+ [\d:\.]+ (?P<json>.*)')としています。

JSON文字列の抽出
WITH capture(message, r'\(\d\) [\d/]+ [\d:\.]+ (?P<json>.*)') as json
SELECT message, json  
FROM Log 
WHERE json is not null

NRQLを実行した結果が以下です。Messageがオリジナルのログメッセージ、JSONがJSON部分だけ抽出したものです。意図通り抽出できていることがわかりました。今回はcapture関数を利用しましたが、もっと簡単なフォーマットであればaparse関数を使っても良いです。

log_2.png

これで準備は完了です。JSONから情報を取り出す様々な方法を試してみます。

JSONからの情報抽出

JSONからの情報抽出にはjparseという関数を利用します。jparseは、jparse(<属性>, ‘<jsonパス>’)という構文になっており、jsonパスの部分を変えることにより、<属性>に指定したJSON文字列から様々な要素を取り出すことができます。jparse関数の詳細は以下のドキュメントを参照ください。

今回は、ドキュメントにある例を実際に動かしてみてどのような結果が得られるかをみていきます。

前提

今回簡単のため、以下のようなJSON文字列を前提として情報を抽出してみます。実際にはLogなどのイベントからJSONのデータを引っ張ってきますが、試験的に動かすのでNRQLのWITH句で該当のJSON文字列を直指定しちゃいます。

JSONの例
{
  "valueA": "test",
  "valueB": {
    "nestedValue1": [1, 2, 3],
    "nestedValue2": 100
  },
  "valueC": [
    { "id": 1, "label": "A", "other": 7 },
    { "id": 2, "label": "B", "other": 9 },
    { "id": 3, "label": "C", "other": 13 }
  ]
}

指定したキーの値を取るNRQL

指定したキーの値を取るNRQL
WITH '{"valueA": "test", "valueB": {"nestedValue1": [1, 2, 3], "nestedValue2": 100}, "valueC": [{ "id": 1, "label": "A", "other": 7 }, { "id": 2, "label": "B", "other": 9 }, { "id": 3, "label": "C", "other": 13 }]}' 
  as json 
SELECT jparse(json, 'valueA') as 'Value' 

キーvalueAに対応する値であるtestが取得できました。

log_3.png

指定したキーの値を取るNRQL(ネストされている場合)

では、値がネストしている場合はどうでしょうか?valueBを指定してみます。

指定したキーの値を取るNRQL(ネストされている場合)
WITH '{"valueA": "test", "valueB": {"nestedValue1": [1, 2, 3], "nestedValue2": 100}, "valueC": [{ "id": 1, "label": "A", "other": 7 }, { "id": 2, "label": "B", "other": 9 }, { "id": 3, "label": "C", "other": 13 }]}'
  as json 
SELECT jparse(json, 'valueB') as 'Value' 

valueBの値のJSONが取り出せました。

log_4.png

ちなみに結果をJSON形式で確認してみると結果は文字列ではなくJSONで返ってきていることがわかります。

log_5.png

指定したキーの値を取るNRQL(ネストされている場合#2)

キーvalueBの値はJSONでしたが、さらにその先のキーを指定してみます。valueBのJSONのnestedValue1です。その場合は、'valueB.nestedValue1'のようにドット (.) でキー名を連結させます。

指定したキーの値を取るNRQL(ネストされている場合#2)
WITH '{"valueA": "test", "valueB": {"nestedValue1": [1, 2, 3], "nestedValue2": 100}, "valueC": [{ "id": 1, "label": "A", "other": 7 }, { "id": 2, "label": "B", "other": 9 }, { "id": 3, "label": "C", "other": 13 }]}'
  as json 
SELECT jparse(json, 'valueB.nestedValue1') as 'Value' 

nestedValue1の値 [1,2,3]が取得できました。

log_6.png

指定したキーの値を取るNRQL(配列の場合#1)

どの程度頻繁に使うか、というのはありますが、値が配列の場合はその要素や一部分を抽出できます。配列から要素を抽出する際は、一般的なプログラミング言語によくみられるように [0][1:2] などで要素の位置や範囲を指定します。

指定したキーの値を取るNRQL(配列の場合#1)
WITH '{"valueA": "test", "valueB": {"nestedValue1": [1, 2, 3], "nestedValue2": 100}, "valueC": [{ "id": 1, "label": "A", "other": 7 }, { "id": 2, "label": "B", "other": 9 }, { "id": 3, "label": "C", "other": 13 }]}'
  as json 
SELECT jparse(json, 'valueB.nestedValue1[0]') as 'Value' 

先ほどは配列全体[1,2,3]が返ってきましたが、今回は最初の要素[0]を指定しているので1だけが返ってきています。

log_7.png

指定したKeyの値を取るNRQL(配列の場合#1)
WITH '{"valueA": "test", "valueB": {"nestedValue1": [1, 2, 3], "nestedValue2": 100}, "valueC": [{ "id": 1, "label": "A", "other": 7 }, { "id": 2, "label": "B", "other": 9 }, { "id": 3, "label": "C", "other": 13 }]}'
  as json 
SELECT jparse(json, 'valueB.nestedValue1[0:2]') as 'Value' 

[0:2]とした場合は、0番目以上から2番目未満(つまり1番目)の要素を抽出します。[1, 2]が返ってきました。

log_8.png

指定したキーの値を取るNRQL(配列の場合#2)

配列の各要素がJSONだったらどうでしょうか?それぞれのJSONの中から特定のキーだけを集めてきたいことはあるでしょう。

今回のデータだとvalueCには{ "id": 1, "label": "A", "other": 7 }という形のJSONが配列で入っていますが、各要素からlabelだけを抜き取ってみます。その場合は、'valueC[*].label'のような形になります。valueCの配列の全要素を対象とし、そのうちlabel属性だけを抽出する感じです。

指定したキーの値を取る(配列の場合#2)
WITH '{"valueA": "test", "valueB": {"nestedValue1": [1, 2, 3], "nestedValue2": 100}, "valueC": [{ "id": 1, "label": "A", "other": 7 }, { "id": 2, "label": "B", "other": 9 }, { "id": 3, "label": "C", "other": 13 }]}'
  as json 
SELECT jparse(json, 'valueC[*].label') as 'Value' 

配列の各要素からlabelの値だけを抽出することで、[‘A’, ‘B’, ‘C’]という値が返ってきました。

log_9.png

色々JSONからのデータの抽出方法をみてきました。この辺りを押さえておけばほぼほぼ間違いないでしょう🙌🙌

応用パターン

これまではJSONから抽出した値をSELECT句で返す例を中心にみてきましたが、抽出した値はSELECT句だけでなくWHERE句の絞り込みに利用したり、FACET句でグルーピングで利用したりできます。これを応用することで、単に一行のログを評価するだけでなく複数のログ横断して可視化ができるのでより効率的に面での分析が可能になります。

例えば、冒頭のログにはJSONの中のcontextというキーに、URL (name)の情報やレイテンシ (duration_ms)の情報が入っています。これを抽出した後、URL(name)事にレイテンシーの平均(average(duration))を出したりできます。

応用パターン
WITH capture(message, r'\(\d\) [\d/]+ [\d:\.]+ (?P<json>.*)') as json,
numeric(jparse(json, 'context.duration_ms')) as duration,
jparse(json, 'context.name') as name
SELECT average(duration) FROM Log 
WHERE json is not null
FACET name

以下の例のようにURL毎にレイテンシーの平均が出せました。

log_10.png

上記の例はログメッセージに含まれるJSONを解析することで複雑な分析にも利用できる例を示したものですが、例として出したものはAPMがデータとして取るので、いちいちログから抽出して分析する必要はありません。

この例はログファーストの分析をお勧めするものではなく、あくまでテレメトリーデータとしてログしか取得する方法がない場合の例であることをご認識ください。例えばCDNのログを統合した場合などが該当するでしょう。

まとめ

今回はログのデータに含まれるJSON文字列を抽出し、解析する方法をご紹介しました。すでにログが出力されているが有効な解析手段がない場合にご利用をご検討ください。

今回ご紹介した関数も含めNRQLの使い方の詳細については公式ドキュメントを参照してください。

その他

New Relicでは、新しい機能やその活用方法について、QiitaやXで発信しています!
無料でアカウント作成も可能なのでぜひお試しください!

New Relic株式会社のX(旧Twitter)Qiita OrganizationOrganizationでは、
新機能を含む活用方法を公開していますので、ぜひフォローをお願いします。

無料のアカウントで試してみよう!
New Relic フリープランで始めるオブザーバビリティ!

NRQLマスターになろうシリーズはこちら

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?