Oracle 如何在没有包状态的情况下传递PL/SQL异常传播中的错误详细信息?

Oracle 如何在没有包状态的情况下传递PL/SQL异常传播中的错误详细信息?,oracle,plsql,oracle11g,Oracle,Plsql,Oracle11g,目前,我正在使用以下模式记录PL/SQL异常传播中的错误详细信息。有关详细信息,请参见下面的注释代码。我对此很满意,因为错误处理代码不会使整个代码库混乱,并且可以记录触发错误的所有细节 不幸的是,v_error变量引入了一个不必要的包状态副作用 如何在PL/SQL异常传播中传递错误详细信息而不引入包状态?(我想消除包状态以使部署更容易。) 使用不同的异常,如rule_2_failure_ex和rule_3_failure_ex不是我正在寻找的解决方案 不需要以不同的方式处理错误条件 对于故障排除

目前,我正在使用以下模式记录PL/SQL异常传播中的错误详细信息。有关详细信息,请参见下面的注释代码。我对此很满意,因为错误处理代码不会使整个代码库混乱,并且可以记录触发错误的所有细节

不幸的是,
v_error
变量引入了一个不必要的包状态副作用

如何在PL/SQL异常传播中传递错误详细信息而不引入包状态?(我想消除包状态以使部署更容易。)

