DBMS_OUTPUT.PUT_LINE:單行255字符限制(10g前)、緩衝區默認20KB易溢出、需SET SERVEROUTPUT ON;-- 10g+ 單行支持 32767 字符
SET SERVEROUTPUT ON SIZE UNLIMITED -- 客戶端緩衝區無限制
BEGIN
DBMS_OUTPUT.ENABLE(1000000); -- 會話級緩衝區 1MB
FOR i IN 1..1000 LOOP
DBMS_OUTPUT.PUT_LINE('Line '||i||': '||RPAD('x', 3000, 'x')); -- 3000字符/行
END LOOP;
END;
/
-- 注意:10g前版本單行確實限制255字符,需分段輸出
CREATE OR REPLACE PROCEDURE debug_log (
p_module IN VARCHAR2,
p_level IN VARCHAR2, -- DEBUG/INFO/WARN/ERROR
p_message IN VARCHAR2,
p_context IN VARCHAR2 DEFAULT NULL
) AS
v_json VARCHAR2(32767);
BEGIN
v_json := '{"ts":"'||TO_CHAR(SYSTIMESTAMP,'YYYY-MM-DD HH24:MI:SS.FF3')||
'","mod":"'||p_module||'","lvl":"'||p_level||
'","msg":"'||REPLACE(p_message,'"','\"')||
'","ctx":"'||NVL(REPLACE(p_context,'"','\"'),'')||'"}';
DBMS_OUTPUT.PUT_LINE(v_json);
END;
/
CREATE GLOBAL TEMPORARY TABLE debug_log_gt (
log_id NUMBER GENERATED ALWAYS AS IDENTITY,
log_ts TIMESTAMP DEFAULT SYSTIMESTAMP,
module VARCHAR2(100),
level VARCHAR2(10),
message VARCHAR2(4000),
context CLOB,
session_id NUMBER DEFAULT SYS_CONTEXT('USERENV','SID')
) ON COMMIT PRESERVE ROWS; -- 會話級保留,適合調試
-- 索引加速查詢
CREATE INDEX idx_debug_gt_ts ON debug_log_gt(log_ts);
CREATE INDEX idx_debug_gt_mod ON debug_log_gt(module);
CREATE OR REPLACE PACKAGE debug_pkg AS
PROCEDURE log (p_module VARCHAR2, p_level VARCHAR2, p_msg VARCHAR2, p_ctx CLOB DEFAULT NULL);
PROCEDURE flush; -- 批量提交
FUNCTION get_logs (p_module VARCHAR2 DEFAULT NULL, p_min_ts TIMESTAMP DEFAULT NULL) RETURN SYS_REFCURSOR;
END debug_pkg;
/
CREATE OR REPLACE PACKAGE BODY debug_pkg AS
TYPE log_arr IS TABLE OF debug_log_gt%ROWTYPE INDEX BY PLS_INTEGER;
g_buffer log_arr;
g_idx PLS_INTEGER := 0;
g_batch_size CONSTANT PLS_INTEGER := 100;
PROCEDURE log (p_module VARCHAR2, p_level VARCHAR2, p_msg VARCHAR2, p_ctx CLOB DEFAULT NULL) AS
BEGIN
g_idx := g_idx + 1;
g_buffer(g_idx).log_ts := SYSTIMESTAMP;
g_buffer(g_idx).module := p_module;
g_buffer(g_idx).level := p_level;
g_buffer(g_idx).message := SUBSTR(p_msg, 1, 4000);
g_buffer(g_idx).context := p_ctx;
g_buffer(g_idx).session_id := SYS_CONTEXT('USERENV','SID');
IF g_idx >= g_batch_size THEN flush; END IF;
END log;
PROCEDURE flush AS
BEGIN
IF g_idx > 0 THEN
FORALL i IN 1..g_idx
INSERT INTO debug_log_gt (log_ts, module, level, message, context, session_id)
VALUES (g_buffer(i).log_ts, g_buffer(i).module, g_buffer(i).level,
g_buffer(i).message, g_buffer(i).context, g_buffer(i).session_id);
g_idx := 0;
END IF;
END flush;
FUNCTION get_logs (p_module VARCHAR2 DEFAULT NULL, p_min_ts TIMESTAMP DEFAULT NULL) RETURN SYS_REFCURSOR AS
c SYS_REFCURSOR;
BEGIN
OPEN c FOR
SELECT * FROM debug_log_gt
WHERE (p_module IS NULL OR module = p_module)
AND (p_min_ts IS NULL OR log_ts >= p_min_ts)
ORDER BY log_ts DESC;
RETURN c;
END get_logs;
END debug_pkg;
/
-- 使用示例
BEGIN
debug_pkg.log('ORDER_PROC', 'DEBUG', 'Processing order 12345', '{"items":5,"total":999}');
debug_pkg.log('ORDER_PROC', 'INFO', 'Order completed');
debug_pkg.flush;
END;
/
-- 1. OS層創建目錄並授權Oracle用戶讀寫
mkdir -p /u01/app/oracle/debug_logs
chown oracle:oinstall /u01/app/oracle/debug_logs
-- 2. 數據庫層創建目錄對象並授權
CREATE OR REPLACE DIRECTORY DEBUG_LOG_DIR AS '/u01/app/oracle/debug_logs';
GRANT READ, WRITE ON DIRECTORY DEBUG_LOG_DIR TO app_user;
CREATE OR REPLACE PACKAGE file_debug_pkg AS
PROCEDURE write_line (p_dir VARCHAR2, p_file VARCHAR2, p_line VARCHAR2, p_append BOOLEAN DEFAULT TRUE);
PROCEDURE write_clob (p_dir VARCHAR2, p_file VARCHAR2, p_clob CLOB);
FUNCTION read_file (p_dir VARCHAR2, p_file VARCHAR2) RETURN CLOB;
END file_debug_pkg;
/
CREATE OR REPLACE PACKAGE BODY file_debug_pkg AS
PROCEDURE write_line (p_dir VARCHAR2, p_file VARCHAR2, p_line VARCHAR2, p_append BOOLEAN DEFAULT TRUE) AS
fh UTL_FILE.FILE_TYPE;
v_mode VARCHAR2(2) := CASE WHEN p_append THEN 'a' ELSE 'w' END;
BEGIN
fh := UTL_FILE.FOPEN(p_dir, p_file, v_mode, 32767);
UTL_FILE.PUT_LINE(fh, p_line);
UTL_FILE.FCLOSE(fh);
EXCEPTION
WHEN UTL_FILE.INVALID_PATH THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid directory: '||p_dir);
WHEN UTL_FILE.INVALID_MODE THEN
RAISE_APPLICATION_ERROR(-20002, 'Invalid mode: '||v_mode);
WHEN UTL_FILE.WRITE_ERROR THEN
RAISE_APPLICATION_ERROR(-20003, 'Write error: '||SQLERRM);
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(fh) THEN UTL_FILE.FCLOSE(fh); END IF;
RAISE;
END write_line;
PROCEDURE write_clob (p_dir VARCHAR2, p_file VARCHAR2, p_clob CLOB) AS
fh UTL_FILE.FILE_TYPE;
v_offset INTEGER := 1;
v_chunk VARCHAR2(32767);
BEGIN
fh := UTL_FILE.FOPEN(p_dir, p_file, 'w', 32767);
LOOP
v_chunk := DBMS_LOB.SUBSTR(p_clob, 32767, v_offset);
EXIT WHEN v_chunk IS NULL;
UTL_FILE.PUT(fh, v_chunk);
v_offset := v_offset + LENGTH(v_chunk);
END LOOP;
UTL_FILE.FCLOSE(fh);
END write_clob;
END file_debug_pkg;
/
-- 使用示例
BEGIN
file_debug_pkg.write_line('DEBUG_LOG_DIR', 'proc_'||TO_CHAR(SYSDATE,'YYYYMMDD_HH24MI')||'.log',
'START|'||TO_CHAR(SYSTIMESTAMP,'YYYY-MM-DD HH24:MI:SS.FF3')||'|ORDER_PROC|DEBUG|Processing order 12345');
END;
/
| 維度 | DBMS_OUTPUT | GTT臨時表 | UTL_FILE文件 |
|---|---|---|---|
| 單次輸出上限 | 32KB(緩衝區)/行32K | 受限於TEMP表空間/4000字符/行(CLOB無限) | 受限於文件系統/無行長限制 |
| 持久性 | 會話結束即失效 | 會話級(PRESERVE ROWS)或事務級(DELETE ROWS) | 永久持久,跨會話/實例可訪問 |
| 查詢/分析能力 | 無(僅客戶端顯示) | 全SQL查詢能力(索引、分析函數、JSON) | 需外部工具(Logstash/Splunk/Grep) |
| 性能開銷 | 極低(內存緩衝) | 低(批量INSERT + 批量提交) | 中(文件I/O,建議異步/批量) |
| 生產環境適用 | 僅開發/測試 | 可用(需控制TEMP空間、定期清理) | 推薦(離線分析、審計留痕) |
| 跨會話共享 | 不可 | 不可(GTT隔離) | 可(文件級共享) |
調試輸出無銀彈,**按數據量、持久需求、分析場景選型**:開發調試用DBMS_OUTPUT,單元測試/集成測試用GTT批量包,生產環境疑難雜症排查用UTL_FILE結構化日誌。建議構建統一日誌門面包,內部根據配置自動路由至三種後端,業務代碼零感知切換。