49.2. 报告服务器里的错误

在服务器代码里生成的错误、警告以及日志信息应该用ereport 或者它的兄弟elog创建。这个函数的使用已经复杂得需要做些解释了。

每条消息都有两个必须的要素:一个严重级别(范围从DEBUGPANIC)和一个主要消息文本。除此之外还有可选的元素, 最常见的就是一个遵循 SQL 标准的 SQLSTATE 习惯的错误标识码。 ereport本身只是一个壳函数, 它的存在主要是为了便于让消息生成看起来像 C 代码里的函数调用。 ereport直接接受的唯一参数是严重级别。 主消息文本和任何附加消息元素都是通过在ereport 调用里调用辅助函数(比如errmsg)生成的。

典型的调用ereport的方式看起来可能像下面这样:

ereport(ERROR,
        (errcode(ERRCODE_DIVISION_BY_ZERO),
         errmsg("division by zero")));

这样就声明了错误严重级别ERROR(一个普通错误)。 errcode调用指定一个在src/include/utils/errcodes.h 里面使用宏定义的SQLSTATE错误代码。errmsg调用提供主要的消息文本。 请注意包围在辅助函数调用周围的额外的圆括弧—这么做虽然烦人, 但是语法上是必须的。

然后是一个更复杂的例子:

ereport(ERROR,
        (errcode(ERRCODE_AMBIGUOUS_FUNCTION),
         errmsg("function %s is not unique",
                func_signature_string(funcname, nargs,
                                      NIL, actual_arg_types)),
         errhint("Unable to choose a best candidate function. "
                 "You might need to add explicit typecasts.")));

这个例子演示了使用格式化代码把运行时数值嵌入一个消息文本的用法。 同样,还提供了一个可选的"暗示"信息。

