52.2. Вывод сообщений об ошибках в коде сервера
Сообщения об ошибках, предупреждения и обычные сообщения, выдаваемые в коде сервера должны создаваться функцией ereport или родственной её предшественницей elog. Использование этой функции достаточно сложно и требует дополнительного объяснения.
У каждого сообщения есть два обязательных элемента: уровень важности (от DEBUG до PANIC) и основной текст сообщения. В дополнение к ним есть необязательные элементы, из которых часто используется код идентификатора ошибки, соответствующий определению SQLSTATE в спецификации SQL. Функция ereport сама по себе является просто оболочкой, которая существует в основном для синтаксического удобства, чтобы выдача сообщения выглядела как вызов функции в коде C. Единственный параметр, который принимает непосредственно функция ereport, это уровень важности. Основной текст и любые дополнительные элементы сообщения генерируются в результате вызова вспомогательных функций, таких как errmsg, в вызове ereport.
Типичный вызов 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_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.")));В нём демонстрируется использование кодов форматирования для включения значений времени выполнения в текст сообщения. Также в нём добавляется дополнительное сообщение «подсказки».
При уровне важности 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. [14] Для%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задаёт целое число, определяющее, какая именно форма множественного числа требуется, а остальные аргументы форматируются согласно выбранной строке формата. За дополнительными сведениями обратитесь к Подразделу 53.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, но поддерживает различные формы сообщения с множественными числами. За дополнительными сведениями обратитесь к Подразделу 53.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, но поддерживает различные формы сообщения с множественными числами. За дополнительными сведениями обратитесь к Подразделу 53.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; для таких сообщений эта функция предпочитается из-за простоты записи.
Советы по написанию хороших сообщений об ошибках можно найти в Разделе 52.3.
[14] То есть значение, которое было текущим, когда была вызвана ereport; изменения errno во вспомогательных функциях выдачи сообщений на него не повлияют. Это будет не так, если вы запишете strerror(errno) явно в списке параметров errmsg; поэтому делать так не нужно.