Upload
taku-miyakawa
View
18.090
Download
0
Embed Size (px)
Citation preview
Javaのログ出力: 道具と考え方
2015-10-14 JJUGナイトセミナー
ハッシュタグ: #jjug
宮川 拓
@miyakawa_taku
JJUG幹事
SI屋で賃労働
尾上部屋の里山関のファンです
オレオレJVM言語Kinkを作っています
https://bitbucket.org/kink/kink
自己紹介 #jjug
2/67
#jjugログとは!
例:$ kink -Vdebug -e ''
2015-10-04 15:58:29 [main] DEBUG BoxingValues - use box proto listener
org.kink_lang.kink.internal.eval.VarAssignEvaluator$VarAssignListener@3af49f1c
2015-10-04 15:58:29 [main] DEBUG BoxingValues - use box proto listener
org.kink_lang.kink.internal.eval.ArgsPassingEvaluator$ListAssignListener@1c20c684
2015-10-04 15:58:29 [main] DEBUG BoxingValues - use box proto listener
org.kink_lang.kink.internal.eval.ThenUtils$BoolThenListener@1218025c
2015-10-04 15:58:29 [main] DEBUG BoxingValues - Setup prototype for java.lang.String
2015-10-04 15:58:29 [main] DEBUG Modules - Load module _enhance/java/lang/Object
from org.kink_lang.kink.internal.box.ObjectEnhancer@5e8c92f4
2015-10-04 15:58:29 [main] INFO Definer -
org.kink_lang.kink.internal.define.frequency_threshold=128
...
システムの状態を
後から見られるように出力したテキスト
3/67
なんで「ログ」って言うの?
#jjug
4/67
なんで「ログ」って言うの? #jjug
log = 丸太
5/67
なんで「ログ」って言うの? #jjug
log = 船の速度標
丸太 (log) を引っ張る綱の張りで
船の速度を測った
6/67
なんで「ログ」って言うの? #jjug
logbook = 航海日誌
Photo by vxla, Licensed as CC BY 2.0, https://www.flickr.com/photos/vxla/5779530912/
logで測った速度や方向などを
帳面 (logbook) につける
7/67
ログファイル
= システムの航海日誌!
#jjug
8/67
セッション内容 #jjug
なぜログ?
ログの道具
Javaのログ
9/67
そもそも
何のためにログを出すの?
#jjug
10/67
ログの主な目的 #jjug
不具合解析のため
来るべき故障の際に、
原因となった不具合が突き止められるようにするため、
システム稼働時の内部状態を記録する
※本セッションで主に扱う
監査のため
認証・入出金・個人情報の利用など、
残しておく必要のあるイベントの発生を記録する
11/67
テストが完璧なら/デバッガがあれば
ログがなくても不具合解析できる?
#jjug
12/67
#jjugvs テスト
テストとログは相補的な関係
テストの領分
個別具体的な要件について不具合がないことを、
ある程度保証する
個別を積み重ねて全体に近づく
ログの領分
しかし完璧なテストはなく、たぶん故障は起きる。
起きた故障を解析するためにはログが必要
ログは、開発・テスト時にシステムの動きを把握
するのにも有用
13/67
#jjugvs デバッガ
デバッガとログも相補的な関係
デバッガの領分
動作中のシステムの状態が閲覧・変更できる
ログの領分
過去のシステムの状態が閲覧できる
本番システムで利用できる
再現させるための条件が厳しかったり、不明だっ
たりする故障について、故障発生前後の状況が
追跡できる
14/67
ログの目的
まとめ
#jjug
15/67
#jjugログの目的 まとめ
ログの目的は不具合解析/監査
システムの過去の状態が分かるのが素敵
不具合解析の手段として、テストや
デバッガとは相補的な関係
16/67
セッション内容 #jjug
なぜログ?
ログの道具
Javaのログ
17/67
#jjug問題設定
ログを出すためには
どんな道具を使えばよいのでしょうか?
18/67
#jjug標準エラーにログを出す?
Unix由来の慣習では、ログは一般に
標準エラー出力に書き出されます
例:
System.err.println(
"ひらく夢などあるじゃなし");
しかし本格的なプログラムでは、
標準エラーへの直接出力は力不足です
19/67
ログの道具に必要な特性
#jjug
20/67
#jjugログの道具に必要な特性
ログの各行がいつ、どこで出力されたか、
文脈が分かるようにできる必要がある
いつ
どこで
日付時刻
ファイル/行/クラス/メソッド
スレッド
HTTPセッション/リクエスト
これら文脈が分かると不具合解析がはかどります
21/67
#jjugログの道具に必要な特性
一部のログ出力が抑止できる必要がある
開発環境
DEBUG doGet開始
INFO 注文#42を閲覧
DEBUG SELECT xxx FROM ...
WARN Bobは注文#42を閲覧不可
DEBUG doGet終了
本番環境
INFO 注文#42を閲覧
WARN Bobは注文#42を閲覧不可
ディスク領域節約・性能確保のため、
重要でないログの出力を抑止することがあります
22/67
#jjugログの道具に必要な特性
その他
ログ出力先が簡単に切り替えられること
ログローテーションできること
複数スレッドからログが出力できること
ディスクまで確実に書き込むこと
速いこと
例外を投げないこと
……
23/67
#jjugログの道具
ログの道具の諸特性を提供するため、
多くの言語は専用ライブラリを
用意しています
Ruby logging添付ライブラリ
Python loggingモジュール
Java
Log4j, java.util.logging,
Commons Logging, SLF4J,
Logback, JBoss Logging, Log4j2,
....
24/67
セッション内容 #jjug
なぜログ?
ログの道具
Javaのログ
25/67
#jjug問題
次の中で
役割が異るライブラリはどれでしょう?
A) java.util.logging
B) Log4j
C) Logback
D) SLF4J
26/67
ログファサードライブラリ
ログ出力ライブラリ
#jjug解答
SLF4Jは他のライブラリにログ出力を
委譲するログファサードライブラリです
A) java.util.logging
B) Log4j
C) Logback
D) SLF4J
27/67
ログ関連ライブラリの分類 #jjug
ログファサードライブラリ
ログ出力ライブラリ
Log4j
java.util.logging
Logback
Log4j2
Commons Logging
SLF4J
JBoss Logging
28/67
ログ出力の階層 #jjug
アプリケーション
ログファサードライブラリ
ログ出力ライブラリ
ファイル/コンソール/ . . .
どう考えても
本質的には要らないもの
なぜこんなことになったのか
それを知るには歴史を紐解く必要があります
29/67
Javaのログライブラリの歴史
#jjug
30/67
Javaのログライブラリの歴史 #jjug
~1999 前史時代
31/67
前史時代 #jjug
Apache Tomcat 3.0(最初期リリース)
// org.apache.tomcat.core.ServletContextFacade
public void log(String msg) {
System.err.println(msg);
}
32/67
Javaのログライブラリの歴史 #jjug
~1999
1999
前史時代
Log4jの登場
33/67
Log4jの登場 #jjug
import org.apache.log4j.Logger;
public class EchoServlet extends HttpServlet {
private Logger logger = Logger.getLogger(getClass());
protected void doGet(
HttpServletRequest req, HttpServletResponse resp) {
String text = req.getParameter("text");
this.logger.info("テキスト: " + text);
...
}
}
Log4jを使うサーブレットの例:
34/67
#jjugLog4jはすごい!
ログライブラリの事実上の標準に
しばらくはAvalon Logkitも有力だった
後続のログ関連ライブラリはだいたい
Log4jの機能を踏襲
階層化されたロガー
ログ書き込みを行うアペンダ
MDCによる文脈情報の保持
35/67
#jjug階層化されたロガー
ロガーはドット区切りのロガー名で
階層化されています
ふつうはログ書き出し元のクラス名を
そのままロガー名にします
基本的に親の設定を継承します
36/67
階層化されたロガー #jjug
ロガー org.kink_lang
レベル= WARN
アペンダ=ConsoleAppender
org.kink_lang.kink
レベル= DEBUG
アペンダ=ConsoleAppender(継承)
org.kink_lang.kink.Value
レベル=DEBUG(継承)
アペンダ=ConsoleAppender(継承)
+ RollingFileAppender
WARN, ERROR, FATALの
ログをコンソールに出す
加えてDEBUG, INFOの
ログもコンソールに出す
コンソールに加えて
ファイルにもログを出す
37/67
#jjugMDC
実行時文脈の値を入れておく
スレッドローカルなHashMap
ログ行の一部として出力できます
有用な実行時文脈:
セッションID, リクエストID
テスト名
38/67
#jjugMDC
例: リクエストIDを設定するフィルタ
import org.apache.log4j.MDC;
public class PutRequestIdFilter implements Filter {
public void doFilter (
ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
String reqId = UUID.randomUUID().toString();
MDC.put("request", reqId);
try {
chain.doFilter(req, resp);
} finally {
MDC.remove("request");
}
}
...
}
39/67
Javaのログライブラリの歴史 #jjug
~1999
1999
2000
前史時代
Log4jの登場
java.util.logging規格化開始
40/67
#jjugjava.util.logging
Log4jを参考にJSR47として規格化
→ 2002年のJ2SE 1.4に採用
41/67
java.util.logging #jjug
Log4jとだいたいおなじ!
import java.util.logging.Logger;
public class EchoServlet extends HttpServlet {
private Logger logger
= Logger.getLogger(getClass().getName());
protected void doGet(
HttpServletRequest req, HttpServletResponse resp) {
String text = req.getParameter("text");
this.logger.info("テキスト: " + text);
...
}
} Log4jとの差分
42/67
#jjugjava.util.logging
Log4jの牙城を崩すには至らず
ログレベルが謎
SEVERE,WARNING,INFO,CONFIG,FINE,FINER,FINEST
デフォルトの書式が扱いづらい
ハンドラ(=アペンダ)の実装が不足
Java 1.3以前で使えない
JVM全体で1つの設定しか持てない
サーブレットコンテナ下で、各アプリケーションが
個別のログ設定を持つための組み込みの方法がない
43/67
Javaのログライブラリの歴史 #jjug
~1999
1999
2000
2001
前史時代
Log4jの登場
java.util.logging規格化開始
Commons Loggingの登場
44/67
#jjugCommons Loggingの登場
Commons HttpClientから派生した
ログファサードライブラリ
HttpClientのような便利ライブラリが、
特定のログ実装に依存するのはちょっと、
という理由ではじまった
Log4jやjava.util.loggingを切り替えて
使えるようになる(はずだった)
45/67
Commons Logging #jjug
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class EchoServlet extends HttpServlet {
private Log logger = LogFactory.getLog(getClass());
protected void doGet(
HttpServletRequest req, HttpServletResponse resp) {
String text = req.getParameter("text");
this.logger.info("テキスト: " + text);
...
}
}
Log4jとだいたいおなじ!
Log4jとの差分
46/67
#jjugCommons Logging
多くのライブラリ・フレームワークが
採用しています
でも正直イケていません
ログ実装の選択方法がぶっ壊れている
ためです
47/67
#jjugCommons Logging
実現したかったこと(のひとつ)
APサーバ共有ライブラリ
アプリA
アプリB
Commons Logging
ログ via Log4j
ログ via java.util.logging
ログ実装の決定方法
Context Classloaderから始まって、
親・祖先のクラスローダ内でアダプタクラスを探索
最初にみつかったアダプタを使う
48/67
#jjugCommons Logging
実際にはうまく行っていません
Java EEコンテナ
OSGiコンテナ
クラスローダ不一致による
NoClassDefFoundErrorの頻発
→ アドホックな try-catchで対処
動かすための設定が非直感的
かつAPサーバごとに異なる
Context Classloaderを使っておらず親ク
ラスローダへの委譲もない
→ そもそも動かない
動的探索という構想に無理がありました
49/67
Javaのログライブラリの歴史 #jjug
~1999
1999
2000
2001
2005
前史時代
Log4jの登場
java.util.logging規格化開始
Commons Loggingの登場
SLF4J / Logback
50/67
#jjugSLF4J / Logback
Log4jの開発者Ceki Gülcüが、
開発の遅延に愛想を尽かして立ち上げた
プロジェクト
SLF4J
Logback
ログファサードライブラリ
ログ出力ライブラリ
SLF4Jと組み合わせて使う前提
2015年現在のデファクトスタンダード
51/67
SLF4J / Logback #jjug
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EchoServlet extends HttpServlet {
private Logger logger = LoggerFactory.getLogger(getClass());
protected void doGet(
HttpServletRequest req, HttpServletResponse resp) {
String text = req.getParameter("text");
this.logger.info("テキスト: {}", text);
...
}
}
やっぱりLog4jとだいたいおなじ
Log4jとの差分
52/67
#jjugSLF4J
特徴
クラス実体の差し替えによる
アダプタの静的なバインディング
他のログファサード/ログ実装に
流し込まれるログを乗っ取る仕組み
53/67
#jjugSLF4J
必要なJAR
slf4j-api-*.jarAPI(必須)
ログ実装への
バインディング
(どれか一個) logback-classic-*.jar (Logback)
slf4j-log4j12-*.jar (Log4j)
slf4j-jdk14-*.jar (java.util.logging)
log4j-slf4j-impl-*.jar (Log4j2)
両方のJARを
同一のクラスローダが参照する場所に配置します
54/67
#jjugSLF4J
静的バインディングの中身
LoggerFactory
アプリ
StaticLoggerBinder
getLogger()
getSingleton()
バインディングの JAR内に
同名のクラスがそれぞれ存在
slf4j-api-*.jarに存在
55/67
#jjugSLF4J
多くのライブラリはSLF4Jではなく
Log4jやCommons Loggingなどを
叩いています
ログ設定を統合するためには、これらを
SLF4Jに横取りする必要があります
56/67
#jjugSLF4J
ログの横取り
Log4j
java.util.logging
Commons
Logging
log4j-over-slf4j-*.jar
Log4jと同名のクラスを提供
実際にはSLF4Jに流し込む
jcl-over-slf4j-*.jar
Commons Loggingと同名のクラスを提供
実際にはSLF4Jに流し込む
jul-to-slf4j-*.jar
SLF4Jに流し込むハンドラを提供
slf4j-api-*.jarと同じ場所に配置します
57/67
#jjugLogback
独自のロガーインタフェースを持たず、
SLF4J経由で呼び出します
提供する機能
マーカー
ログ行のラベル的なもの
マーカー「auth」がついてるログはauth.logに
出す、みたいなことができます
設定のリロード
アプリごとにログ出力先を分ける機能
58/67
Javaのログライブラリの歴史 #jjug
~1999
1999
2000
2001
2005
前史時代
Log4jの登場
java.util.logging規格化開始
Commons Loggingの登場
SLF4J / Logback
2014 Log4j2
59/67
#jjugLog4j2
Log4j 1.2系と互換性のない新しい実装
機能・構成はLogbackに似ています
60/67
Log4j2 #jjug
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class EchoServlet extends HttpServlet {
private Logger logger = LogManager.getLogger(getClass());
protected void doGet(
HttpServletRequest req, HttpServletResponse resp) {
String text = req.getParameter("text");
this.logger.info("テキスト: {}", text);
...
}
}
早い話がLog4jです
Log4jとの差分
61/67
結局なにを使えばいいの?
#jjug
62/67
#jjug使うべきログ関連ライブラリ
共有ライブラリ
SLF4Jにログを出す
compileスコープでは
ログ実装ライブラリに依存しない
テストでは好みのログ実装を使う
Gradle dependencies:
compile 'org.slf4j:slf4j-api:1.7.12'
testCompile 'ch.qos.logback:logback-core:1.1.3'
testCompile 'ch.qos.logback:logback-classic:1.1.3'
63/67
#jjug使うべきログ関連ライブラリ
アプリケーション
基盤・ミドルウェアの制約しだい
制約がなければSLF4J+Logbackが無難
Log4j(1/2)や java.util.loggingを直接叩く
のも可ですが、あえてSLF4Jを避ける
理由はなさそう
64/67
Javaのログ
まとめ
#jjug
65/67
#jjugJavaのログ まとめ
Log4jがJavaのログの道具立てを
作った
ロガー/アペンダ/MDC...
ライブラリの依存性の都合から
ログファサードが生まれた
SLF4Jを使っておけばとりあえずOK
66/67
セッション内容 #jjug
なぜログ?
ログの道具
Javaのログ
67/67