ereport可用的附属过程有:

  • errcode(sqlerrcode)为该条件声明 SQLSTATE 错误标识符代码。 如果没有调用这个过程,并且错误严重级别是ERROR或更高, 那么错误标识符缺省是ERRCODE_INTERNAL_ERROR, 如果错误严重级别是WARNING则为ERRCODE_WARNING, 否则(用于NOTICE或者更低级别)为ERRCODE_SUCCESSFUL_COMPLETION。 虽然这些缺省都很方便,但是最好还是在省略errcode()调用之前三思。

  • errmsg(const char *msg, ...)声明主错误消息文本, 以及可能的插入其中的运行时数值。插入是使用sprintf 风格的格式代码插入的。除了sprintf接受的标准格式代码, 还接受%m用于插入strerror为当前errno 值返回的错误信息。 [1] %m并不要求在errmsg的参数列表里有任何对应的项目。 请注意这个消息字符串在格式代码得到处理之前将不会通过gettext 运行获取合适的本地化。

  • errmsg_internal(const char *msg, ...)errmsg 一样,只是消息字符串将不会包含在国际化消息字典里。这个函数应该用于 "不可能发生"的情况,也就是不值得展开进行翻译的场合。

  • errmsg_plural(const char *fmt_singular, const char *fmt_plural, unsigned long n, ...)就像是errmsg, 但是支持消息的各种复数形式。_fmt_singular_是英语单数形式, _fmt_plural_是英语复数形式,_n_ 是决定使用哪个复数形式的整数值,剩余的参数根据选择的格式字符串进行格式化。 更多信息请查看Section 50.2.2

  • errdetail(const char *msg, ...)提供一个可选的"详细"信息; 在存在额外的信息,并且很适合放在主消息里面的时候使用这个函数。 消息字符串处理的方法和errmsg完全一样。

  • errdetail_internal(const char *msg, ...)errdetail一样, 只是消息字符串将不会包含在国际化消息字典里。这个函数应该用于不值得展开进行翻译的详细消息, 比如,因为它们太技术了以至于对大多数用户来说无用。

  • errdetail_plural(const char *fmt_singular, const char *fmt_plural, unsigned long n, ...)类似于errdetail, 但是支持消息的各种复数形式。更多信息请查阅Section 50.2.2

  • errdetail_log(const char *msg, ...)errdetail 一样,除了这个字符串只到服务器日志,从不到客户端。 如果errdetail(或它的以上相同的其中之一)和errdetail_log 都使用了,那么一个字符串到客户端,另一个到服务器日志。 这对于过于安全敏感或太大而不能包含进发送到客户端的报告中的错误细节来说是有用的。

  • errhint(const char *msg, ...)提供一个可选的"暗示"消息; 这个函数用于提供如何修补问题的建议,而不是提供错误的事实。 消息字符串处理的方式和errmsg一样。

  • errcontext(const char *msg, ...)通常不会直接从ereport 消息点里直接调用;而是用在error_context_stack 回调函数里提供有关错误发生的环境的信息,比如,当前的位置是在一个 PL 函数里等等。 消息字符串的处理和errmsg完全一样。和其它辅助函数不同, 这个函数在一次ereport调用里可以调用多次; 随后的调用生成的字符串将带着各自的换行符连接在原来的字符串上。

  • errposition(int cursorpos)声明一个错误在查询字符串里的文本位置。 目前它只是在汇报查询处理过程中的词法和语法分析阶段检测到的错误有用。

  • errtable(Relation rel)指定一个关系, 关系名和模式名应该作为辅助字段包含在错误报告内。

  • errtablecol(Relation rel, int attnum)指定一个字段, 该字段的名字、表名和模式名应该作为辅助字段包含在错误报告中。

  • errtableconstraint(Relation rel, const char *conname) 指定一个表约束,该表约束的名字、表名和模式名应该作为辅助字段包含在错误报告中。 不管它们是否有一个相关的pg_constraint项,索引都应该被认为是约束。 小心地传递底层堆关系,不只是索引本身,作为rel

  • errdatatype(Oid datatypeOid)指定一个数据类型, 它的名字和模式名应该作为辅助字段包含在错误报告中。

  • errdomainconstraint(Oid datatypeOid, const char *conname) 指定一个域约束,该域约束的名字、域名和模式名应该作为附属字段包含在错误报告中。

  • errcode_for_file_access()是一个便利函数, 它可以为一个文件访问类的系统调用选择一个合适的 SQLSTATE 错误标识符。 它利用保存下来的errno判断生成哪个错误代码。 通常它应该和主错误消息文本里的%m结合使用。

  • errcode_for_socket_access()是一个便利函数, 它可以为一个套接字相关的系统调用选择一个合适的 SQLSTATE 错误标识符。

  • errhidestmt(bool hide_stmt)可以用来在主日志中指定消息的 STATEMENT:部分的消除。如果消息文本早已包括当前语句, 那么这通常是合适的。

Note: errtableerrtablecolerrtableconstrainterrdatatypeerrdomainconstraint 中最多只有一个函数应该用在一个ereport调用中。 这些函数的存在允许应用提取与没有检测潜在本地化错误消息文本的错误条件相关的数据库对象的名字。 这些函数应该用在应用希望自动错误处理的错误报告中。自PostgreSQL 9.3起, 完全覆盖只是为了SQLSTATE class 23中的错误而存在(违反完整约束), 但是有可能会在未来扩展。

还有一个老一些的elog函数,仍然在频繁使用。一个elog调用:

elog(level, "format string", ...);

完全等效于:

ereport(level, (errmsg_internal("format string", ...)));

请注意 SQLSTATE 错误代码总是缺省的,并且消息字符串并没有翻译。因此, elog应该只用于内部错误以及低层的调试日志。 任何普通用户感兴趣的消息都应该通过ereport生成。当然, 还有大量内部的"不可能发生"的错误检查使用elog; 因为这些信息最好还是表示得简单些好。

书写好的错误消息的建议可以在Section 49.3找到。

Notes

[1] 也就是说,在到达ereport调用的时候当前的数值; 在附属报告过程里对errno的修改将不会影响他。但是如果你在 errmsg的参数列表里明确地写strerror(errno), 这一点就不能保证了,因此,请不要这么做。