PatternLayoutに変換文字を追加する

"Paul Glezen" January 2001


概要

この記事は、 PatternLayout クラスを使用して適切にフォーマットすることができる属性を追加するために log4j APIを拡張する 系統立った方法を記載します。


内容


はじめに

この記事は、log4jユーザー・マニュアルに 精通していることを仮定しています。 それは、ユーザー・マニュアルとJavadoc APIで 説明されている基本的なクラス上で構築します。 概念を説明する際に助けとなるように、単純な事例研究に沿って開発 していきます。 結果として作成されるクラスが、あなた自身の拡張のためにテンプレート として使われるかもしれません。 事例研究コードの要約された(すなわち圧縮され、コメントの削除 されたさた文の)断片は、この文書に含まれます。

事例研究

事例研究は、以下の個々のログ・エントリの情報が要求されるCORBA環境で開発されました。 括弧の中の文字は、対応する文字がフォーマットするために PatternLayout クラスによって使われます。

コンポーネント名のために "b" を使うことは、奇妙に見えます。これは、現在、 PatternLayout はすでに、"C"と"c"をクラス名とカテゴリー名のために予約しているためです。

A Peek Under the Hood

原則として、下で記述されるステップが密接に従われるならば、 拡張されたクラスがlog4jによってどのように使われるかについて理解する必要はありません。 しかし、時々、ソフトウェア開発は、完全に無節操でありえる。 あなたは異なる方法においてlog4jを拡張したいかもしれません ここで記述しますまたは、あなたは本当に続いていることについての知識を必要と する間違いをするかもしれません。 (そこで禁じられている天が、この文書におけるミスです)。 いずれにせよ、そのように考えを続くけることは、無駄ではありません。

以下に、log4jを拡張しない、"典型的な"ログ出力シナリオを説明します。

  1. アプリケーションコードは、 Category オブジェクトを使用してlog要求を実行します。 info メソッドが呼び出されたとしましょう。

  2. まずはじめに、 info は、INFOレベルの出力が完全にオフ かどうか確認します。そうであれば、ただちに戻ります。 我々は、ログ出力がINFOレベルでオフにされなかったと仮定してシナリオを 進めます。

  3. 次に info は、このカテゴリーの PriorityPriority.INFOのレベルとの比較をします。 優先度保証のログメッセージと仮定して、カテゴリー・インスタンスは LoggingEvent オブジェクトにある情報がログ出力のために有効になります。

  4. Category インスタンスは、LoggingEvent インスタンスに、そのすべての Appender 実装に渡します。

  5. ほとんどの(全てではないが)Appender実装は、 関連する Layout サブクラスがあります。 Layout サブクラスは、 LoggingEvent インスタンスに渡し、Layoutの 設定によって、イベントの情報をフォーマットした String を戻します。

    Layout クラスが PatternLayout, C 言語のライブラリの printf ルーチンと 同等の文字シーケンスによって決定されたイベントの情報のフォーマット. PatternLayout は文字シーケンスの解析を PatternParser インスタンスに委譲します。

    PatternLayout が構築されると、 PatternParser が文字シーケンスをトークナイズするために、 作成されます。 トークンを認識すると、PatternParserが適切な PatternConverterサブクラスを構築して、 それをパスしてトークンからの情報をフォーマットします。 たびたび、PatternConverterサブクラスは PatternParserの解析メソッドはstatic内部のクラスとして実行されます。 PatternParserのメソッドは、これらのPatternConverter サブクラスのリンク・リストを返します。

  6. PatternLayout.format() は、LoggingEventの リンクリストの それぞれのPatternConverter サブクラスを渡します。 リストの中の各リンクは、LoggingEventから特定のアイテムを選んで、 このアイテムを適当なフォーマットの中の文字列に変換して、 StringBufferにそれを追加します。

  7. format メソッドは、 Appender が出力するための 結果の Stringを戻します。

上記の議論は、我々が拡張または実装するクラスのほとんどに関連します。


工程

下記は、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の拡張は、文字列の集合と コンストラクタ以上のものはありません。 大部分の作業はスーパー・クラスによって完了しています。

2. 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;
  }
}

3. PatternParserPatternConverterの拡張

PatternParserがその作業の多くが ( peek under the hood 思い出します) parsePatternParserオブジェクト。 PatternLayoutは、それから呼びます解析しますPatternConverterサブクラス インスタンスの結ばれたリストを生産するPatternParserのメソッド。 イベント・インスタンスをappendersによって使用される文字列に変えることは、 使われるコンバータのこのリンク・リストです。

我々の仕事は正しく我々が加えたい文字をフォーマットすることを 解釈するためにPatternParserサブクラスにあります。 幸いにも、各文字をフォーマットするために異なっている プロセスを解析することにおける1つのステップだけが 越えられなければならないように、PatternParserは設計されました。 解析することのブーブー作品はPatternParser.parse()メソッドに よってさらに実行されてす。PatternParser.finalizeConverterメソッドだけは 越えられなければなりません。これはつくるPatternConverterがフォーマット している文字にどれの基礎をおいたかについて決めるメソッドです。

PatternParserAppServerPatternParser)への 拡張は、そのスーパー・クラスに類似しています。それは、以下を使用します。

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;  }
   }
}

4. Categoryの拡張

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; }
}

5. CategoryFactoryの拡張

最後のステップは正しく我々の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.");
   }
}

Configurators

There is one a word of caution concerning the use of configurators that may create 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拡張が強化されるかもしれないいくつかのその他の方向が、あります。

  1. ホスト名属性はクラスとカテゴリー名のそれに類似したフォーマットしている取り決めをincorportateすることができました。そして、より重要な構成要素の特定の番号だけは表示されますそれによって。しかし、クラスとカテゴリー名で最も重要な構成要素が正の上にあること、で名前を主催します、それは正の上にす。
  2. プログラマーがバージョンを指定するコードにおいて、 文字列定数を変更することなくコードのバージョンを変更しやすくして以後、 バージョン番号を指定することは危険でありえました。 いくつかのソース管理プログラムはソースにバージョン番号を挿入することができます。 そうしないもののために、定数としてバージョン番号を含むことは後ほど混乱に至りそうであること。 それはこの欠点が言及したということを知っているのに良いです。

[訳注: これは熊坂祐二が翻訳しました。 日本語訳に対するコメントは、jajakarta-report@nekoyanagi.com宛に送って下さい。]