その他
    ホーム 技術発信 DoRuby RailsでLoggerを使ってLogローテション
    RailsでLoggerを使ってLogローテション
     

    RailsでLoggerを使ってLogローテション

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

    RailsでLogローテーションについて学んだのでそれの備忘録兼、復習です。

    はじめに

     初めまして、17新卒でエンジニアとして入社したむらさきです。
     4月にエンジニアとして入社し、Ruby on Railsを使用してwebサイトの改修や機能の追加などを行っています。入社前のインターンに参加するまでRubyもRailsもwebサイト構築もしたことの無かったのですが、この3ヵ月弱で色々なことを学んで少しずつですが成長を日々実感しています。
     さて、今回から記事を書くわけですが、他のエンジニアの方々のような難しいことを書く知識はないので、実際に仕事で制作した「Rubyでlogを作成してローテーションさせる」機能について復習を兼ねてまとめたいと思います。

    開発環境

    Windows10(64bit)  CentOS7.3.1611  Rails 4.1.4   ruby2.2.0

    今回の目的

     今回は以下の2つを目的としていきます

    • 処理ごとに出力される内容をLogとして日ごとにファイルに出力する。
    • 出力したLogファイルを定期的に削除する。

    目次

    1. CustomLoogerの作成
    2. Logへの出力方法
    3. Logのフォーマットを変更する
    4. 古いLogを削除させる
    5. crontabの設定

    1.CustomLoggerの作成

     Logを書き出す方法はいくつかありますが今回はrubyに標準搭載されているライブラリのLoggerを使用します。
    Logファイルを格納するフォルダを/public/custom_log/に作成して、その直下にcustom.logを日付ごとに作成していくようにします。
    開発環境、テスト環境、本番環境によって記述する場所が違うので気を付けましょう。今回は開発環境で行うのでdevelopment.rbに記述します。

    #config/environments/development.rb
    config.custom_logger = logger.new('public/custom_log/custom.log', 'daily')
    

    第1引数で作成するファイルの場所と名称を、第2引数で期間を基準に新しいファイルを作ってくれるようになります。

    • ‘daily’は1日ごと
    • ‘weekly’は1週間ごと
    • ‘monthly’は1ヵ月ごと

    となっています。
    ※サーバーを起動した際の翌日の0時がトリガーとなっているようで、サーバーを起動している状態で日付を跨がないと新しいファイルが作成されないのでlocal環境で作成している人は注意が必要です。
    うまく作成された場合は

    custom.log
    custom.log.20170620
    custom.log.20170621
    custom.log.20170622
    ・
    ・
    ・
    

    という感じで.logの後に年月日が付いて作られていきます。今日作られたばかりのファイルには後ろに何も付きません。
     
     ちなみに、第2引数に整数、第3引数にサイズを指定することで、第3引数で指定したファイルサイズごとにファイルが作成され、第2引数で指定した数を超えた場合は古いファイルから削除させていくことができます。

    config.contact_logger = logger.new('public/contact_log/contact.log', 4, 10 * 1024 * 1024)
    

    今回の場合は日付ごとに管理したいという要望があったため、日にち指定の’daily’で実装していきますが、こちらには古いファイルを削除していく機能はないので後で別で実装する必要があります。自分またはチームが管理しやすい方法で実装してください。

    2.Logへの出力方法

     Logに出力するにはcontrollerなどに以下の記述をします。

    Rails.application.config.custom_logger.info("ここの内容をLogに出力します")
    

    .info()は表示したいLogのレベルを表しており、

    enter image description here

    の5つに分類されています。指定したレベル以上のログを出力するようになります。debugを指定した場合はすべての情報が表示されるようになります。
    .info()の中身はブロックや文字列が指定でき、その値がメッセージとして使用されます。

    3.Logのフォーマットを変更する

     ここまでくれば希望した出力先にLogが吐き出せるようになっていると思います。
    public/custom_log/custom.logを見に行くと下図のように出力できています。
    enter image description here
    しかし今のままだとLogの先頭に色々書いてあって少し見づらいですね。

    ここで使えるのがFormatterです。
    FormatterはLoggerのフォーマット文字列を扱うクラスです。

    call(severity, time, progname, msg) -> String
    ログ情報をフォーマットして返します。
    [PARAM] severity:
      ログレベル。
    [PARAM] time:
      時間。Time クラスのオブジェクト。
    [PARAM] progname:
      プログラム名
    [PARAM] msg:
      メッセージ。
    
     https://docs.ruby-lang.org/ja/latest/class/Logger=3a=3aFormatter.html
    

    ドキュメントにある様にcallメソッドの4つの引数によってフォーマットを変えられます。今回はserverity, time, prognameは必要なく、msgだけあればいいので

    #config/environment.rb
    # Load the Rails application.
    require File.expand_path('../application', __FILE__)
    
    #ここから記述
    class CustomLogger
      class Formatter
        def call(_severity, _time, _progname, msg)
          "#{msg}\n"
        end
      end
    end
    #ここまで記述
    
    # Initialize the Rails application.
    Rails.application.initialize!
    

    と書き込めばmsgだけが表示されるようになります。
    あとはこれをcustom_loggerにセットします。

    #config/environments/development.rb
    
    config.custom_logger.formatter = CustomLogger::Formatter.new
    

    こうすることでLogのフォーマットが変更されているはずです。実際に確かめてみましょう。
    enter image description here
    ちょっと画像が見づらいですが、前についていたものがなくなって変わっていることが確認できました。

    4.古いLogを削除させる

     上記までの方法でLogを出力する機能の実装は完了しました。
    「1.CustomLoggerの作成」の部分で触れましたが、’daily’等の日付を引数にした場合はローテーションで削除していく機能はありません。なのでcronを使用して自動的に古いファイルを削除していく機能を最後に実装していきます。
     いきなりcronという言葉が出てきましたが、知らない人のために説明しておくと(自分も知りませんでしたが)、UNIX系システムの常駐プログラム(デーモンの一種)で、ユーザーが設定したスケジュールに基づいてコマンドやスクリプトを自動で実行してくれるすごいやつらしいです。起動や設定方法まで書くと長くなってしまうので各自で調べてください。
     とりあえず今回はlib/tasks/にlog_rotate.rbを作成してそこに削除用のスクリプトを書き込んでいきます。

    #lib/tasks/log_rotate.rb
    module Tasks
      module LogRotate
        require 'fileutils'
        require 'active_support'
        class << self
          def rotate
            time = Time.current.months_ago(3).strftime('%Y%m%d%H%M%S')
            log_all = Dir.glob("#{Rails.root}/public/contact_log/*")
            log_all.each do |i|
              if File.stat(i).mtime.strftime('%Y%m%d%H%M%S') < time
                FileUtils.rm_f(i)
              end
            end
          end
        end
      end
    end
    

    他にもっと簡潔な書き方があるとは思いますが、自分はこのような感じで書きました。

    1. 3ヵ月前の年月日時分秒を取得してtimeに入れます(例:20170322155030)
    2. 作成されたLogファイルすべてを配列にしてlog_allに入れます
    3. そのファイルを1つずつ最終更新時間を年月日時分秒で取得してtimeと比較します
    4. timeよりも値が小さい(古い)場合はそのファイルを削除させます

    これでこのスクリプトを呼べば3ヵ月前より古いファイルを削除できるようになりました。

    5.crontabの設定

     さて、これで最後の実装です。cronに先ほどのスクリプトを動かしてもらうためにcrontabに設定を書いていきます。
     毎月1日に1度先ほどのスクリプトを走らせます。時間は0時ちょうどだと他のものが走っていたりした場合にサイトが重くなってしまう可能性があるので深夜3時5分頃に走らせるようにします。

    % crontab -e
    

    でエディタが起動して設定を書き込むことができます。

    % crontab -r
    

    としてしまうと確認もなく今まで設定していたものがすべて消去されるので注意が必要です!
    ‘e’と’r’はキーボードで隣り合わせなのでうっかり押し間違えないようにしましょう(っていうのがどこのサイトにも書いてあったので自分も言ってみたかった)

    */5 03 1 * * /bin/bash -lc 'cd /アプリケーションのディレクトリ && bundle exec rails runner Tasks::LogRotate.rotate'
    

    分 時 日 月 曜日 で並んでおり*/5とすると5分おきとなります。曜日は0-7で設定でき、0と7が日曜日で1=月曜, 2=火曜…と続いていきます。
    その後ろで自分が作っているアプリケーションのディレクトリまで移動し、runnnerでスクリプトを起動します。
    すぐに確認したい場合は

    */1 * * * *
    

    とすることで1分おきに起動させられます。

    これで、Logの出力とローテーションでの削除が実装できました!

    おわりに

     今更ですが自分の環境でのことを書いたので環境次第では動作が上手くいかない等あるかもしれません。それでも誰かの助けになっていれば幸いです。
     コードの細かいところとか全然理解できてないことだらけですがこれからも実際に使ったことを備忘録兼、復習として書いていきたいと思います。ここまでご覧いただきありがとうございました。
     さて、1行前に言ったことは忘れて、次は息抜き的なことを書こうかな(。・ ω<)ゞ

    記事を共有