49.2. 在服务器内部报告错误 #
服务器代码内生成的错误、警告和日志消息应使用 ereport,或其更老的近亲 elog 来创建。这一函数的用法相当复杂,因此需要一些解释。
每条消息都必须包含两个元素:严重性级别(范围从
DEBUG 到 PANIC)以及主消息文本。此外还可以有可选元素,其中最常见的是遵循 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当前值返回的错误消息。 [12]%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是决定需要哪种复数形式的整数值,其余参数按所选格式字符串进行格式化。更多信息见第 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,但支持消息的各种复数形式。更多信息见第 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)来指定抑制 postmaster 日志中消息的STATEMENT:部分。一般来说,如果消息文本本身已经包含当前语句,这么做就是合适的。
注意
在一次 ereport 调用中,errtable、errtablecol、errtableconstraint、errdatatype 和
errdomainconstraint 这些函数中最多只能使用一个。这些函数的存在,是为了让应用能够提取与错误条件关联的数据库对象名称,而无需检查可能已经本地化的错误消息文本。这些函数应当用于应用很可能希望自动处理的错误报告。截至 PostgreSQL 9.3,只有 SQLSTATE 类 23(完整性约束违反)中的错误实现了完整覆盖,但未来很可能会扩展。
还有一个较旧的函数elog,至今仍被大量使用。一个elog调用:
elog(level, "format string", ...);
完全等价于:
ereport(level, (errmsg_internal("format string", ...)));注意,SQLSTATE 错误代码总会取默认值,而且消息字符串不会被翻译。因此,elog只应用于内部错误和低层调试日志。凡是普通用户可能感兴趣的消息,都应通过ereport。尽管如此,系统中仍有足够多的内部“不可能发生”错误检查,因此elog依然被广泛使用;对这类消息来说,由于记法更简洁,它更受青睐。
关于如何编写良好的错误消息,可参见第 49.3 节。
[12] 也就是说,是到达 ereport 调用点时的那个值;辅助报告例程内部对 errno 的更改不会影响它。如果显式写出 strerror(errno) 作为
errmsg 的参数列表内容,就不是这样了;因此不要这么做。