Перейти к основному содержимому

Сообщения об ошибках на сервере

Сообщения об ошибках, предупреждения и обычные сообщения, выдаваемые в коде сервера должны создаваться функцией ereport или родственной ее предшественницей elog. Использование этой функции достаточно сложно и требует некоторых пояснений.

Для каждого сообщения есть два обязательных элемента: уровень серьезности (от DEBUG до PANIC) и основной текст сообщения. Кроме того, есть необязательные элементы, наиболее распространенным из которых является код идентификатора ошибки, который соответствует SQL-соглашениям SQLSTATE. Сам 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_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 (то есть, значение, которое было текущим, когда был достигнут вызов ereport; изменения errno в вспомогательных процедурах отчетности не повлияют на него. Это было бы неверно, если бы написали strerror(errno) явно в списке параметров errmsg; соответственно, не делайте этого). %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 - целочисленное значение, которое определяет, какая форма множественного числа необходима, а остальные аргументы форматируются в соответствии с выбранной строкой формата. Подробнее см. в разделе «Рекомендации по написанию сообщений»;
  • 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, но с поддержкой различных множественных форм сообщения. Подробнее см. раздел «Рекомендации по написанию сообщений»
  • 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, но с поддержкой различных множественных форм сообщения. Подробнее см. раздел «Рекомендации по написанию сообщений»;
  • errhint(const char \*msg, ...) предоставляет дополнительное сообщение «подсказка»; это должно использоваться при предложении советов о том, как исправить проблему, а не фактических подробностей о том, что пошло не так. Строка сообщения обрабатывается так же, как и для errmsg;
  • errhint_plural(const char \*fmt\_singular, const char \*fmt\_plural, unsigned long n, ...) похож на errhint, но с поддержкой различных множественных форм сообщения. Подробнее см. раздел «Рекомендации по написанию сообщений»
  • 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; для таких сообщений эта функция предпочитается из-за простоты записи.

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