关于在 PLSQL 中实现调试功能的方法
王宪(samt007@qq.com)
2017/4/6
前言
一个健康的 PLSQL,应该都带有一套完整的调试逻辑。特别是那些功能很复杂的 PLSQL,就更加有必要具备调试功
能了。否则,当 PLSQL 处理数据出现问题的时候,分析(处理)起来会相当的困难。
举个例子,Oracle EBS 标准功能的 PLSQL(特别是 API),如果 Oracle 没有自带调试功能给我们做看每一步骤的调试
结果,单单通过看代码模拟其执行逻辑来找问题,基本上是不可能处理问题的!
当然,我们编写的代码,实际上大部分的都并没有很复杂,所以对调试部分没太高的要求。这里也建议按照实际情
况来做。但是一些重要的并且是复杂的功能,还是必须要考虑如何添加调试!
实现调试的办法
现在根据这几年写 PLSQL 的经验,得出的一套如何在 PLSQL 中实现调试的方法,特意分享一下。如果有更加好的方
法,也可以一起讨论一下!
具体实现办法:
1. 首先,统一新建 3 个客户化的 Profile 配置来实现调试功能的开关:
CUX:程序调试级别
设置调试的级别。这里是仿照标准功能的调试逻辑来做的一个设定。作用就是是否启用调试,以及调试的数据输出的
明细级别!
具体:
XYG_ALD_DEBUG_LEVEL:设置查看调试消息的级别
0:不查看调试(默认值)
1:查看调试程序主要消息级别=1 的消息
2:查看消息的调试级别<=2 的信息
3:查看消息的调试级别<=3 的信息
4:查看消息的调试级别<=4 的信息
5:查看消息的调试级别<=5 的信息
举个例子,如果我的调试消息设置是:调试级别 3,调试显示 3 的信息。
则当 XYG_ALD_DEBUG_LEVEL 级别是 2 的时候,这个 3 的调试信息是不会显示的。
当 XYG_ALD_DEBUG_LEVEL 级别大于等于 3 的时候,这个消息才会显示出来!
具体可以自己写一个例子来理解。
CUX:程序调试方式
就是调试结果显示的方式。
一般来说有下面几种方式:
调试方式 XYG_ALD_DEBUG_TYPE:
DBMS_OUTPUT 直接输出
FILE_DEBUGLOG 文档输出
REQUEST_DEBUGLOG 请求日志输出
DATA_DEBUGLOG 表格数据输出
DBMS_OUTPUT:这个不用多说了,实际上就是调用 DBMS_OUTPUT.PUT_LINE 在开发工具直接显示消息。
FILE_DEBUGLOG:这个很重要!不过要配合一些额外的定义,才可以看到这种输出的结果。这种方式的调试结果以文件
的形式存在服务器里面。适合那种超大数据量输出的调试信息。
REQUEST_DEBUGLOG:如果 PLSQL 是在请求中调用的话,可以考虑用这种方式查看调试信息。调试信息会直接显示在
请求的日志里面。
DATA_DEBUGLOG 表格数据输出:就是将调试的结果输出在 fnd_log_messages 表。和标准功能的保持一致!
CUX:程序调试文件
结合调试方式= FILE_DEBUGLOG 文档输出使用。
就是定义调试结果产生的文件是什么。
注意:如果这个设定为空,则默认文件= XYG_PUB_AUTOMAIL_PKG.XYG_DF_UTL_FILE_DIR||'/'||userenv('SESSIONID')||'.log'
例如:
2. PLSQL 里面如何使用调试的方法:
首先,在要添加调试逻辑的 PKG 包体里面,建议直接添加下面这些脚本,二次简单封装一下公用的调试处理包,方便
调用:
例如:
CREATE OR REPLACE PACKAGE BODY APPS.XYG_ALBND_PACK_PKG
AS
--===============================================================
-- Debug 处理
--===============================================================
--P_DEBUG_LEVEL:该调试信息的级别
PROCEDURE DEBUGLOG (P_DEBUG_LEVEL IN NUMBER,P_DEBUG_MSG IN VARCHAR2)
IS
BEGIN
XYG_ALD_DEBUG_PKG.DEBUGLOG(' XYG_ALBND_PACK_PKG ',P_DEBUG_LEVEL,P_DEBUG_MSG);
END DEBUGLOG;
--简化版本,默认该消息的是 level=1 的
PROCEDURE DEBUGLOG1 (P_DEBUG_MSG IN VARCHAR2)
IS
BEGIN
XYG_ALD_DEBUG_PKG.DEBUGLOG(' XYG_ALBND_PACK_PKG ',1,P_DEBUG_MSG);
END DEBUGLOG1;
….
接着,在需要显示调试消息的地方,直接调用这个函数即可:
DEBUGLOG(1,'GENERATE_XXXXXXXXXXXXX(+)'); 或者:DEBUGLOG1('GENERATE_XXXXXXXXXXXXX(+)');
需要注意的是,DEBUGLOG 的第一个参数是该消息的显示级别。换句话说,就是你希望这个消息在什么级别显示它!
举个例子,如果设置为 3,则消息基本是 3 级,当设置显示的消息级别大于等于 3 的时候,该消息会显示。
3. 程序如何查看调试的结果:
------------------------------
---1 普通在 Toad 的 output 调试样例:
DECLARE
L_RETCODE NUMBER;
L_ERRBUF VARCHAR2(4000);
BEGIN
FND_PROFILE.PUT('XYG_ALD_DEBUG_LEVEL',1);
FND_PROFILE.PUT('XYG_ALD_DEBUG_TYPE','DBMS_OUTPUT');
XYG_ALBND_PACK_PKG.AUTO_CREATE_PACK(
'PO_HEADERS_ALL'
,2579287
,SYSDATE
,5954
,'NORMAL'
,l_retcode
,l_errbuf
);
DBMS_OUTPUT.PUT_LINE(L_RETCODE||'-'||L_ERRBUF);
END;
调试结果:
------------------------------
---2 调试结果文件输出样例:
DECLARE
L_RETCODE NUMBER;
L_ERRBUF VARCHAR2(4000);
BEGIN
FND_PROFILE.PUT('XYG_ALD_DEBUG_LEVEL',2);
FND_PROFILE.PUT('XYG_ALD_DEBUG_TYPE','FILE_DEBUGLOG');
FND_PROFILE.PUT('XYG_ALD_DEBUG_FILE'
,'/oracle/vis/apps/apps_st/appl/attchment/12.0.0/BATCH_UPLOAD_TEMP/SAMT_TEST001.log');
XYG_ALBND_PACK_PKG.AUTO_CREATE_PACK(
'PO_HEADERS_ALL'
,2579287
,SYSDATE
,5954
,'NORMAL'
,l_retcode
,l_errbuf
);
DBMS_OUTPUT.PUT_LINE(L_RETCODE||'-'||L_ERRBUF);
END;
--查看文件的路径
SELECT FND_PROFILE.VALUE('XYG_ALD_DEBUG_FILE') FROM DUAL
调试结果:
------------------------------
--3 如果用数据输出的
DECLARE
L_RETCODE NUMBER;
L_ERRBUF VARCHAR2(4000);
BEGIN
FND_PROFILE.PUT('XYG_ALD_DEBUG_LEVEL',2);
FND_PROFILE.PUT('XYG_ALD_DEBUG_TYPE','DATA_DEBUGLOG');
XYG_ALBND_PACK_PKG.AUTO_CREATE_PACK(
'PO_HEADERS_ALL'
,2579287
,SYSDATE
,5954
,'NORMAL'
,l_retcode
,l_errbuf
);
DBMS_OUTPUT.PUT_LINE(L_RETCODE||'-'||L_ERRBUF);
END;
---查看调试信息:
select to_char(systimestamp, 'yyyy-mm-dd hh24:mi:ss:ff3') debug_time,session_id,module||'[---]'||message_text
from fnd_log_messages
where timestamp > sysdate-1
and session_id=userenv('SESSIONID')
order by LOG_SEQUENCE;
调试结果:
完工!就是这么的简单,让您写的 PLSQL 就拥有了完整的而且有非常好扩展性的调试功能!
实现调试功能的核心源代码
其实就是封装好的:XYG_ALD_DEBUG_PKG
CREATE OR REPLACE PACKAGE APPS.XYG_ALD_DEBUG_PKG
AS
/******************************************************************************
NAME: XYG_ALD_DEBUG_PKG
PURPOSE: PKG For XYG_ALD_DEBUG_PKG debug 处理专用的 PKG
REVISIONS:
Ver Date Author Description
--------- ---------- --------------- ------------------------------------
1.0 2016/09/13 Sam.T 1,New Create the pkg
******************************************************************************/
---全局参数
G_USER_ID NUMBER := NVL(FND_PROFILE.VALUE('USER_ID'), -1);
--FND_GLOBAL.USER_ID;
--User ID, Sysadmin here
G_LOGIN_ID NUMBER := NVL(FND_PROFILE.VALUE('login_ID'), -1);
---消息的调试的级别定义
/*
0:不启动调试
1:调试程序主要流程,不进入任何的循环
2:第一层主循环(游标的每批处理)的信息
3:第二层循环,进入到每一行的处理
4:第三层循环,每一行的明细的调试信息
5:第四层循环,调试最明细的信息*/
G_L0 CONSTANT NUMBER := 0;
G_L1 CONSTANT NUMBER := 1;
G_L2 CONSTANT NUMBER := 2;
G_L3 CONSTANT NUMBER := 3;
G_L4 CONSTANT NUMBER := 4;
G_L5 CONSTANT NUMBER := 5;
-----------------------------
--debug 信息输出标识
-----------------------------
FUNCTION DEBUG_ENABLE(P_DEBUG_LEVEL IN NUMBER)
RETURN BOOLEAN;
-----------------------------
--debug 信息输出处理
--P_MODULE : 调 试 的 是 哪 个 包 , 例 如 XYG_ALBND_PACK_PKG ,; 也 可 以 准 确 到 是 : 包 . 过 程 。 例 如 :
XYG_ALBND_PACK_PKG.AUTO_CREATE_PACK
--P_DEBUG_LEVEL:调试级别
--P_DEBUG_MSG:调试的信息
-----------------------------
PROCEDURE DEBUG_OUT (P_MODULE IN VARCHAR2
,P_DEBUG_LEVEL IN NUMBER
,P_DEBUG_MSG IN VARCHAR2);
-----------------------------
--debug 信息处理封装版本
-----------------------------
PROCEDURE DEBUGLOG (P_MODULE IN VARCHAR2
,P_DEBUG_LEVEL IN NUMBER
,P_DEBUG_MSG IN VARCHAR2);
--该包可以不用显式调用。在有需要的时候调用即可。
--因为当 session 断开之后,其打开的文件会自动 close。
PROCEDURE STOP_DEBUGLOG;
-----------------------------
--debug 信息写入 fnd_log_messages
-----------------------------
PROCEDURE INSERT_LOG(P_MODULE IN VARCHAR2,
P_LOG_LEVEL IN NUMBER,
P_MESSAGE_TEXT IN VARCHAR2,
P_CALL_STACK IN VARCHAR2 DEFAULT NULL,
P_ERR_STACK IN VARCHAR2 DEFAULT NULL);
END;
CREATE OR REPLACE PACKAGE BODY APPS.XYG_ALD_DEBUG_PKG
AS
g_dbgpath varchar2(256) := '_';
g_file_debug boolean:=false;
--===============================================================
-- Debug 处理
--===============================================================
---输入消息的级别。决定消息是否输出
---P_DEBUG_LEVEL:该消息的级别
----如果 G_DEBUG_LEVEL 设定:0:则是失效消息输出;1:消息级别<=1 的会输出;2:消息级别<=2 的会输出
---调试级别
/*
XYG_ALD_DEBUG_LEVEL:查看调试的级别
0:不启动调试
1:查看调试程序主要消息级别=1 的消息
2:查看消息的调试级别<=2 的信息
3:查看消息的调试级别<=3 的信息
4:查看消息的调试级别<=4 的信息
5:查看消息的调试级别<=5 的信息
*/
FUNCTION DEBUG_ENABLE(P_DEBUG_LEVEL IN NUMBER)
RETURN BOOLEAN
IS
BEGIN
RETURN P_DEBUG_LEVEL <= NVL(FND_PROFILE.VALUE('XYG_ALD_DEBUG_LEVEL'),0);
END;
--调试信息输出。P_DEBUG_LEVEL:定义该消息的调试级别。
--当 XYG_ALD_DEBUG_LEVEL 设置大于 P_DEBUG_LEVEL,该消息会显示。
--调试方式 XYG_ALD_DEBUG_TYPE:
---DBMS_OUTPUT 直接输出/FILE_DEBUGLOG 文档输出/REQUEST_DEBUGLOG 请求日志输出/CONTEXT_OUTPUT 将日志
改为上下文输出/DATA_DEBUGLOG 表格数据输出
PROCEDURE DEBUG_OUT (P_MODULE IN VARCHAR2
,P_DEBUG_LEVEL IN NUMBER
,P_DEBUG_MSG IN VARCHAR2)
IS
l_dbgpath varchar2(240);--包括完整路径的调试文件
l_dbgdir varchar2(256);--路径
l_dbgfile varchar2(256);--文件名
l_dbgdir_name varchar2(50);
l_ndx number;
l_dir_separator varchar2(1);
l_strlen number;
--
l_utl_file_dir varchar2(250);
L_DEBUG_MEG varchar2(10000);
BEGIN
L_DEBUG_MEG:=to_char(systimestamp,
hh24:mi:ss:ff3')||'->'||P_MODULE||'->'||'L'||P_DEBUG_LEVEL||':'||P_DEBUG_MSG;
CASE NVL(FND_PROFILE.VALUE('XYG_ALD_DEBUG_TYPE'),'DBMS_OUTPUT')
WHEN 'DBMS_OUTPUT' THEN
DBMS_OUTPUT.PUT_LINE(L_DEBUG_MEG);
WHEN 'FILE_DEBUGLOG' THEN
l_dbgpath := NVL(fnd_profile.value('XYG_ALD_DEBUG_FILE')
,XYG_PUB_AUTOMAIL_PKG.XYG_DF_UTL_FILE_DIR||'/'||userenv('SESSIONID')||'.log') ;
IF g_dbgpath = '_' OR l_dbgpath<>g_dbgpath THEN--说明是第一次运行,需要打开新文件的
g_dbgpath:=l_dbgpath;
IF fnd_profile.value('XYG_ALD_DEBUG_FILE') IS NULL THEN--设置文件路径,方便查看!
FND_PROFILE.PUT('XYG_ALD_DEBUG_FILE',l_dbgpath);
END IF;
-- Separate the filename from the directory
l_strlen := length(l_dbgpath);
l_dbgfile := l_dbgpath;
l_dir_separator := '/';
--Check if separator exits, could be different depending on os
l_ndx := instr(l_dbgfile, l_dir_separator);
if ( l_ndx = 0 ) then
l_dir_separator := '\';
end if;
l_dbgfile:=substr(l_dbgfile,instr(l_dbgfile, l_dir_separator,-1)+1);
l_dbgdir := substr(l_dbgpath, 1, l_strlen - length(l_dbgfile) - 1);
l_dbgdir_name:=XYG_ALD_FILE_PKG.GET_DIRECTORY_NAME(l_dbgdir,0);
--如果是 utl_file_dir 的,需要特殊处理。它可以直接输出文档的!
SELECT value
'yyyy-mm-dd
'
New
********
INTO l_utl_file_dir
FROM V$PARAMETER
WHERE NAME = 'utl_file_dir';
IF l_dbgdir_name is null and (l_dbgdir = '/var/tmp' OR INSTR(l_dbgdir,l_utl_file_dir)>0) THEN
l_dbgdir_name :=l_dbgdir;
END IF;
IF l_dbgdir IS NOT NULL AND l_dbgdir_name IS NOT NULL THEN
XYG_FND_FILE.INITIAL;
XYG_FND_FILE.PUT_NAMES(l_dbgfile,NULL,l_dbgdir_name);
XYG_FND_FILE.PUT_LINE(FND_FILE.LOG,
Session:'||userenv('SESSIONID')||'****'||to_char(sysdate, 'DD-MON-YY:HH.MI.SS')||' **********');
--DBMS_OUTPUT.PUT_LINE('l_dbgfile:'||l_dbgfile||',l_dbgdir:'||l_dbgdir);
g_file_debug:=true;
ELSE
g_dbgpath:= '_';
DBMS_OUTPUT.PUT_LINE('警告:文件调试处理失败!可能是路径('||l_dbgdir||')没有定义正确或
者路径没有注册到 all_directories!');
END IF;
END IF;
IF g_file_debug THEN
XYG_FND_FILE.PUT_LINE(FND_FILE.LOG, L_DEBUG_MEG);
ELSE
DBMS_OUTPUT.PUT_LINE(L_DEBUG_MEG);
END IF;
WHEN 'REQUEST_DEBUGLOG' THEN
IF NVL(fnd_profile.value('CONC_REQUEST_ID'), 0)>0 THEN
FND_FILE.PUT_LINE (FND_FILE.LOG, L_DEBUG_MEG);
ELSE--如果当前的 PLSQL 执行的上下文环境不是请求,则只好用这个简单的方式调试了!
DBMS_OUTPUT.PUT_LINE(L_DEBUG_MEG);
END IF;
WHEN 'DATA_DEBUGLOG' THEN
INSERT_LOG(P_MODULE,P_DEBUG_LEVEL,P_DEBUG_MSG);
END CASE;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('DEBUG_OUT 出现异常,错误信息:'||SQLCODE||'-'||SQLERRM);
END DEBUG_OUT;
--封装的输出调试信息
PROCEDURE DEBUGLOG (P_MODULE IN VARCHAR2
,P_DEBUG_LEVEL IN NUMBER
,P_DEBUG_MSG IN VARCHAR2)
IS
BEGIN
IF DEBUG_ENABLE(P_DEBUG_LEVEL) THEN
DEBUG_OUT(P_MODULE,P_DEBUG_LEVEL,P_DEBUG_MSG);
END IF;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('DEBUGLOG 出现异常,错误信息:'||SQLCODE||'-'||SQLERRM);