Javaロギングの概要

最終更新日: 2001年11月26日

1.0ロギングの概要
    1.1制御フローの概要
     1.2ログLevel
     1.3 Logger
     1.4ロギング・メソッド
     1.5 Handler
     1.6 Formatter
     1.7 LogManager
     1.8構成ファイル
     1.9デフォルトの構成
     1.10構成の動的更新
     1.11ネイティブ・メソッド
     1.12 XML DTD
     1.13一意のメッセージID
     1.14セキュリティ
     1.15構成管理
     1.16パッケージ化
     1.17ローカリゼーション
     1.18リモート・アクセスと直列化
2.0例
     2.1簡単な用法
     2.2構成の変更
     2.3グローバル設定を無視した簡単な用法
     2.4 XMLの出力例
3.0付録A: XMLFormatter出力用のDTD

1.0 Javaロギングの概要

ロギングAPIの詳細は、「Java SE API仕様」を参照してください。このドキュメントは、主な要素の概要について説明することを目的としています。

1.1 制御フローの概要

アプリケーションはLoggerオブジェクトでロギング呼出しを行います。Loggerは階層構造を持つ名前空間に編成され、子Loggerは名前空間における親からロギング・プロパティの一部を継承できます。

アプリケーションはLoggerオブジェクトでロギング呼出しを行います。このLoggerオブジェクトはLogRecordオブジェクトを割り当てます。LogRecordオブジェクトは、Handlerオブジェクトに渡され公開されます。LoggerもHandlerも、ロギングLevelと、場合によってはFilterを使用して、特定のLogRecordと関係があるかどうかを判断します。LogRecordを外部に公開する必要がある場合、HandlerはI/Oストリームに公開する前に、Formatterを使用してメッセージをローカライズしたりフォーマットしたりできます。

前のコンテキストがこのグラフィックスを説明しています。

各Loggerは出力Handlerのセットを追跡します。デフォルトでは、すべてのLoggerが自分の親Loggerに出力を送信します。より高い階層にあるHandlerは無視するようにLoggerを設定することもできます。

Handlerの中にはほかのHandlerに出力を送信するものもあります。たとえば、MemoryHandlerLogRecordの内部リング・バッファを保持し、トリガー・イベントでターゲットHandlerを通してLogRecordを公開します。そのような場合、チェーン内の最後のHandlerで任意のフォーマットが実行されます。

前のコンテキストがこのグラフィックスを説明しています。

APIは、ロギングが無効になっている場合、Logger APIの呼出しコストを抑えるように構成されています。あるログ・レベルでロギングが無効になっている場合、Loggerは低コストの比較テストを行なって結果を返すことができます。あるログ・レベルでロギングが有効になっている場合も、Loggerはコストを抑えるように注意しながらHandlerにLogRecordを渡します。特にローカリゼーションとフォーマットは比較的コストがかかるので、Handlerからの要求があるまでは実行されません。たとえば、MemoryHandlerはフォーマット・コストを払わずにLogRecordの循環バッファを維持できます。

1.2 ログLevel

各ログ・メッセージには、ログLevelが関連付けられています。Levelによって、ログ・メッセージの重要度と緊急度がほぼわかります。ログ・レベル・オブジェクトは整数値をカプセル化します。値が大きいほど優先度も高くなります。

Levelクラスでは、FINEST (最低の優先度、値は最小)からSEVERE (最高の優先度、値は最大)まで7つの標準ログ・レベルが定義されています。

1.3 Logger

前述のように、クライアント・コードはLoggerオブジェクトにログ要求を送信します。各ロガーは関係のあるログ・レベルを追跡し、このレベル以下のログ要求を破棄します。

通常、Loggerは名前の付いたエンティティであり、「java.awt」のようにドット区切りの名前を使用します。名前空間には階層があり、LogManagerによって管理されています。多くの場合、名前空間はJavaパッケージ名前空間に対応していますが、必ずしも対応する必要はありません。たとえば、「java.awt」というLoggerはjava.awtパッケージ内のクラスのロギング要求を処理しますが、java.awt.packageで定義された、クライアントから見える抽象化をサポートするsun.awtのクラスのロギングを処理することもあります。

名前付きLoggerのほかに、共有名前空間に表示されない匿名Loggerを作ることもできます。セクション1.14を参照してください。

各Loggerは、ロギングの名前空間における親ロガーを追跡します。ロガーの親とは、ロギングの名前空間に現存するもっとも近い祖先のことです。ルートLoggerの名前は""で、ルートLoggerに親はありません。すべての匿名ロガーにとって、ルート・ロガーが親となります。Loggerは、ロガーの名前空間における親からさまざまな属性を継承できます。特に、次のような属性を継承できます。

1.4 ロギング・メソッド

