この記事は、
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
思い出します) parse
PatternParserオブジェクト。
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宛に送って下さい。]