1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事はReviCo Advent Calendar 2025 の9日目の記事です。

はじめに

こんにちは、ReviCoの開発者のうっちです😎

ReviCoでは、サービスの監視・可観測性の実現にNewRelicを活用しています。
アプリケーションパフォーマンス監視(APM)、インフラ監視、ログ管理など、NewRelicの各種機能を使ってシステム全体の健全性を保っています。

マルチテナント型のSaaSを運用していると、テナント毎にログを監視したいというニーズが出てきます。
今回は、NewRelicのLog Parsing機能を使ってこの課題を解決した事例を紹介します。

NewRelicとは?

NewRelicは、アプリケーションやインフラストラクチャの監視・可観測性を提供するSaaSプラットフォームです。

主な機能:

  • APM (Application Performance Monitoring): アプリケーションのパフォーマンスをリアルタイムで監視
  • Infrastructure Monitoring: サーバー、コンテナ、クラウドリソースの監視
  • Log Management: ログの収集、検索、分析
  • Alerts & Anomaly Detection: 異常検知とアラート通知
  • Distributed Tracing: マイクロサービス間のリクエストフローを可視化
  • Custom Dashboards: カスタマイズ可能なダッシュボードで重要なメトリクスを一元管理

ReviCoでは特に、ログ可視化/アラート通知/APMを活用してサービス品質の向上に取り組んでいます。

課題:frowning2: テナント毎のログ監視がしたい

ReviCoはマルチテナント型のSaaSであるため、テナント毎に監視を分けることが重要です。
NewRelicでアラート設定をする際、テナント毎に監視を分けたいというケースがありました。

例えば:

  • 特定テナントでエラーが多発した場合にアラートを発報
  • テナントAとテナントBで異なる閾値を設定
  • ダッシュボードでテナント毎の集計を表示

これを実現するには、ログにTenantIdのような属性を持たせる必要があります。

従来の方法:neutral_face: Log API (v1) でカスタム属性を追加

当初は、NewRelicのLog API (v1)を使ってカスタム属性を追加していました。

{
  "logs": [
    {
      "message": "商品登録でエラーが発生しました。",
      "timestamp": 1701388800000,
      "attributes": {
        "tenantId": "00000000-440d-444e-a84c-d0017a33a001"
      }
    }
  ]
}

このアプローチの問題点:

  • レート制限(スロットリング): Log APIには、1分あたり300,000リクエストの制限がある
  • 実装コスト: 各サービスでAPIコールを実装する必要がある
  • ログ量が多いとAPI呼び出しが膨大になり、制限に引っかかるリスク

実際、ログ量が増えてくるとレート制限が懸念され、乱用できない状況でした。

解決策:heart_eyes: Log Parsing機能を活用

そこで見つけたのが、NewRelicのLog Parsing機能です。

Log Parsingとは?

Log Parsingは、既にNewRelicに取り込まれているログのmessageフィールドから、特定のパターンを抽出してカスタム属性として追加できる機能です。

メリット:

  • ✅ ログ取り込み後に属性を追加できる(API呼び出し不要)
  • ✅ Grokパターンや正規表現で柔軟に抽出可能
  • ✅ 既存のログフォーマットを変更せずに対応可能
  • ✅ レート制限の心配がない

前提条件: AWS CloudWatch LogsとNewRelicの連携

Log Parsing機能を使用するには、AWS CloudWatch LogsのログがNewRelicに連携されている必要があります。
ReviCoでは既に連携が済んでいるので、詳細は割愛します。

実際のログ構造例:

{
  "aws.logGroup": "/aws/lambda/ReviCo-ProductRegister",
  "aws.logStream": "2025/12/01/[$LATEST]aaacf1d44f22483bbda588ab53065028",
  "message": "2025-12-01T06:02:17.172Z\taaa1a644-c3d9-471e-871d-5a6c2a060c2d\tinfo\t{ \"time\": \"2025-12-01 06:02:17.1721\", \"level\": \"INFO\", \"logger\": \"ReviCo.Job.ProductRegisterUseCase\", \"message\": \"9件の商品を登録します。 TenantId: 00000000-440d-444e-a84c-d0017a33a001\" }\n",
  "plugin.type": "lambda",
  "timestamp": 1764568937172
}

やりたいこと:

messageフィールドの中に埋め込まれたJSONから、以下の情報を抽出してログのトップレベル属性として利用したい:

  • TenantId: テナント識別子(例: 00000000-440d-444e-a84c-d0017a33a001)
  • level: ログレベル(例: INFO, ERROR, WARN)

Log Parsingの設定手順

1. Logsページにアクセス

NewRelicのUIで、左メニューからLogsParsingを選択します。

2. 新しいParsing Ruleを作成

Create parsing ruleボタンをクリックします。

