[Rails]エラーの内容をログに出力したい ~ログレベルとloggerについて。また、例外処理との合わせ技も
はじめに - この記事の内容はどんなときに役立つか
アプリケーション上に表示されるエラー文が抽象的すぎてどうしてエラーが起きているのかわからない、しかもログを見てもエラー内容がわからない、なんてことはないでしょうか?
実装者にすぐ確認ができれば良いのですが実装者がすでにチームを去っているなんてことは度々ありますし、そもそもこういうエラーは実装者が想定していなかったりします。テストちゃんと書けよとか思ったりしますが
私が仕事上でそういう事案にぶつかった際にログに出力する方法を教えていただいて解決できたので、その方法を残しておきます
前提知識 - ログレベルについて
Rubyのログにはログレベルがあります
ログレベル(数字) | ログレベル | 意味 |
---|---|---|
5 | unknown | 常に記録されるべき不明なエラー |
4 | fatal | プログラムをクラッシュさせるような制御不可能なエラー |
3 | error | 制御可能なエラー |
2 | warn | 警告 |
1 | info | 一般的な情報 |
0 | debug | 低レベルの情報 |
ログは設定しているログレベル以上のものが出力されます
ex) warn
(2)で設定すれば、warn
(2),error
(3),fatal
(4),unknown
(5)のものがログに出力されます
ログレベルの設定方法(2023/02/05追記)
ログレベルはconfig/environments
配下のファイルで、環境ごとに設定することができます。
1つ例を出しましょう
config.log_level = :info # 数値で表せば1
このような記述があれば、development
環境のログにはログレベルはinfo
(1)以上のものが出力されます。
ログレベルの設定はconfig.log_level = :ログレベル
で行うことが可能です
また、デフォルトの設定はdebug
(0)となっており、全てのログが出力されます
加えて、config/application.rb
にconfig.log_level = :ログレベル
と記述すれば、環境を問わず ログレベルが設定されます。
config.log_level = :warn # 数値で表せば2
このような記述をすれば、全ての環境でログレベル2以上のものが出力されるように設定されます。
また、Rails.logger.level
を使うことでもログレベルの設定ができますが、こちらは私の理解が及んでいないため、正しく理解でき次第追記します。
ログに情報を出力する(2023/02/05追記)
ログに出力したい情報を出力するには、logger
を使います
def index
logger.info("indexメソッドだよ")
# 処理
end
このように記述すれば、hoge_controller.rb
のindex
メソッドを実行した際に、ログレベルinfo
(1)でindexメソッドだよ
と出力されます。
I,[yyyy-mm-dd ~省略~ ]INFO -- :indexメソッドだよ
のような出力になります
ログレベルと出力されるログについて練習しましょ〜(2023/02/05追記)
以下の場合、development環境のログにhoge
fuga
piyo
がそれぞれ出力されるか考えてみましょう。
config.log_level = :error # 数字で表すと3
def index
logger.info("hoge")
logger.fatal("fuga")
logger.error("piyo")
end
解説と答え
この問題でのdevelopment環境のログはログレベルがerror
(3) 以上 の情報が出力されます。
hoge
はログレベルinfo
(1)の情報です => 出力されません
fuga
はログレベルfatal
(4)の情報です => 出力されます
piyo
はログレベルerror
(3)の情報です => 出力されます
したがって、以下のような出力になります
F,[yyyy-mm-dd ~省略~ ]FATAL -- :fuga
E,[yyyy-mm-dd ~省略~ ]ERROR -- :piyo
本題に戻ります
ローカル環境のログレベルは大抵debug
(0)に設定されている = 全ての情報がログに出力される状態になっているかと思います。
しかし、本番環境のログレベルはinfo
(1)に設定されていたりします。そのためバグ調査をしようにも調べようがない。。。なんてことも起き得ます
そんなわけで、エラー内容を強制的に出力させましょう
ログレベルの設定がinfo
(1)のアプリケーションで、新しい情報を登録する処理中によくわからないエラーが起きたと想定しましょう
アプリケーション上でもログでもわからないのでソースコードを見にいきます
def create
# 処理
rescue
flash.now[:alert] = '不正だよ'
render(:new)
end
例外処理が行われていることしかわかりません。まあ場合によってはバリデーションエラーかな?みたいな推測は立ちますが、推測の域を出ないかと思います。
こんなフラッシュ出されても意味ねえだろと殺意を覚える方もいらっしゃるかもしれませんが、まあそこはユーザーに見せられないのかもしれないと許しましょう。
はい、そんなわけで(?)ログにエラー内容を出力されます
def create
# 処理
rescue => e
logger.error(e.message) # ログレベル3で出力する
flash.now[:alert] = '不正だよ'
render(:new)
end
このように記述することで、ログレベルerror
(3)でエラー内容を出すことができます
このアプリケーションのログレベルはinfo
(1)に設定されていますので、無事にエラー内容がログに出力されることになります
E,[yyyy-mm-dd ~省略~ ]ERROR -- :Userを入力してください。
このようなログが出力されれば、Userの入力に問題があることがわかりますね
仕組み
rescue => e
この行でエラー内容をe
に格納します
logger.error(e.message)
続いて、こちらの行でe
に格納されたエラー内容をログに出力します
分解していきましょう
logger.ログレベル(hoge)
で指定のログレベルでhoge
を出力します
e.message
はe
に格納されているエラーメッセージです
つまり、この行での処理は
ログレベルerror
(3)でエラーメッセージを出力してね
ということになります
まとめ
ログに関する基本的な知識と、例外処理に関する基本的な知識の合わせ技ですが、まあ使える場面はままあるかなと。
e.message
をアプリケーション上に出力することの方が多い気がせんこともないですが。。。
私は今回紹介した方法でエラー内容がわかり修正パッチのリリースができたので、私みたいな状況に陥ったよわよわエンジニア(私のなかーま!)が助かったらいいな〜ぐらいの気持ちで残しておきます。
Discussion
こうのさん、こんにちは。
ログレベルとログ出力メソッドの関係はたしかに最初は理解しづらいかもしれませんね。
いい着眼点だと思います👍
僕の登壇を聞いてこの投稿をされたと思うので、「こうするとさらにわかりやすくなりそう」というポイントを書いておきますね。
logger.error(e.message)
を呼ぶと、こんなふうにログ出力されます(またはされません)」というように、具体的な出力例を見せるとさらに理解しやすいちなみに、本題とは無関係ですが、controller内でrescueを書くのは、その多くがアンチパターンです。
業務エラーは例外ではなく、条件分岐で処理していく方がいいですね。
もちろん、既存のコードが全部こうなってたので、同じように書かざるを得なかった、ということもよくありますが・・・😅
詳しくは以下の記事をご確認ください〜。
Railsアプリケーションにおけるエラー処理(例外設計)の考え方 - Qiita
伊藤様、コメントありがとうございます。
また、先日のウェビナー非常に勉強になりました。ありがとうございました。
ありがとうございます。正直、私自身もちゃんと理解できているか不安が残っている箇所ですので、今後の自身のためにとも思い、記事にしました。
確かに、これがないと自身の環境ログレベルがどのようになっているのか確認できないですね。追記します。
なるほど。私自身もあまり出力例を見ていない状態ですので、自身が関わっているプロダクトで色々なケースを試し、追記しようと思います。
わかりやすさを追求するとはこういうことなのですね。実例を示していただきありがとうございます。こちらも教えていただいた例を参考に、追記させていただきます。
教えてくださりありがとうございます。私が関わっているプロダクトでこのような実装があったので、「このように実装していいんだ」と思っておりました。
添付してくださったリンク先の記事を読んで勉強させていただきます。
修正ありがとうございます!かなりわかりやすくなりました👍
そのうえでいくつかコメントさせてください。
数字よりシンボルの方が読みやすい
ログレベルの設定は数値でもシンボルでも大丈夫ですが、シンボルの方が可読性が高いと思います。その上で、数字も併記してあげるとより親切ですね。
Rubyのコメントはシャープ
// 処理
とありますが、Rubyの場合は# 処理
ですね。以下の記事も参考にしてみてください。
ログレベルを変更したときの有効範囲がおかしい?
この点ですが、「このメソッドのときだけ」というわけではなく、そのあともその変更が引き継がれませんか?
以下は僕のローカル環境で実験した結果です。
この点はもうちょっと深掘りが必要になりそうですね!
伊藤さま、さらなるご指摘ありがとうございます!
どちらでの表記の方が良いか悩んでいたのですが、併記すると良いのですね。修正します
全くもってご指摘のとおりです。おそらく過去に投稿した技術記事でも同様の記載をしてしまっていると思うので、過去のもの含めて修正します。
また、参考となる記事のリンクを添付してくださり、ありがとうございます
こちら、私の理解が及んでいませんでした。
不正確な情報となっているので、こちらに関する記載は一度削除して、正しく理解できてから再度追記します。