53.2. Вывод сообщений об ошибках в коде сервера
Сообщения об ошибках, предупреждения и обычные сообщения, выдаваемые в коде сервера должны создаваться функцией ereport
или родственной её предшественницей elog
. Использование этой функции достаточно сложно и требует дополнительного объяснения.
У каждого сообщения есть два обязательных элемента: уровень важности (от DEBUG
до PANIC
) и основной текст сообщения. В дополнение к ним есть необязательные элементы, из которых часто используется код идентификатора ошибки, соответствующий определению SQLSTATE в спецификации SQL. Макрос ereport
сам по себе является просто оболочкой, которая существует в основном для синтаксического удобства, чтобы выдача сообщения выглядела как вызов одной функции в коде C. Единственный параметр, который принимает непосредственно ereport
, — это уровень важности. Основной текст и любые дополнительные элементы сообщения генерируются в вызове ereport
в результате использования вспомогательных функций, таких как errmsg
.
Типичный вызов ereport
выглядит примерно так:
ereport(ERROR, errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"));
В нём задаётся уровень важности ERROR
(заурядная ошибка). В вызове errcode
указывается код ошибки SQLSTATE по макросу, определённому в src/include/utils/errcodes.h
. Вызов errmsg
даёт текст основного сообщения.
Вы часто также будете встречать вызовы в старом стиле, с дополнительными скобками, обрамляющими вызовы вспомогательных функций:
ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero")));
Эти дополнительные скобки были обязательными до PostgreSQL версии 12, но сейчас они не требуются.
Более сложный пример:
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."));
В нём демонстрируется использование кодов форматирования для включения значений времени выполнения в текст сообщения. Также в нём добавляется дополнительное сообщение «подсказки». Вызовы вспомогательных функций можно записывать в любом порядке, но обычно сначала вызываются errcode
и errmsg
.
При уровне важности ERROR
или более высоком, ereport
прерывает выполнение текущего запроса и не возвращает управление в вызывающий код. Если уровень важности ниже ERROR
, ereport
завершается обычным образом.
Для ereport
предлагаются следующие вспомогательные функции:
errcode(sqlerrcode)
задаёт код идентификатора ошибки SQLSTATE для данной ошибки. Если эта функция не вызывается, подразумевается идентификатор ошибкиERRCODE_INTERNAL_ERROR
при уровне важностиERROR
или выше, либоERRCODE_WARNING
при уровне важностиWARNING
, иначе (при уровнеNOTICE
или ниже) —ERRCODE_SUCCESSFUL_COMPLETION
. Хотя эти значения по умолчанию довольно разумны, всегда стоит подумать, насколько они уместны, прежде чем опустить вызовerrcode()
.errmsg(const char *msg, ...)
задаёт основной текст сообщения об ошибке и, возможно, значения времени выполнения, которые будут в него включаться. Эти включения записываются кодами формата в стилеsprintf
. В дополнение к стандартным кодам формата, принимаемым функциейsprintf
, можно использовать код формата%m
, который вставит сообщение об ошибке, возвращённое строкойstrerror
для текущего значенияerrno
. [15] Для%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
задаёт целое число, определяющее, какая именно форма множественного числа требуется, а остальные аргументы форматируются согласно выбранной строке формата. За дополнительными сведениями обратитесь к Подразделу 54.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
, но поддерживает различные формы сообщения с множественными числами. За дополнительными сведениями обратитесь к Подразделу 54.2.2.errdetail_log(const char *msg, ...)
подобнаerrdetail
, но выводимая строка попадает только в журнал сервера, и никогда не передаётся клиенту. Если используется иerrdetail
(или один из её эквивалентов), иerrdetail_log
, тогда одна строка передаётся клиенту, а другая отправляется в журнал. Это полезно для вывода подробных сообщений, имеющих конфиденциальный характер или большой размер, так что передавать их клиенту нежелательно.errdetail_log_plural(const char *fmt_singular, const char *fmt_plural, unsigned long n, ...)
действует подобноerrdetail_log
, но поддерживает различные формы сообщения с множественными числами. За дополнительными сведениями обратитесь к Подразделу 54.2.2.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:
) в журнал сервера. Обычно это уместно, когда само сообщение включает текст текущего оператора.errhidecontext(bool hide_ctx)
может быть вызвана для подавления вывода поляКОНТЕКСТ:
(CONTEXT:
) в журнал сервера. Это следует использовать только для подробных отладочных сообщений, в которых одна и та же информация о контексте, выводимая в журнал, будет только чрезмерно замусоривать его.
Примечание
В вызове 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
; для таких сообщений эта функция предпочитается из-за простоты записи.
Советы по написанию хороших сообщений об ошибках можно найти в Разделе 53.3.
[15] То есть значение, которое было текущим, когда была вызвана ereport
; изменения errno
во вспомогательных функциях выдачи сообщений на него не повлияют. Это будет не так, если вы запишете strerror(errno)
явно в списке параметров errmsg
; поэтому делать так не нужно.