67
Javaのログ出力: 道具と考え方 2015-10-14 JJUGナイトセミナー ハッシュタグ: #jjug 宮川 拓

Javaのログ出力: 道具と考え方

Embed Size (px)

Citation preview

Page 1: Javaのログ出力: 道具と考え方

Javaのログ出力: 道具と考え方

2015-10-14 JJUGナイトセミナー

ハッシュタグ: #jjug

宮川 拓

Page 2: Javaのログ出力: 道具と考え方

@miyakawa_taku

JJUG幹事

SI屋で賃労働

尾上部屋の里山関のファンです

オレオレJVM言語Kinkを作っています

https://bitbucket.org/kink/kink

自己紹介 #jjug

2/67

Page 3: Javaのログ出力: 道具と考え方

#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

Page 4: Javaのログ出力: 道具と考え方

なんで「ログ」って言うの?

#jjug

4/67

Page 5: Javaのログ出力: 道具と考え方

なんで「ログ」って言うの? #jjug

log = 丸太

5/67

Page 6: Javaのログ出力: 道具と考え方

なんで「ログ」って言うの? #jjug

log = 船の速度標

丸太 (log) を引っ張る綱の張りで

船の速度を測った

6/67

Page 7: Javaのログ出力: 道具と考え方

なんで「ログ」って言うの? #jjug

logbook = 航海日誌

Photo by vxla, Licensed as CC BY 2.0, https://www.flickr.com/photos/vxla/5779530912/

logで測った速度や方向などを

帳面 (logbook) につける

7/67

Page 8: Javaのログ出力: 道具と考え方

ログファイル

= システムの航海日誌!

#jjug

8/67

Page 9: Javaのログ出力: 道具と考え方

セッション内容 #jjug

なぜログ?

ログの道具

Javaのログ

9/67

Page 10: Javaのログ出力: 道具と考え方

そもそも

何のためにログを出すの?

#jjug

10/67

Page 11: Javaのログ出力: 道具と考え方

ログの主な目的 #jjug

不具合解析のため

来るべき故障の際に、

原因となった不具合が突き止められるようにするため、

システム稼働時の内部状態を記録する

※本セッションで主に扱う

監査のため

認証・入出金・個人情報の利用など、

残しておく必要のあるイベントの発生を記録する

11/67

Page 12: Javaのログ出力: 道具と考え方

テストが完璧なら/デバッガがあれば

ログがなくても不具合解析できる?

#jjug

12/67

Page 13: Javaのログ出力: 道具と考え方

#jjugvs テスト

テストとログは相補的な関係

テストの領分

個別具体的な要件について不具合がないことを、

ある程度保証する

個別を積み重ねて全体に近づく

ログの領分

しかし完璧なテストはなく、たぶん故障は起きる。

起きた故障を解析するためにはログが必要

ログは、開発・テスト時にシステムの動きを把握

するのにも有用

13/67

Page 14: Javaのログ出力: 道具と考え方

#jjugvs デバッガ

デバッガとログも相補的な関係

デバッガの領分

動作中のシステムの状態が閲覧・変更できる

ログの領分

過去のシステムの状態が閲覧できる

本番システムで利用できる

再現させるための条件が厳しかったり、不明だっ

たりする故障について、故障発生前後の状況が

追跡できる

14/67

Page 15: Javaのログ出力: 道具と考え方

ログの目的

まとめ

#jjug

15/67

Page 16: Javaのログ出力: 道具と考え方

#jjugログの目的 まとめ

ログの目的は不具合解析/監査

システムの過去の状態が分かるのが素敵

不具合解析の手段として、テストや

デバッガとは相補的な関係

16/67

Page 17: Javaのログ出力: 道具と考え方

セッション内容 #jjug

なぜログ?

ログの道具

Javaのログ

17/67

Page 18: Javaのログ出力: 道具と考え方

#jjug問題設定

ログを出すためには

どんな道具を使えばよいのでしょうか?

18/67

Page 19: Javaのログ出力: 道具と考え方

#jjug標準エラーにログを出す?

Unix由来の慣習では、ログは一般に

標準エラー出力に書き出されます

例:

System.err.println(

"ひらく夢などあるじゃなし");

しかし本格的なプログラムでは、

標準エラーへの直接出力は力不足です

19/67

Page 20: Javaのログ出力: 道具と考え方

ログの道具に必要な特性

#jjug

20/67

Page 21: Javaのログ出力: 道具と考え方

#jjugログの道具に必要な特性

ログの各行がいつ、どこで出力されたか、

文脈が分かるようにできる必要がある

いつ

どこで

日付時刻

ファイル/行/クラス/メソッド

スレッド

HTTPセッション/リクエスト

これら文脈が分かると不具合解析がはかどります

21/67

Page 22: Javaのログ出力: 道具と考え方

#jjugログの道具に必要な特性

一部のログ出力が抑止できる必要がある

開発環境

DEBUG doGet開始

INFO 注文#42を閲覧

DEBUG SELECT xxx FROM ...

WARN Bobは注文#42を閲覧不可

DEBUG doGet終了

本番環境

INFO 注文#42を閲覧

