PL/SQLの障害対応で困るのは、エラーそのものよりも「どの処理が、どの入力で、どのセッションで、どこまで進んだのか」が分からない状態です。本番で再現しにくい性能劣化やロック待ち、外部API連携の失敗では、ログとトレースの設計が復旧時間を大きく左右します。
インストゥルメンテーションは、プログラムの中に観測点を入れて、運用時に状態を追えるようにする設計です。PL/SQLでは、ログテーブルだけでなく、DBMS_APPLICATION_INFO、DBMS_SESSION.SET_IDENTIFIER、DBMS_MONITOR、V$SESSION、AWR/ASH、SQL Traceを組み合わせると、障害対応と性能調査がかなり楽になります。
DBMS_APPLICATION_INFOの詳しい使い方は DBMS_APPLICATION_INFOで運用観測性を底上げする方法、Oracle側の監視観点は Oracle DBMS_APPLICATION_INFO完全ガイド もあわせて確認してください。
- ログ、トレース、監査、メトリクスの使い分け
- 相関ID、MODULE、ACTION、CLIENT_IDENTIFIERの設計
DBMS_APPLICATION_INFOとV$SESSIONによる可視化DBMS_MONITOR、SQL Trace、AWR/ASHとの連携- 自律トランザクションのログパッケージ
- ログ肥大化、個人情報、性能劣化を避ける本番チェック
最初に結論:ログだけでなく観測軸をそろえる
PL/SQLのインストゥルメンテーションは、単にログテーブルへ文字列をINSERTすることではありません。本番で使いやすい形にするには、最低でも次の4つをそろえます。
ログ・トレース・監査・メトリクスの違い
これらを混同すると、ログテーブルが肥大化したり、監査に必要な情報が残らなかったりします。目的別に役割を分けるのが安全です。
ログテーブルを設計する
ログはあとから検索するためのデータです。メッセージだけでなく、相関ID、モジュール、アクション、処理キー、経過時間、エラー情報を列として持たせます。すべてを1つのCLOBに詰めると検索しづらくなります。
ログテーブルの基本形
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の設定
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_MODULE と SET_ACTION を使うと、現在実行中の処理名と段階を V$SESSION に表示できます。これにより、DBAがセッションを見たときに、どのPL/SQL処理がどこで止まっているか判断しやすくなります。
MODULE/ACTION設定ラッパー
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;
/
業務処理での利用例
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を同じ形式で残します。
自律トランザクションのログパッケージ
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 を残します。さらに、入力値をそのまま残すのではなく、個人情報や機密値をマスクした要約を残します。
例外ログのテンプレート
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単位でトレースする
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で呼び出し経路を追跡する方法 にまとめています。
AWR/ASHとつながる名前を付ける
MODULE/ACTIONを設定しておくと、AWRやASHで処理単位を見つけやすくなります。モジュール名は人間が検索しやすい固定名にし、アクション名は処理段階を表す名前にします。相関IDのように毎回変わる値をMODULEへ入れると、集計しづらくなります。
AWR/ASHの読み方は Oracle AWR・ASH完全ガイド、AWRレポート単体は Oracle AWRレポート完全ガイド が関連します。
進捗とメトリクスを残す
バッチや大量処理では、開始・終了ログだけでは不十分です。処理済み件数、成功件数、失敗件数、現在チャンク、経過時間を残すと、止まっているのか進んでいるのか判断できます。
進捗ログの例
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.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レスポンス全文は肥大化しやすいです。個人情報、認証トークン、カード番号、パスワード、秘密鍵は絶対に平文で残さないようにします。
パーティション運用は Oracleパーティションテーブル完全ガイド が関連します。
避けたい設計
本番前チェックリスト
- 相関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、自律トランザクションログを役割分担して使うことが、本番で効くログトレース設計です。

