[Java] 標準APIのLoggerを使用してログを外部ファイルに出力する。

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

実行ログをファイルに出力したくて調べてみるといくつかの方法が有りましたが、Javaの標準APIであるLoggerを使用してみたので備忘録として書いておきます。

環境

Mac OSX 10.10.5 Yosemite
Java 1.8.0_91

ログのレベル

ログ毎に要求レベルを設定できます。

Loggerメソッド 対応する要求レベル 出力される名称
-- Level.ALL --
Logger.finest() Level.FINEST 最も詳細
Logger.finer() Level.FINER 詳細
Logger.fine() Level.FINE 普通
Logger.config() Level.CONFIG 構成
Logger.info() Level.INFO 情報
Logger.warning() Level.WARNING 警告
Logger.severe() Level.SEVERE 重大

例えば、Level.CONFIGを設定するとログとして出力されるのは、「構成」「情報」「警告」「重大」という風に、設定レベル以下が表示されます。
Level.ALLはその名の通り、全てを表示させます。

コード

package com.test.logging;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class Logging {

	static final Logger logger = Logger.getLogger(Logging.class.getName());
	static final String filePath = "ログ用ファイルのパス";

	public static void main(String[] args) {
		try {

			FileHandler fileHandler = new FileHandler(filePath, false);
			fileHandler.setFormatter(new SimpleFormatter());
			logger.addHandler(fileHandler);
			logger.setLevel(Level.FINE);

			ConsoleHandler consoleHandler = new ConsoleHandler();
			consoleHandler.setLevel(Level.CONFIG);			
			logger.addHandler(consoleHandler);

			logger.setUseParentHandlers(false);

			logger.finest("FNST");
			logger.finer("FNR");
			logger.fine("FN");
			logger.config("CFG");
			logger.info("INF");
			logger.warning("WNG");
			logger.severe("SVR");

			throw new IOException();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IOException e) {

			logger.log(Level.FINER, "エラー発生", e);
			System.out.println("エラー");

		}
	}

}

Loggerの名前を設定。(14行目)
Javadocには下記の説明がなされています。
ロガーの名前はどのような文字列でもかまいませんが、通常は、java.netやjavax.swingなど、 ロギング対象のコンポーネントのパッケージ名やクラス名に基づいた名前にすべきです。
さらに、Loggerの名前空間に格納されない「匿名」のLoggerを作成することも可能となっています。
今回は、Logging.class.getName()で、”com.test.logging.Logging(パッケージ+クラス)”を取得して設定しました。
また、getLoggerの注意点として下記があります。 指定されたサブシステムのロガーを検出または作成します。指定された名前のロガーがすでに作成されていた場合はそれが返されます。それ以外の場合は新しいロガーが作成されます。

ファイルで出力する設定。(20〜23行目)
20行目、FileHandlerを使用し、第1引数(ファイルパス)と第2引数(上書き/追記)を設定。
上書き=false、true=追記。デフォルトは上書き。
パスに設定した対象ファイルが無ければ自動的に作成されます。
21行目、書式のデフォルトはXMLなので、見やすくするためにSimpleFormatter()で普通の出力を設定。
22行目、Loggerに追加。
23行目、ファイルに出力する際のレベル設定。

標準出力の設定。(25〜27行目)
25〜26行目、ConsoleHandlerを使用して、標準出力の要求レベルを設定。
27行目、Loggerに追加。

親ロガーの設定。(29行目)
falseにしてますがtrueにすると、要求レベル未満の標準出力が二重に表示されたりレベルが合わなかったりする様です。
二重に表示されるのは、作成したログは親ロガーに送信されるので、親ロガーに登録されたログが出力されるのでしょう。

ログの設定。(31〜37行目)
確認用として全てのレベルを書いています。

最後にエラーを投げる。(39行目)
IOExceptionを発生させ、47行目のエラー発生時のログにつなげています。

実行結果

標準出力

9 20, 2016 12:13:12 午後 com.test.logging.Logging main
普通: FN
9 20, 2016 12:13:12 午後 com.test.logging.Logging main
構成: CFG
9 20, 2016 12:13:12 午後 com.test.logging.Logging main
情報: INF
9 20, 2016 12:13:12 午後 com.test.logging.Logging main
警告: WNG
9 20, 2016 12:13:12 午後 com.test.logging.Logging main
重大: SVR
エラー

最後のIOExceptionのログは、Level.ALLにしても出力されない様です。

ファイル出力

9 20, 2016 12:13:12 午後 com.test.logging.Logging main
普通: FN
9 20, 2016 12:13:12 午後 com.test.logging.Logging main
構成: CFG
9 20, 2016 12:13:12 午後 com.test.logging.Logging main
情報: INF
9 20, 2016 12:13:12 午後 com.test.logging.Logging main
警告: WNG
9 20, 2016 12:13:12 午後 com.test.logging.Logging main
重大: SVR

最後のIOExceptionのログは、レベルをFINERに設定したので出力されていませんが、FINER以上に変更すれば出力されます。

実行結果(XML)

SimpleFormatterを設定しない場合も出力してみました。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2016-09-20T12:19:16</date>
  <millis>1474341556815</millis>
  <sequence>0</sequence>
  <logger>com.test.logging.Logging</logger>
  <level>FINE</level>
  <class>com.test.logging.Logging</class>
  <method>main</method>
  <thread>1</thread>
  <message>FN</message>
</record>
<record>
  <date>2016-09-20T12:19:16</date>
  <millis>1474341556838</millis>
  <sequence>1</sequence>
  <logger>com.test.logging.Logging</logger>
  <level>CONFIG</level>
  <class>com.test.logging.Logging</class>
  <method>main</method>
  <thread>1</thread>
  <message>CFG</message>
</record>
<record>
  <date>2016-09-20T12:19:16</date>
  <millis>1474341556854</millis>
  <sequence>2</sequence>
  <logger>com.test.logging.Logging</logger>
  <level>INFO</level>
  <class>com.test.logging.Logging</class>
  <method>main</method>
  <thread>1</thread>
  <message>INF</message>
</record>
<record>
  <date>2016-09-20T12:19:16</date>
  <millis>1474341556855</millis>
  <sequence>3</sequence>
  <logger>com.test.logging.Logging</logger>
  <level>WARNING</level>
  <class>com.test.logging.Logging</class>
  <method>main</method>
  <thread>1</thread>
  <message>WNG</message>
</record>
<record>
  <date>2016-09-20T12:19:16</date>
  <millis>1474341556856</millis>
  <sequence>4</sequence>
  <logger>com.test.logging.Logging</logger>
  <level>SEVERE</level>
  <class>com.test.logging.Logging</class>
  <method>main</method>
  <thread>1</thread>
  <message>SVR</message>
</record>
</log>

やっぱり読み辛いですね。

さいごに

他のログ作成手段も試してみて、実際に使う際は目的に沿うものを選びたいですね。