🔖

Slackでリアルタイムにログを確認する

2021/10/12に公開

これはなに?

アプリのバッチを動かすときに、私はSlackでログを見たくなります。
Slackで見れることでPCが見れない環境下でも状況を確認できますし、メールなど他チャネルに分散せず一箇所で確認・議論・対応できる点がお気に入りです。

バッチ監視の従来の手法

古来からサーバーに潜ってログファイルをtailして確認したし、最近だとCloudWatch Logsでtailしたりしますよね。

Slackでのバッチログについてバッチ開始時と終了時にpostするくらいがよくある形かと思いますが、Slackのメッセージ更新APIを使えばリアルタイムにバッチの状況を確認できます。
本記事はそういった使い方の共有です。

利用イメージ

後述のようにloggerを用意することで、このような形でバッチの途中経過が更新されます。

使い方


log_filepath = "log/sample.log"
logger = SlackLogger.new(logger: Jets::Logger.new(log_filepath))

logger.post!("Start.")
(:A .. :E).each do |alphabet|
  logger.post!("#{alphabet}の処理 開始")
  sleep(1)

  # 大量に出力されるものはリアルタイムにSlack通知せず、ファイルにバッファして終了時にスレッドにポスト
  1000.times do |i|
    logger.write!("#{alphabet} - #{i}回目")
  end

  logger.post!("#{alphabet}の処理 終了")
  sleep(1)
end

# ログファイルをポスト
logger.post_thread_file!(File.new(log_filepath))

# アタッチメントカラーを緑に
logger.good!("Completed.")

なお、かなりの数のバッチをこれで長年更新していますが、(本ロガーの件で)Slackの制限に引っかかったことはありません。

ロガークラスの実装

class SlackLogger
  attr_accessor :color

  def initialize(pretext = nil, *args)
    pretext ||= begin
      c = caller_locations(2, 1).first
      "#{c.path.gsub(Jets.root.to_s, '')} - #{c.label}"
    end

    @options = args.extract_options!
    @options[:channel] ||= :test
    @options[:username] ||= "バッチログ"
    @options[:username] += "(開発環境)" if Rails.env.development?
    @logs = []
    @pretext = pretext
    @color = :default
    @post_to_slack = true
    @logger = @options.delete(:logger) || Rails.logger
  end

  def post!(text)
    begin
      full_text = "#{Time.current.strftime('%T')} - #{text}"
      @logger.info(text)
      unless @post_to_slack
        puts full_text
        return
      end

      @logs << full_text
      attachments = [{
        pretext: @pretext,
        color: @color,
        text: @logs.reverse.join("\n"),
      }]

      # 初回はpost、2回目からはupdate
      if @message.nil?
        @message = Slack.client.chat_postMessage(@options.merge({
          attachments: attachments.to_json,
        }))
      else
        Slack.client.chat_update(@options.merge({
          channel: @message["channel"],
          ts: @message["ts"],
          attachments: attachments.to_json,
        }))
      end
    rescue => e
      @logger.error("fail. \n#{e.message}\n" + %!#{e.backtrace[0..3].join("\n")}!)
    end
  end

  def good!(text)
    @color = :good
    post!(text)
  end

  def danger!(text)
    @color = :danger
    post!(text)
  end

  # 頻度が多い場合はpost!せずwrite!でファイルに溜めて、処理の終了時にログファイルをpost_thread_file!する
  def write!(text)
    @logger.info(text)
  end

  # スレッドにファイルをpostする
  def post_thread_file!(text_file, filetype: "text",  mime_type: "text/plain", filename: "tmp")
    ret = Slack.client.files_upload({
      channels: @message['channel'],
      thread_ts: @message['ts'],
      filename: filename.to_s,
      filetype: filetype.to_s,
      file: Faraday::UploadIO.new(text_file, mime_type),
    })
  end
end

バッチに余計な処理をはさみたくないというケースもあるかと思いますが、長年使っても安定していてここでトラブったことはないので快調ではあります。
(とはいえデリケートな処理の箇所ではタイムアウト設定や、一度でもタイムアウトしたらその日は更新しないなどの制御が必要です。)

誰かのお役に立てば幸いです!

Happy Elements

Discussion