NRQLでNew Relicに収集したデータに含まれる文字列から特定部分を抽出する方法(aparse / capture)をご紹介します。いくつか方法はありますがその違いもみていきます。
はじめに
NRQL(New Relic Query Language)はNew Relicに収集したデータをいい感じで可視化し、分析するためのクエリ言語です。ビルトインで提供されているUIで十分に分析をすることはできますが、複雑な条件でデータ分析をしたい場合やビルトインのUIではカバーされていないような可視化をしたい場合にはNRQLによる可視化は非常に強力な手段となります。
今回は収集したデータに含まれる文字から特定の部分を抽出する方法をご紹介します。以下のようなことが理解できるようにします。
- 文字から特定の部分を抽出する方法の理解
- 抽出したい部分が複数ある場合の抽出方法
- 抽出した後に数値などに変換して分析する方法
- 結局どっちがいいの?
想定ケース
New Relicに送信するデータが構造化されており、利用したいデータが全て独立した属性として格納されている場合はそのままそのデータを使えますが、場合によっては利用したいデータが構造化されていなかったり、特定の属性値の一部に含まれるということがあるでしょう。BrowserエージェントやAPMエージェントが収集するURLの中の一部を使いたい場合であったり、構造化されていない旧来のログを利用する必要があったり、3rd Partyのログをそのまま取り込んでいるようなケースです。
例えば、以下のようなパターンのURLがあったとして、商品毎の集計をしたい場合は1234の部分を抽出したいかもしれませんし、商品のカテゴリ毎の集計をしたい場合はsportsの部分を抽出したいケースもあるでしょう。
https://xxxxx/products/sports/1234
プログラムを修正して構造化したデータを送ったり、sportsや1234の部分を独立した属性として送ることも可能ですが、今回は上記のようなデータがそのまま送られた場合にNRQLを使って抽出する方法をご紹介します。
文字列から特定部分を抽出する方法
文字列から特定部分を抽出する方法としてNRQLでは二つの関数を提供しています。
一つがcapture関数で、もう一つがaparse関数です。
capture関数は、RE2構文の正規表現を使用して属性から値を抽出するものです。正規表現が細かく指定できて柔軟性が高いので、複雑なパターンの文字列であっても対応できます。
aparse関数は、capture関数の代替となるものです。構文としてはシンプルでcapture関数ほどの複雑な指定はできませんが、可読性はありメンテナンスのしやすさという意味ではaparseが良さそうです。また、capture関数よりは処理速度が早いというのが特徴です。
では、それぞれの関数での抽出方法をみてみましょう。
capture関数
capture関数の基本的な構文は以下のような形式です。第一引数は抽出対象となる属性、第二引数は正規表現を指定します。パターンに該当する部分がマッチすると、抽出された部分がnameという変数に格納されます。
capture(対象の属性名, r'... (?P<name>パターン ) ...')
では、PageViewイベントのpageUrlという属性にあるhttps://xxxxx/products/sports/1234
というデータを対象にcapture関数を使ってみます。
まずは末尾の数字を取り出したい場合です。
正規表現の書き方としては以下のような感じになります。URLの前半部分は固定だと仮定して、最後の部分で1文字以上の数値を抽出して、idという属性に入れています。数値の部分の指定方法は通常の正規表現と同じように、\dでもいいですし、[0-9]でも大丈夫です。
r'https://xxxxx/products/sports/(?P<id>\d+)'
NRQL全体をみてみると以下のような感じです。WITHは固定値を渡すために使っていて今回はそんなに重要じゃありません。またlimit 1も例として書いているので無視して大丈夫です。重要なのはcaptureの部分です。
WITH 'https://xxxxx/products/sports/1234' as pageUrl
FROM PageView
SELECT pageUrl as '元の文字列',
capture(pageUrl, r'https://xxxxx/products/sports/(?P<id>\d+)') as 'ID'
LIMIT 1
New RelicのQuery Builderで上記のクエリを試すと以下のような感じで出ると思います。
次に、URLの途中にあるsportsの部分だけ抽出してみます。正規表現の書き方としては、例えば以下のようにすれば取れるます。[a-zA-Z]+ は、アルファベット小文字か大文字1文字以上の文字列を表す正規表現です。この時点でアレルギー反応が出ている方は頑張ってください。
r'https://xxxxx/products/(?P<category>[a-zA-Z]+)/.*'
もうちょっと柔軟性を持たせるのであれば以下の正規表現でも大丈夫です。[^\/]+ は、/以外(^は以外)の1文字以上の文字列を表します。/sports/のスラッシュの間がどんな文字でも取れます。
r'https://xxxxx/products/(?P<category>[^\/]+)/.*'
ちょっと脱線しましたが、NRQL全体としては以下のようになります。
WITH 'https://xxxxx/products/sports/1234' as pageUrl
FROM PageView
SELECT pageUrl as '元の文字列',
capture(pageUrl, r'https://xxxxx/products/(?P<category>[a-zA-Z]+)/.*') as 'Category'
LIMIT 1
結果はこのような感じになります。
いかがでしょうか?構文さえ覚えてしまえば簡単に抽出することができます。
aparse関数
次にaparse関数を使ってみます。aparse関数の構文は以下の通りです、第一引数はcapture関数と同様に抽出対象としたい属性名です。第二引数は、*や%といったワイルドカードを使ったパターンを指定します。
aparse(対象の属性、パターン)
https://xxxxx/products/sports/1234
から、1234部分を取り出したい場合は以下のようなパターンを指定します。ワイルドカード*で指定された部分が抽出されます。
https://xxxxx/products/sports/*
NRQLとしては以下のような感じです。
WITH 'https://xxxxx/products/sports/1234' as pageUrl
FROM PageView
SELECT pageUrl as '元の文字列',
aparse(pageUrl, 'https://xxxxx/products/sports/*') as 'ID'
LIMIT 1
もっとラフにしたい場合は、 以下のように%のワイルドカードを指定して前半部分を軽くすることもできます。%のワイルドカードは抽出対象とはなりません。
aparse(pageUrl, '%products/sports/*')
先ほどと同様にID部分が抽出できました。
次に、https://xxxxx/products/sports/1234
からsportsの部分だけを抽出してみます。
パターンの指定方法の一例としては以下のようになります。productsのすぐ後の部分(sportsの部分)だけ抽出し、スラッシュを含むそれ以降は無視する形です。
aparse(pageUrl, '%products/*/%')
NRQLとしては以下のようになります。
WITH 'https://xxxxx/products/sports/1234' as pageUrl
FROM PageView
SELECT pageUrl as '元の文字列',
aparse(pageUrl, '%products/*/%') as 'Category'
LIMIT 1
aparse関数でも同様に取得することができました。
aparse関数は正規表現をゴリゴリ書かなくて良いので簡単ですね。簡単なパターンであればaparse関数で十分そうです。
一方で、みてわかる通り、products/*/% の部分のパターンは非常に曖昧性があります。抽出したいデータの構造が複雑であったり、可変の部分が多い場合は抽出したい部分が一意にならないケースも出てくるでしょう。対象とする属性とそのパターンによってどちらが的しているのか選択するのがおすすめです。
以上が部分文字列を抽出するcapture関数とaparse関数の使い方になります。
この後はもう少し複雑なケースなどみていきます。
複数の値をとりたいとき
一つの属性から複数の値を取りたい時はどうすると良いでしょうか。capture関数もaparse関数も両方複数の値を取る方法をサポートしています。
複数の値を取る場合は、WITH構文を使って一度抽出した複数の値をそれぞれ別の属性値に格納します。以下のようなイメージです。
WITH aparse関数またはcapture関数 as (attr1, attr2, …) FROM … SELECT ….
例えば、https://xxxxx/products/sports/1234
から、sportsというカテゴリと、1234というIDを取りたい場合を想定します。
capture関数だと以下のような書き方になります。
WITH capture(pageUrl, r'https://[^\/]+/products/(?P<category>[a-zA-Z]+)/(?P<id>\d+)') as (category, id)
FROM PageView
SELECT pageUrl as '元の文字列', category as 'Category', id as 'ID'
aparse関数だと以下のような書き方になります。
WITH aparse(pageUrl, '%products/*/*') as (category, id)
FROM PageView
SELECT pageUrl as '元の文字列', category as 'Category', id as 'ID'
一度に複数の値を抽出できました。
いかがでしょうか。簡単です。
数値変換
抽出した文字を数値として扱いたい場合が度々あります。例えばCDNの生ログからキャッシュヒット率を計算したり、Time to First Byteを統計的に見たり。HerokuではCPUやリソース利用率がログとして出力されたりもします。
そのような時は、numeric関数を使ってみてください。文字列として保存されている数字を数値として認識させることができるので、例えばアベレージやパーセンタイルをとって統計的に評価することが可能です。
例えば、以下の例ではログに含まれるCPU Timeを、capture関数を使って文字列として抽出した後、numeric関数で数値に変換した上で平均をとっています。
SELECT average(numeric(capture(message, r'.*CpuTime:\s(?P<cpuTime>\d+)')))
FROM Log
WHERE message LIKE '%CpuTime:%' SINCE 1 hour ago
文字列としてだけでなく数値にもできると利用の幅は広がりますね。
その他変換の関数については公式ドキュメントをご参照ください。
処理時間の違い
aparse関数はcapture関数より早いという特徴があるとリリースノートには書かれていますが実際はどうでしょうか。対象とするデータ量が多いログを使って測ってみます。
ログには有象無象のテキストが出力されますが、今回はその中で以下のようなパターンを持つログの中からuserId (xxxxxx)の部分を抽出し、ユニーク数をカウントする時間を比べてみます。
[GetCart\]: Get cart for user: xxxxxxx
実行するNRQLは雑に以下のような感じにしてみます。測定環境は4000万行ぐらいのログがありました。
WITH capture(message, r'.* user: (?P<user>.*)') as user
FROM Log
SELECT uniqueCount(user)
since 1 day ago
WITH aparse(message, '%user: *') as user
FROM Log
SELECT uniqueCount(user)
since 1 day ago
NRQLの実行はコマンドライン (curl) からAPI (NerdGraph)経由でやってみます。コマンドラインから実行する方法は以下をご参照ください。curlに-wオプションをつけて最初のバイト転送までの時間を比べてみます (time_starttransfer)
コマンドは以下のような感じです。
curl https://api.newrelic.com/graphql \
-H 'Content-Type: application/json' \
-H 'API-Key: <User Key>' \
-w '\n%{time_starttransfer}\n' \
--data-binary '{"query":"{\n actor {\n account(id: xxxxxx) {\n nrql(\n query: \"<NRQL>\"\n ) {\n results\n }\n }\n }\n}", "variables":""}'
結果は以下の通りです。
{"data":{"actor":{"account":{"nrql":{"results":[{"uniqueCount.user":66114}]}}}}}
0.952087
{"data":{"actor":{"account":{"nrql":{"results":[{"uniqueCount.user":66114}]}}}}}
0.518737
結果、何回か繰り返すと概ねaparse関数の方が早いということは言えそうです。どちらを使うかの判断材料の一つになるかと思います。なお、結果に関しては本記事執筆時点でのものであることや、ネットワークやバックエンドの負荷の違いもあった上での結果であり、未来永劫同じであるという保証はないことをご認識ください。
その他の手段
今回はNRQLを使ってすでに格納されているデータからうまく抽出する方法をご紹介しましたが、最初から構造化したデータを送ったり、送ったデータを構文解析した状態で格納できるとより効率的ではあります。
その一つがカスタム属性です。カスタム属性とはAPMやBrowserなどの各種エージェントが送信するデータに任意の属性を付与できる機能です。例えば、User IDやテナントID、カテゴリ情報など様々な属性をプログラム中で付与しておくことによって、例えばどのテナントのアクセスが多いか、どのユーザーで問題が起きているかなど一瞬でわかるようになります。また、APMなどのビルトインのUIでもその属性を利用したパフォーマンス分析やエラー分析などが可能になりますのでこちらがオススメです。
カスタム属性の詳細は以下をご覧ください。
また、ログに関しては、取り込んだ時点で解析する方法もあります。ログの解析方法については以下のドキュメントもご参考にしてください。
まとめ
NRQLでサポートされる様々な機能のうち、文字列のデータを抽出するaparse/capture関数について解説しました。
簡単な例で解説しましたがaparse/capture関数は色々な部分で使えます。SELECT句で値をとったり、WHERE句で条件の絞り込みに使ったり、FACET句でグルーピングして集計するのに使ったりできます。
うまくデータから情報を抽出できると、それをさらに集計して分析できるので活用の幅はさらに広まります。是非お試しください。
活用方法の例については公式ブログもご参照ください。
New Relicでは、新しい機能やその活用方法について、QiitaやXで発信しています!
無料でアカウント作成も可能なのでぜひお試しください!
New Relic株式会社のX(旧Twitter) や Qiita OrganizationOrganizationでは、
新機能を含む活用方法を公開していますので、ぜひフォローをお願いします。
無料のアカウントで試してみよう!
New Relic フリープランで始めるオブザーバビリティ!