📣

Rubyを用いたAWS LambdaからSlackに通知する仕組みを作った話

2023/07/28に公開

本記事では、Rubyを用いたLambda関数におけるSlack通知の実装例と実装の際に工夫した点を紹介します。

従来は、Lambda関数の処理中にエラーが発生した場合に、エラークラスやトラブルシュート用の情報をAmazon SNSを用いてアラート通知用のメールアドレス宛に通知していました。今回Slackに通知先を移行するにあたり、下記のサンプルコードのように実装しました。

通知例

Lambda関数のサンプルコード

notify_to_slack.rb
# ランタイム: Ruby3.2
# 必要な環境変数: SLACK_CHANNEL, SLACK_WEBHOOK_URL
require 'net/http'
require 'uri'

def lambda_handler(event: nil, context: nil)
  @context = context
  dummy_method
rescue StandardError => e
  message = "追加情報だよ"
  notify_error_to_slack(e.class, e.message, message)
end

def dummy_method
  raise StandardError.new("エラーメッセージだよ")
end

def notify_error_to_slack(err_class, err_message, message)
  subject = '[ERROR] Error occurred'
  notify_to_slack(
    generate_message(:alert, subject, err_class, err_message, message)
  )
end

# 通知内容に応じて絵文字を変更
def emoji_for(severity)
  case severity
  when :info
    ":mega:" # 📣
  when :alert
    ":rotating_light:" # 🚨
  end
end

# メッセージの生成
def generate_message(severity, subject, err_class, err_message, message)
  {
    "channel": "#{ENV['SLACK_CHANNEL']}",
    "username": "Lambda Bot",
    "blocks": [
      {
        "type": "header",
        "text": {
          "type": "plain_text",
          "text": "#{emoji_for(severity)} #{subject}",
          "emoji": true
        }
      },
      {
        "type": "section",
        "fields": [
          {
            "type": "mrkdwn",
            "text": "*lambda_function_name:*\n#{@context.function_name}"
          },
          {
            "type": "mrkdwn",
            "text": "*lambda_request_id*\n#{@context.aws_request_id}"
          },
          {
            "type": "mrkdwn",
            "text": "*lambda_log_stream_name:*\n#{@context.log_stream_name}"
          },
          {
            "type": "mrkdwn",
            "text": "*ErrorClass:*\n#{err_class}"
          },
          {
            "type": "mrkdwn",
            "text": "*ErrorMessage:*\n#{err_message}"
          },
          {
            "type": "mrkdwn",
            "text": "*Info:*\n#{message}"
          }
        ]
      },
      {
        "type": "section",
        "text": {
          "type": "mrkdwn",
          "text": "<#{cloud_watch_log_stream_url}|Please check CloudWatch log from here...>"
        }
      }
    ]
  }
end

# 関数のログストリームのURLの生成
def cloud_watch_log_stream_url
  encoded_function_name = URI.encode_www_form_component(URI.encode_www_form_component("/aws/lambda/#{@context.function_name}"))
  encoded_log_stream_name = URI.encode_www_form_component(URI.encode_www_form_component(@context.log_stream_name))
  url_prefix = "https://ap-northeast-1.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-1#logsV2:log-groups/log-group/"
  url_interfix = "/log-events/"
  url_prefix + encoded_function_name + url_interfix + encoded_log_stream_name
end

# Slack APIのリクエスト
def notify_to_slack(message)
  Net::HTTP.post(URI(ENV['SLACK_WEBHOOK_URL']), message.to_json)
end

Block Kitを使用したSlackメッセージ

Block Kit builderを用いたメッセージ作成の効率化

Slack APIでは、Block Kitと呼ばれるUIフレームワークを用いてリッチなUIのメッセージを送ることができます。メッセージのUIを検討するにあたり、Block Kit builderを用いたことで下図のように簡単のUIを作ることができます。今回は、このBlock Kitを使用してメッセージを作成し、メッセージ作成の効率化を図りました。

メッセージ作成にはblocksとattachmentsのどちらを使うべきか

メッセージの書き方を調べてみるとattchmentsblocksの2種類が存在しますが、公式ドキュメントによるとblocksを使うことが推奨されています。

{
    "channel": "CHANNEL_NAME",
    "attachments": []
}

👇 こちらが推奨されている
{
    "channel": "CHANNEL_NAME"
    "blocks": []
}

CloudWatch LogsのログストリームのURL

従来の運用では、Lambda関数のログを調べる際に、ログストリーム名を確認して該当のログストリーム名を検索するという流れがとても手間だと感じていました。そこで、Slackのチャットに該当のログストリームのリンクを付けるようにしました。CloudWatch LogsのログストリームのURLは少し特徴的で、下記のような文字列の組み合わせになっています。下記の組み合わせで生成したリンクをSlackの通知内容に含めることで、リンクを押下するとすぐに該当のログストリームを確認することができ、トラブルシュートの時間を削減することができました。

"https://#{region}.console.aws.amazon.com/cloudwatch/home?region=#{region}#logsV2:log-groups/log-group/"
+
関数名を2回エンコードした文字列
+
"/log-events/"
+
ログストリーム名を2回エンコードした文字列

まとめ

以上がRubyを用いたLambda関数からのSlack通知の実装例と工夫した点になります。
似たようなユースケースの通知の実装を検討されている方の参考になると嬉しいです。😊

参考

株式会社スタメン

Discussion