56.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. [16] Для %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 задаёт целое число, определяющее, какая именно форма множественного числа требуется, а остальные аргументы форматируются согласно выбранной строке формата. За дополнительными сведениями обратитесь к Подразделу 57.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, но поддерживает различные формы сообщения с множественными числами. За дополнительными сведениями обратитесь к Подразделу 57.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, но поддерживает различные формы сообщения с множественными числами. За дополнительными сведениями обратитесь к Подразделу 57.2.2.

  • errhint(const char *msg, ...) передаёт дополнительное сообщение «подсказки»; это позволяет предложить решение проблемы, а не просто сообщить факты, связанные с ней. Строка сообщения обрабатывается так же, как и для errmsg.

  • errhint_plural(const char *fmt_singular, const char *fmt_plural, unsigned long n, ...) действует подобно errhint, но поддерживает различные формы сообщения с множественными числами. За дополнительными сведениями обратитесь к Подразделу 57.2.2.

  • 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; для таких сообщений эта функция предпочитается из-за простоты записи.

Советы по написанию хороших сообщений об ошибках можно найти в Разделе 56.3.



[16] То есть значение, которое было текущим, когда была вызвана ereport; изменения errno во вспомогательных функциях выдачи сообщений на него не повлияют. Это будет не так, если вы запишете strerror(errno) явно в списке параметров errmsg; поэтому делать так не нужно.