Lambdaログの message 表示を改善する — NRQL の capture() を使った実践ガイド
はじめに
New Relic でLambdaのログを見ていると、message 欄に日時やログレベル、関数名などのプレフィックスがずらっと並んでいて、肝心のエラーメッセージやスタックトレースが読みづらい...なんてことありませんか?
今回は、NRQL の capture() 関数を使って、そんな煩雑なプレフィックスを取り除いて本文だけをスッキリ表示する方法をご紹介します。ただし、ログのフォーマットが混在している場合に「あれ?何も表示されない...」という落とし穴もあるので、その対処法も含めて解説していきますね。
記事中の NRQL や正規表現はコピペですぐ試せるようにしてあるので、ぜひ Query Builder で実際に動かしてみてください!
Lambda のログ出力と課題
Lambda での一般的なログ設定
Python の Lambda 関数では、以下のように logging モジュールを使ってログを出力するのが一般的です。
import logging
# ロガーの設定
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# フォーマッタの設定
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(funcName)s - %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
def lambda_handler(event, context):
logger.info("Processing started")
# 処理...
logger.error("An error occurred")
このコードは、以下のようなフォーマットでログを出力します:
2026-01-12 21:15:03,123 - INFO - lambda_handler - Processing started
2026-01-12 21:16:10,456 - ERROR - lambda_handler - An error occurred
New Relic で見たときの問題
このログが New Relic に送られると、message 属性には上記の文字列がそのまま格納されます。つまり:
現状の表示(読みづらい):
message: 2026-01-12 21:15:03,123 - INFO - lambda_handler - Processing started
message: 2026-01-12 21:16:10,456 - ERROR - lambda_handler - An error occurred
日時、ログレベル、関数名といった情報は、New Relic では別の属性(timestamp、level など)として既に持っているのに、message 内にも重複して含まれてしまいます。
特にエラーメッセージやスタックトレースが長い場合、プレフィックスが邪魔で本当に見たい情報が埋もれてしまいます。
理想の表示(読みやすい):
message: Processing started
message: An error occurred
このように、message 欄には本文だけを表示したいわけです。
さらに複雑な問題:ログフォーマットの混在
Lambda では、自分で出力するログ以外にも、AWS が自動で出力するログが混在します:
START RequestId: 12345678-1234-1234-1234-123456789012 Version: $LATEST
2026-01-12 21:15:03,123 - INFO - lambda_handler - Processing started
END RequestId: 12345678-1234-1234-1234-123456789012
REPORT RequestId: 12345678-1234-1234-1234-123456789012 Duration: 123.45 ms
これらの AWS 自動出力ログにはプレフィックスが付いていないため、単純に「プレフィックスを削除する」だけの正規表現では、これらのログが表示されなくなってしまいます。
この記事では、こうした混在するログフォーマットにも対応しながら、message を見やすく表示する方法を解説します。
NRQL とは?
NRQL(New Relic Query Language)は、New Relic が提供するクエリ言語です。ログやメトリクスに対して検索や集計ができる、いわば New Relic 版の SQL みたいなものですね。
基本的な構文は以下のような感じです:
SELECT attribute1, attribute2
FROM Log
WHERE condition
SINCE 1 hour ago
LIMIT 100
詳しい構文や使い方は公式ドキュメントを参照してください:
ログ文字列から特定の部分を取り出したいときは、capture() 関数が便利です。これを使うと、正規表現で属性内の特定パターンを抽出できます。
ちなみに、NRQL で使える正規表現は RE2 構文に準拠しています。PCRE とは少し違うので注意が必要です(後方参照や lookbehind は使えません)。
RE2 正規表現について
RE2 は Google が開発した正規表現エンジンで、パフォーマンスと安全性を重視した設計になっています。NRQL では RE2 を採用しているため、一般的な PCRE(Perl Compatible Regular Expressions)とは使える機能が異なります。
RE2 で使える主な機能:
- 基本的なメタ文字:
.*+?^$|[]() - 文字クラス:
\d(数字)、\w(単語文字)、\s(空白文字) - 非キャプチャグループ:
(?:...) - 名前付きキャプチャ:
(?P<name>...) - 量指定子:
{n}{n,}{n,m} - 先読み(lookahead):
(?=...)(?!...)
RE2 で使えない主な機能:
- 後方参照:
\1\2など(マッチした内容を再利用) - 後読み(lookbehind):
(?<=...)(?<!...) - 条件分岐:
(?(condition)yes|no) - 再帰パターン
実用上のポイント:
- 改行を含むマッチには
.ではなく[\s\S]を使う(.は改行にマッチしない) - 複雑な抽出が必要な場合は、ログ出力側で構造化(JSON など)することを検討
- RE2 は線形時間で動作するため、大量のログに対しても安定したパフォーマンスを発揮
詳しくは RE2 公式ドキュメント を参照してください。
capture() 関数の解説
capture(attribute, regex) は、指定した属性の文字列に対して RE2 の正規表現を適用し、名前付きキャプチャグループ (?P<name>...) の内容を取り出す関数です。
重要なポイント
-
名前付きキャプチャが必須:
(?P<name>...)の形式で指定した部分が返却されます -
マッチしないと NULL:正規表現にマッチしない場合、
capture()は NULL を返します。これがダッシュボード上で「何も表示されない」状態になるので要注意です -
RE2 の制約:後方参照(
\1)や lookbehind((?<=...))といった PCRE 固有の機能は使えません
公式ドキュメント:
基本的な使い方
SELECT capture(message, r'Error: (?P<error_msg>.*)')
FROM Log
この例では、"Error: " の後ろの部分を error_msg として抽出します。
直面した問題
実際に capture() を使ってプレフィックス(日時・ログレベル・関数名など)を取り除こうとしたところ、想定外の問題が発生しました。
通常のログ(プレフィックス付き)については正しく本文が表示される一方で、AWS が自動で出力する "START RequestId: ..." のような行や、プレフィックスが付いていないログについては、正規表現にマッチせず、結果として何も表示されない(NULL)という状態になりました。
期待していた挙動は以下の通りです:
- プレフィックスがある場合 → 本文のみを表示
- プレフィックスがない場合 → 元の message 全体をそのまま表示
しかし実際には、プレフィックスがないログの表示が欠落してしまうという問題が発生しました。
原因
この問題の原因は、使用していた正規表現が「プレフィックスが必ず存在する」ことを前提としていたためです。
具体的には、正規表現が先頭に日時やハイフン区切りのフィールドを必須で要求していたため、それらが存在しないログ(START 行や別フォーマット)ではマッチしませんでした。
capture() の仕様上、正規表現にマッチしない場合は NULL が返されるため、結果的に表示が欠落してしまいます。
解決方法
解決策は、正規表現を「プレフィックスをオプショナル化」することです。
具体的には:
- 先頭のプレフィックス部分を
(?:...)?でオプショナルにする - 最後に
(?P<msg>[\s\S]*)を置いて、必ず本文をキャプチャする
[\s\S]* を使うことで、改行を含む本文もしっかり捕まえられます(. は通常改行を除くため)。
実際に使える NRQL
SELECT
timestamp AS 'timestamp',
aparse(faas.arn, 'arn:aws:lambda:ap-northeast-1:123456789012:function:*') AS LambdaFunctionName,
level AS Level,
capture(
message,
r'^(?:\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}\s*-\s*[A-Z]+\s*-\s*[^-]*\s*-\s*)?(?P<msg>[\s\S]*)'
) AS Message
FROM Log
WHERE faas.arn LIKE '%123456789012%'
SINCE 1 hour ago
LIMIT 1000
この正規表現のポイントは:
- 先頭の
YYYY-MM-DD hh:mm:ss,mmm - LEVEL - func_name -といったプレフィックス全体を(?:...)?でオプショナルにする - 必ず
(?P<msg>[\s\S]*)で本文(あるいは元のメッセージ全体)をキャプチャする
これで、フォーマットの異なるログが混在していても、本文を確実に表示できます!
動作イメージ(架空のログサンプル)
以下はすべて架空のログデータです。これらで試すと、どう抽出されるかイメージしやすいと思います。
ログサンプル 1(通常フォーマット、プレフィックス付き)
2026-01-12 21:15:03,123 - INFO - myLambdaHandler - User 1234 processed successfully
→ 期待する Message: User 1234 processed successfully
ログサンプル 2(AWS の START 行、プレフィックス無し)
START RequestId: 12345678-9abc-def0-1234-56789abcdef0 Version: $LATEST
→ 期待する Message: START RequestId: 12345678-9abc-def0-1234-56789abcdef0 Version: $LATEST
サンプル1ではプレフィックスを除いた本文のみ、サンプル2では元の message 全体が Message 列に入ります。
運用上の注意点
capture() を使う設計でほとんどのケースをカバーできますが、完全に未知のフォーマットが混在している場合は:
- 複数の正規表現を順に試す(パターン毎に OR を組む/別列で試す)
- ログ収集側で事前にフォーマットを標準化しておく
といった対処が確実です。
また、RE2 の仕様上使えない正規表現機能があるため、どうしても複雑な抽出が必要な場合は、Lambda のログ出力側で JSON 構造に整形してから送るなどの対処も検討してください。
まとめ
capture() を正しく使えば、プレフィックスのあるログから本文を取り出しつつ、プレフィックスがないログは元のメッセージを保持する、という理想的な挙動を実現できます。
ポイントは「プレフィックス部分をオプショナル化して、必ず本文を捕らえる名前付きキャプチャを使う」こと。
まずは Query Builder などで手元のログサンプルを使って上記の NRQL を試してみて、必要に応じて正規表現を調整してみてください。きっとログが見やすくなるはずです!
参考リンク
※この記事のログサンプルはすべて架空のデータです。実際のログフォーマットや属性名(faas.arn の値や level の取り扱いなど)は環境により異なりますので、実運用ではまず uniques() 等で利用可能なフィールド名・値を確認してから NRQL を適用してください。