【PL/SQL】インストゥルメンテーション設計|DBMS_APPLICATION_INFO・ログトレース

【PL/SQL】インストゥルメンテーション設計とログトレース手法 PL/SQL

PL/SQLの障害対応で困るのは、エラーそのものよりも「どの処理が、どの入力で、どのセッションで、どこまで進んだのか」が分からない状態です。本番で再現しにくい性能劣化やロック待ち、外部API連携の失敗では、ログとトレースの設計が復旧時間を大きく左右します。

インストゥルメンテーションは、プログラムの中に観測点を入れて、運用時に状態を追えるようにする設計です。PL/SQLでは、ログテーブルだけでなく、DBMS_APPLICATION_INFODBMS_SESSION.SET_IDENTIFIERDBMS_MONITORV$SESSION、AWR/ASH、SQL Traceを組み合わせると、障害対応と性能調査がかなり楽になります。

DBMS_APPLICATION_INFOの詳しい使い方は DBMS_APPLICATION_INFOで運用観測性を底上げする方法、Oracle側の監視観点は Oracle DBMS_APPLICATION_INFO完全ガイド もあわせて確認してください。

この記事で扱うこと

  • ログ、トレース、監査、メトリクスの使い分け
  • 相関ID、MODULE、ACTION、CLIENT_IDENTIFIERの設計
  • DBMS_APPLICATION_INFOV$SESSION による可視化
  • DBMS_MONITOR、SQL Trace、AWR/ASHとの連携
  • 自律トランザクションのログパッケージ
  • ログ肥大化、個人情報、性能劣化を避ける本番チェック
スポンサーリンク

最初に結論:ログだけでなく観測軸をそろえる

PL/SQLのインストゥルメンテーションは、単にログテーブルへ文字列をINSERTすることではありません。本番で使いやすい形にするには、最低でも次の4つをそろえます。

相関ID1リクエスト、1ジョブ、1外部連携を追跡するIDです。ログ、V$SESSION、外部APIログで同じ値を使います。
MODULE / ACTIONDBMS_APPLICATION_INFOで現在処理名と処理段階をV$SESSIONへ出します。AWR/ASHにも残りやすくなります。
ログレベルERROR、WARN、INFO、DEBUGなどを分け、本番で過剰なDEBUGログを出さないようにします。
エラー情報SQLCODE、SQLERRM、FORMAT_ERROR_BACKTRACE、処理キー、入力要約を残します。

ログ・トレース・監査・メトリクスの違い

これらを混同すると、ログテーブルが肥大化したり、監査に必要な情報が残らなかったりします。目的別に役割を分けるのが安全です。

ログアプリケーション処理の出来事を残します。障害調査、再処理、問い合わせ対応に使います。
トレース性能解析や呼び出し経路の調査に使います。DBMS_MONITOR、SQL Trace、DBMS_TRACEなどが該当します。
監査誰が何をしたかを証跡として残します。改ざん耐性や保管期間を通常ログより厳しく考えます。
メトリクス処理件数、処理時間、失敗率、キュー滞留数などを数値化し、監視やアラートに使います。

ログテーブルを設計する

ログはあとから検索するためのデータです。メッセージだけでなく、相関ID、モジュール、アクション、処理キー、経過時間、エラー情報を列として持たせます。すべてを1つのCLOBに詰めると検索しづらくなります。

ログテーブルの基本形

log-table.sql
CREATE TABLE plsql_log (
  log_id            NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  log_level         VARCHAR2(10) NOT NULL,
  correlation_id    VARCHAR2(100),
  module_name       VARCHAR2(100),
  action_name       VARCHAR2(100),
  process_key       VARCHAR2(200),
  message           VARCHAR2(4000),
  detail_json        CLOB,
  sql_code          NUMBER,
  sql_error_message VARCHAR2(4000),
  error_backtrace   VARCHAR2(4000),
  elapsed_ms        NUMBER,
  session_user      VARCHAR2(128),
  current_user      VARCHAR2(128),
  client_identifier VARCHAR2(128),
  created_at        TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL
);

