Tay*_*tay 11 postgresql error-handling exception plpgsql postgresql-9.3
在 Postgres 中,我们使用以下代码获取异常的“堆栈跟踪”:
EXCEPTION WHEN others THEN
GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;
Run Code Online (Sandbox Code Playgroud)
这适用于“自然”异常,但如果我们使用
RAISE EXCEPTION 'This is an error!';
Run Code Online (Sandbox Code Playgroud)
...然后没有堆栈跟踪。根据邮件列表条目,这可能是故意的,尽管我终生无法弄清楚原因。这让我想找出另一种抛出异常的方法,而不是使用RAISE
. 我只是错过了一些明显的东西吗?有没有人有这个技巧?我可以让 Postgres 抛出一个包含我选择的字符串的异常,这样我不仅可以在错误消息中获得我的字符串,还可以获得完整的堆栈跟踪吗?
这是一个完整的例子:
CREATE OR REPLACE FUNCTION error_test() RETURNS json AS $$
DECLARE
v_error_stack text;
BEGIN
-- Comment this out to see how a "normal" exception will give you the stack trace
RAISE EXCEPTION 'This exception will not get a stack trace';
-- This will give a divide by zero error, complete with stack trace
SELECT 1/0;
-- In case of any exception, wrap it in error object and send it back as json
EXCEPTION WHEN others THEN
-- If the exception we're catching is one that Postgres threw,
-- like a divide by zero error, then this will get the full
-- stack trace of the place where the exception was thrown.
-- However, since we are catching an exception we raised manually
-- using RAISE EXCEPTION, there is no context/stack trace!
GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;
RAISE WARNING 'The stack trace of the error is: "%"', v_error_stack;
return to_json(v_error_stack);
END;
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
此行为似乎是设计使然。
在src/pl/plpgsql/src/pl_exec.c
错误上下文回调中显式检查它是否在 PL/PgSQLRAISE
语句的上下文中被调用,如果是,则跳过发出错误上下文:
/*
* error context callback to let us supply a call-stack traceback
*/
static void
plpgsql_exec_error_callback(void *arg)
{
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
/* if we are doing RAISE, don't report its location */
if (estate->err_text == raise_skip_msg)
return;
Run Code Online (Sandbox Code Playgroud)
我找不到任何关于为什么会这样的具体参考。
在服务器内部,上下文堆栈是通过处理 生成的error_context_stack
,这是一个链式回调,在调用时将信息附加到列表中。
当 PL/PgSQL 进入一个函数时,它会向错误上下文回调堆栈添加一个项目。当它离开一个函数时,它会从该堆栈中删除一个项目。
如果 PostgreSQL 服务器的错误报告函数,例如ereport
或被elog
调用,它会调用错误上下文回调。但是在 PL/PgSQL 中,如果它注意到它是从RAISE
它的回调中被调用的,它故意什么都不做。
鉴于此,我看不出有任何方法可以在不修补 PostgreSQL 的情况下实现您想要的目标。我建议向 pgsql-general 发送邮件,询问为什么RAISE
既然 PL/PgSQL 必须GET STACKED DIAGNOSTICS
使用它,为什么不提供错误上下文。
(顺便说一句,异常上下文本身并不是堆栈跟踪。它看起来有点像,因为 PL/PgSQL 将每个函数调用添加到堆栈中,但它也用于服务器中的其他细节。)
您可以解决此限制,并通过调用另一个为您引发(警告、通知...)错误的函数来使 plpgsql根据需要发出错误上下文。
几年前我发布了一个解决方案 -在我在 dba.SE 上的第一篇文章中:
-- helper function to raise an exception with CONTEXT
CREATE OR REPLACE FUNCTION f_raise(_lvl text = 'EXCEPTION'
,_msg text = 'Default error msg.')
RETURNS void AS
$func$
BEGIN
CASE upper(_lvl)
WHEN 'EXCEPTION' THEN RAISE EXCEPTION '%', _msg;
WHEN 'WARNING' THEN RAISE WARNING '%', _msg;
WHEN 'NOTICE' THEN RAISE NOTICE '%', _msg;
WHEN 'DEBUG' THEN RAISE DEBUG '%', _msg;
WHEN 'LOG' THEN RAISE LOG '%', _msg;
WHEN 'INFO' THEN RAISE INFO '%', _msg;
ELSE RAISE EXCEPTION 'f_raise(): unexpected raise-level: "%"', _lvl;
END CASE;
END
$func$ LANGUAGE plpgsql STRICT;
Run Code Online (Sandbox Code Playgroud)
细节:
我扩展了您发布的测试用例以证明它在 Postgres 9.3 中有效: