• トップ
  • ブログ一覧
  • Railsでスラック通知機能を実装する
  • Railsでスラック通知機能を実装する

    はじめに

    最近、実務で「バッチ処理中に処理の経過をスラックに通知したい」といった要件があり、スラック通知機能を実装する機会がありました。
    他のプロジェクトでも利用できるような汎用的なモジュールとして実装しているので、同じような要件がある方は是非利用してみてください。

    通知モジュールの全体コード

    以下がスラック通知モジュールです。
    こちらを利用することでスラック通知を実装することができます。
    ベースはslack-notifier gemを利用しています

    1// app/services/slack_service/notifier.rb
    2
    3module SlackService
    4  # Slackに通知を行うクラス
    5  class Notifier
    6    # 通知を有効にするかどうか
    7    #
    8    # @return [Boolean] 通知を有効にするかどうか
    9    def self.enable_notifier?
    10      ENV['ENABLE_SLACK_NOTIFICATION'] == 'true'
    11    end
    12
    13    # 初期化メソッド
    14    # @param channel [String] スラックのチャンネル名
    15    # @param user_name [String] 通知のユーザー名
    16    def initialize(user_name:, channel: ENV['SLACK_CHANNEL_NAME'])
    17      @client = Slack::Notifier.new(ENV['SLACK_CHANNEL_URL'], channel:, username: user_name)
    18      @blocks = []
    19      @attachments = {
    20        mrkdwn_in: ['text'],
    21        color:     'good',
    22        fields:    [],
    23      }
    24    end
    25
    26    # カラーバーの状態を変更
    27    #
    28    # @param type [String] 状態識別文字(good=緑、warning=オレンジ、danger=赤)
    29    def change_attachments_type(type)
    30      @attachments[:color] = type
    31    end
    32
    33    # ヘッダーを追加する
    34    #
    35    # @param text [String] ヘッダーのテキスト
    36    def add_header(text:)
    37      @blocks << {
    38        type: 'header',
    39        text: {
    40          type: 'plain_text',
    41          text:,
    42        },
    43      }
    44    end
    45
    46    # セクションを追加する
    47    #
    48    # @param text [String] セクションのテキスト
    49    # @param text_bold [Boolean] テキストを太字にするかどうか
    50    def add_section(text:, text_bold: false)
    51      result_text = text_bold ? "*#{text}*" : text
    52
    53      @blocks << {
    54        type: 'section',
    55        text: {
    56          type: 'mrkdwn',
    57          text: result_text,
    58        },
    59      }
    60    end
    61
    62    # 区切り線を追加する
    63    def add_block_divider
    64      @blocks << {
    65        type: 'divider',
    66      }
    67    end
    68
    69    # attachmentsのフィールドを追加する
    70    #
    71    # @param title [String, nil] フィールドのタイトル
    72    # @param values [Array, String] フィールドの値
    73    # @param prefix_marker [Boolean] タイトルにマーカーを付けるかどうか
    74    # @param value_type [Symbol] 値の表示形式(:normal=通常、:bold=太字、:code=コード)
    75    # @param short [Boolean] フィールドを短く表示するかどうか
    76    def add_attachments_field(title: nil, values: [], prefix_marker: false, value_type: :normal, short: false)
    77      result_title = prefix_marker && title ? "◾️#{title}" : title
    78      values = [values] if values.is_a?(String) || values.is_a?(Integer)
    79
    80      result_value = values.map { |v|
    81        case value_type
    82        when :bold
    83          "*#{v}*"
    84        when :code_block
    85          "```#{v}```"
    86        when :backtrace
    87          "```#{v.tr("'", '"').tr('`', '"')}```"
    88        else
    89          v
    90        end
    91      }.join("\n")
    92
    93      content = {
    94        short:,
    95      }
    96
    97      @attachments[:fields] << content.tap do |c|
    98        c[:value] = result_value if result_value.present?
    99        c[:title] = result_title if result_title.present?
    100      end
    101    end
    102
    103    # メッセージを送信する
    104    def send
    105      return Rails.logger.warning '送信するメッセージがありません' if @blocks.blank? && @attachments[:fields].blank?
    106
    107      post_hash = {
    108        blocks:      @blocks.presence,
    109        attachments: @attachments[:fields].present? ? @attachments : nil,
    110      }.compact
    111
    112      @client.post(**post_hash)
    113    end
    114  end
    115end

    以下はエラー通知用に特化したスラック通知モジュールです。
    例外を通知したい時などに利用します。
    こちらを利用しないでも、AWSなどのクラウド環境を利用している場合は、クラウドが提供しているサービスを使用してエラー通知をするとかでももちろん大丈夫です。

    1// app/services/slack_service/error_notification.rb
    2
    3module SlackService
    4  # Slackにエラー通知を行うクラス
    5  class ErrorNotification
    6    # 初期化メソッド
    7    # @param channel [String] スラックのチャンネル名
    8    # @param user_name [String] 通知のユーザー名
    9    # @param exception [Exception] 例外オブジェクト
    10    def self.send(header:, user_name:, exception:, chanel: ENV['SLACK_CHANNEL_NAME'])
    11      notifier = SlackService::Notifier.new(channel: chanel, user_name:)
    12      notifier.change_attachments_type('danger')
    13      notifier.add_header(text: header)
    14      notifier.add_block_divider
    15      notifier.add_attachments_field(title:         'エラーメッセージ',
    16                                     values:        exception.message,
    17                                     value_type:    :code_block,
    18                                     prefix_marker: true)
    19      notifier.add_attachments_field(title:         'backtrace',
    20                                     values:        exception.backtrace[0..5],
    21                                     value_type:    :backtrace,
    22                                     prefix_marker: true)
    23      notifier.send
    24    end
    25  end
    26end

    使い方

    例として、以下のような利用方法を想定して使用方法を解説していきます。

    • Userを追加するrakeタスクがある
    • タスク実行時に実行開始通知
    • タスクの結果通知
    • エラー時にも通知

    通知処理実装例

    1namespace :user do
    2  desc 'ユーザーを追加する'
    3  task create: :environment do |_t|
    4    start_slack_notifier
    5    start_time = current_time
    6
    7    before_count = User.count
    8    1000.times do |i|
    9      User.create(name: "テスト太郎#{i}", email: "test#{i}@example_com", hoge: "hoge")
    10    end
    11    after_count = User.count
    12
    13    finish_slack_notifier(before_count, after_count, start_time)
    14  rescue StandardError => e
    15    if SlackService::Notifier.enable_notifier?
    16      SlackService::ErrorNotification.send(
    17        header:    'ユーザー追加処理中にエラーが発生しました',
    18        user_name: 'ユーザー追加バッチ',
    19        exception: e
    20      )
    21    end
    22  end
    23
    24  def start_slack_notifier
    25    if SlackService::Notifier.enable_notifier?
    26      notifier = SlackService::Notifier.new(user_name: 'ユーザー追加バッチ')
    27      notifier.add_header(text: '◆ユーザー追加処理を開始します')
    28      notifier.add_block_divider
    29      notifier.add_section(text: '開始時間', text_bold: true)
    30      notifier.add_section(text: current_time)
    31      notifier.send
    32    end
    33  end
    34
    35  def finish_slack_notifier(before_count, after_count, start_time)
    36    if SlackService::Notifier.enable_notifier?
    37      notifier = SlackService::Notifier.new(user_name: 'ユーザー追加バッチ')
    38      notifier.add_header(text: 'ユーザー追加処理完了')
    39      notifier.add_attachments_field(title: '処理前件数', values: before_count, prefix_marker: true)
    40      notifier.add_attachments_field(title: '処理後件数', values: after_count, prefix_marker: true)
    41      notifier.add_attachments_field(title: '処理時間', prefix_marker: true)
    42      notifier.add_attachments_field(title: '開始時間', values: start_time, short: true)
    43      notifier.add_attachments_field(title: '終了時間', values: current_time, short: true)
    44      notifier.send
    45    end
    46  end
    47
    48  def current_time
    49    Time.current.strftime('%Y/%m/%d %H:%M:%S')
    50  end
    51end

    コード解説

    1.開始通知

    1  task create: :environment do |_t|
    2    start_slack_notifier

    最初に処理の開始通知を送信しています。
    通知内容は以下です。

    1  def start_slack_notifier
    2    if SlackService::Notifier.enable_notifier?
    3      notifier = SlackService::Notifier.new(user_name: 'ユーザー追加バッチ')
    4      notifier.add_header(text: '◆ユーザー追加処理を開始します')
    5      notifier.add_block_divider
    6      notifier.add_section(text: '開始時間', text_bold: true)
    7      notifier.add_section(text: current_time)
    8      notifier.send
    9    end
    10  end

    送信される通知は以下です。

    それぞれのメソッドは以下のような役割があります。

    enable_notifier?
    スラック通知を送信するかどうかを判別しています
    環境変数で制御しています
    add_header
    通知内のヘッダーです
    add_block_divider
    横線を追加しています
    add_section
    通知内容を追加しています
    オプションで太字にすることもできます

    2.ユーザー追加処理

    1before_count = User.count
    21000.times do |i|
    3  User.create(name: "テスト太郎#{i}", email: "test#{i}@example_com")
    4end
    5after_count = User.count

    1000人ユーザーを追加しています。
    処理前と処理後のテーブルの件数を後続の通知で利用したいので取得しています。

    3.処理後通知

    1finish_slack_notifier(before_count, after_count, start_time)
    1def finish_slack_notifier(before_count, after_count, start_time)
    2  if SlackService::Notifier.enable_notifier?
    3    notifier = SlackService::Notifier.new(user_name: 'ユーザー追加バッチ')
    4    notifier.add_header(text: 'ユーザー追加処理完了')
    5    notifier.add_attachments_field(title: '処理前件数', values: before_count, prefix_marker: true)
    6    notifier.add_attachments_field(title: '処理後件数', values: after_count, prefix_marker: true)
    7    notifier.add_attachments_field(title: '処理時間', prefix_marker: true)
    8    notifier.add_attachments_field(title: '開始時間', values: start_time, short: true)
    9    notifier.add_attachments_field(title: '終了時間', values: current_time, short: true)
    10    notifier.send
    11  end
    12end

    処理後に通知を送信しています。

    新しく出てきたメソッドの役割は以下です。

    add_attachments_fieldカラーバー内の要素を追加する
    【以下オプション】
    prefix_markerで「◾️」を先頭に付与できる
    shortで要素を複数横並びに表示できる

    送信される通知は以下です。
    処理前と処理後のユーザー件数の変動を通知することができました。

    4.エラー時の通知

    エラー時はrescueで例外をキャッチした後に送信しています。

    1rescue StandardError => e
    2  if SlackService::Notifier.enable_notifier?
    3    SlackService::ErrorNotification.send(
    4      header:    'ユーザー追加処理中にエラーが発生しました',
    5      user_name: 'ユーザー追加バッチ',
    6      exception: e
    7    )
    8  end
    9end

    エラーを発生させるためにuserテーブル内に存在しないhogeという属性で保存してみます。
    すると以下のようなエラーを通知をすることができます。

    さいごに

    今回はrailsでスラック通知をする方法をご紹介しました。
    実際にプロジェクトに導入してみると、処理の経過が視覚的にわかって結構良かったです。
    どなたかの参考になると幸いです。

    参考にしたサイト
    https://zenn.dev/strsbn/articles/4084cde4ed2df6
    https://api.slack.com/reference/messaging/attachments
    https://api.slack.com/reference/block-kit/blocks

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    はっと(エンジニア)
    はっと(エンジニア)
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background