WARN Bobは注文#42を閲覧不可

ディスク領域節約・性能確保のため、

重要でないログの出力を抑止することがあります

22/67

Page 23: Javaのログ出力: 道具と考え方

#jjugログの道具に必要な特性

その他

ログ出力先が簡単に切り替えられること

ログローテーションできること

複数スレッドからログが出力できること

ディスクまで確実に書き込むこと

速いこと

例外を投げないこと

……

23/67

Page 24: Javaのログ出力: 道具と考え方

#jjugログの道具

ログの道具の諸特性を提供するため、

多くの言語は専用ライブラリを

用意しています

Ruby logging添付ライブラリ

Python loggingモジュール

Java

Log4j, java.util.logging,

Commons Logging, SLF4J,

Logback, JBoss Logging, Log4j2,

....

24/67

Page 25: Javaのログ出力: 道具と考え方

セッション内容 #jjug

なぜログ?

ログの道具

Javaのログ

25/67

Page 26: Javaのログ出力: 道具と考え方

#jjug問題

次の中で

役割が異るライブラリはどれでしょう?

A) java.util.logging

B) Log4j

C) Logback

D) SLF4J

26/67

Page 27: Javaのログ出力: 道具と考え方

ログファサードライブラリ

ログ出力ライブラリ

#jjug解答

SLF4Jは他のライブラリにログ出力を

委譲するログファサードライブラリです

A) java.util.logging

B) Log4j

C) Logback

D) SLF4J

27/67

Page 28: Javaのログ出力: 道具と考え方

ログ関連ライブラリの分類 #jjug

ログファサードライブラリ

ログ出力ライブラリ

Log4j

java.util.logging

Logback

Log4j2

Commons Logging

SLF4J

JBoss Logging

28/67

Page 29: Javaのログ出力: 道具と考え方

ログ出力の階層 #jjug

アプリケーション

ログファサードライブラリ

ログ出力ライブラリ

ファイル/コンソール/ . . .

どう考えても

本質的には要らないもの

なぜこんなことになったのか

それを知るには歴史を紐解く必要があります

29/67

Page 30: Javaのログ出力: 道具と考え方

Javaのログライブラリの歴史

#jjug

30/67

Page 31: Javaのログ出力: 道具と考え方

Javaのログライブラリの歴史 #jjug

~1999 前史時代

31/67

Page 32: Javaのログ出力: 道具と考え方

前史時代 #jjug

Apache Tomcat 3.0(最初期リリース)

// org.apache.tomcat.core.ServletContextFacade

public void log(String msg) {

System.err.println(msg);

}

32/67

Page 33: Javaのログ出力: 道具と考え方

Javaのログライブラリの歴史 #jjug

~1999

1999

前史時代

Log4jの登場

33/67

Page 34: Javaのログ出力: 道具と考え方

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

Page 35: Javaのログ出力: 道具と考え方

#jjugLog4jはすごい!

ログライブラリの事実上の標準に

しばらくはAvalon Logkitも有力だった

後続のログ関連ライブラリはだいたい

Log4jの機能を踏襲

階層化されたロガー

ログ書き込みを行うアペンダ

MDCによる文脈情報の保持

35/67

Page 36: Javaのログ出力: 道具と考え方

#jjug階層化されたロガー

ロガーはドット区切りのロガー名で

階層化されています

ふつうはログ書き出し元のクラス名を

そのままロガー名にします

基本的に親の設定を継承します

36/67

Page 37: Javaのログ出力: 道具と考え方

階層化されたロガー #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

Page 38: Javaのログ出力: 道具と考え方

#jjugMDC

実行時文脈の値を入れておく

スレッドローカルなHashMap

ログ行の一部として出力できます

有用な実行時文脈:

セッションID, リクエストID

テスト名

38/67

Page 39: Javaのログ出力: 道具と考え方

#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

Page 40: Javaのログ出力: 道具と考え方

Javaのログライブラリの歴史 #jjug

~1999

1999

2000

前史時代

Log4jの登場

java.util.logging規格化開始

40/67

Page 41: Javaのログ出力: 道具と考え方

#jjugjava.util.logging

Log4jを参考にJSR47として規格化

→ 2002年のJ2SE 1.4に採用

41/67

Page 42: Javaのログ出力: 道具と考え方

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

Page 43: Javaのログ出力: 道具と考え方

#jjugjava.util.logging

Log4jの牙城を崩すには至らず

ログレベルが謎

SEVERE,WARNING,INFO,CONFIG,FINE,FINER,FINEST

デフォルトの書式が扱いづらい

ハンドラ(=アペンダ)の実装が不足

Java 1.3以前で使えない

JVM全体で1つの設定しか持てない

サーブレットコンテナ下で、各アプリケーションが

個別のログ設定を持つための組み込みの方法がない

43/67

Page 44: Javaのログ出力: 道具と考え方

Javaのログライブラリの歴史 #jjug

~1999

1999

2000

2001

前史時代

Log4jの登場

java.util.logging規格化開始

Commons Loggingの登場

44/67

Page 45: Javaのログ出力: 道具と考え方

#jjugCommons Loggingの登場

