
Railsでスラック通知機能を実装する

IT技術

はじめに
最近、実務で「バッチ処理中に処理の経過をスラックに通知したい」といった要件があり、スラック通知機能を実装する機会がありました。
他のプロジェクトでも利用できるような汎用的なモジュールとして実装しているので、同じような要件がある方は是非利用してみてください。
通知モジュールの全体コード
以下がスラック通知モジュールです。
こちらを利用することでスラック通知を実装することができます。
ベースは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
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ

服部晋平と申します! 前職では映像業や配送業に携わっていました。 趣味は、バイクツーリングに行ったり、美味しいラーメン屋巡りです。 未経験という身で入社させていただいたので、人一倍努力して頑張っていきたいと思います!