レベル: 中級 Hari H. Krishna (harkrish@in.ibm.com), Software Engineer, IBM
2005年 8月 09日 GLA(Generic Log Adapter)は、製品固有のネイティブ・ログ・フォーマットからCommon Base Eventフォーマットへと、ログファイルを変換します。このプロセスは、ルール・ベースのアダプター、あるいはスタティック・アダプターを使って行われます。どちらの方法を使うのが適切かを、ログ・エントリーの特性に基づいて判断する方法を学びましょう。ここで挙げる例と皆さんのログ・フォーマットを比較し、自分の目的に最適なアダプターを作成するためのヒントを得てください。
はじめに
GLA(Generic Log Adapter)は、IBMのオートノミック・コンピューティング・ツールキットの中に含まれているツールであり、様々なデータ・ソースから発生する、製品固有の様々なログ・フォーマットを、IBM Common Base Eventフォーマットに変換します。生成されるCommon Base Eventは、様々なモニタリング・ツールによって、さらに解析が行われます(GLAのダウンロードに関しては、参考文献のリンクを見てください)。
GLAには、Common Base Eventフォーマットへの変換を行うための様々な方法が用意されています。GLAの入力は、アダプターと呼ばれるコンフィギュレーション・ファイルです。GLAで定義されているアダプターには、ルール・ベースのアダプターとスタティック・アダプターという、2つのタイプがあります。ルール・ベースのアダプターでは正規表現を使い、製品固有のログ・フォーマットを、対応するCommon Base Eventフィールドに割り当てます。スタティック・アダプターでは、Java™クラスを使ってログ変換を行います。
どちらのタイプのアダプターを使うべきか、判断に迷うことがよくあります。この記事では、アダプターのタイプを選択するためのシナリオを、幾つか挙げて説明します。この記事を最大限に活用するためには、オートノミック・コンピューティングの基本的な原理を理解していること、そしてLTA(Log and Trace Analyzer)とGLAに関する実用的な知識を持っていることが必要です。
オートノミック・コンピューティングの面から見たログ変換の重要性
オートノミック・コンピューティングは、アプリケーションやシステム、あるいはネットワーク全体が、自己管理できるようにするための一連の技術を言います。自己管理には4つの特性があります。つまり自己構成(self-Configure)、自己修復(self-Heal)、自己最適化(self-Optimize)、自己防御(self-Protect)であり、これらを合わせて、よく「self-CHOP」特性と呼ばれます。
問題の根本原因を発見するためのプロセスを表すために、開発者は『問題判別(problem determination)』という言葉を使います。問題判別には、様々なオートノミック・コンポーネントの間でのコミュニケーションが含まれます。オートノミックでの問題判別も、何ら異なることはありません。つまりその目的は、おかしくなった部分に関する情報を保持し、根本原因が検出できるようにすることなのです。
Common Base Eventフォーマットは、ログやトレースのためのフォーマットですが、マルチ・コンポーネント・システムでの問題判別に伴うオートノミック・コンポーネント間でのコミュニケーション、という複雑な課題に対応しています。Common Base Eventフォーマットを利用することによって、複数のデータ・ソースからのデータを基に、システムの状態を相関させることができます。Common Base Eventフォーマットに関する詳しい情報は、参考文献の項を見てください。
『GLAアダプター・ファイル』は一般的に、特定なデータを抽出するためのルール・セットを含むXMLファイルです。IBMでは、LTAでアダプターを開発するためのUI(user interface)サポートを提供しています。アダプター・ファイルには、次のような様々なコンポーネントが含まれています。
コンテキスト・コンポーネント
アダプターのコンフィギュレーション・ファイルは、幾つかのコンテキストから構成されています。こうしたコンテキストはそれぞれ、そのコンテキストに関連付けられるログ・ファイルの変換ルールを記述した一連のコンポーネントから構成されます。また、各コンテキストには、GLAアオウトプッター・クラス(outputter class)を実行するために必要な他の情報や、センサー・クラス、入力ログファイル、出力情報(ファイル名やファイルが存在するディレクトリーなど)なども含まれています。各コンテキストは、同じアダプターのコンフィギュレーション・ファイルの中にある他のコンテキストとは独立に、別スレッドで実行します。こうしたコンポーネントの典型的な構成を図1に示します。
図1.コンテキスト・コンポーネントのビュー
センサー
処理対象のログ内容を読み取るための仕組みを定義します。
エクストラクター
センサーからのライン情報を受信し、イベント・メッセージを分離するための仕組みを提供します。単純に言うと、メッセージ境界を認識するためのルールを定義します。
パーサー
エクストラクターから受信するメッセージをCommon Base Eventエントリーに変換するための、一連のストリング・マッピングを定義します。
フォーマッター
属性とその値をパーサーから受け取り、Common Base EventのJavaオブジェクト・インスタンスを作ります。
アウトプッター(Outputter)
フォーマッターが提供する、フォーマットされたJavaオブジェクトを、保存に適当な形で加工します。
製品ログをCommon Base Eventに変換する上での様々な方法
問題判別における主要構成ブロックは、製品固有のログ・フォーマットからCommon Base Eventフォーマットと呼ばれる共通のログ・フォーマットに、製品ログを変換する部分です。GLAを使ったこの変換プロセスには、2つの方法、つまりルール・ベースのアダプターを使う方法と、スタティック・アダプターを使う方法とがあります。以下のセクションでは、IBM製品が生成するログのサンプルを使って、この2つの方法を詳細に説明します。そしてサンプルのログ・ファイルそれぞれに対して、次のような解説を行います。
- ログ・フォーマットに関する説明。ログ・フォーマットの説明と、ログから抽出されCommon Base Eventフィールドにマップされる、様々なフィールドの説明をします。また、ログ変換の方法にどんなものがあるか、その概要と、それぞれの長所短所を説明し、最後に推奨の方法を提案します。
- Common Base Eventフォーマットへの変換方法。推奨の方法を実装する上でのヒントとして、複雑なフィールドをCommon Base Eventフィールドに変換するためのコード断片を示します。
ルール・ベースのアダプターによる方法
多くの場合、特定のログ・フォーマットからCommon Base Eventフォーマットへのログファイルの変換は、ルール・ベースによる方法で行われます。この方法では、GLAが、ルールを使ってログ・メッセージを構文解析します。これらのルールは、典型的なJava正規表現です。通常は、特別な構造を持たない生のログ・メッセージやイベントから一片のデータを抽出するための、特定のパターンが書かれます。
ある特定のログ・ファイルに対して、この方法が有効かどうかを判断するためには、次のような基準を考えると良いでしょう。
The log has a clear start or end pattern (or both) for each record so that this can be provided to the extractor component of the GLA to extract individual records.
- GLAのエクストラクター・コンポーネントが個々のレコードを抽出できる程度に明確な、開始または終了(あるいはその両方)のパターンを、ログの各レコードが持っているかどうか。
- Common Base Eventにマップすべき全レコード・フィールドが、区切り文字で区切られているか、あるいは抽出が容易な、キーと値の対になっているかどうか。
- ログ・エントリーが上記のような「ログ情報」の形にフォーマットされていない場合には、少なくとも何らかのルールを使って抽出可能かどうか。
通常は、ほとんどの製品が生成するログは、単純であり、読める形のフォーマットになっています。こうしたログには、特別に複雑なことをしなくてもCommon Base Event形式に変換できるような直接的な情報を含んでいるものです。リスト1とリスト2に挙げたサンプル・ログは、単純なログ・フォーマットのログの好例です。
IBM HTTP Serverのアクセス・ログの例
リスト1のサンプル・ログは、IBM HTTP Serverのアクセス・ログから生成したものです。
リスト1. IBM HTTP Serverのアクセス・ログの例
9.26.157.44 - - [13/Jan/2003:11:44:21 -0500] "GET /WSsamples HTTP/1.1" 302 0
9.26.157.44 - - [13/Jan/2003:11:44:21 -0500] "GET /WSsamples/ HTTP/1.1" 302 550
9.26.157.44 - - [13/Jan/2003:11:44:21 -0500] "GET /WSsamples/en/index.html HTTP/1.1" 200 1127
9.26.157.44 - - [13/Jan/2003:11:44:21 -0500] "GET /WSsamples/en/Menu/Title.html HTTP/1.1" 200 1570
9.26.157.44 - - [13/Jan/2003:11:44:21 -0500] "GET /WSsamples/en/Menu/SamplesIntro.html HTTP/1.1" 200 2966
9.26.157.44 - - [13/Jan/2003:11:44:21 -0500] "GET /SamplesGallery/GalleryMenu HTTP/1.1" 200 5258
9.26.157.44 - - [13/Jan/2003:11:44:21 -0500] "GET /WSsamples/en/SamplesMaster.css HTTP/1.1" 200 11600
|
ログ・フォーマットに関する説明。リスト1のサンプル・ログは、各ログ・エントリーまたはログ・レコードが1ラインに完全に収まるログ・レコードの例です。このログ・レコードのレコード区切りは明確であり、ログ・レコードのフィールドには明確な境界(空白文字)があり、そして正規表現を使って抽出することができます。さらに各レコードには、レコードが作成された時を示すタイムスタンプ(つまりCommon Base Eventの「creationTime」)があり、容易にこれを抽出することができます。この場合では、ログ・フォーマットは単純であり、どのログ・フィールドも明確な境界を持っているため、ルール・ベースのアダプターによる方法でも、スタティック・アダプターによる方法(後のセクションで詳細に説明します)でも、どちらでも使用することができます。
このタイプのログでは、ルール・ベースのアダプターが最適なソリューションと言えます。つまり、ログ・エントリーが単純であり、単純なルール(正規表現)を使ってエントリーを分割できる場合には、固有フォーマットからCommon Base Eventフォーマットへの変換に、ルール・ベースのアダプターを使うべきです。ルール・ベースによる方法の方が、理解もしやすく維持管理も容易なので、どんな場合でも、まずルール・ベースの方法が使えないかどうかを検討した方が賢明です。ルール・ベースによる方法では、製品固有のログ・フィールドとCommon Base Eventプロパティーとの対応が、ルールを構築する中で容易に理解できる上、GLAルール・エディターを使えば、自分に必要な要件に従ってマッピングを修正することも簡単にできます。
例えば、あるログ・フイールドがCommon Base Eventのサブ要素、ExtendedDataElementにマップされており、エンドユーザーが、そのフィールドのマッピングをContextDataElementに変更したい、という状況を考えてみてください。ルール・ベースのアダプターでは、この種の変更は可能です。一方で、Javaパーサーの場合は、一般的にエンドユーザーはJavaソースファイルにアクセスできないため、自分の要求に合わせてソースファイルを変更することはできません。ルール・ベースのアダプターのもう1つの利点は、言語に関する『慣れ』が必要ないことです。例えば、このアダプターを使おうとするユーザーは、正規表現に関するスキルの他は、要素のマッピングの変更に関して、言語特有のスキルを持っている必要がありません。
Common Base Eventフォーマットへの変換方法。このサンプル・ログでは、どのラインも1つのログ・レコードに含まれており、レコードの全フィールドは空白で区切られています。どのレコードもIPアドレスで始まっているので、開始パターンは、^(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})|(\s+)のようになるでしょう。
レコードの全フィールドは空白で区切られているため、セパレーター・トークンの値としては、\s+が良いでしょう。これで、ログの中の他のフィールドは単純なルールで抽出することができます。例えば、レコードから作成時間を抽出するためには、図2のようなルールを使うことができます。
図2. IBM HTTP Serverのアクセス・ログから作成時間を抽出するルール
注意: セパレーター・トークンに関する詳しい情報は、「Generic Log Adapter用にハイパフォーマンスのルールを書く」(参考文献)を参照してください。
IBM WebSphere Application Serverのアクティビティー・ログ
リスト2のログ・サンプルは、IBM WebSphereR® Application Serverのログから抜粋したものです。
リスト2. IBM WebSphere Application Serverのアクティビティー・ログのサンプル
---------------------------------------------------------------
ComponentId: Application Server
ProcessId: 2676
ThreadId: 6a61d29f
SourceId: com.ibm.ws.management.connector.soap.JMXSoapAdapter
ClassName:
MethodName:
Manufacturer: IBM
Product: WebSphere
Version: Platform 5.0 [BASE 5.0.0 s0245.03]
ServerName: ninjazx7\ninjazx7\server1\
TimeStamp: 2003-01-16 10:53:10.445000000
UnitOfWork:
Severity: 3
Category: AUDIT
PrimaryMessage: ADMC0013I: SOAP connector available at port 8880
ExtendedMessage:
---------------------------------------------------------------
ComponentId: Application Server
ProcessId: 2676
ThreadId: 6a61d29f
SourceId: com.ibm.ws.messaging.JMSEmbeddedProviderImpl
ClassName:
MethodName:
Manufacturer: IBM
Product: WebSphere
Version: Platform 5.0 [BASE 5.0.0 s0245.03]
ServerName: ninjazx7\ninjazx7\server1
TimeStamp: 2003-01-16 10:53:11.286000000
UnitOfWork:
Severity: 3
Category: AUDIT
PrimaryMessage: MSGS0050I: Starting the Queue Manager
ExtendedMessage:
--------------------------------------------------------------
|
ログ・フォーマットに関する説明。リスト2は、マルチライン・レコードの単純ログ・フォーマットの例です。この場合でも、ログ・レコードには明確な区切りがあります。このサンプル・ログの全フィールドは明確に区切られており、鍵と値の対の形でログがとられています。この場合では、スタティック・アダプターによる方法、ルール・ベースのアダプターによる方法のどちらも可能ですが、維持管理の容易さや、理解しやすさを考えると、ここでもルール・ベースのアダプターによる方法を使った方が良いでしょう。
Common Base Eventフォーマットへの変換方法。リスト2のログは、マルチライン・レコードの例です。各レコードは、開始パターンとして -{15,} を使うことで区切ることができます。セパレーター・トークンの値として\s+を、指定トークン(designation token)の値として : を入れるのが良いでしょう。セパレーター・トークンを使うと、$h('') 機能を使って他のフィールドを容易に抽出できます。例えば図3に示すルールを使うと、作成時間を検索することができます。
図3. IBM WebSphere Application Serverのアクティビティー・ログから作成時間を抽出するルール
また、特別な構造を持たないデータやイベントをCommon Base Eventに変換する場合、ルール・ベースの方法と合わせてカスタムのJavaクラス・コールアウトを使うこともできます。こうすると、カスタムのJavaクラスを使って正規表現ルールをカスタム化することが、一層楽になります。これを次のセクションで説明します。
カスタムのJavaクラス・コールアウトを使う
ルール・ベースの方法とカスタムのJavaクラス・コールアウトを組み合わせると、正規表現では不十分な場合に、正規表現とJavaコードを一緒に使うことができます。このシナリオとして、あるCommon Base Eventプロパティーに対する値を、動的に置き換える場合が考えられます。
一部の製品が生成するログには、ログ・エントリーをCommon Base Eventに変換するために必要な情報の、一部しか含んでいません。こうした場合に変換を完全に行うためには、追加のデータが必要です。次に示す、AIX Syslogの例を見てください。
AIX Syslogログの例
リスト3のコード例は、AIXR® Syslogから抜粋したものです。ログされたデータは、単純で素直なものです。ログ・フィールドは空白で区切られています。
リスト3. AIX Syslogの例
May 2 15:51:15 dlfssrv syslogd: restart
May 2 15:51:15 dlfssrv syslogd: restart
May 2 15:51:15 dlfssrv unix: dlfs_mount entered..
May 2 15:51:15 dlfssrv unix: check_stubvp entered .....
May 2 15:51:15 dlfssrv unix: check_stubvp exited .....
May 2 15:51:15 dlfssrv unix: dlfs_change_vfsops entered....
May 2 15:51:15 dlfssrv unix: AMITA:1: dlfs_sync address:1D0FD0
May 2 15:51:15 dlfssrv unix: AMITA:2: dlfs_sync address:1D0FD0
May 2 15:51:15 dlfssrv unix: dlfs_change_vfsops exited....
|
ログ・フォーマットに関する説明。リスト3に示すログも、単純なフォーマットです。ログ・フィールドは空白で区切られており、レコードは1ラインに収まっています。各レコードの間は明確に区切られています。この場合もルール・ベースのアダプターを使うことができますが、ログ作成時間の情報に、year(年)のフィールドが含まれていません。この場合は、スタティック・アダプターによる方法を使うか、あるいはカスタムのJavaクラス・コールアウトを使います。
スタティック・アダプターを使うためには、ログ・ファイルが置かれているマシンの現在のyearを、デフォルトとして追加します。この方法の欠点は、yearを追加するメソッドをコード化する必要があることと、そのメソッドを変更する場合には、スタティック・アダプターを完全に再構築し、それに伴う回帰テストもすべて再構築しなければならない点です。
カスタムのJavaクラス・コールアウトを使うと、正規表現とJavaクラスを一緒に使うことができます。最適のソリューションは、システムのyearのデフォルトを置き換える機能を持つ拡張クラスを書くことです。そうすれば、もしメソッドを変更する場合にも、特定なクラスを置き換えるだけで済み、構文解析ルール全体を変更する必要はありません。1つか2つのフィールドの場合を除いて、正規表現では不十分な場合には、カスタムのJavaクラス・コールアウト・メソッドを使う方法の方が、(スタティック・アダプターを使うよりも)簡単です。
Common Base Eventフォーマットへの変換方法。AIX Syslogのログ・フォーマットは、IBM HTTP Serverのアクセス・ログと似ており、IBM HTTP Serverのアクセス・ログの場合と同じようなパターンで、ルール・ベースのアダプターを実装することができます。唯一の違いは、CreationTimeの抽出方法です。AIX Syslogで提供されている時間は不完全なので、デフォルトのyearを追加するために、図4に示すようなSubstation Extensionクラスを開発します。代替拡張クラスの作り方に関する詳しい情報は、「Using Java class callouts with the Generic Log Adapter」(参考文献にリンクがあります)を参照してください。
図4. IAIX Syslogでの作成時間から作成時間を抽出するルール
GLAでは、構文解析プロセスを容易にするための組み込み機能が用意されています。Common Base Event要素にはすべて、『use built in function』オプションという拡張機能を使って、デフォルト値を割り当てることができます(この機能の詳細に関しては、Eclipseの製品ヘルプ・ドキュメントを見てください)。この方法は、カスタムのJavaクラス・コールアウトを使う方法と似ています。この方法を使うと、複数のルールを組み合わせてCommon Base Eventプロパティーを規定することができます。つまり一部のルールはJavaメソッドを呼び出し、別のルールは正規表現を使う、といったことができるのです。
スタティック・アダプターによる方法
GLAでは、その機能の拡張として、カスタムのスタティック・アダプターが作れるようになっており、そのために一連のインターフェースが提供されています。スタティック・アダプターを使うと、正規表現を使う必要が全く無くなります。別の言い方をすると、一部のプロパティーに正規表現を使い、その他のプロパティーにJavaコードを使う、ということができなくなります。この方法では、構文解析もCommon Base Eventのプロパティーへの値の割り当ても、すべてJavaコードで処理する必要があります。
z/OSのコンポーネント・ログとsyslog
では、ルール・ベースのアダプターが使えない場合の例として、z/OSR®のコンポーネント・ログとz/OSのsyslogの例を見てみましょう。リスト4に示すログは、z/OSコンポーネント・ログの例です。通常、z/OSR®のコンポーネント・ログは、ログ・フォーマットで始まります。リスト4は、3つの異なるフォーマット、FULL FORMATとSHORT FORMAT、TALLY REPORTを示しています。これらのフォーマットは、それぞれロギング・レベルに対応します(Detailed/Summary/Tally Report Formatなど)。フォーマットの次には、システム名と作成時間が来ます。
リスト4. z/OSのコンポーネント・ログの例
COMPONENT TRACE FULL FORMAT
SYSNAME(SY1)
COMP(SYSRSM)
**** 06/11/2004
SYSNAME MNEMONIC ENTRY ID TIME STAMP DESCRIPTION
------- -------- -------- --------------- -------------
SY1 TRACEB 00000023 06:45:47.435823 Trace Buffer
FUNC1... TRACE Trace
JOBN1... *MASTER* ASID1... 0001 PLOCKS.. 00000000 CPU..... 0000
JOBN2... *MASTER* ASID2... 0001 RLOCKS.. 00000000
KEY..... 000F ADDR.... 022E0B28 ALET.... 00000000
07061000 00000076 00000000 00000000 00000000 00000000 00000000 022E0BF8
03000000 BB5A05F9 E832F44B 00000000 00000000
SY1 RSEPAG 00000008 06:55:42.761695 Enqueue Pageable Frame
FUNC1... VSMGTMN VSM Getmain Service
JOBN1... JES2MON ASID1... 001A PLOCKS.. 88004001 CPU..... 0000
JOBN2... JES2MON ASID2... 001A RLOCKS.. 88004000
KEY..... 0036 ADDR.... 02368408 ALET.... 00000000
1900
KEY..... 0001 ADDR.... 00183780 ALET.... 01000002
02274354 00047F80 81000000 07000000 0000001A 00000000 005E8000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
COMPONENT TRACE SHORT FORMAT
SYSNAME(SY1)
COMP(SYSRSM)
**** 06/11/2004
SYSNAME MNEMONIC ENTRY ID TIME STAMP DESCRIPTION
------- -------- -------- --------------- -----------
SY1 TRACEB 00000023 06:45:47.435823 Trace Buffer
SY1 RSEPAG 00000008 06:55:42.761695 Enqueue Pageable Frame
COMPONENT TRACE TALLY REPORT
SYSNAME(SY1)
COMP(SYSRSM)
TRACE ENTRY COUNTS AND AVERAGE INTERVALS (IN MICROSECONDS)
FMTID COUNT Interval MNEMONIC DESCRIBE
-------- ----------- ------------ -------- --------------------------------
00000001 371 10,162 XEPENTRY External Entry Point Entry
00000002 372 10,134 XEPEXIT External Entry Point Exit
00000003 54 1,088 FIX Page Being Fixed
|
作成時間の後、次のラインには通常、ヘッダー・ラインが含まれています。これを次のリスト5に示します。
リスト5. z/OSのコンポーネント・ログでのヘッダー構造の例
SYSNAME MNEMONIC ENTRY ID TIME STAMP DESCRIPTION
|
ヘッダー・ラインの後は、各ヘッダー・フィールドに対する値です。これをリスト6に示します。
リスト6. リスト5に示したヘッダー構造に対応する、値のライン
SY1 TRACEB 00000023 06:45:47.435823 Trace Buffer
|
こうしたタイプのログでは、ヘッダー・フィールドが一定であるという保証はありません。ヘッダーの位置は、様々な要因によって変動する可能性があります。その次のラインは、ヘッダー・ラインに対応する値を含んでいます。値のラインの次には、名前と値の対が延々と続きます(例えばFUNC1... TRACEは、FunctionName (FUNC1)とValue (TRACE)という、キーと鍵と値の対を表します)。こうした対の形式は任意です。
z/OSには、約20種類のコンポーネントがあります。それぞれのコンポーネントは、それぞれ特有の形式で自分のログを生成し、これらの形式はお互いに少しずつ異なっています。他のコンポーネント・トレース・レコードにも、似たようなタイプの名前と値の対が含まれていますが、名前は少し異なっています。ヘッダー・ラインとその値も、異なっている可能性があります。
ログ・フォーマットに関する説明。ログ・レコードは明確に区切られていますが、レコード中の要素の順序は不同です。つまり、ログ情報に関するセクションで説明した通り、ある一定の順序で要素が並んでいるという保証はありません。サンプル・ログの中の他の要素(キーと値の対)はCommon Base Eventフィールドにマップする必要がありますが、これも動的、つまり順序は不同です。従ってこのタイプのログ・レコード・フォーマットは、複雑なログ・フォーマットとして分類できます。ログの中での要素の順序は、その要素に対応するヘッダーの情報によって決まります。ヘッダー構造の例がリスト5、それに対応する値の例がリスト6です。
この場合では、ルール・ベースの方法は困難です。リスト4に示したログ・レコードは複雑であり、様々なヘッダーを含んでいます。Common Base Eventプロパティーに割り当てるべき値は、ヘッダーに依存します。もし、様々なヘッダー・フォーマットの組み合わせが全て分かっていれば、カスタムのJavaクラス・コールアウトと組み合わせてルール・ベースのアダプターを使う方法も可能かもしれません。しかし実際にはヘッダーと、それに対応する値は動的なため、スタティック・アダプターを使った方が賢明です。
Common Base Eventフォーマットへの変換方法。この記事では、アダプターを作成するために必要な通常のオペレーション(例えばアダプターのモニタリングから拡張して、Common Base Eventに必要な全要素を初期化するようなクラスの作り方)については説明しません。スタティック・アダプターを作るための基本的な情報については、Eclipseの製品ヘルプ・ドキュメンテーションを見てください。通常のオペレーションを行うことの他に、幾つか複雑なタスクがあるのです。それをここで説明します。
フォーマットのタイプを識別するために、条件付きループでレコードの最初のラインをスキャンします。リスト7に示すコード断片は、現在のラインがレコードの開始かどうかをチェックし、レコード・タイプを識別し、レコード・タイプをグローバルなrecord_type変数に割り当てます。
リスト7. ヘッダーの開始をチェックするコード断片
/**
* This function is used to check if the current line is start of the record and
* also the type of record
* @return true if current line is the first line of a header, false otherwise
*/
public boolean reachedFirstLineOfHeader()
{
// SUMMARY_FORMAT = "COMPONENT TRACE SUMMARY FORMAT"
if(currentLine.startsWith(SUMMARY_FORMAT))
{ {
//Set the global log_format variable to Summary format log
log_format = iSmmary_Format ;
return true ;
}
// SHORT_FORMAT = "COMPONENT TRACE SHORT FORMAT"
else if(currentLine.startsWith(SHORT_FORMAT))
{
//Set the global log_format variable to Short format log
return true ;
}
// TALLY_REPORT = "COMPONENT TRACE TALLY REPORT"
else if(currentLine.startsWith(TALLY_REPORT))
{
//Set the global log_format variable to Tally report format
return true ;
}
return false ;
}
|
構文解析ヘッダーを識別する。レコード・タイプが分かったら、次のラインには、システム名、コンポーネント名、作成時間などの情報が続きます。これらは通常の文字列操作を使って抽出します。次のラインは、ヘッダー・メッセージ(サンプルのヘッダー・ラインはリスト5に示します)であり、これは全フォーマットに共通です。ヘッダーを構文解析するコード断片を、リスト8に示します。
リスト8. ヘッダーを構文解析するコード断片
/**
* This function is used to parse the headers
* @param str this contains the string to be parsed
* @param sep this is the separator token
* @return, returns 1 in case of success and < 1 in case of failure
*/
public int parseHaeaders(String base, String hdrStr, String sep)
{
try{
//function used to set the start point for each field which is stored in
//the variable called m_hStartIndexHash
updateStartPoints(base) ;
int headerCount = 0;
String headerString ;
while(headerCount < m_hStartIndexHash.size())
{
headerString = "" ;
if(headerCount == (m_hStartIndexHash.size()-1) )
headerString = hdrStr.substring(getValueAt(headerCount) , hdrStr.length() );
else
headerString = hdrStr.substring(getValueAt(headerCount), getValueAt(headerCount+1));
//trimAll function is used to trim all the special characters and blank spaces
headerString = trimAll(headerString," ");
//once the header is extracted its being stored in a variable called m_hHeaderHash
if(!(headerString.equals("")))
m_hHeaderHash.put(""+headerCount , headerString) ;
headerCount++ ;
}
}catch(Exception e)
{
return -1;
}
return 1 ;
}
|
ヘッダーのどのフィールドも明確に(空白で)区切られていますが、各フィールドの開始点を決めなければなりません。例えば、リスト5に示したヘッダーでは、どのヘッダー・フィールドも空白で区切られています。ただし、空白の後に必ず新しいヘッダーが来るとは限りません。例えば、ENTRY IDとTIME STAMPには、変数の間に空白(フィールド区切り)がありますが、どちらも単一のレコード・フィールドです。
リスト9のコード断片を使うと、各フィールドの開始点を判定することができます。開始点の値は、m_hStartIndexHashというグローバル変数の中に保存されます。
リスト9. 開始点を抽出するコード断片
/**
* This function is used to determine start point for every header field
* @param base, string which contains start points
* @param hdrstr, string which contains header names
* @param sep, string which contains separator
* @return, returns 1 in case of success and < 1 in case of failure
*/
public int updateStartPoints(String base)
{
/* 10 20 30 47
* ------- -------- -------- --------------- -------------
* */
try {
int count = 0;
int indexCount = 0;
boolean stFlag = true;
while (count < base.length())
{
if ((base.charAt(count) == '-') && (stFlag))
{
m_hStartIndexHash.put("" + indexCount, "" + count);
stFlag = false;
indexCount++;
}
else if (base.charAt(count) == ' ')
{
stFlag = true;
}
count++;
}
} catch (Exception e)
{
return -1 ;
}
return 1 ;
}
|
ログ・レコードから開始点とヘッダーが抽出できたら、次のステップは、ヘッダーに対応する値を、値の文字列から抽出することです。値のラインのサンプルはリスト6に示しました。値を構文解析するコードをリスト10に示します。
リスト10. 値を構文解析するコード
/**
* This function is used to parse the values
* @param str this contains the string to be parsed
* @return, returns 1 in case of success and < 1 in case of failure
*/
public int parseValues(String str)
{
try{
if (m_hHeaderHash.size() < 1)
return 1;
if (getValueAt(m_hHeaderHash.size()-1) > str.length())
return 1;
}catch (Exception e)
{
return 1;
}
int valuesCount = 0;
String valueString ;
while(valuesCount < m_hStartIndexHash.size())
valueString = "" ;
if(valuesCount == (m_hStartIndexHash.size()-1) )
{
valueString = str.substring(getValueAt(valuesCount) , str.length() );
}
else
{
valueString = str.substring(getValueAt(valuesCount) , getValueAt(valuesCount+1));
}
valueString = trimAll(valueString , " ");
if(!(valueString.equals("")))
{
m_hValuesHash.put(""+valuesCount , valueString) ;
}
valuesCount++ ;
}
}catch(Exception e)
{
PrintOnConsole("CTRACE BACE 00003# "+e) ;
return 1;
}
return 1 ;
}
|
また、作成時間を割り当てるためのコードと、Common Base Event変数を作り、必要な要素に対してデフォルト値を割り当てるためのコードも必要です。さらに、ログ全体を横断するためのwhileループも必要です。
ヘッダー・ラインと、それに対応する値のラインを含んだ実際の製品ログでは、もしヘッダー要素のシーケンスが完全に分かっていれば、ルール・ベースのアダプターを使うこともできます。しかし、一部の製品ログでは、特定なログ・フォーマットからCommon Base Eventフォーマットへの変換にルール・ベースのアダプターを使うことはできません。次の製品ログは、ルール・ベースのアダプターが使えない場合の例です。
z/OSのSyslogの例
リスト11に示すログは、zOS Syslogが生成したログの例です。このサンプル・ログは、分割メッセージ(split message)を説明しています。他の製品用のログ、例えばIBM DB2R®の診断ログでも、分割メッセージ形式を使います。z/OS Syslogでのレコード・タイプは、レコード・ラインの開始文字で識別されます。そのラインが開始文字「M」で始まっていれば、マルチライン・レコードの開始であることを示します。もしレコード・ラインが文字「D」で始まっていれば、マルチライン・レコードの続きであることを示します。もし文字「E」で始まっていれば、マルチライン・レコードの終わりであることを示します。このログには、このコンテキストでは必要のない、大量の情報が含まれています。ここでは、現在のシナリオを説明するために必要なフォーマット情報のみを示し、他は無視しました。
リスト11. zOS Syslogの例
M 0000000 SY1 04098 08:39:29.74 00000290 IEA007I STATIC SYSTEM SYMBOL VALUES 060
D 060 00000290 &SYSNAME. = "SY1"
D 060 00000290 &SYSPLEX. = "LOCAL"
D 060 00000290 &DUMPQUAL. = "ALPS"
M 4040000 SY1 04098 08:39:29.94 00000290 ILR032I PAGE DATA SET HAS BEEN USED BY ANOTHER SYSTEM: 062
D 062 00000290 DATA SET NAME - SYS1.PLPA.PAGCOM
D 060 00000290 &SYSNUM. = "1"
E 060 00000290 &SYSR3. = "ZDR16Y"
D 062 00000290 SYSTEM NAME - SY1
D 062 00000290 VM USERID - SBAILEY
E 062 00000290 DATA SET LAST UPDATED AT 06:02:59 ON 04/03/2004 (GMT)
|
ログ・フォーマットに関する説明。リスト11に示したログは、マルチライン・レコードの例です(マルチライン・レコードは、開始文字「M」で識別できます)。z/OS Syslogでのマルチライン・レコードは制御ラインで始まり、その最初のラインの文字列の終わりには、マルチラインIDが付きます。(例えば、リスト11に示した最初のラインのマルチラインIDは060です。)後に続くラインは、同じ接続IDをヘッダーに持っています。このIDは、オリジナルのメッセージと他のラインとをリンクするために必要です。このタイプのメッセージ・フォーマットが、『分割メッセージ・フォーマット(split message format)』と呼ばれます。分割メッセージであることは、レコードの最初のカラムのコードに、前のレコードとの関連を示すテキストがあることからも分かります。この例には、2つの分割メッセージがあります。「D」は「データ・ライン」を表し、またマルチライン・メッセージの一部であることを示します。「E」は「エンド・ライン」を表し、このメッセージの最後のラインであることを示します。DラインのメッセージもEラインのメッセージも、接続ID として060と062を含んでいるため、メッセージがどのマルチラインに属すのかを、これを使って判断することができます。
z/OSのサンプル・ログの場合では、完全なレコードは一度にログされるわけではありません。ログ・レコードは部分的にしか得られないのです。そのため製品固有のログ・フォーマットからCommon Base Event形式への変換を行う場合には、最後の部分に突き当たるまでレコードを保持する必要があります。ルール・ベースのアダプターによる方法では、レコードをメモリー内に保持できないため、スタティック・アダプターを使う他に選択肢はありません。
Common Base Eventフォーマットへの変換方法。この場合の実装のうち、一般的な部分は、Common Base Eventを作り、その必要な要素に対してデフォルト値を割り当てる、といったタスクです。一方、複雑な部分は、レコードの開始を識別し、レコードをメモリーに保持し、レコードの最後に突き当たったら、そのログ・レコードをCommon Base Eventレコードに変換する、というタスクです。
リスト12に示すコード断片は、レコードの最初の文字に基づいてレコード・タイプを識別するロジックを示しています。このコードには、ライン・タイプに従ってさらに処理を行うためのロジックも含まれています。ラインの最初の文字が「D」の場合は、そのラインの接続IDが抽出され、そのラインは、マルチライン・メッセージ全てを保存しているマルチライン・ハッシュの中で、対応するラインに追加されます。開始文字「E」はラインの終わりを示すので、その接続IDを持つラインが抽出され、さらに処理を行うために送り出されます。最後に、開始文字「M」は新しいマルチライン・メッセージの始まりを示すので、そのラインは接続IDをキーとして、マルチライン・ハッシュに保存されます。次のコードは、このメッセージがどのように処理されるかを示しています。
リスト12. メッセージを処理するためのコード断片
/**
* This function is used to process the log messages based on the first character
* of the log record.
* @param str this contains the string to be parsed
*/
public void processMessage(String currentLine)
{
char firstChar = currentLine.charAt(0) ;
boolean newLineFlag = false ;
switch(firstChar)
{
case 'D':
case 'E':
case 'M':
{
// If character starts with any of the above mentioned character
// continue processing further else return and start reading next line.
newlineFlag = true ;
// check if the current line is not null
if(CompleteLine == null)
{
CompleteLine = currentLine ;
continue ;
}
if(firstChar == 'D' || firstChar == 'E' ) {
try{
/*
* Method used to extract connection id from the current line.
* If the input of the function is the line mentioned below then
* the value of connection id would be 060
* --------------------------------------------------------
* D 060 00000290 &SYSNAME. = "SY1"
* --------------------------------------------------------
*/
m_strconnID = getConnectionId(currentLine) ;
/*
* m_oMultiLine_hash variables the complete record as value with
* corresponding connection id as key. After the remaining part of
* the message is encountered it has to be appended to the existing
* original message for further processing
*/
String mainMessageLine = m_oMultiLine_Hash.get(m_strconnID);
mainMessageLine += currentLine ;
if (firstChar == 'E')
{
// If the first char is E, it indicates end of the multiline message
// hence process the message and added to Common Base Event array
CommonBaseEvent event = processCommon Base Event(currentLine) ;
}
else
{
// Append the current line message to existing multiline message line
// and update the m_oMultiLine_hash with most recent message
appendExistingLine(currentLine);
}
messages[arrayIndex] = event;
m_oMultiLine_Hash.remove(m_strconnID);
}
}
}catch(Exception ex){}
}else if(firstChar == 'M')
{
/*
* If first character is 'M' this indicates start of a multiline message
* Hence create a new key-value pair with connection id as key and current
* line as value and added it to the object m_oMultiLine_Hash object. The
* logic for the same has to be implemented in the below mentioned function
*/
createMultiLineMessage(CompleteLine) ;
}
}
|

 |
まとめ
製品のログをCommon Base Eventを変換するには、幾つかの方法があります。常に、まずルール・ベースのアダプターを使えないかどうかを検討すべきです。スタティック・アダプターを選択するのは、ログ・エントリーが明確な構造を持っていない場合、あるいは簡単に検出できるパターンや目立つ特徴、データを見つけて抽出できるような区切り文字がない場合に限るべきです。
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | |  | Hari H. Krishnaは、IBMのソフトウェア・エンジニアとして、オートノミック・コンピューティングのLTA(Log and Trace Analyzer)に焦点を当てており、ログ解析やレポート・ベースのアプリケーション開発に5年以上の経験を持っています。インドのHyderabadのOsmania Universityにて、コンピューター・サイエンスで修士号を取得しています。連絡先はharkrish@in.ibm.comです。 |
記事の評価
|