やりたいこと
実行に時間のかかるコマンドがある。その進捗を確認するために、出力を n 秒ごとに Slack に送信したい。
- 要件
- 標準出力と標準エラー出力をそれぞれ n 秒ごとに Slack に送信する。
- 差分だけ送信する。つまり、一度 Slack に送信した内容は再度送信しないようにする。
前提
任意の Slack チャンネルにメッセージを送信するための Incoming Webhook URL を持っていること。もし持っていない場合は以下を参考に URL を生成してください。
方法
実行に 30 秒程度かかる show_numbers.rb
という Ruby スクリプトがある。
show_numbers.rb
require 'prime'
# 同期モードを有効にしないと、後で使用する Open3.popen3 で出力を読み込めないので注意。
$stdout.sync = true
$stderr.sync = true
(1..30).each_with_index do |n, i|
sleep(1) unless i == 0
if Prime.prime?(n)
$stderr.puts("[WARN] #{n} is a prime number. It's a lonely number.")
next
end
puts("[INFO] #{n}")
end
$ ruby show_numbers.rb
[INFO] 1
[WARN] 2 is a prime number. It's a lonely number.
[WARN] 3 is a prime number. It's a lonely number.
[INFO] 4
[WARN] 5 is a prime number. It's a lonely number.
[INFO] 6
(略)
このスクリプトを実行するために ruby show_numbers.rb
というコマンドを外部プログラムのコマンドとして Open3.popen3 を使って実行する。その出力を 10 秒ごとに Slack に送信する。
notify_to_stack.rb
require 'json'
require 'net/http'
require 'open3'
# ここに Slack の Incoming Webhook URL を設定する。
SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/xxx'
# メッセージを Slack に送信する。
def send_to_slack(message)
Net::HTTP.post_form(URI(SLACK_WEBHOOK_URL), payload: { text: message }.to_json)
end
# バッファ (このメソッドの下で定義) の内容を Slack に送信し、その後バッファをクリアする。
def send_and_clear_buffers(stdout_buffer:, stderr_buffer:, now: Time.now)
messages = []
messages << now
messages << <<~TEXT.chomp
stdout
```
#{stdout_buffer.any? ? stdout_buffer.join.chomp : '(none)'}
```
TEXT
messages << <<~TEXT.chomp
stderr
```
#{stderr_buffer.any? ? stderr_buffer.join.chomp : '(none)'}
```
TEXT
send_to_slack(messages.join("\n"))
stdout_buffer.clear
stderr_buffer.clear
end
# 標準出力と標準エラー出力のためのバッファ (文字列の一時的な保存場所) を用意する。
stdout_buffer = []
stderr_buffer = []
command = 'ruby show_numbers.rb'
tick = 10
Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
# 標準出力をバッファに書き込むスレッド。
stdout_thread = Thread.new(stdout, stdout_buffer) do |out, buffer|
out.each_line { |line| buffer << line }
end
# 標準エラー出力をバッファに書き込むスレッド。
stderr_thread = Thread.new(stderr, stderr_buffer) do |out, buffer|
out.each_line { |line| buffer << line }
end
# 10 秒ごとにバッファの内容を Slack に送信するスレッド。
slack_thread = Thread.new(stdout_buffer, stderr_buffer) do |stdout_b, stderr_b|
loop do
sleep(tick)
send_and_clear_buffers(stdout_buffer: stdout_b, stderr_buffer: stderr_b)
end
end
send_to_slack("#{Time.now}: Start")
# 実行したコマンドが終了するのを待つ。
wait_thr.join
# コマンドの実行が終了したら各スレッドを終了し、バッファに残った内容を Slack に送信する。
[stdout_thread, stderr_thread, slack_thread].each(&:exit)
send_and_clear_buffers(stdout_buffer: stdout_buffer, stderr_buffer: stderr_buffer)
send_to_slack("#{Time.now}: Finish")
end
この Ruby スクリプトを実行すると、以下のように Slack にメッセージが送信される。