CREATE INDEX ix_plsql_log_01
  ON plsql_log(correlation_id, created_at);

CREATE INDEX ix_plsql_log_02
  ON plsql_log(module_name, action_name, created_at);

JSON形式で詳細を残す場合は、検索対象の主要キーだけ列にも出しておくと運用しやすくなります。Oracle JSONの基本は Oracle JSON完全ガイド、PL/SQLでJSONを扱う処理は JSON_TABLEでJSONを取り込む方法 が関連します。

相関IDとCLIENT_IDENTIFIERを設定する

相関IDは、Webリクエスト、バッチID、外部連携ID、ファイル名など、処理全体を追跡するためのIDです。DBセッションにも CLIENT_IDENTIFIER として設定すると、ログと V$SESSION を結びつけやすくなります。

CLIENT_IDENTIFIERの設定

client-identifier.sql
BEGIN
  DBMS_SESSION.SET_IDENTIFIER(
    client_id => :correlation_id
  );
END;
/

SELECT sid,
       serial#,
       username,
       client_identifier,
       module,
       action
  FROM v$session
 WHERE client_identifier = :correlation_id;

接続プールを使う場合は、リクエスト開始時に設定し、リクエスト終了時にクリアする運用が重要です。前リクエストの識別子が残ると、調査時に誤った紐付けになります。

DBMS_APPLICATION_INFOでMODULEとACTIONを出す

DBMS_APPLICATION_INFO.SET_MODULESET_ACTION を使うと、現在実行中の処理名と段階を V$SESSION に表示できます。これにより、DBAがセッションを見たときに、どのPL/SQL処理がどこで止まっているか判断しやすくなります。

MODULE/ACTION設定ラッパー

app-info-wrapper.sql
CREATE OR REPLACE PACKAGE instr_pkg AS
  PROCEDURE set_action(
    p_module IN VARCHAR2,
    p_action IN VARCHAR2
  );

  PROCEDURE clear_action;
END;
/

CREATE OR REPLACE PACKAGE BODY instr_pkg AS
  PROCEDURE set_action(
    p_module IN VARCHAR2,
    p_action IN VARCHAR2
  ) IS
  BEGIN
    DBMS_APPLICATION_INFO.SET_MODULE(
      module_name => p_module,
      action_name => p_action
    );
  END;

  PROCEDURE clear_action IS
  BEGIN
    DBMS_APPLICATION_INFO.SET_MODULE(NULL, NULL);
  END;
END;
/

業務処理での利用例

app-info-usage.sql
BEGIN
  instr_pkg.set_action('ORDER_IMPORT', 'VALIDATE');
  validate_orders(:load_id);

  instr_pkg.set_action('ORDER_IMPORT', 'MERGE');
  merge_orders(:load_id);

  instr_pkg.set_action('ORDER_IMPORT', 'NOTIFY');
  notify_result(:load_id);

  instr_pkg.clear_action;
EXCEPTION
  WHEN OTHERS THEN
    instr_pkg.clear_action;
    RAISE;
END;

遅いSQLの特定では、モジュール名・アクション名があると絞り込みやすくなります。SQL調査の全体像は Oracleで遅いSQLを特定する方法、実行計画の確認は Oracle DBMS_XPLAN完全ガイド が関連します。

ログ出力パッケージを作る

ログ出力は全処理で同じ形にします。各処理が自由にINSERTすると、列の使い方やメッセージ粒度がばらつきます。共通パッケージを作り、ログレベル、相関ID、モジュール、処理キー、詳細JSONを同じ形式で残します。

自律トランザクションのログパッケージ

