如何在PL / pgSQL中获取手动引发的异常的异常上下文?


11

在Postgres中,我们使用以下代码获取异常的“堆栈跟踪”:

EXCEPTION WHEN others THEN
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

对于“自然”异常,这很好用,但是如果我们使用

RAISE EXCEPTION 'This is an error!';

...那么就没有堆栈跟踪。根据邮件列表条目,这可能是故意的,尽管我一生都无法弄清原因。这让我想找出引发异常的另一种方法,而不是使用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;

在这里显示一个简单的示例可能是一个好主意。
Craig Ringer

好点@CraigRinger。做完了!
Taytay 2015年

它不是独立的。什么error_info啊 看起来像自定义类型。
Craig Ringer

抱歉-以为您只想了解一般情况。我删除了多余的东西。
Taytay 2015年

Answers:


9

此行为似乎是设计使然。

src/pl/plpgsql/src/pl_exec.c错误上下文中,回调显式检查以查看它是否在PL / PgSQL RAISE语句的上下文中被调用,如果是,则跳过发出错误上下文:

/*
 * 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;

我无法找到任何具体的参考,以为什么是这种情况。

在服务器内部,上下文堆栈是通过处理生成的error_context_stack,该链接是链式回调,在调用时会将信息附加到列表。

当PL / PgSQL输入函数时,它将向错误上下文回调堆栈添加一个项目。当离开函数时,它将从该堆栈中删除一个项目。

如果PostgreSQL服务器的错误报告功能(如ereport或)elog被调用,它将调用错误上下文回调。但是在PL / PgSQL中,如果它注意到它的RAISE回调函数正在故意调用它,则它什么也不做。

鉴于此,如果不对PostgreSQL进行修补,我看不到任何方法来实现您想要的。我建议将邮件发布到pgsql-general,询问为什么RAISEPL / PgSQL必须GET STACKED DIAGNOSTICS利用它而不提供错误上下文。

(顺便说一句,异常上下文本身并不是堆栈跟踪。它看起来有点像一个堆栈跟踪,因为PL / PgSQL将每个函数调用都添加到了堆栈中,但也用于服务器中的其他详细信息。)


非常感谢Craig的快速而彻底的回答。这对我来说确实很奇怪,并且肯定违背了我的期望。该RAISE检查会削弱的用处。我会写信给他们。
Taytay 2015年

@Taytay请在此处包括您问题的链接,但请确保您的邮件是完整的,并且无需按链接即可被理解;许多人忽略仅链接或主要是链接的帖子。如果您有机会通过archives.postgresql.org在此处的评论中弹出指向您帖子的链接,那真是太棒了,以后可以帮助其他人。
Craig Ringer

谢谢克雷格。好建议。我在这里创建了一个线程:postgresql.org/message-id/… 到目前为止,他们正在寻找解决该问题的好方法。
Taytay 2015年

6

您可以解决此限制,并通过调用另一个为您引发错误的函数来使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;

细节:

我扩展了您发布的测试用例,以证明它可以在Postgres 9.3中使用:

SQL提琴。


非常感谢Erwin!有趣的是,我实际上在发布之前尝试了您的解决方案,但是我一定做错了什么,但我没有得到我期望的上下文。现在,我已经看过小提琴了(也感谢您向我展示),我再给它一个镜头!
Taytay 2015年

做得很好; 它没有必要,但看起来可以解决问题。
Craig Ringer

@CraigRinger:因为异常应该是异常,所以对性能的最小影响也不重要。这样我们就有所有选择。
Erwin Brandstetter 2015年

完全同意,我只想看到解决方法的需求在某个时候消失了。
Craig Ringer

@CraigRinger:是的。如果这种情况不会很快发生,我们可能会在手册中建议这种解决方法...
Erwin Brandstetter 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.