こんにちは、香田です。
今回はNew RelicとOpenAIを活用したエラーログ調査の自動化について紹介していきます。
この記事はNew Relic Advent Calender 2023 シリーズ2の10日目の記事になります。
全体像について
今回紹介する構成の全体像は下記になります。
Lambda アプリケーションのエラーログをNew Relicでアラート検知し、アラート通知先としてEventBridgeを利用しLambda アプリケーションを起動します。
起動したLambda アプリケーションにて、エラーログ調査の自動化としてエラー対応方法をOpenAIへリクエストし、Slackへ通知する流れになります。

最終的にSlackへ下記のようなエラー内容とエラー対応方法を通知するイメージとなります。

OpenAI API キーの発行
エラーログの対応方法をOpenAIへリクエストする為、 APIキーを発行します。
APIキーの発行方法は、下記を参考に発行してみてください。
Slack Incoming Webhook URLの発行
Slack 通知用にIncoming Webhook URLを発行します。
Incoming Webhook URLの発行方法は、下記を参考に発行してみてください。
New Relic ログ転送の設定
次にCloudWatchのログをNew Relicへ転送する為、New Relicで提供されているnewrelic-log-ingestionを作成します。
AWS Serverless Application Repositoryを利用することで、簡単にデプロイ可能です。
下記のように[NewRelic-log-ingestion]を選択しデプロイしていきます。

New Relicへログが転送されるように、[アプリケーションの設定]より[NRLicenseKey]へライセンスキーの入力と、[NRLoggingEnabled]をTrueへ変更しデプロイしてください。

しばらくすると下記のようにnewrelic-log-ingestionが作成されているはずです。

New Relic アラートポリシーの作成
次にNew Relicでアラートポリシーを作成していきます。
[Alerts & AI]、[Alert Policies]よりアラートポリシーを作成します。

New Relic アラート条件の作成
[New alert conditions]より、NRQLを使用しアラート条件を作成します。

下記のNRQLを設定後、しきい値を適宜設定しアラート条件を作成します。
SELECT count(*) FROM Log 
FACET aws.logGroup, messageId 
WHERE message LIKE '[ERROR]%'
New Relic Destinationの作成
次にDestinationとしてEventBridgeを設定します。
[Alerts & AI]、[Destinations]より[AWS EventBridge]を選択します。

[Name]、[AWS region]、[AWS account ID]を適宜入力します。

New Relic Workflowの作成
次にWorkflowを作成していきます。
[Alerts & AI]、[Workflows]より通知先にEventBridgeを設定したWorkflowを作成します。

[Filter data]に作成したアラートポリシーを指定し、[state]が[ACTIVATED]を条件として追加します。

検知したエラーログを抽出し、エラーの内容をEventBridgeへ連携できるように[Additional settings]よりエンリッチメントを設定します。

クエリ名とNRQLを指定しログを抽出できるようにします。
- クエリ名
 
cloudwatch-logs-message- NRQL
 
SELECT message FROM Log
WHERE aws.logGroup IS NOT NULL 
AND messageId = {{ accumulations.tag.messageId }}
NotifyにEventBridgeを指定し、EventBridgeにてイベントソースを関連付けしていきます。
EventBridgeの詳細な設定は下記も参考にしてみてください。
EventBridgeに通知するテンプレートとして、下記の内容を入力します。
下記のテンプレートで定義された内容を元に、Lambda アプリケーションでイベントデータとして受け取り通知する流れです。
- Payload
 
{
    "id": {{ json issueId }},
    "issueUrl": {{ json issuePageUrl }},
    "priority": {{ json priority }},
    "state": {{ json state }},
    "trigger": {{ json triggerEvent }},
    "isCorrelated": {{ json isCorrelated }},
    "createdAt": {{ createdAt }},
    "updatedAt": {{ updatedAt }},
    "enrichData" : {{ json cloudwatch-logs-message }},
    "accumulations" : {{ json accumulations }}  
}
Workflowの作成により、New Relic側の準備は一通り完了です。
ローカル開発環境 セットアップ
次にLambda アプリケーション セットアップの為、Docker Composeを利用しローカル開発環境を準備していきます。
Lambda アプリケーションのデプロイはServleress Frameworkを利用していきます。
作業ディレクトリ作成
mkdir newrelic-openai
cd newrelic-openaiDocker Compose環境の作成
- Dockerfile
 
