こんにちは、香田です。
今回は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-openai
Docker 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 bash
openai パッケージ インストール
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 serverless
Serverless 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: true
Serverless Frameworkで利用するPlugin インストール
sls plugin install -n serverless-python-requirements
sls plugin install -n serverless-plugin-log-subscription
Lambda アプリケーション 作成
次に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:
raise
EventBridge経由で実行され、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)
raise
Lambda アプリケーション デプロイ
次にLambda アプリケーションをデプロイしていきます。
デプロイする為に必要となる環境変数を設定していきます。
AWS環境 認証情報の設定
export AWS_ACCESS_KEY_ID=xxxx
export AWS_SECRET_ACCESS_KEY=xxxx
export AWS_DEFAULT_REGION=ap-northeast-1
EventBridgeで作成したイベントバスの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 emitter
New 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を組み合わせることで、エラー調査のステップが簡略化できるのではないでしょうか。
最後までご覧いただきありがとうございます。