Представьте, что вы отправили своего бота в свободное плавание по базе данных, чтобы он выполнял сложные операции. Рано или поздно он споткнётся, ошибётся или столкнётся с неожиданной ситуацией. Без логирования он может просто замолчать, а вы останетесь в недоумении, что пошло не так. Автоматическое логирование помогает:
- Отслеживать возникновение ошибок и предупреждений.
- Понимать природу и причины сбоев.
- Улучшать диагностику и производительность кода.
С помощью автоматического логирования вы создаёте "чёрный ящик", который фиксирует события в базе данных и помогает вам находить ошибки, как если бы вы были первоклассным детективом.
Создание автоматического логирования через функции
- Определяем таблицу для логов
Чтобы логировать ошибки, нам нужно место для их хранения. Мы уже создали таблицу error_log в предыдущей лекции:
CREATE TABLE error_log (
id SERIAL PRIMARY KEY, -- Уникальный идентификатор записи
error_message TEXT NOT NULL, -- Сообщение об ошибке
error_time TIMESTAMP DEFAULT NOW(), -- Время возникновения ошибки
function_name TEXT -- Имя функции, вызвавшей ошибку
);
Эта таблица имеет всё необходимое для записи ошибок: текст ошибки, её время и функцию, из которой она возникла.
- Создаём функцию для записи логов
Следующий шаг — создать универсальную функцию, которая будет записывать ошибки в таблицу error_log. Эта функция будет вызываться всякий раз, когда мы хотим зафиксировать ошибку.
CREATE OR REPLACE FUNCTION log_error(p_error_message TEXT, p_function_name TEXT)
RETURNS VOID AS $$
BEGIN
INSERT INTO error_log (error_message, function_name)
VALUES (p_error_message, p_function_name);
-- Сообщение об успешном логировании
RAISE NOTICE 'Error logged: %', p_error_message;
END;
$$ LANGUAGE plpgsql;
Разберём этот код:
p_error_messageиp_function_name— параметры функции, которые принимают сообщение об ошибке и имя вызвавшей функции.INSERT INTO error_logдобавляет запись в таблицу.RAISE NOTICEвыводит сообщение в консоль, чтобы разработчик был осведомлён о процессе логирования.
Теперь у нас есть решение первой задачи: мы можем записывать ошибки в нашу таблицу с минимальными усилиями.
Использование функции log_error в реальных задачах
Пример 1: Логирование ошибок при делении на ноль
Создадим функцию, которая выполняет простое деление, но логирует ошибку, если знаменатель равен 0.
CREATE OR REPLACE FUNCTION divide_numbers(a NUMERIC, b NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
result NUMERIC;
BEGIN
IF b = 0 THEN
-- Вызываем функцию логирования
PERFORM log_error('Division by zero attempted!', 'divide_numbers');
-- Генерируем исключение
RAISE EXCEPTION 'Division by zero is not allowed.';
END IF;
-- Выполняем деление
result := a / b;
RETURN result;
END;
$$ LANGUAGE plpgsql;
- Если знаменатель равен 0, вызывается функция
log_error, которая записывает ошибку в таблицу. - После записи ошибки генерируется исключение
RAISE EXCEPTION, чтобы уведомить пользователя.
Пример вызова:
SELECT divide_numbers(10, 0);
Результат:
- Вызов этой функции с делением на ноль приведёт к записи ошибки в таблицу
error_log. - Пользователь увидит сообщение об ошибке в своей консоли.
Пример 2: логирование при вставке невалидных данных
Рассмотрим пример с функцией, которая добавляет нового студента в таблицу students. Если имя студента пустое, мы логируем событие и прерываем выполнение.
CREATE OR REPLACE FUNCTION add_student(p_name TEXT)
RETURNS VOID AS $$
BEGIN
IF p_name IS NULL OR p_name = '' THEN
PERFORM log_error('Student name must be provided!', 'add_student');
RAISE EXCEPTION 'Student name cannot be empty.';
END IF;
INSERT INTO students (name) VALUES (p_name);
END;
$$ LANGUAGE plpgsql;
Пример вызова:
SELECT add_student('');
Если мы попытаемся добавить студента без имени, функция создаст запись в error_log с соответствующим сообщением.
Пример 3: логирование предупреждений
Не всегда нужно генерировать исключение для пользователя. Иногда достаточно зарегистрировать предупреждение. Сделаем функцию для проверки возраста студента:
CREATE OR REPLACE FUNCTION check_age(p_age INT)
RETURNS VOID AS $$
BEGIN
IF p_age < 18 THEN
-- Логируем предупреждение, но не прерываем выполнение
PERFORM log_error('Student age is below 18.', 'check_age');
RAISE NOTICE 'Warning: Student age is below 18.';
END IF;
RAISE NOTICE 'Age check passed.';
END;
$$ LANGUAGE plpgsql;
Пример вызова:
SELECT check_age(16);
Результат:
- Запись предупреждения в таблицу
error_log. - Уведомление в консоли о том, что возраст студента меньше 18.
Логирование и обработка исключений
Давайте объединим запись ошибок и обработку исключений в более сложной функции. Представим, что у нас есть задача пересчитать оценки студентов в таблице grades. Если процесс не выполнен для одного из студентов, ошибка логируется, но операция продолжается.
CREATE OR REPLACE FUNCTION recalculate_grades()
RETURNS VOID AS $$
DECLARE
student RECORD;
BEGIN
FOR student IN SELECT * FROM students LOOP
BEGIN
-- Пример задачи: обновление оценок студента
UPDATE grades SET final_grade = final_grade + 1
WHERE student_id = student.id;
RAISE NOTICE 'Updated grades for student %', student.name;
EXCEPTION WHEN OTHERS THEN
-- Логируем ошибку и продолжаем выполнение
PERFORM log_error('Failed to update grades for student ' || student.name, 'recalculate_grades');
END;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Пример вызова:
SELECT recalculate_grades();
Этот подход делает вашу функцию гораздо более устойчивой, поскольку ошибки отдельно логируются и не останавливают выполнение для всех данных.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