使用不同的异常,如
rule_2_failure_ex
rule_3_failure_ex
不是我正在寻找的解决方案

  • 不需要以不同的方式处理错误条件
  • 对于故障排除,能够记录任意信息非常重要
  • (我已经在使用日志记录,因此错误条件信息可用,但它不在“正确的位置”。)

    我正在寻找Oracle 11g解决方案,但12c解决方案(如果与11g中的不同)也很受欢迎,因为有一天我可能也会停止使用12c(我个人不关心10g)

    您可以使用与异常关联的错误代码:

    create or replace package body so50 is
      processing_failure_ex exception;
      pragma exception_init(processing_failure_ex, -20999);
    
    并提出你想要的信息:

        raise_application_error(-20999,
          'Failed to process rule 3: (p_num = ' || p_num || ')', true);
    
    如果要存储整个堆栈,可以使用:

    因此,完全删除
    v_错误

    create or replace package so50 is
      procedure run(p_num in number);
    end;
    /
    
    create or replace package body so50 is
      processing_failure_ex exception;
      pragma exception_init(processing_failure_ex, -20999);
    
      -- in reality the processing and details are more complex
      procedure p3(p_num in number) is
      begin
        if p_num = 3
        then
          -- it's important to be able to record arbitrary information at this point
            raise_application_error(-20999,
              'Failed to process rule 3: (p_num = ' || p_num || ')', true);
        end if;
      end;
    
      -- the comments on p3 apply
      procedure p2(p_num in number) is
      begin
        if p_num = 2
        then
            raise_application_error(-20999,
              'Failed to process rule 2: (p_num = ' || p_num || ')', true);
        end if;
      end;
    
      procedure p1(p_num in number) is
      begin
        p2(p_num);
        p3(p_num);
      exception
        when others then
          raise_application_error(-20999,
            'Additional details of failure', true);
      end;
    
      procedure run(p_num in number) is
      begin
        begin
          p1(p_num);
        exception
          when processing_failure_ex then
            -- in reality an error recovery will be tried first and only then
            -- the error will be forwarded to a monitoring framework that will
            -- raise an alert for human action
            dbms_output.put_line('Error details:');
            dbms_output.put_line(dbms_utility.format_error_stack);
            raise;
        end;
      exception
        when others then
          -- out of the scope of the question
          raise;
      end;
    end;
    /
    
    打电话会得到:

    SQL> set serveroutput on
    SQL> exec so50.run(1);
    
    PL/SQL procedure successfully completed.
    
    SQL> exec so50.run(2);
    
    ORA-20999: Additional details of failure
    ORA-06512: at "STACKOVERFLOW.SO50", line 42
    ORA-20999: Failed to process rule 2: (p_num = 2)
    ORA-06512: at "STACKOVERFLOW.SO50", line 64
    ORA-06512: at line 1
    
    Error details:
    ORA-20999: Additional details of failure
    ORA-06512: at "STACKOVERFLOW.SO50", line 42
    ORA-20999: Failed to process rule 2: (p_num = 2)
    
    SQL> exec so50.run(3);
    
    ORA-20999: Additional details of failure
    ORA-06512: at "STACKOVERFLOW.SO50", line 42
    ORA-20999: Failed to process rule 3: (p_num = 3)
    ORA-06512: at "STACKOVERFLOW.SO50", line 64
    ORA-06512: at line 1
    
    Error details:
    ORA-20999: Additional details of failure
    ORA-06512: at "STACKOVERFLOW.SO50", line 42
    ORA-20999: Failed to process rule 3: (p_num = 3)
    
    在这两种情况下,“Error details:”之前的堆栈跟踪都来自最终的范围外提升;如果它被暂时挤压(只是为了演示,而不是建议你真的挤压!),你只会看到:

    SQL> exec so50.run(3);
    
    PL/SQL procedure successfully completed.
    Error details:
    ORA-20999: Additional details of failure
    ORA-06512: at "STACKOVERFLOW.SO50", line 42
    ORA-20999: Failed to process rule 3: (p_num = 3)
    

    对于不同的过程和场景,您可以使用不同的异常编号。当然,我现在只使用了一个通用的异常编号来简化事情。如果您想按名称捕获它们,则只需命名它们(通过pragma与名称绑定)。如果您这样做,您可以在一个地方定义所有异常。

    我当前基于@AlexPoole answer的解决方案:

    例外包

    -- encapsulates the uglyness to keep calling code clean
    create or replace package so50_ex is
      -- exception type and error code reserved for this purpose only
      general_ex exception;
      general_ex_code constant number := -20999;
      pragma exception_init(general_ex, -20999);
    
      procedure raise(p_msg in varchar2, p_ex_code in number default -20999);
    
      function full_error_stack return varchar2;
    end;
    /
    show errors
    
    create or replace package body so50_ex is
      procedure raise(p_msg in varchar2, p_ex_code in number default -20999) is
      begin
        raise_application_error(p_ex_code,
                                substrb(p_msg, 1, 2048),
                                true);
      end;
    
      function full_error_stack return varchar2 as
        -- will always fit to buffer as format_error_stack returns 2000 bytes at
        -- maximum
        v_stack varchar2(32767) := dbms_utility.format_error_stack;
      begin
        -- might not fit to buffer as format_error_backtrace return length is not
        -- limited
        v_stack := v_stack ||
          substrb(dbms_utility.format_error_backtrace, 1, 30767);
        return v_stack;
      end;
    end;
    /
    show errors
    
    create or replace package so50 is
      -- a user can always have his own exceptions
      processing_failure_ex exception;
      processing_failure_ex_code constant number := -20100;
      pragma exception_init(processing_failure_ex, -20100);
    
      procedure run(p_num in number);
    end;
    /
    show errors
    
    create or replace package body so50 is
      procedure p3(p_num in number) is
      begin
        if p_num = 3
        then
          -- use specific exception
          so50_ex.raise('Failed to process rule 3: (p_num = ' || p_num || ')',
                        processing_failure_ex_code);
        end if;
      end;
    
      procedure p2(p_num in number) is
      begin
        if p_num = 2
        then
          -- use default exception
          so50_ex.raise('Failed to process rule 2: (p_num = ' || p_num || ')');
        end if;
      end;
    
      procedure p1(p_num in number) is
      begin
        p2(p_num);
        p3(p_num);
      exception
        when processing_failure_ex then
          dbms_output.put_line('ERROR RECOVERED SUCCESFULLY.');
          dbms_output.put_line('DETAILS:');
          dbms_output.put_line(so50_ex.full_error_stack);
        when others then
          so50_ex.raise('Additional details of failure.');
      end;
    
      procedure run(p_num in number) is
      begin
        p1(p_num);
      exception
        when others then
          dbms_output.put_line('EXCEPTION: ' || so50_ex.full_error_stack);
          raise;
      end;
    end;
    /
    show errors
    
    用法示例

    -- encapsulates the uglyness to keep calling code clean
    create or replace package so50_ex is
      -- exception type and error code reserved for this purpose only
      general_ex exception;
      general_ex_code constant number := -20999;
      pragma exception_init(general_ex, -20999);
    
      procedure raise(p_msg in varchar2, p_ex_code in number default -20999);
    
      function full_error_stack return varchar2;
    end;
    /
    show errors
    
    create or replace package body so50_ex is
      procedure raise(p_msg in varchar2, p_ex_code in number default -20999) is
      begin
        raise_application_error(p_ex_code,
                                substrb(p_msg, 1, 2048),
                                true);
      end;
    
      function full_error_stack return varchar2 as
        -- will always fit to buffer as format_error_stack returns 2000 bytes at
        -- maximum
        v_stack varchar2(32767) := dbms_utility.format_error_stack;
      begin
        -- might not fit to buffer as format_error_backtrace return length is not
        -- limited
        v_stack := v_stack ||
          substrb(dbms_utility.format_error_backtrace, 1, 30767);
        return v_stack;
      end;
    end;
    /
    show errors
    
    create or replace package so50 is
      -- a user can always have his own exceptions
      processing_failure_ex exception;
      processing_failure_ex_code constant number := -20100;
      pragma exception_init(processing_failure_ex, -20100);
    
      procedure run(p_num in number);
    end;
    /
    show errors
    
    create or replace package body so50 is
      procedure p3(p_num in number) is
      begin
        if p_num = 3
        then
          -- use specific exception
          so50_ex.raise('Failed to process rule 3: (p_num = ' || p_num || ')',
                        processing_failure_ex_code);
        end if;
      end;
    
      procedure p2(p_num in number) is
      begin
        if p_num = 2
        then
          -- use default exception
          so50_ex.raise('Failed to process rule 2: (p_num = ' || p_num || ')');
        end if;
      end;
    
      procedure p1(p_num in number) is
      begin
        p2(p_num);
        p3(p_num);
      exception
        when processing_failure_ex then
          dbms_output.put_line('ERROR RECOVERED SUCCESFULLY.');
          dbms_output.put_line('DETAILS:');
          dbms_output.put_line(so50_ex.full_error_stack);
        when others then
          so50_ex.raise('Additional details of failure.');
      end;
    
      procedure run(p_num in number) is
      begin
        p1(p_num);
      exception
        when others then
          dbms_output.put_line('EXCEPTION: ' || so50_ex.full_error_stack);
          raise;
      end;
    end;
    /
    show errors
    

    此时,它将打印“Error details:”行,然后获取ORA-06510:PL/SQL:unhandled用户定义的异常(如果调用值为1或2)。您实际上是在尝试在异常堆栈跟踪中查看“错误详细信息”字符串?@AlexPoole是的,基本上是这样。IMO
    raise\u application\u error
    需要我想要避免的不必要的额外样板。但这在PL/SQL中并不总是可能的。