Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

29,544 views

Published on

Concepts and tools of logging in Java.

Javaにおけるログ出力の考え方と道具について説明.

CC Attribution Licenseの元に公開します.

Published in: Technology
  • Be the first to comment

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

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

×