Расширения PostgreSQL: логирование и исключения
28 марта 2022
Хотелось бы продолжить рассказ о расширениях PostgreSQL и поговорить о логировании и исключениях. Рассматривать их нужно вместе, поскольку в PostgreSQL это связанные механизмы. Хотя ранее мы уже и использовали макрос elog(), тема была затронута лишь поверхностно.
Большую часть того, что нужно знать о логировании и исключениях, можно почерпнуть из файла src/include/utils/elog.h в исходном коде PostgreSQL.
В простейшем случае логирование осуществляется при помощи elog():
elog(NOTICE, "Transaction start timestamp: %lld", (long long)now);
Форматирование практически такое же, как в обычном printf(), но с некоторыми отличиями. Например, можно написать %m
, и это будет аналогично строке, возвращаемой strerror(errno)
. Дополнительных аргументов для %m
передавать не нужно. Для форматирования 64-х битных чисел можно воспользоваться %lld
или %llu
. Чтобы это правильно работало, соответствующие аргументы нужно явно преобразовать в long long или unsigned long long соответственно. Все подробности касаемо форматированного вывода в PostgreSQL можно найти в файле src/port/snprintf.c.
Есть разные уровни логирования — DEBUG5…DEBUG1, INFO, NOTICE, WARNING, ERROR, FATAL, PANIC и другие. Они ведут себя по-разному. Например, NOTICE отображается в пользовательской сессии, но по-умолчанию не пишется в лог-файл. ERROR бросает исключение (о них ниже) и отменяет исполнение текущей транзакции. FATAL приводит к завершению работы текущего процесса, а PANIC останавливает работу всей СУБД. Подробнее о всех существующих уровнях логирования можно прочитать в elog.h.
Отображаемый уровень логирования бывает удобно изменить в рамках сессии, например, для отладки. Сделать это можно так:
Что пишется в лог-файл определяется настройками, описанными в разделе Error Reporting and Logging документации.
Макрос elog() определен, как обвязка над ereport():
ereport(elevel, errmsg_internal(__VA_ARGS__))
В новом коде рекомендуется использовать именно ereport(). Типичный пример:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Invalid argument \"%s\"", arg),
errhint("This is a hint message.")));
Указывать errhint() не обязательно. Хотя использовать elog() и не рекомендуется в новом коде, иногда он полезен. Например, бывает так, что какая-то ошибка воспроизводится на CI, но не локально. Как правило, в подобных случаях быстрее и проще всего диагностировать проблему при помощи отладочного вывода, и elog() оказывается чуточку удобнее, чем ereport().
PostgreSQL имеет собственную реализацию исключений, основанную на setjmp() и longjmp(). Чтобы бросить исключение, нужно вызвать elog(ERROR, ...
или ereport(ERROR, ...
. Также предусмотрены макросы PG_TRY(), PG_CATCH(), PG_FINALLY(), и другие. Их семантика аналогична try, catch и finally в языках программирования с исключениями.
Типичный пример использования:
{
elog(ERROR, "oops...");
}
PG_FINALLY();
{
elog(NOTICE, "cleaning up");
}
PG_END_TRY();
Обычно эти макросы используют для освобождения ресурсов. Например, если в PG_TRY() мы открыли сетевое соединение, после чего возникло исключение, соединение можно закрыть в PG_FINALLY(). Важно понимать, что здесь мы пишем не на C++ и не на Java. Если было брошено исключение, это значит, что транзакция не может быть завершена. Поэтому код, который ловит исключение, обрабатывает, и затем пытается продолжить что-то делать, обычно бесмысленен. В большинстве расширений вы вообще не увидите PG_TRY().
Это по большому счету все, что нужно знать об исключениях и логировании в PostgreSQL. Здесь нет ничего сложного, лишь кое-какая местная специфика. Полную версию исходников к посту вы найдете на GitHub.
Дополнение: В продолжение темы см Расширения PostgreSQL: управление памятью, Расширения PostgreSQL: разделяемая память и локи, Расширения PostgreSQL: работа с таблицами и далее по ссылкам.
Метки: C/C++, PostgreSQL, СУБД.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.