FROM python:3.9
RUN apt-get update && \
    apt-get install -y nodejs npm && \
    rm -rf /var/lib/apt/lists/*- docker-compose.yml
 
version: "3.8"
services:
  app:
    build: .
    container_name: app
    working_dir: /usr/src/app
    tty: true
    volumes:
      - ./:/usr/src/appコンテナ起動しログイン
docker compose up -d
docker compose exec app bashopenai パッケージ インストール
pip install --upgrade pip
pip install openai==0.27.0
pip freeze > requirements.txt最新のopenai パッケージをLambdaで利用する場合、「No module named ‘pydantic_core._pydantic_core’」のエラーにより実行環境の問題が発生する為、ここでは問題なく動作するopenaiのバージョンを指定しています。
- https://github.com/pydantic/pydantic/issues/6557
 - https://forum.serverless.com/t/no-module-named-pydantic-core-pydantic-core/19616
 
Serverless Framework インストール
npm install -g serverlessServerless Frameworkの設定ファイル作成
- serverless.yml
 
service: newrelic-openai
frameworkVersion: "3"
provider:
  name: aws
  runtime: python3.9
  stage: dev
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - logs:CreateLogStream
        - logs:PutLogEvents
      Resource:
        - "*"
plugins:
  # Python依存関係の管理
  - serverless-python-requirements
  # ログサブスクリプションプラグイン
  - serverless-plugin-log-subscription
custom:
  # New Relicのログ収集先ARN
  logSubscription:
    destinationArn: ${env:NEW_RELIC_LOG_INGESTION}
  # Pythonの依存関係をLambdaレイヤーとして使用
  pythonRequirements:
    layer: true
functions:
  # ログレポーター関数
  reporter:
    name: ai-log-reporter
    handler: ai-log-reporter/handler.lambda_handler
    # Python依存関係レイヤーの参照
    layers:
      - Ref: PythonRequirementsLambdaLayer
    # 環境変数
    environment:
      OPENAI_API_KEY: ${env:OPENAI_API_KEY}
      SLACK_WEBHOOK_URL: ${env:SLACK_WEBHOOK_URL}
    # イベントトリガー
    events:
      - eventBridge:
          # イベントバスの設定
          eventBus: ${env:EVENT_BUS}
          pattern:
            source:
              - prefix: "aws.partner/newrelic.com"
  # エラーログエミッター関数
  emitter:
    name: error-log-emitter
    handler: error-log-emitter/handler.lambda_handler
    # ログサブスクリプションの有効化
    logSubscription: trueServerless Frameworkで利用するPlugin インストール
sls plugin install -n serverless-python-requirements
sls plugin install -n serverless-plugin-log-subscriptionLambda アプリケーション 作成
次にLambda アプリケーションを作成していきます。
アラート通知を検証できるように、エラー出力専用のLambda アプリケーションを作成します。
mkdir error-log-emitter- error-log-emitter/handler.py
 
def lambda_handler(event, context):
    try:
        # 存在しないインデックスにアクセスしエラーを発生させる
        data = [1, 2, 3]
        fourth_element = data[3]
        print(fourth_element)
    except Exception as e:
        raiseEventBridge経由で実行され、OpenAI APIへリクエストしSlack通知するLambda アプリケーションを作成します。
mkdir ai-log-reporter- ai-log-reporter/handler.py
 
import json
import openai
import os
import logging
import urllib.request
logger = logging.getLogger()
logger.setLevel(logging.INFO)
SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
OPENAI_API_KEY = os.environ['OPENAI_API_KEY']
OPENAI_MODEL = "text-davinci-003"
def send_to_slack(payload: dict) -> None:
    """Slack通知の送信"""
    try:
        data = json.dumps(payload).encode('utf-8')
        req = urllib.request.Request(SLACK_WEBHOOK_URL, data=data, headers={
                                     'Content-Type': 'application/json'})
        with urllib.request.urlopen(req) as response:
            if response.status == 200:
                logger.info("Slack通知が送信されました")
            else:
                logger.error("Slack通知の送信に失敗しました: ステータスコード %s", response.status)
    except Exception as e:
        logger.exception("Slack通知の送信中にエラーが発生しました: %s", e)
        raise
def create_slack_payload(result: str,
                         message: str,
                         issue_url: str,
                         condition_name: str,
                         log_group: str) -> dict:
    """Slack通知用のペイロードを作成する"""
    return {
        "text": "*<!channel> New Relic アラート通知*",
        "attachments": [
            {
                "color": "#E01E5A",
                "blocks": [
                    {
                        "type": "section",
                        "text": {
                         "type": "mrkdwn",
                         "text": f"*アラート名*\n<{issue_url}|{condition_name}>"}
                    },
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": f"*ロググループ名*\n {log_group}"
                        }
                    },
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": f"*エラー内容*\n {message}"
                        }
                    },
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": f"*エラー対応方法*\n {result}"
                        }
                    },
                ]
            }
        ]
    }
def lambda_handler(event, context):
    # OpenAI APIキーの設定
    openai.api_key = OPENAI_API_KEY
    try:
        # EventBriteのイベント情報を取得
        message = event['detail']['enrichData']['result'][0]['message']
        issue_url = event['detail']['issueUrl']
        condition_name = event['detail']['accumulations']['conditionName'][0]
        log_group = event['detail']['accumulations']['tag']['aws']['logGroup'][0]
        # OpenAI APIの呼び出し
        response = openai.Completion.create(
            model=OPENAI_MODEL,
            prompt=f"このエラーに対してどういった対応が必要ですか?: '{message}'",
            max_tokens=1000,
        )
        # OpenAI API 回答結果の取得
        result = response.choices[0].text.strip()
        # Slack通知の送信
        payload = create_slack_payload(
            result, message, issue_url, condition_name, log_group)
        send_to_slack(payload)
    except Exception as e:
        logger.exception("Lambda関数の実行中にエラーが発生しました: %s", e)
        raiseLambda アプリケーション デプロイ
次にLambda アプリケーションをデプロイしていきます。
デプロイする為に必要となる環境変数を設定していきます。
AWS環境 認証情報の設定
export AWS_ACCESS_KEY_ID=xxxx
export AWS_SECRET_ACCESS_KEY=xxxx
export AWS_DEFAULT_REGION=ap-northeast-1EventBridgeで作成したイベントバスのARN設定
export EVENT_BUS=<EventBridge イベントバス ARN>newrelic-log-ingestionのARNの設定
export NEW_RELIC_LOG_INGESTION=<Lambda newrelic-log-ingestion ARN>OpenAI API キーの設定
export OPENAI_API_KEY=<OpenAI API キー>Slack Webhook URLの設定
export SLACK_WEBHOOK_URL=<Slack Incoming Webhook URL>Lambda アプリケーションのデプロイ
sls deploy --verboseデプロイが成功すると、下記のようにLambda アプリケーションが確認できるはずです。

エラーログのアラート通知確認
動作確認として、エラー出力専用のLambda アプリケーションを実行しエラーを擬似的に出力させます。
sls invoke --function emitterNew Relic Logsに実行したLambda アプリケーションのログが、下記のように転送されているか確認します。

エラー発生によって、しばらくするとエラーログに対するアラートが検知されるはずです。

アラート検知によりEventBrdige経由でLambda アプリケーションが起動し、下記のようにSlakcへ通知されていれば成功です。

クリーンアップ
作成した環境が不要になったら、下記でクリーンアップしてください。
デプロイしたLambda アプリケーションの削除
sls remove --verboseローカル開発環境として作成したDocker Compose環境の削除
docker compose down --rmi all --volumes --remove-orphansさいごに
New RelicとOpenAIを活用したエラーログ調査の自動化方法いかがでしたでしょうか。
New RelicとOpenAIを組み合わせることで、エラー調査のステップが簡略化できるのではないでしょうか。
最後までご覧いただきありがとうございます。