設定項目
  • Name
    わかりやすい名前を付けます。

    Extract TenantId from application logs
    

     

  • Filter logs based on NRQL
    どのログにこのParsingルールを適用するかをNRQL形式で指定します。
    例: 特定のLambda関数のログのみ対象にする場合

    aws.logGroup = '/aws/lambda/ReviCo-ProductRegister' AND message LIKE '%TenantId%'
    

    または、plugin.typeでLambdaログのみを対象にする場合:

    plugin.type = 'lambda' AND message LIKE '%TenantId%'
    

 

  • Parsing rule
    実際の抽出ロジックを記述します。
    NewRelicではGrokパターンまたは正規表現が使用できます。今回はGrokパターンを使用します。

    • TenantIdを抽出するパターン
      Lambdaログのmessage内のJSON文字列からTenantId:というテキストの後に続くUUID形式の値を抽出します。
      TenantId: %{UUID:tenantId}
      
       
    • levelを抽出するパターン
      JSON内の"level": "INFO"のような形式から値を抽出します。
      "level": "%{WORD:level}"
      
       
    • 複数のフィールドを抽出するパターン
      1つのParsingルールで複数のフィールドを抽出したい場合は、パターンを組み合わせることができます。
      ただし、管理のしやすさを考慮して、TenantIdとlevelで別々のルールを作成することも推奨されます。
      "level": "%{WORD:level}".*TenantId: %{UUID:tenantId}
      

3. テストしてみる

設定画面のPaste logエリアにログを直接貼り付けることで、テストができます。

テストログ(messageフィールドの値):

2025-12-01T06:02:17.172Z\taaa1a644-c3d9-471e-871d-5a6c2a060c2d\tinfo\t{ \"time\": \"2025-12-01 06:02:17.1721\", \"level\": \"INFO\", \"logger\": \"ReviCo.Job.ProductRegisterUseCase\", \"message\": \"9件の商品を登録します。 TenantId: 00000000-440d-444e-a84c-d0017a33a001\" }

Grokパターン:

"level": "%{WORD:level}".*TenantId: %{UUID:tenantId}

結果:

{
  "level": "INFO",
  "tenantId": "00000000-440d-444e-a84c-d0017a33a001"
}

4. 保存して有効化

Create ruleをクリックすると、ルールが有効化されます。

Parsingルールは保存後、新しく取り込まれるログにのみ適用されます。
過去のログには遡って適用されませんのでご注意ください。

Parsingの効果: before/after

Parsing前のログ構造

{
  "aws.logGroup": "/aws/lambda/ReviCo-ProductRegister",
  "aws.logStream": "2025/12/01/[$LATEST]aaacf1d44f22483bbda588ab53065028",
  "message": "2025-12-01T06:02:17.172Z\taaa1a644-c3d9-471e-871d-5a6c2a060c2d\tinfo\t{ \"time\": \"2025-12-01 06:02:17.1721\", \"level\": \"INFO\", \"logger\": \"ReviCo.Job.ProductRegisterUseCase\", \"message\": \"9件の商品を登録します。 TenantId: 00000000-440d-444e-a84c-d0017a33a001\" }\n",
  "plugin.type": "lambda",
  "timestamp": 1764568937172
}

tenantIdlevelの情報はmessageフィールドの中に埋もれており、NRQLで直接クエリできない状態でしたが…:point_down:

Parsing後のログ構造

{
  "aws.logGroup": "/aws/lambda/ReviCo-ProductRegister",
  "aws.logStream": "2025/12/01/[$LATEST]aaacf1d44f22483bbda588ab53065028",
  "message": "2025-12-01T06:02:17.172Z\taaa1a644-c3d9-471e-871d-5a6c2a060c2d\tinfo\t{ \"time\": \"2025-12-01 06:02:17.1721\", \"level\": \"INFO\", \"logger\": \"ReviCo.Job.ProductRegisterUseCase\", \"message\": \"9件の商品を登録します。 TenantId: 00000000-440d-444e-a84c-d0017a33a001\" }\n",
  "plugin.type": "lambda",
  "timestamp": 1764568937172,
  "tenantId": "00000000-440d-444e-a84c-d0017a33a001",
  "level": "INFO"
}

tenantIdlevelがトップレベル属性として追加されました:raised_hands:
これらの属性を使ってNRQLでフィルタリング、集計、アラート設定が可能になります!

まとめ

NewRelicのLog Parsing機能を使うことで:

  • ✅ Log APIのレート制限を気にせずテナント毎の監視が実現
  • ✅ 既存のログフォーマットを変更せず属性を追加
  • ✅ NRQL/アラート/ダッシュボードで柔軟に活用

マルチテナントSaaSの監視において、Parsingは非常に強力なツールです。ぜひ活用してみてください!

参考リンク

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?