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
ロギング API の詳細は、「Java SE API 仕様」を参照してください。このドキュメントは、主な要素の概要について説明することを目的としています。
アプリケーションは 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 に出力を送信するものもあります。たとえば、MemoryHandler は LogRecord の内部リングバッファーを保持し、トリガーイベントでターゲット Handler を通して LogRecord を公開します。そのような場合、チェーン内の最後の Handler で任意のフォーマットが実行されます。
API は、ロギングが無効になっている場合、Logger API の呼び出しコストを抑えるように構成されています。あるログレベルでロギングが無効になっている場合、Logger は低コストの比較テストを行なって結果を返すことができます。あるログレベルでロギングが有効になっている場合も、Logger はコストを抑えるように注意しながら Handler に LogRecord を渡します。特にローカリゼーションとフォーマットは比較的コストがかかるので、Handler からの要求があるまでは実行されません。たとえば、MemoryHanlder はフォーマットコストを払わずに LogRecord の循環バッファーを維持できます。
各ログメッセージには、ログ Level が関連付けられています。Level によって、ログメッセージの重要度と緊急度がほぼわかります。ログレベルオブジェクトは整数値をカプセル化します。値が大きいほど優先順位も高くなります。
Level クラスでは、FINEST (最低の優先順位、値は最小) から SEVERE (最高の優先順位、値は最大) まで 7 つの標準ログレベルが定義されています。
前述のように、クライアントコードは Logger オブジェクトにログ要求を送信します。各ロガーは関係のあるログレベルを追跡し、このレベル以下のログ要求を破棄します。
通常、Logger は名前の付いたエンティティーであり、「java.awt」のようにドット区切りの名前を使用します。名前空間には階層があり、LogManager によって管理されています。多くの場合、名前空間は Java パッケージ名前空間に対応していますが、必ずしも対応する必要はありません。たとえば、「java.awt」という Logger は java.awt パッケージ内のクラスのロギング要求を処理しますが、java.awt.package で定義された、クライアントから見える抽象化をサポートする sun.awt のクラスのロギングを処理することもあります。
名前付き Logger のほかに、共有名前空間に表示されない匿名 Logger を作ることもできます。セクション 1.14 を参照してください。
各 Logger は、ロギングの名前空間における親ロガーを追跡します。ロガーの親とは、ロギングの名前空間に現存するもっとも近い祖先のことです。ルート Logger の名前は "" で、ルート Logger に親はありません。すべての匿名ロガーにとって、ルートロガーが親となります。Logger は、ロガーの名前空間における親からさまざまな属性を継承できます。特に、次のような属性を継承できます。
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 処理の際に大規模な最適化を行なってスタックフレームをすべて削除するため、呼び出し元のクラスとメソッドを確実に検出することは不可能となっています。
Java SE には、次のような Handler があります。
新しい Handler を開発するのは比較的簡単です。特殊な機能が必要な開発者は、Handler をゼロから開発することも、提供された Handler の 1 つをサブクラス化することもできます。
Handler と同様に、新しい Formatter の開発は比較的簡単です。
static LogManager.getLogManager メソッドを使って取得できる単一 LogManager オブジェクトがあります。これは、LogManager の初期化中にシステムプロパティーに基づいて作成されます。このプロパティーを使うと、コンテナアプリケーション (EJB コンテナなど) は LogManager の独自のサブクラスをデフォルトクラスと置き換えることができます。
ロギング構成は、起動時に読み取られるロギング構成ファイルで初期化できます。このロギング構成ファイルは、標準の java.util.Properties 形式です。
また、初期化プロパティーの読み込みに使用するクラスを指定してロギング構成を初期化することもできます。このメカニズムを使うと、LDAP や JDBC など、任意のソースから構成データを読み取ることができます。詳細は、「LogManager API 仕様」を参照してください。
グローバル構成情報の量はわずかです。これは LogManager クラスの記述に明記されており、起動時にインストールするルートレベルの Handler のリストが含まれています。
初期構成で名前付きロガーのレベルを指定することもできます。これらのレベルは、その名前付きロガーおよび名前階層でその下にあるすべてのロガーに適用されます。レベルは、構成ファイルで定義した順に適用されます。
初期構成には、Handler やロギングを行うサブシステムが使用する任意のプロパティーが含まれます。便宜上、これらのプロパティーには、ハンドラクラスの名前やサブシステムのメイン Logger 名で始まる名前を使用します。
たとえば、MemoryHandler はプロパティー「java.util.logging.MemoryHandler.size」を使ってリングバッファーのデフォルトサイズを決定します。
JRE の出荷時に設定されているデフォルトのロギング構成はデフォルトに過ぎないので、ISV、システム管理者、およびエンドユーザーがオーバーライドできます。
デフォルトの構成は、かぎられたディスク容量だけを利用します。処理できないほど情報量が多くなることはありませんが、重要な不具合情報は必ず取り込みます。
デフォルトの構成では、ルートロガーのハンドラの 1 つがコンソールへの出力用として設定されます。
ネイティブコードで Java ロギングメカニズムを使う場合は、通常の JNI 呼び出しで Java ロギング API を呼び出す必要があります。
XMLFormatter で使用される XML DTD は付録 A で明記されています。
この DTD では、トップレベルのドキュメントとして「<log>」要素が設計されます。次にそれぞれのログレコードが「<record>」要素として記述されます。JVM がクラッシュすると、</log> で XMLFormatter ストリームを正しく閉じてきちんと終了できないことがあります。そのため、ログレコードを分析するツールを用意して、終了していないストリームに対処する必要があります。
Java ロギング API は、一意のメッセージ ID を直接にはサポートしません。一意のメッセージ ID を必要とするアプリケーションやサブシステムは、独自の規則を定義してメッセージ文字列に適切な一意の ID を付与する必要があります。
セキュリティーの第一要件は、信頼されていないコードがロギング構成を変更できないようにすることです。特に、特定のカテゴリの情報を特定の Handler に記録するようにロギング構成を設定した場合は、信頼されていないコードがロギングを妨害したり中断したりできないようにする必要があります。
新しいセキュリティーアクセス権である LoggingPermission を定義して、ロギング構成に対する更新を制御します。
信頼されているアプリケーションには適切な LoggingPermission が与えられ、任意のロギング構成 API を呼び出すことができます。ただし、これは信頼されていないアプレットには当てはまりません。信頼されていないアプレットは、名前付き Logger を通常の方法で作成して使用できますが、ロギング制御設定を変更してハンドラの追加や削除を行なったり、ログレベルを変更することはできません。ただし、信頼されていないアプレットは Logger.getAnonymousLogger で「匿名」ロガーを独自に作成して使用することはできます。このような匿名ロガーはグローバル名前空間には登録されません。また匿名ロガーのメソッドのアクセスはチェックされないので、信頼されていないコードであってもそうしたメソッドのロギング制御設定を変更できます。
ロギングフレームワークは不正行為を防止しようとはしません。ロギング呼び出しのソースは確実に識別できるとはかぎらないので、特定のソースクラスとソースメソッドからのものであるという LogRecord が公開されても、偽物であることがあります。同様に、XMLFormatter などのフォーマッタも、メッセージ文字列内の入れ子のログメッセージに対して自身を保護しようとしません。このように、偽の LogRecord はメッセージ文字列内に偽の XML を含んでいることがあり、出力時に別の XML レコードがあるように見えることがあります。
また、ロギングフレームワークはサービス妨害攻撃に対して自身を保護しようとはしません。任意のロギングクライアントがロギングフレームワークを意味のないメッセージであふれさせ、重要なログメッセージを隠すことができます。
API は、構成情報の初期セットを構成ファイルからプロパティーとして読み取るように構成されています。次に構成情報はさまざまなロギングクラスやオブジェクトを呼び出して、プログラムによって変更できます。
さらに、LogManager には構成ファイルを再読み込みできるメソッドがあります。再読み込みを行うと、構成ファイルの値がプログラムが行なった変更をオーバーライドします。
すべてのロギングクラスは、java.util.logging パッケージにある名前空間の java.* の部分にあります。
ログメッセージをローカライズする必要のある場合があります。
各 Logger には、関連付けられたリソースバンドル名があります。対応する ResourceBundle を使って、原文のメッセージ文字列とローカライズするメッセージ文字列をマッピングできます。
通常、ローカリゼーションは Formatter が行います。便宜上、フォーマッタクラスは基本的なローカリゼーションとフォーマットをサポートする formatMessage メソッドを提供します。
ほとんどの Java プラットフォーム API では、ロギング API は単一アドレス空間で使用する設計になっています。呼び出しはすべてローカルとなります。ただし、出力をほかのシステムに転送しようとする Handler があることも考えられます。次のようなさまざまな方法でこれを行うことができます。
SocketHandler など、XMLFormatter を使ってほかのシステムにデータを書き込む Handler があります。これにより、さまざまなシステムで構文解析と処理が可能な簡単で標準的な交換可能なフォーマットが提供されます。
RMI で LogRecord オブジェクトを渡す Handler もあります。したがって、LogRecord クラスは直列化可能です。ただし、LogRecord パラメータをどのように扱うかという問題があります。直列化できないパラメータがある一方で、ロギングに必要とされる以上の状態に直列化するパラメータもあるからです。この問題を回避するため、LogRecord クラスには、Object.toString() でパラメータを文字列に変換してから書き出すカスタムの writeObject メソッドが用意されています。詳細は、「LogRecord API 仕様」を参照してください。
ほとんどのロギングクラスは、直列化可能にはなっていません。Logger も Handler も、特定の仮想マシンに結び付けられたステートフルクラスです。この点では、どちらも java.io クラスと似ています。このクラスも直列化できません。
デフォルト設定を使ってロギングを実行する小さなプログラムを次に示します。
このプログラムは、構成ファイルに基づいて 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"); } }
ロギング構成を動的に調整して特定のファイルに出力を送信し、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); ... }
独自のロギング 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"); } }
<?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>
<!-- 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)>