4-3. ログ出力の共通化

ログファイル出力の共通化

一般的な業務システムであれば、ログ出力に必要なのは、実行中の処理を特定するIDとメッセージ文言、それにメッセージの種類(情報、警告、異常)が必要最低限の情報かと思います。
出力先のディレクトリ、ファイル名はシステム内で決められたルールに則り、提供する共通関数は、必要最低限とし、個々の開発者が独自に指定できないようにします。

また、ファイルのオープン・クローズの制御も前章で記述したとおり、共通関数への一度のファイル書込みの処理の中で、ファイルのオープン・クローズを行ってしまうという方法もあります。
その場合のソースコードは以下のようになります(仮の定数や関数があるのでこのままでは動きません)。

CREATE PROCEDURE xxx_put_log(
  ln_proc_id IN NUMBER,
  lv_message_type IN VARCHAR2,
  lv_message_text IN VARCHAR2)
IS
  file_type utl_file.file_type;
BEGIN
  -- ファイルオープン
  -- DIROBJは出力先のディレクトリオブジェクト。定数にしておきたい。
  -- filenameはシステム固有のファイル命名ルールに応じたファイル名を戻す関数
  file_type := utl_file.fopen(DIROBJ, filename(ln_proc_id), 'a', 32767);
  -- ファイル書込み
  -- msg_formatは、出力文字列をmessage_typeに応じて加工する関数
  utl_file.put_line(file_type, msg_format(lv_message_type, lv_message_text));
  -- ファイルクローズ
  utl_file.fclose(file_type);
EXCEPTION
  WHEN OTHERS THEN
    IF utl_file.isopen(file_type) THEN
      utl_file.fclose(file_type);
    END IF;
    RAISE;
END xxx_write;

ディレクトリオブジェクト名はグローバルの固定値とします。
システムの個別要件にもよりますが、ログの出力先が複数存在するシステムはレアなケースかと思います。
大抵の場合は、同一システム内で同じ出力先になるかと思います。

ファイル名は、個々に指定するのではなく、システムの命名ルールを関数化します(filename関数)。

ログ出力メッセージは、上記サンプルでは受け取った文言をそのまま出力するだけにしていますが、ここも実際の開発においては、文言のフォーマット処理を関数化し、第2引数のメッセージタイプと合わせて整形された文言を出力するのが望ましいでしょう(msg_format関数)。

この方式を採用した場合のデメリットですが、ファイルのオープン・クローズはそれなりの処理コストが発生しますので、性能要件が厳しく、ディスクI/O性能の低いシステムでは、このような方式はお勧めできません。各々の処理でファイルのオープン・クローズを行う方が良いでしょう。
ただし、その際もディレクトリオブジェクトやファイル名の命名ルール、出力文言のフォーマットは共通化し、統一感を持たせるべきです。

エラー発生箇所の特定

ログに何を出力すべきかは、「実装方針の検討~ログ出力~」の中で触れました。

その中で一点詳細を掘り下げずにおきました。
ステップ番号の記述です。
例外発生時に、原因となった箇所を特定するためにプログラム中に一意となる値を変数に保持しておくものです。
これは、エラー発生時に調査をしやすくするために、エラー発生箇所を記しておくためのものでした。

このステップ番号にはもう一つの用途があります。
単体テストで、網羅テストを行う際に、このステップ番号を全て出力することができれば、プログラムの実行に際して全てのロジックを網羅できたかどうかの検証は楽になります。

また、想定しないエラーが発生した際に、エラーがどこで発生したかだけではなく、ロジックがどういう流れでエラー発生箇所に到達したかを検証する際にも、このステップ番号を全て出力することに意義があります。

しかし、それらを実現するために都度、ログ出力を行うためにプロシージャを記述するのでは作業量に見合うメリットが得られません。

それを効率的にするためには、「ステップ番号を代入すると同時にデバッグログも出力する。かつ不要時はログ出力しない」とすれば良いのです。
変数は共通パッケージのグローバル変数として保持することになってしまいますが、やむを得ないでしょう。
デバッグログの要・不要については、システムで保持するマスタにY/Nの固定値を保持し、Yなら出力、Nなら出力しなければ良いです。

ログをDBに出力する場合

「3-3. 実装方針の検討~「ログ」の出力~」で述べたように、自律型トランザクションを使用してログをDBに出力するという方法もあります。
その場合も考えるべき事は変わりません。
utl_file.put_lineで文言を出力する代わりに、自律型トランザクションのためにPRAGMA AUTONOMOUS_TRANSACTION を宣言した関数内で、ログテーブルにINSERTを発行するだけです。

自律型トランザクションを使用することで、例外発生時のrollbackを気にする必要はありませんし、ログテーブルへの書込はINSERTしか使用しませんので、トランザクション制御も難しいことを考える必要はありません。
参照方法については検討が必要ですが、システム担当者しか見ないのであればSQLで十分ですし、昨今であればBIツールも合わせて導入することが多いかと思いますので、BIツールを使用してログ参照するという方法も考えられます。

2018-07-01

Posted by tfurukaw