logger-package.sql
CREATE OR REPLACE PACKAGE logger_pkg AS
  PROCEDURE write_log(
    p_level          IN VARCHAR2,
    p_message        IN VARCHAR2,
    p_process_key    IN VARCHAR2 DEFAULT NULL,
    p_detail_json    IN CLOB DEFAULT NULL,
    p_sql_code       IN NUMBER DEFAULT NULL,
    p_error_message  IN VARCHAR2 DEFAULT NULL,
    p_backtrace      IN VARCHAR2 DEFAULT NULL,
    p_elapsed_ms     IN NUMBER DEFAULT NULL
  );
END;
/

CREATE OR REPLACE PACKAGE BODY logger_pkg AS
  PROCEDURE write_log(
    p_level          IN VARCHAR2,
    p_message        IN VARCHAR2,
    p_process_key    IN VARCHAR2 DEFAULT NULL,
    p_detail_json    IN CLOB DEFAULT NULL,
    p_sql_code       IN NUMBER DEFAULT NULL,
    p_error_message  IN VARCHAR2 DEFAULT NULL,
    p_backtrace      IN VARCHAR2 DEFAULT NULL,
    p_elapsed_ms     IN NUMBER DEFAULT NULL
  ) IS
    PRAGMA AUTONOMOUS_TRANSACTION;
  BEGIN
    INSERT INTO plsql_log (
      log_level,
      correlation_id,
      module_name,
      action_name,
      process_key,
      message,
      detail_json,
      sql_code,
      sql_error_message,
      error_backtrace,
      elapsed_ms,
      session_user,
      current_user,
      client_identifier
    ) VALUES (
      p_level,
      SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER'),
      SYS_CONTEXT('USERENV', 'MODULE'),
      SYS_CONTEXT('USERENV', 'ACTION'),
      p_process_key,
      p_message,
      p_detail_json,
      p_sql_code,
      p_error_message,
      p_backtrace,
      p_elapsed_ms,
      SYS_CONTEXT('USERENV', 'SESSION_USER'),
      SYS_CONTEXT('USERENV', 'CURRENT_USER'),
      SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER')
    );

    COMMIT;
  EXCEPTION
    WHEN OTHERS THEN
      ROLLBACK;
      NULL;
  END;
END;
/

ログだけは本体処理がROLLBACKしても残したいことが多いため、自律トランザクションが候補になります。ただし、本体データ更新に自律トランザクションを使うのは避けます。詳しくは AUTONOMOUS_TRANSACTIONで独立処理を実装する完全ガイドPRAGMA AUTONOMOUS_TRANSACTION完全ガイド を参照してください。

例外処理とバックトレースを残す

例外ログでは、SQLERRM だけでは足りません。どの行で発生したのかを見るために DBMS_UTILITY.FORMAT_ERROR_BACKTRACE を残します。さらに、入力値をそのまま残すのではなく、個人情報や機密値をマスクした要約を残します。

例外ログのテンプレート

exception-log.sql
DECLARE
  v_started_at TIMESTAMP := SYSTIMESTAMP;
BEGIN
  instr_pkg.set_action('PAYMENT_JOB', 'APPLY_PAYMENT');

  apply_payment(:payment_id);

EXCEPTION
  WHEN OTHERS THEN
    logger_pkg.write_log(
      p_level         => 'ERROR',
      p_message       => 'apply_payment failed',
      p_process_key   => 'payment_id=' || :payment_id,
      p_sql_code      => SQLCODE,
      p_error_message => SQLERRM,
      p_backtrace     => DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,
      p_elapsed_ms    => EXTRACT(SECOND FROM (SYSTIMESTAMP - v_started_at)) * 1000
    );
    RAISE;
END;

エラースタックの読み方は ORA-06512の原因と読み方、PL/SQL例外処理の基本は PL/SQL例外処理の基本 が関連します。

DBMS_MONITORで条件付きトレースを有効化する

常に詳細ログを出すと本番負荷が高くなります。性能調査では、MODULE / ACTION やセッションを条件にして、必要な範囲だけSQL Traceを有効化します。DBMS_MONITOR はそのための標準的な手段です。