Commons HttpClientから派生した

ログファサードライブラリ

HttpClientのような便利ライブラリが、

特定のログ実装に依存するのはちょっと、

という理由ではじまった

Log4jやjava.util.loggingを切り替えて

使えるようになる(はずだった)

45/67

Page 46: Javaのログ出力: 道具と考え方

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

Page 47: Javaのログ出力: 道具と考え方

#jjugCommons Logging

多くのライブラリ・フレームワークが

採用しています

でも正直イケていません

ログ実装の選択方法がぶっ壊れている

ためです

47/67

Page 48: Javaのログ出力: 道具と考え方

#jjugCommons Logging

実現したかったこと(のひとつ)

APサーバ共有ライブラリ

アプリA

アプリB

Commons Logging

ログ via Log4j

ログ via java.util.logging

ログ実装の決定方法

Context Classloaderから始まって、

親・祖先のクラスローダ内でアダプタクラスを探索

最初にみつかったアダプタを使う

48/67

Page 49: Javaのログ出力: 道具と考え方

#jjugCommons Logging

実際にはうまく行っていません

Java EEコンテナ

OSGiコンテナ

クラスローダ不一致による

NoClassDefFoundErrorの頻発

→ アドホックな try-catchで対処

動かすための設定が非直感的

かつAPサーバごとに異なる

Context Classloaderを使っておらず親ク

ラスローダへの委譲もない

→ そもそも動かない

動的探索という構想に無理がありました

49/67

Page 50: Javaのログ出力: 道具と考え方

Javaのログライブラリの歴史 #jjug

~1999

1999

2000

2001

2005

前史時代

Log4jの登場

java.util.logging規格化開始

Commons Loggingの登場

SLF4J / Logback

50/67

Page 51: Javaのログ出力: 道具と考え方

#jjugSLF4J / Logback

Log4jの開発者Ceki Gülcüが、

開発の遅延に愛想を尽かして立ち上げた

プロジェクト

SLF4J

Logback

ログファサードライブラリ

ログ出力ライブラリ

SLF4Jと組み合わせて使う前提

2015年現在のデファクトスタンダード

51/67

Page 52: Javaのログ出力: 道具と考え方

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

Page 53: Javaのログ出力: 道具と考え方

#jjugSLF4J

特徴

クラス実体の差し替えによる

アダプタの静的なバインディング

他のログファサード/ログ実装に

流し込まれるログを乗っ取る仕組み

53/67

Page 54: Javaのログ出力: 道具と考え方

#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

Page 55: Javaのログ出力: 道具と考え方

#jjugSLF4J

静的バインディングの中身

LoggerFactory

アプリ

StaticLoggerBinder

getLogger()

getSingleton()

バインディングの JAR内に

同名のクラスがそれぞれ存在

slf4j-api-*.jarに存在

55/67

Page 56: Javaのログ出力: 道具と考え方

#jjugSLF4J

多くのライブラリはSLF4Jではなく

Log4jやCommons Loggingなどを

叩いています

ログ設定を統合するためには、これらを

SLF4Jに横取りする必要があります

56/67

Page 57: Javaのログ出力: 道具と考え方

#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

Page 58: Javaのログ出力: 道具と考え方

#jjugLogback

独自のロガーインタフェースを持たず、

SLF4J経由で呼び出します

提供する機能

マーカー

ログ行のラベル的なもの

マーカー「auth」がついてるログはauth.logに

出す、みたいなことができます

設定のリロード

アプリごとにログ出力先を分ける機能

58/67

Page 59: Javaのログ出力: 道具と考え方

Javaのログライブラリの歴史 #jjug

~1999

1999

2000

2001

2005

前史時代

Log4jの登場

java.util.logging規格化開始

Commons Loggingの登場

SLF4J / Logback

2014 Log4j2

59/67

Page 60: Javaのログ出力: 道具と考え方

#jjugLog4j2

Log4j 1.2系と互換性のない新しい実装

機能・構成はLogbackに似ています

60/67

Page 61: Javaのログ出力: 道具と考え方

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

Page 62: Javaのログ出力: 道具と考え方

結局なにを使えばいいの?

#jjug

62/67

Page 63: Javaのログ出力: 道具と考え方

#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

Page 64: Javaのログ出力: 道具と考え方

#jjug使うべきログ関連ライブラリ

アプリケーション

基盤・ミドルウェアの制約しだい

制約がなければSLF4J+Logbackが無難

Log4j(1/2)や java.util.loggingを直接叩く

のも可ですが、あえてSLF4Jを避ける

理由はなさそう

64/67

Page 65: Javaのログ出力: 道具と考え方

Javaのログ

まとめ

#jjug

65/67

Page 66: Javaのログ出力: 道具と考え方

#jjugJavaのログ まとめ

Log4jがJavaのログの道具立てを

作った

ロガー/アペンダ/MDC...

ライブラリの依存性の都合から

ログファサードが生まれた

SLF4Jを使っておけばとりあえずOK

66/67

Page 67: Javaのログ出力: 道具と考え方

セッション内容 #jjug

なぜログ?

ログの道具

Javaのログ

67/67