LogbackのFileAppender
Slf4jの代表的な実装ライブラリ、「Logback」には、FileAppender があります。
(実際に使うのは RollingFileAppender でしょう)
ログファイルを出力するからには、エンコーディングの設定があるはずです。(I/Oしてるところにエンコーディングあり!)
ググればすぐに出てきそうなものですが、意外にエンコーディングの設定なしのサンプルも多く、そのまま参考して抜けた状態で設定しちゃう、ってなこともあり得ます。
ということもあり、せっかくのjfluteの日記ということもあり、コードリーディングで探してみましょう。
Appenderの継承関係
継承関係はこのようになっています。
OutputStreamAppender △ | FileAppender △ | RollingFileAppender
Fileの上に OutputStreamAppender という概念がいるんですね。ファイルとは限らないじゃない!ってことですね。
どちらの Appender のコードを見ても、encoding とか charset とか出てきません。
encoderさんの登場
一方で、encoder というちょっと近い名前の属性を持っていることがわかります。
(in OutputStreamAppender)
encoderがどのように使われているのが辿ると...
// in OutputStreamAppender of logback protected void writeOut(E event) throws IOException { byte[] byteArray = this.encoder.encode(event); writeBytes(byteArray); } private void writeBytes(byte[] byteArray) throws IOException { if(byteArray == null || byteArray.length == 0) return; lock.lock(); try { this.outputStream.write(byteArray); if (immediateFlush) { this.outputStream.flush(); } } finally { lock.unlock(); } }
encoderさんがバイト配列に変換してます。そのバイト配列をoutputStreamにwriteしていますから、まさしくencoderさんが、出力されるログの文字列を、エンコーディングしてそうです。名前もしっくり来ますし。
encoderの継承関係
Encoderを追ってみましょう。ただのインターフェースです。
(もし、Eclipseであれば)
command + T で実装クラスを探しましょう。
Encoder △ | EncoderBase △ | LayoutWrappingEncoder △ | PatternLayoutEncoderBase △ | PatternLayoutEncoder
他にも枝分かれの実装クラスが出てきますが、怪しいところだけ切り出しています。
charsetの発見
この中のどこかに、encodingとかcharsetとか、それっぽいものがないでしょうか?
LayoutWrappingEncoderにいました。
// in LayoutWrappingEncoder of logback public void setCharset(Charset charset) { this.charset = charset; } ... private byte[] convertToBytes(String s) { if (charset == null) { return s.getBytes(); } else { return s.getBytes(charset); } }
String@getBytes()の引数で指定しています。
もう、これっぽいですね。
logback.xmlの設定
さあ、logback.xml だとどこでしょう?
(LastaFlute の Example の logback.xml を元に)
<!-- logback example --> <appender name="appfile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${log.file.basedir}/app_${domain.name}.log</File> <Append>true</Append> <encoder><pattern>${log.pattern}</pattern></encoder> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.file.basedir}/backup/app_${domain.name}${backup.date.suffix}.log</fileNamePattern> <maxHistory>${backup.max.history}</maxHistory> </rollingPolicy> </appender>
おお、encoder っているじゃないですか。
ネスト要素に pattern がいます。Encoderの階層のクラスの中の pattern を探すと、PatternLayoutEncoderBase にいます。普通に setter が用意されています。
ということは、charsetも同じような要領で、設定できるんじゃないかと考えます。試しに、UTF-8と入れてみましょう。
<!-- logback example --> <appender name="appfile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${log.file.basedir}/app_${domain.name}.log</File> <Append>true</Append> <encoder><charset>UTF-8</charset><pattern>${log.pattern}</pattern></encoder> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.file.basedir}/backup/app_${domain.name}${backup.date.suffix}.log</fileNamePattern> <maxHistory>${backup.max.history}</maxHistory> </rollingPolicy> </appender>
UnitTestで動かすなどして、実際にログファイルに出力してみましょう。UTF-8になっていますか?
...
いま、ほとんどの環境でデフォルトでUTF-8 で動くことが多いので、変わらないかもしれませんね。
であれば、一時的に SJIS とかに設定して、実際にログファイルを出力して、UTF-8として開いてみましょう。日本語が文字化けすればOK。さらに、SJISで開いて正常表示されればOK。エンコーディング指定が効いた証拠です。
実際には、ベタ打ちするのではなく、他の項目のように ${log.file.encoding} という感じで、変数に切り出すほうが良いでしょう。FileAppenderは何個も設定するでしょうから。(アプリ通知用ファイル、エラー用ファイルなど)
こちらの logback.xml を参考に:
=> logback.xml の Example in LastaFlute
ただ、環境によって切り替えるとか、時期によって切り替えるとか考えにくいので、値自体は properties 化する必要もなく、固定で UTF-8 で良いとは思います。
Encoderはそれでいいの?
さっき、他の枝分かれの Encoder がいて、厳密には、この路線の実装クラスが、利用しているクラスとは確定していないのですが、元々指定していた pattern もあるし、ってことが考えると、ほぼこの路線で考えてよいだろうと考えられます。実際に、枝分かれの EchoEncoder には、pattern も charset もいませんし、何よりも実際に設定して動きましたから。
ただ、"動いたから" だけじゃ不安ではあります。たまたま動いただけかもしれないし。なので、改めてここで追ってみてると...
OutputStreamAppender にて、encoder変数の代入箇所を探したら、new LayoutWrappingEncoder() が見つかります。
逆に LayoutWrappingEncoder の方から、(Eclipseなら) ctrl+shift+G して探せば、OutputStreamAppender が簡単に見つかります。
もちろん、さっきの時点でしっかり追って、それを確定させてから追っても良いでしょう。ただ、焦点を絞って読んでいるときは、ある程度は推測で進んで早く到達するやり方も大切で、「pattern も charset もある」、「そして実際に設定して試したら動く」というのを先に検証してゴールを見定めてから、詳細を探すほうが当たりが付けやすくなって、結果的に早いということもあります。
というのは、encoderの実体が、簡単に見つからない可能性も十分にあり、そこでハマって時間ロスの可能性もあるからです。もし最終的にもencoderの実体がわからなくても、実際に試して動いたことで、焦点を絞った検索で世の文献を探すことができて、このやり方で問題ないと業務的に判断できれば、別にそれでいいわけです。今の論点はencoderの実体を知ることじゃないので。
「推測・検証の方が気軽にできるのであれば、とっととやってしまったほうが良い」
というのも一つのコツです。
※一方で、encoderの実体探しでサクッと見つからないな...じゃあすぐに切り替えて推測・検証だ、ってのを、安定してできるのが一番ではありますが。
ConsoleAppenderでも
ConsoleAppenderでも、同じように設定ができます。
実際に、設定して SJIS に設定すると、UTF-8 のつもりで表示してるコンソールで文字化けします。
なので、Example の方では、ConsoleAppender にも同じように設定しています。
コード的には、ConsoleAppenderのコードを開けば、すぐにわかります。
開いた瞬間に目の前に入ってきた情報で、「あー、はいはい」となりますから。
LastaFluteユーザーの方へ
というわけで...見事に、LastaFlute の Example で抜けておりました。いまここで懺悔します。。。
なので...Example からスタートアップされた方、Example を参考に logback.xml を構築された方、ぜひ、エンコーディング指定の移行をオススメします。
こちらの変更履歴を参考に:
=> add log.file.encoding for FileAppender
=> also add log.file.encoding to ConsoleAppender
jfluteの知ってる限りのほとんどの現場では、サーバーが Linux ばかりなので!? 全く問題なく UTF-8 で出力されていますが、それもあくまで環境依存なので、明示的に指定する方が将来的に安心でしょう。
※例えば、Windows Server だったら、SJIS (MS932) に、なっちゃうかも!?
=> ファイル操作/デフォルトのファイル文字コードを確認する