MODULE/ACTION単位でトレースする

dbms-monitor-trace.sql
BEGIN
  DBMS_MONITOR.SERV_MOD_ACT_TRACE_ENABLE(
    service_name => SYS_CONTEXT('USERENV', 'SERVICE_NAME'),
    module_name  => 'ORDER_IMPORT',
    action_name  => 'MERGE',
    waits        => TRUE,
    binds        => TRUE
  );
END;
/

-- 調査後は必ず無効化する
BEGIN
  DBMS_MONITOR.SERV_MOD_ACT_TRACE_DISABLE(
    service_name => SYS_CONTEXT('USERENV', 'SERVICE_NAME'),
    module_name  => 'ORDER_IMPORT',
    action_name  => 'MERGE'
  );
END;
/

取得したSQL Traceはtkprofで整形して、待機、実行回数、フェッチ回数、バインド、実行計画を見ます。手順は Oracle SQL Trace・tkprof完全ガイド が関連します。

DBMS_TRACEは呼び出し経路の調査に使う

SQLの性能ではなく、PL/SQL内の呼び出し経路や例外発生箇所を追いたい場合は DBMS_TRACE が候補になります。常用ログではなく、調査時に一時的に使うトレースとして考えます。

DBMS_TRACEを使う場面

dbms-trace-usecase.sql
-- どのサブプログラムを通って例外に到達したか調べたい
-- 複雑なパッケージ間呼び出しの経路を可視化したい
-- 本番常時ではなく、調査対象を絞って短時間だけ使う

具体的な使い方は DBMS_TRACEで呼び出し経路を追跡する方法 にまとめています。

AWR/ASHとつながる名前を付ける

MODULE/ACTIONを設定しておくと、AWRやASHで処理単位を見つけやすくなります。モジュール名は人間が検索しやすい固定名にし、アクション名は処理段階を表す名前にします。相関IDのように毎回変わる値をMODULEへ入れると、集計しづらくなります。

MODULEORDER_IMPORT、PAYMENT_JOB、REST_ORDER_APIのような処理名にします。
ACTIONVALIDATE、MERGE、CALL_API、COMMITなど、処理段階を表します。
CLIENT_IDENTIFIERリクエストID、バッチID、ユーザーIDなど、追跡用の値を入れます。
ログMODULE/ACTION/CLIENT_IDENTIFIERを同じ値で残し、DBビューとアプリログを突合できるようにします。

AWR/ASHの読み方は Oracle AWR・ASH完全ガイド、AWRレポート単体は Oracle AWRレポート完全ガイド が関連します。

進捗とメトリクスを残す

バッチや大量処理では、開始・終了ログだけでは不十分です。処理済み件数、成功件数、失敗件数、現在チャンク、経過時間を残すと、止まっているのか進んでいるのか判断できます。

進捗ログの例

progress-log.sql
CREATE TABLE batch_metric (
  batch_id      VARCHAR2(100),
  module_name   VARCHAR2(100),
  action_name   VARCHAR2(100),
  chunk_no      NUMBER,
  processed_cnt NUMBER,
  success_cnt   NUMBER,
  error_cnt     NUMBER,
  elapsed_ms    NUMBER,
  recorded_at   TIMESTAMP DEFAULT SYSTIMESTAMP
);

INSERT INTO batch_metric(
  batch_id,
  module_name,
  action_name,
  chunk_no,
  processed_cnt,
  success_cnt,
  error_cnt,
  elapsed_ms
) VALUES (
  :batch_id,
  'ORDER_IMPORT',
  'MERGE',
  :chunk_no,
  :processed_cnt,
  :success_cnt,
  :error_cnt,
  :elapsed_ms
);

大量処理のコミット頻度や再実行設計は 大量データ処理のコミット頻度とUNDO最適化、トランザクション全体の設計は PL/SQLトランザクション設計パターン も参考になります。