Loggerクラスは、ログ・メッセージを生成するための簡易メソッドを数多く提供します。ロギング・レベルごとにメソッドがあり、便宜上、メソッド名にはロギングのレベル名が使用されています。このため、開発者は「logger.log(Level.WARNING,...」ではなく、簡易メソッド「logger.warning(...」を呼び出すことができます。

ロギング・メソッドには2種類のスタイルがあり、さまざまなユーザーの要求に対応しています。

1つ目は、明示的なソース・クラス名とソース・メソッド名を取るメソッドです。このメソッドは、特定のロギング・メッセージのソースをすばやくつきとめる必要がある開発者向けです。次にこのスタイルの例を示します。

void warning(String sourceClass, String sourceMethod, String msg);

2つ目は、明示的なソース・クラス名とソース・メソッド名を取らないメソッドです。これは、簡単なロギングを使用するだけで、詳細なソース情報を必要としない開発者向けです。

void warning(String msg);

2つ目のメソッドでは、ロギング・フレームワークは呼出し元のクラスとメソッドの識別を「ベスト・エフォート」で行い、この情報をLogRecordに追加します。ただし、自動的に推測されたこの情報は概略に過ぎないことを理解しておく必要があります。最新の仮想マシンでは、JIT処理の際に大規模な最適化を行なってスタック・フレームをすべて削除するため、呼出し元のクラスとメソッドを確実に検出することは不可能となっています。

1.5 Handler

Java SEには、次のようなHandlerがあります。

新しいHandlerを開発するのは比較的簡単です。特殊な機能が必要な開発者は、Handlerをゼロから開発することも、提供されたHandlerの1つをサブクラス化することもできます。

1.6 Formatter

Java SEには、次の2つの標準Formatterがあります。

Handlerと同様に、新しいFormatterの開発は比較的簡単です。

1.7 LogManager

グローバル・ロギング情報を追跡するグローバルなLogManagerオブジェクトがあります。このオブジェクトには次のものが含まれます。

static LogManager.getLogManagerメソッドを使って取得できる単一LogManagerオブジェクトがあります。これは、LogManagerの初期化中にシステム・プロパティに基づいて作成されます。このプロパティを使うと、コンテナ・アプリケーション(EJBコンテナなど)はLogManagerの独自のサブクラスをデフォルト・クラスと置き換えることができます。

1.8 構成ファイル

ロギング構成は、起動時に読み取られるロギング構成ファイルで初期化できます。このロギング構成ファイルは、標準のjava.util.Properties形式です。

また、初期化プロパティの読取りに使用するクラスを指定してロギング構成を初期化することもできます。このメカニズムを使用すると、LDAPやJDBCなど、任意のソースから構成データを読み取ることができます。詳細は、LogManager APIの仕様を参照してください。

グローバル構成情報の量はわずかです。これはLogManagerクラスの記述に明記されており、起動時にインストールするルート・レベルのHandlerのリストが含まれています。

初期構成で名前付きロガーのレベルを指定することもできます。これらのレベルは、その名前付きロガーおよび名前階層でその下にあるすべてのロガーに適用されます。レベルは、構成ファイルで定義した順に適用されます。

初期構成には、Handlerやロギングを行うサブシステムが使用する任意のプロパティが含まれます。便宜上、これらのプロパティには、ハンドラ・クラスの名前やサブシステムのメインLogger名で始まる名前を使用します。

たとえば、MemoryHandlerはプロパティ「java.util.logging.MemoryHandler.size」を使ってリング・バッファのデフォルト・サイズを決定します。

1.9. デフォルトの構成

JREの出荷時に設定されているデフォルトのロギング構成はデフォルトに過ぎないので、ISV、システム管理者、およびエンド・ユーザーがオーバーライドできます。

デフォルトの構成は、かぎられたディスク容量だけを利用します。処理できないほど情報量が多くなることはありませんが、重要な不具合情報は必ず取り込みます。

デフォルトの構成では、ルート・ロガーのハンドラの1つがコンソールへの出力用として設定されます。

1.10 構成の動的更新

プログラマは、さまざまな方法で実行時にロギング構成を更新できます。

1.11 ネイティブ・メソッド

ロギングにはネイティブAPIがありません。

ネイティブ・コードでJavaロギング・メカニズムを使う場合は、通常のJNI呼出しでJavaロギングAPIを呼び出す必要があります。

1.12 XML DTD

XMLFormatterで使用されるXML DTDは付録Aで明記されています。

このDTDでは、トップ・レベルのドキュメントとして「<log>」要素が設計されます。次にそれぞれのログ・レコードが「<record>」要素として記述されます。

JVMがクラッシュすると、</log>でXMLFormatterストリームを正しく閉じてきちんと終了できないことがあります。そのため、ログ・レコードを分析するツールを用意して、終了していないストリームに対処する必要があります。

1.13 一意のメッセージID

JavaロギングAPIは、一意のメッセージIDを直接にはサポートしません。一意のメッセージIDを必要とするアプリケーションやサブシステムは、独自の規則を定義してメッセージ文字列に適切な一意のIDを付与する必要があります。

1.14 セキュリティ

セキュリティの第一要件は、信頼されていないコードがロギング構成を変更できないようにすることです。特に、特定のカテゴリの情報を特定のHandlerに記録するようにロギング構成を設定した場合は、信頼されていないコードがロギングを妨害したり中断したりできないようにする必要があります。

新しいセキュリティ・アクセス権であるLoggingPermissionを定義して、ロギング構成に対する更新を制御します。

信頼されているアプリケーションには適切なLoggingPermissionが与えられ、任意のロギング構成APIを呼び出すことができます。ただし、これは信頼されていないアプレットには当てはまりません。信頼されていないアプレットは、名前付きLoggerを通常の方法で作成して使用できますが、ロギング制御設定を変更してハンドラの追加や削除を行ったり、ログ・レベルを変更したりすることはできません。ただし、信頼されていないアプレットは、Logger.getAnonymousLoggerを使用して独自の「匿名」ロガーを作成して使用できます。このような匿名ロガーはグローバル名前空間には登録されません。また匿名ロガーのメソッドのアクセスはチェックされないので、信頼されていないコードであってもそうしたメソッドのロギング制御設定を変更できます。

ロギング・フレームワークは不正行為を防止しようとはしません。ロギング呼出しのソースは確実に識別できるとはかぎらないので、特定のソース・クラスとソース・メソッドからのものであるというLogRecordが公開されても、偽物であることがあります。同様に、XMLFormatterなどのフォーマッタも、メッセージ文字列内の入れ子のログ・メッセージに対して自身を保護しようとしません。このように、偽のLogRecordはメッセージ文字列内に偽のXMLを含んでいることがあり、出力時に別のXMLレコードがあるように見えることがあります。

また、ロギング・フレームワークはサービス妨害攻撃に対して自身を保護しようとはしません。任意のロギング・クライアントがロギング・フレームワークを意味のないメッセージであふれさせ、重要なログ・メッセージを隠すことができます。

1.15 構成管理

APIは、構成情報の初期セットを構成ファイルからプロパティとして読み取るように構成されています。次に構成情報はさまざまなロギング・クラスやオブジェクトを呼び出して、プログラムによって変更できます。

さらに、LogManagerには構成ファイルを再読取りできるメソッドがあります。再読込みを行うと、構成ファイルの値がプログラムが行なった変更をオーバーライドします。

1.16 パッケージ化

すべてのロギング・クラスは、java.util.loggingパッケージにある名前空間のjava.*の部分にあります。

1.17 ローカリゼーション

ログ・メッセージをローカライズする必要のある場合があります。

各Loggerには、関連付けられたリソース・バンドル名があります。対応するリソース・バンドルを使用して、原文のメッセージ文字列とローカライズするメッセージ文字列をマッピングできます。

通常、ローカリゼーションはFormatterが行います。便宜上、フォーマッタ・クラスは基本的なローカリゼーションとフォーマットをサポートするformatMessageメソッドを提供します。

1.18 リモート・アクセスと直列化

ほとんどのJavaプラットフォームAPIでは、ロギングAPIは単一アドレス空間で使用する設計になっています。呼出しはすべてローカルとなります。ただし、出力をほかのシステムに転送しようとするHandlerがあることも考えられます。次のようなさまざまな方法でこれを行うことができます。

SocketHandlerなど、XMLFormatterを使ってほかのシステムにデータを書き込むHandlerがあります。これにより、さまざまなシステムで構文解析と処理が可能な簡単で標準的な交換可能なフォーマットが提供されます。

RMIでLogRecordオブジェクトを渡すHandlerもあります。したがって、LogRecordクラスは直列化可能です。ただし、LogRecordパラメータをどのように扱うかという問題があります。直列化できないパラメータがある一方で、ロギングに必要とされる以上の状態に直列化するパラメータもあるからです。この問題を回避するため、LogRecordクラスには、Object.toString()でパラメータを文字列に変換してから書き出すカスタムのwriteObjectメソッドが用意されています。詳細は、「LogRecord API仕様」を参照してください。

ほとんどのロギング・クラスは、直列化可能にはなっていません。LoggerもHandlerも、特定の仮想マシンに結び付けられたステートフル・クラスです。この点では、どちらもjava.ioクラスと似ています。このクラスも直列化できません。

2.0 例

2.1 簡単な用法

デフォルト設定を使ってロギングを実行する小さなプログラムを次に示します。

このプログラムは、構成ファイルに基づいてLogManagerが設定したルート・ハンドラに依存しています。独自のLoggerオブジェクトを作成し、これを呼び出してさまざまなイベントをレポートするプログラムです。

package com.wombat;
import java.util.logging.*;

public class Nose{
    // Obtain a suitable logger.
    private static Logger logger = Logger.getLogger("com.wombat.nose");
    public static void main(String argv[]) {
        // Log a FINE tracing message
        logger.fine("doing stuff");
        try{
            Wombat.sneeze();
        } catch (Exception ex) {
            // Log the exception
            logger.log(Level.WARNING, "trouble sneezing", ex);
        }
        logger.fine("done");
    }
}

2.2 構成の変更

ロギング構成を動的に調整して特定のファイルに出力を送信し、wombatに関する多くの情報を取得する小さなプログラムを次に示します。パターン「%t」は、システムの一時ディレクトリを表します。

public static void main(String[] args) {
    Handler fh = new FileHandler("%t/wombat.log");
    Logger.getLogger("").addHandler(fh);
    Logger.getLogger("com.wombat").setLevel(Level.FINEST);
    ...
}

2.3 グローバル設定を無視した簡単な用法

独自のロギングHandlerを設定し、グローバル設定を無視する小さなプログラムを次に示します。

package com.wombat;

import java.util.logging.*;

public class Nose {
    private static Logger logger = Logger.getLogger("com.wombat.nose");
    private static FileHandler fh = new FileHandler("mylog.txt");
    public static void main(String argv[]) {
        // Send logger output to our FileHandler.
        logger.addHandler(fh);
        // Request that every detail gets logged.
        logger.setLevel(Level.ALL);
        // Log a simple INFO message.
        logger.info("doing stuff");
        try {
            Wombat.sneeze();
        } catch (Exception ex) {
            logger.log(Level.WARNING, "trouble sneezing", ex);
        }
        logger.fine("done");
    }
}

2.4 XMLの出力例

次に、XMLFormatter XML出力の例を示します。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2000-08-23 19:21:05</date>
  <millis>967083665789</millis>
  <sequence>1256</sequence>
  <logger>kgh.test.fred</logger>
  <level>INFO</level>
  <class>kgh.test.XMLTest</class>
  <method>writeLog</method>
  <thread>10</thread>
  <message>Hello world!</message>
</record>
</log>

3.0 付録A: XMLFormatter出力用のDTD

<!-- DTD used by the java.util.logging.XMLFormatter -->
<!-- This provides an XML formatted log message. -->

<!-- The document type is "log" which consists of a sequence
of record elements -->
<!ELEMENT log (record*)>

<!-- Each logging call is described by a record element. -->
<!ELEMENT record (date, millis, sequence, logger?, level,
class?, method?, thread?, message, key?, catalog?, param*, exception?)>

<!-- Date and time when LogRecord was created in ISO 8601 format -->
<!ELEMENT date (#PCDATA)>

<!-- Time when LogRecord was created in milliseconds since
midnight January 1st, 1970, UTC. -->
<!ELEMENT millis (#PCDATA)>

<!-- Unique sequence number within source VM. -->
<!ELEMENT sequence (#PCDATA)>

<!-- Name of source Logger object. -->
<!ELEMENT logger (#PCDATA)>

<!-- Logging level, may be either one of the constant
names from java.util.logging.Level (such as "SEVERE"
or "WARNING") or an integer value such as "20". -->
<!ELEMENT level (#PCDATA)>

<!-- Fully qualified name of class that issued
logging call, e.g. "javax.marsupial.Wombat". -->
<!ELEMENT class (#PCDATA)>

<!-- Name of method that issued logging call.
It may be either an unqualified method name such as
"fred" or it may include argument type information
in parenthesis, for example "fred(int,String)". -->
<!ELEMENT method (#PCDATA)>

<!-- Integer thread ID. -->
<!ELEMENT thread (#PCDATA)>

<!-- The message element contains the text string of a log message. -->
<!ELEMENT message (#PCDATA)>

<!-- If the message string was localized, the key element provides
the original localization message key. -->
<!ELEMENT key (#PCDATA)>

<!-- If the message string was localized, the catalog element provides
the logger's localization resource bundle name. -->
<!ELEMENT catalog (#PCDATA)>

<!-- If the message string was localized, each of the param elements
provides the String value (obtained using Object.toString())
of the corresponding LogRecord parameter. -->
<!ELEMENT param (#PCDATA)>

<!-- An exception consists of an optional message string followed
by a series of StackFrames. Exception elements are used
for Java exceptions and other java Throwables. -->
<!ELEMENT exception (message?, frame+)>

<!-- A frame describes one line in a Throwable backtrace. -->
<!ELEMENT frame (class, method, line?)>

<!-- an integer line number within a class's source file. -->
<!ELEMENT line (#PCDATA)>

Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.