この記事は、
PatternLayout
クラスを使用して適切にフォーマットすることができる属性を追加するために
log4j APIを拡張する
系統立った方法を記載します。
この記事は、log4jユーザー・マニュアルに 精通していることを仮定しています。 それは、ユーザー・マニュアルとJavadoc APIで 説明されている基本的なクラス上で構築します。 概念を説明する際に助けとなるように、単純な事例研究に沿って開発 していきます。 結果として作成されるクラスが、あなた自身の拡張のためにテンプレート として使われるかもしれません。 事例研究コードの要約された(すなわち圧縮され、コメントの削除 されたさた文の)断片は、この文書に含まれます。
事例研究は、以下の個々のログ・エントリの情報が要求されるCORBA環境で開発されました。
括弧の中の文字は、対応する文字がフォーマットするために
PatternLayout
クラスによって使われます。
コンポーネント名のために "b" を使うことは、奇妙に見えます。これは、現在、
PatternLayout
はすでに、"C"と"c"をクラス名とカテゴリー名のために予約しているためです。
原則として、下で記述されるステップが密接に従われるならば、 拡張されたクラスがlog4jによってどのように使われるかについて理解する必要はありません。 しかし、時々、ソフトウェア開発は、完全に無節操でありえる。 あなたは異なる方法においてlog4jを拡張したいかもしれません ここで記述しますまたは、あなたは本当に続いていることについての知識を必要と する間違いをするかもしれません。 (そこで禁じられている天が、この文書におけるミスです)。 いずれにせよ、そのように考えを続くけることは、無駄ではありません。
以下に、log4jを拡張しない、"典型的な"ログ出力シナリオを説明します。
Category
オブジェクトを使用してlog要求を実行します。
info メソッドが呼び出されたとしましょう。
info は、INFOレベルの出力が完全にオフ
かどうか確認します。そうであれば、ただちに戻ります。
我々は、ログ出力がINFOレベルでオフにされなかったと仮定してシナリオを
進めます。
info は、このカテゴリーの
Priority
とPriority.INFOのレベルとの比較をします。
優先度保証のログメッセージと仮定して、カテゴリー・インスタンスは
LoggingEvent
オブジェクトにある情報がログ出力のために有効になります。
Category インスタンスは、LoggingEvent
インスタンスに、そのすべての
Appender
実装に渡します。
Appender実装は、
関連する
Layout
サブクラスがあります。
Layout サブクラスは、 LoggingEvent
インスタンスに渡し、Layoutの
設定によって、イベントの情報をフォーマットした
String を戻します。
Layout クラスが
PatternLayout,
C 言語のライブラリの printf ルーチンと
同等の文字シーケンスによって決定されたイベントの情報のフォーマット.
PatternLayout は文字シーケンスの解析を
PatternParser
インスタンスに委譲します。
PatternLayout が構築されると、
PatternParser が文字シーケンスをトークナイズするために、
作成されます。
トークンを認識すると、PatternParserが適切な
PatternConverterサブクラスを構築して、
それをパスしてトークンからの情報をフォーマットします。
たびたび、PatternConverterサブクラスは
PatternParserの解析メソッドはstatic内部のクラスとして実行されます。
PatternParserのメソッドは、これらのPatternConverter
サブクラスのリンク・リストを返します。
PatternLayout.format() は、LoggingEventの
リンクリストの
それぞれのPatternConverter サブクラスを渡します。
リストの中の各リンクは、LoggingEventから特定のアイテムを選んで、
このアイテムを適当なフォーマットの中の文字列に変換して、
StringBufferにそれを追加します。
format メソッドは、
Appender が出力するための
結果の Stringを戻します。
上記の議論は、我々が拡張または実装するクラスのほとんどに関連します。
org.apache.log4j.PatternLayout
org.apache.log4j.Category
org.apache.log4j.spi.CategoryFactory
org.apache.log4j.spi.LoggingEvent
org.apache.log4j.helpers.PatternParser
org.apache.log4j.helpers.PatternConverter
下記は、log4jを拡張することによってログ記録に利用できる属性を
追加するのに必要なステップです。
これは、PatternLayoutクラスによって提供されるそれらとして、
同様に彼らの出力フォーマットを指定することができます。
ステップは、参照のためだけのに番号がふられています。
それによって、以下の順番によって異なることはありません。
あなたがあなたが加えたい属性を知っているいて、あなたの前の
それぞれのためのPatternLayoutシンボルが開始するならば、
それは役に立ちます。
必ずあなたが選ぶシンボルがすでに使用中でないことを確実にするために
PatternLayoutドキュメンテーションを参照するようにしてください。
作り出す前に、私はコメントの標準化の説明をしなければなりません。 log4jライブラリがよく文書化されないならば、それはlog4j作成者以外の 誰にでも役に立たないでしょう;これはあなたの拡張でも同様です。 ほとんど野菜を食べて、環境を保護することのと同じように、 我々全ては、コメントしているコードが正しくされなければならないことに同意します。 それでも、それはたびたびより即時の喜びのために犠牲になります。 我々全ては、コメントなしでより速いコードを書きます; 特にそれらのJavadocコメントは厄介です。 しかし、現実は文書にされていないコードの有用性が 急速に時間とともに弱まるということです。
log4j製品は、Javadocコメントとドキュメンテーションが共に、 提供されるので、Javadocコメントをあなたの拡張に含めることは 意味があります。これによりロギングツールとしての再利用性を 促進します。それらが強力なドキュメントによってサポートされれば 再利用が高まります。
これが言いたいことの全てで、私は口やかましいメモとして役に立つためにそれらを含
むことよりむしろスペースのために例から大部分のコメントを削除することに
決めました。
読者は、Javadoc取り決めに関するより多くの情報のために事例研究ソース・
コード・ファイルを、Javadoc版とJavadocウェブサイト
で参照できます。
1.
LoggingEventの拡張
LoggingEvent クラスの拡張は
平凡なステップのうちの1つでなければなりません。
拡張において必要である全ては、新しい属性と新しいコンストラクタが
それらが抱えるpublic データ・メンバーの追加です。
import org.apache.log4j.Category;
import org.apache.log4j.Priority;
import org.apache.log4j.spi.LoggingEvent;
public class AppServerLoggingEvent extends LoggingEvent
implements java.io.Serializable
{
public String hostname;
public String component;
public String server;
public String version;
public AppServerLoggingEvent( String fqnOfCategoryClass,
AppServerCategory category,
Priority priority,
Object message,
Throwable throwable)
{
super( fqnOfCategoryClass,
category,
priority,
message,
throwable );
hostname = category.getHostname();
component = category.getComponent();
server = category.getServer();
version = category.getVersion();
}
}
|
コンストラクタはほとんどの場合、Categoryサブクラスが
LoggingEventサブクラスの一般的に必要な属性のほとんどが
含まれるます。LoggingEventの拡張は、文字列の集合と
コンストラクタ以上のものはありません。
大部分の作業はスーパー・クラスによって完了しています。
PatternLayoutの拡張
PatternLayoutクラスを拡張することは単純なもう一つの問題でなければなりません。
PatternLayoutへの拡張はその両親と
PatternParserインスタンスの作成だけにおいて
異ならなければなりません。拡張されたPatternLayoutは
拡張されたPatternParser
クラスを作成しなければなりません。幸いにも、PatternLayoutで
のこの作業は一つのメソッドの範囲内でカプセル化されています。
import org.apache.log4j.PatternParser;
import org.apache.log4j.PatternLayout;
public class AppServerPatternLayout extends PatternLayout
{
public AppServerPatternLayout()
{
this(DEFAULT_CONVERSION_PATTERN);
}
public MyPatternLayout(String pattern)
{
super(pattern);
}
public PatternParser createPatternParser(String pattern)
{
PatternParser result;
if ( pattern == null )
result = new AppserverPatternParser( DEFAULT_CONVERSION_PATTERN );
else
result = new AppServerPatternParser ( pattern );
return result;
}
}
|
PatternParser と PatternConverterの拡張
PatternParserがその作業の多くが ( peek under the hood
思い出します) parsePatternParserオブジェクト。
PatternLayoutは、それから呼びます解析しますPatternConverterサブクラス
インスタンスの結ばれたリストを生産するPatternParserのメソッド。
イベント・インスタンスをappendersによって使用される文字列に変えることは、
使われるコンバータのこのリンク・リストです。
我々の仕事は正しく我々が加えたい文字をフォーマットすることを
解釈するためにPatternParserサブクラスにあります。
幸いにも、各文字をフォーマットするために異なっている
プロセスを解析することにおける1つのステップだけが
越えられなければならないように、PatternParserは設計されました。
解析することのブーブー作品はPatternParser.parse()メソッドに
よってさらに実行されてす。PatternParser.finalizeConverterメソッドだけは
越えられなければなりません。これはつくるPatternConverterがフォーマット
している文字にどれの基礎をおいたかについて決めるメソッドです。
PatternParser(AppServerPatternParser)への
拡張は、そのスーパー・クラスに類似しています。それは、以下を使用します。
AppServerPatternParserのprivate static内部のクラスとして定義しました。
finalizeConverterメソッドのインスタンスを作成して与えられた
フォーマット文字のための適切なコンバータ。
AppServerPatternParserは主に各フォーマットされる属性を記録する
ために別々のコンバータ・タイプを捧げることによって異なります。
コンバータの中にスイッチ・ロジックを置くよりはむしろ、
その親クラスのように、各コンバータが1つのフォーマット文字を変えるだけです。
これはインスタンスを作成するコンバータsubclassが時間を記録することでの
スイッチ・ステートメントにおいてよりむしろレイアウトインスタンスを作成する
時間に作られる決定を意味します。
フォーマット定数が整数よりむしろ文字であるという点で、それも異なります。
import org.apache.log4j.*;
import org.apache.log4j.helpers.FormattingInfo;
import org.apache.log4j.helpers.PatternConverter;
import org.apache.log4j.helpers.PatternParser;
import org.apache.log4j.spi.LoggingEvent;
public class AppServerPatternParser extends PatternParser
{
static final char HOSTNAME_CHAR = 'h';
static final char SERVER_CHAR = 's';
static final char COMPONENT_CHAR = 'b';
static final char VERSION_CHAR = 'v';
public AppServerPatternParser(String pattern)
{
super(pattern);
}
public void finalizeConverter(char formatChar)
{
PatternConverter pc = null;
switch( formatChar )
{
case HOSTNAME_CHAR:
pc = new HostnamePatternConverter( formattingInfo );
currentLiteral.setLength(0);
addConverter( pc );
break;
case SERVER_CHAR:
pc = new ServerPatternConverter( formattingInfo );
currentLiteral.setLength(0);
addConverter( pc );
break;
case COMPONENT_CHAR:
pc = new ComponentPatternConverter( formattingInfo );
currentLiteral.setLength(0);
addConverter( pc );
break;
case VERSION_CHAR:
pc = new VersionPatternConverter( formattingInfo );
currentLiteral.setLength(0);
addConverter( pc );
break;
default:
super.finalizeConverter( formatChar );
}
}
private static abstract class AppServerPatternConverter extends PatternConverter
{
AppServerPatternConverter(FormattingInfo formattingInfo)
{
super(formattingInfo);
}
public String convert(LoggingEvent event)
{
String result = null;
AppServerLoggingEvent appEvent = null;
if ( event instanceof AppServerLoggingEvent )
{
appEvent = (AppServerLoggingEvent) event;
result = convert( appEvent );
}
return result;
}
public abstract String convert( AppServerLoggingEvent event );
}
private static class HostnamePatternConverter extends AppServerPatternConverter
{
HostnamePatternConverter( FormattingInfo formatInfo )
{ super( formatInfo ); }
public String convert( AppServerLoggingEvent event )
{ return event.hostname; }
}
private static class ServerPatternConverter extends AppServerPatternConverter
{
ServerPatternConverter( FormattingInfo formatInfo )
{ super( formatInfo ); }
public String convert( AppServerLoggingEvent event )
{ return event.server; }
}
private static class ComponentPatternConverter extends AppServerPatternConverter
{
ComponentPatternConverter( FormattingInfo formatInfo )
{ super( formatInfo ); }
public String convert( AppServerLoggingEvent event )
{ return event.component; }
}
private static class VersionPatternConverter extends AppServerPatternConverter
{
VersionPatternConverter( FormattingInfo formatInfo )
{ super( formatInfo ); }
public String convert( AppServerLoggingEvent event )
{ return event.version; }
}
}
|
Categoryとそのfactoryを拡張することはPatternParserと
コンバータを拡張することより直接的です。
以下の作業は我々の目的のためにカテゴリーをオーバーライドすることに関係しています。
下記の大部分のコードはいくぶん略記された標準のゲッター/セッターverbageです。 顕著な部分はボールドの中にあります。 我々はもう5つの属性をカテゴリー:4つの新しい記録している属性さらに静的AppServerCategoryFactory リファレンスに加えます。これはプレ予防の計測としてヌルにセットされる属性で、インスタンスに初期化します。 その他の点では、setFactoryメソッドの前に呼ばれるならば、 getInstanceメソッドは無効なポインター例外で結果として起こます。
getInstanceメソッドは、単にカテゴリー名に加えて
CategoryFactoryリファレンスを受け入れるその親クラス・メソッドを呼び出します。
forcedLogメソッドは、密接に対応する親クラス・メソッドに続きます。
最も重要な違いが、AppServerLoggingEventのインスタンスを作成することです。
マイナーであるが、必要な違いはカテゴリーの場合のようにデータ・
メンバー・ディレクトリにアクセスすることよりむしろgetRendererMap()
メソッドの使用です。rendererMapがアクセスできるパッケージ・レベルで
あるので、Categoryはこれをすることができます。
setFactoryメソッドは、getInstanceメソッドにおいて使われるfactoryを
決めるためにアプリケーション・コードを許すために提供されます。
import org.apache.log4j.Priority;
import org.apache.log4j.Category;
import org.apache.log4j.spi.CategoryFactory;
import org.apache.log4j.spi.LoggingEvent;
public class AppServerCategory extends Category
{
protected String component;
protected String hostname;
protected String server;
protected String version;
private static CategoryFactory factory =
new AppServerCategoryFactory(null, null, null);
protected AppServerCategory( String categoryName,
String hostname,
String server,
String component,
String version )
{
super( categoryName );
instanceFQN = "org.apache.log4j.examples.appserver.AppServerCategory";
this.hostname = hostname;
this.server = server;
this.component = component;
this.version = version;
}
public String getComponent()
{ return (component == null ) ? "" : result; }
public String getHostname()
{ return ( hostname == null ) ? "" : hostname; }
public static Category getInstance(String name)
{
return Category.getInstance(name, factory);
}
public String getServer()
{ return ( server == null ) ? "" : server; }
public String getVersion()
{ return ( version == null ) ? "" : version; }
protected void forcedLog( String fqn,
Priority priority,
Object message,
Throwable t)
{
LoggingEvent event = new AppServerLoggingEvent(fqn, this, priority, message, t);
callAppenders( event );
}
public void setComponent(String componentName)
{ component = componentName; }
public static void setFactory(CategoryFactory factory)
{ AppServerCategory.factory = factory; }
public void setHostname(String hostname)
{ this.hostname = hostname; }
public void setServer(String serverName)
{ server = serverName; }
public void setVersion(String versionName)
{ version = versionName; }
}
|
最後のステップは正しく我々のAppServerCategoryオブジェクトをinstanciateする CategoryFactoryインタフェースの実装を提供することです。 それはそれが属性のためにゲッターとセッターに実装される紹介された、 唯一のメソッドを提供することを除いてjava.net API を使うことを実行するマシンのホスト名を得ますmakeNewCategoryInstance.ある
以下の部分は、AppServerCategoryFactoryと
getterメソッド、setterメソッドと、コメントは削除されています。
import org.apache.log4j.Category;
import org.apache.log4j.spi.CategoryFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class AppServerCategoryFactory implements CategoryFactory
{
protected String hostname;
protected String server;
protected String component;
protected String version;
protected ResourceBundle messageBundle;
protected AppServerCategoryFactory( String serverName,
String componentName,
String versionName )
{
try
{
hostname = java.net.InetAddress.getLocalHost().getHostName();
}
catch ( java.net.UnknownHostException uhe )
{
System.err.println("Could not determine local hostname.");
}
server = serverName;
component = componentName;
version = versionName;
}
public Category makeNewCategoryInstance(String name)
{
Category result = new AppServerCategory(name,
hostname,
server,
component,
version);
return result;
}
}
|
我々は今や我々が作成したものを使う方法に辿りつきました。
覚えておかなければならないのは
AppServerCategoryFactory のインスタンスに
よって生成されることによってlog4jが初期化され
それが、AppServerCategoryに渡されることです。
いったんこれが行われれば、
AppServerCategoryInstance を実行することで
いつでも、
AppServerCategoryのstatic getInstance
メソッドを使用することで得ることができます。
これは、確実に AppServerLoggingEventインスタンス
がカテゴリーログ記録メソッドによって生成されます。
import org.apache.log4j.*;
import org.apache.log4j.appserver.AppServerCategory;
import org.apache.log4j.appserver.AppServerCategoryFactory;
import org.apache.log4j.appserver.AppServerPatternLayout;
public class test
{
private static String formatString =
"---------------------------------------------------%n" +
"Time: %d%n" +
"Host: %h%n" +
"Server: %s%n" +
"Component: %b%n" +
"Version: %v%n" +
"Priority: %p%n" +
"Thread Id: %t%n" +
"Context: %x%n" +
"Message: %m%n";
public static void main(String[] args)
{
AppServerCategoryFactory factory;
factory = new AppServerCategoryFactory("MyServer", "MyComponent", "1.0");
AppServerCategory.setFactory( factory );
Category cat = AppServerCategory.getInstance("some.cat");
PatternLayout layout = new AppServerPatternLayout( formatString );
cat.addAppender( new FileAppender( layout, System.out) );
cat.debug("This is a debug statement.");
cat.info("This is an info statement.");
cat.warn("This is a warning statement.");
cat.error("This is an error statement.");
cat.fatal("This is a fatal statement.");
}
}
|
Category instances (such as
PropertyConfigurator
and
DOMConfigurator). Since these configurators do not
know about our extensions, any Category instances they
create will not be AppServerCategory instances. To
prevent this problem, any AppServerCategory that one
might want to be configured through a configurator should be
instanciated before the configure method is invoked. In this way,
the configurator will configure the AppServerCategory
that already exists rather than creating an instance of its super
class.
The consequence of a configurator creating the super class by mistake is merely that the extra attributes will not appear in the log output. All other attributes are conveyed properly.
人がカテゴリー・インスタンス(例えば
PropertyConfigurator
と
DOMConfigurator)をつくるかもしれないconfiguratorsの使用に関する注意の語であってそこで。これらのconfiguratorsが我々の拡張を知らなくして以後、彼らがつくる少しのカテゴリー・インスタンスもAppServerCategoryインスタンスでなくて。人がconfiguratorを通して構成されて欲しいかもしれないどんなAppServerCategoryでもinstanciatedされなければならなくて、この問題を防ぐために構成します、メソッドは呼ばれてす。この方法の中の、configuratorがその場所においてそのスーパー・クラスのインスタンスをつくることよりむしろすでに存在するAppServerCategoryを構成すること。
このlog4j拡張が強化されるかもしれないいくつかのその他の方向が、あります。
[訳注: これは熊坂祐二が翻訳しました。 日本語訳に対するコメントは、jajakarta-report@nekoyanagi.com宛に送って下さい。]