REST API連携では相関IDを外へ渡す

PL/SQLから外部REST APIを呼ぶ場合は、DB内ログだけでなく外部システム側のログとも紐付ける必要があります。HTTPヘッダーへ相関IDを渡し、レスポンスコード、レスポンス本文の要約、リトライ回数をログへ残します。

UTL_HTTPで相関IDを渡す例

utl-http-correlation.sql
UTL_HTTP.SET_HEADER(
  r     => req,
  name  => 'X-Correlation-ID',
  value => SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER')
);

logger_pkg.write_log(
  p_level       => 'INFO',
  p_message     => 'external api called',
  p_process_key => 'order_id=' || p_order_id,
  p_detail_json => '{"endpoint":"payment","status":200}'
);

UTL_HTTPの実装は UTL_HTTPでREST API連携する方法、REST向けの例外レスポンス設計は 例外をJSON形式で返すREST対応API設計 が関連します。

ログ肥大化と機密情報に注意する

ログは便利ですが、無制限に残すと性能と容量を圧迫します。特にCLOB詳細、DEBUGログ、入力値全文、外部APIレスポンス全文は肥大化しやすいです。個人情報、認証トークン、カード番号、パスワード、秘密鍵は絶対に平文で残さないようにします。

保管期間調査に必要な期間と監査要件を分け、通常ログは期限を決めて削除またはアーカイブします。
パーティション日付や月単位でログテーブルをパーティション化すると、削除や退避が楽になります。
マスキングメール、電話、トークン、認証情報、個人情報はマスクまたはハッシュ化します。
ログレベル本番ではDEBUGを抑制し、対象相関IDや対象MODULEだけ詳細化できる仕組みにします。

パーティション運用は Oracleパーティションテーブル完全ガイド が関連します。

避けたい設計

ログ文字列だけ残す検索軸がなく、障害時に相関IDや処理キーで追えません。
全処理でDEBUGを常時出す本番負荷と容量を圧迫します。条件付きで詳細化できる設計にします。
例外を握りつぶすログだけ残して正常終了にすると、呼び出し元が失敗に気づけません。原則RAISEします。
MODULEに可変IDを入れるAWR/ASHで集計しづらくなります。可変IDはCLIENT_IDENTIFIERやログ列に入れます。
個人情報をそのまま残す調査しやすさより情報漏えいリスクが勝ちます。必要最小限にマスクします。

本番前チェックリスト

  • 相関IDをDBログ、V$SESSION、外部APIログで共通利用している
  • DBMS_APPLICATION_INFO でMODULE/ACTIONを設定している
  • 接続プール利用時にCLIENT_IDENTIFIERを開始時設定・終了時クリアしている
  • エラーログにSQLCODE、SQLERRM、FORMAT_ERROR_BACKTRACEを残している
  • ログパッケージは本体処理の失敗を邪魔しない
  • DBMS_MONITORやSQL Traceは対象を絞って有効化し、調査後に無効化する
  • AWR/ASHで検索しやすい固定MODULE名を使っている
  • 個人情報、認証情報、秘密値を平文ログに残していない
  • ログテーブルの保管期間、削除、アーカイブ、パーティション方針がある
  • DEBUGログを本番で条件付きにできる

まとめ

PL/SQLのインストゥルメンテーションは、ログを出すだけではなく、相関ID、MODULE/ACTION、CLIENT_IDENTIFIER、例外情報、進捗メトリクスを一貫して設計することです。これにより、DBA、開発者、運用担当が同じ軸で障害や性能問題を追えるようになります。

通常ログは検索しやすく、トレースは必要なときだけ詳細に、監査は改ざんや保管要件を意識し、メトリクスは監視に使います。DBMS_APPLICATION_INFO、DBMS_MONITOR、AWR/ASH、SQL Trace、自律トランザクションログを役割分担して使うことが、本番で効くログトレース設計です。