Java

JavaTM ロギングの概要

最終更新日: 2001 年 11 月 26 日
ドキュメントの目次

1.0 ロギングの概要
    1.1 制御フローの概要
     1.2 ログレベル
     1.3 ロガー
     1.4 ロギングメソッド
     1.5 ハンドラ
     1.6 フォーマッタ
     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 JavaTM ロギングの概要

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

1.1 制御フローの概要

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

アプリケーションは Logger オブジェクトでロギング呼び出しを行います。この Logger オブジェクトは LogRecord オブジェクトを割り当てます。LogRecord オブジェクトは、Handler オブジェクトに渡され発行されます。ロガーもハンドラも、ロギングの「レベル」と、場合によっては「フィルタ」を使って、特定の LogRecord と関係があるかどうかを判断します。LogRecord を外部に発行する必要がある場合、ハンドラは I/O ストリームに発行する前に、「フォーマッタ」を使ってメッセージをローカライズしたり書式化したりすることができます。

前の文でのこのグラフィックスを説明しています。

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

ハンドラの中にはほかのハンドラに直接出力するものもあります。たとえば、MemoryHandlerLogRecord の内部リングバッファーを保持し、トリガイベントでターゲットハンドラを通して LogRecord を発行します。そのような場合、連鎖内の最後のハンドラで任意の書式化が実行されます。

前の文でのこのグラフィックスを説明しています。

API は、ロギングが無効になっている場合、ロガー API の呼び出しコストを抑えるように構成されています。あるログレベルでロギングが無効になっている場合、ロガーはコストの比較テストを行って結果を返すことができます。あるログレベルでロギングが有効になっている場合も、ロガーはコストを抑えるように注意しながらハンドラに LogRecord を渡します。特に各国語化と書式化は比較的コストがかかるので、ハンドラからの要求があるまでは実行されません。たとえば、MemoryHanlder は書式化コストを払わずに LogRecord の循環バッファーを維持することができます。

1.2 ログレベル

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

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

1.3 ロガー

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

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

名前付きロガーのほかに、共有名前空間に表示されない匿名ロガーを作ることもできます。詳細は、1.14 項を参照してください。

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

1.4 ロギングメソッド

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

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

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

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

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

void warning(String msg);

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

1.5 ハンドラ

Java SE には、次のようなハンドラがあります。

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

1.6 フォーマッタ

Java SE には、次の 2 つの標準フォーマッタがあります。

ハンドラと同様に、新しいフォーマッタの開発は比較的簡単です。

1.7 LogManager

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

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

1.8 構成ファイル

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

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

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

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

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

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

1.9. デフォルトの構成

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

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

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

1.10 構成の動的更新

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

1.1.1 ネイティブメソッド

ロギングにはネイティブ 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 セキュリティー

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

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

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

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

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

1.15 構成管理

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

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

1.16 パッケージ化

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

1.17 各国語化

ログメッセージを各国語化させる必要のある場合があります。

各ロガーには、関連付けられたリソースバンドル名があります。対応するリソースバンドルを使って、原文のメッセージ文字列と各国語化させるメッセージ文字列をマッピングすることができます。

通常、各国語化はフォーマッタが行います。便宜上、フォーマッタクラスは基本的な各国語化と書式化をサポートする formatMessage メソッドを提供します。

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

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

SocketHandler など、XMLFormatter を使ってほかのシステムにデータを書き込むハンドラがあります。これにより、さまざまなシステムで構文解析と処理が可能な簡単で標準的なインターチェンジ形式が提供されます。

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

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

2.0 例

2.1 簡単な用法

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

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


package com.wombat;

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("com.wombat", Level.FINEST);
    ...
}

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

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

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.Constants (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 © 2002 Sun Microsystems, Inc. All Rights Reserved.

Sun
Java Software