Imagina que has soltado a tu bot en la base de datos para que haga operaciones complicadas. Tarde o temprano va a tropezar, cometer un error o encontrarse con algo inesperado. Sin logging, simplemente se va a quedar callado y tú te vas a quedar pensando qué demonios ha pasado. El logging automático te ayuda a:
- Rastrear cuándo ocurren errores y advertencias.
- Entender la naturaleza y las causas de los fallos.
- Mejorar la diagnosis y el rendimiento del código.
Con logging automático creas una "caja negra" que registra eventos en la base de datos y te ayuda a encontrar errores como si fueras un detective de primera.
Cómo crear logging automático usando funciones
- Definir una tabla para los logs
Para guardar los errores necesitamos un sitio donde almacenarlos. Ya hemos creado la tabla error_log en la lección anterior:
CREATE TABLE error_log (
id SERIAL PRIMARY KEY, -- Identificador único del registro
error_message TEXT NOT NULL, -- Mensaje de error
error_time TIMESTAMP DEFAULT NOW(), -- Momento en que ocurrió el error
function_name TEXT -- Nombre de la función que lanzó el error
);
Esta tabla tiene todo lo necesario para guardar errores: el texto del error, cuándo ocurrió y la función desde la que salió.
- Crear una función para guardar logs
El siguiente paso es crear una función universal que guarde errores en la tabla error_log. Esta función se va a llamar cada vez que quieras registrar un error.
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);
-- Mensaje de logging exitoso
RAISE NOTICE 'Error registrado: %', p_error_message;
END;
$$ LANGUAGE plpgsql;
Vamos a ver este código:
p_error_messageyp_function_nameson los parámetros de la función, que reciben el mensaje de error y el nombre de la función que lo lanzó.INSERT INTO error_logañade el registro a la tabla.RAISE NOTICEmuestra un mensaje en la consola para que el desarrollador sepa que se está haciendo logging.
Ahora tenemos resuelto el primer problema: podemos guardar errores en nuestra tabla con el mínimo esfuerzo.
Usando la función log_error en tareas reales
Ejemplo 1: Logging de errores al dividir por cero
Vamos a crear una función que hace una división simple, pero guarda un error si el denominador es 0.
CREATE OR REPLACE FUNCTION divide_numbers(a NUMERIC, b NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
result NUMERIC;
BEGIN
IF b = 0 THEN
-- Llamamos a la función de logging
PERFORM log_error('¡Intento de división por cero!', 'divide_numbers');
-- Generamos una excepción
RAISE EXCEPTION 'No se permite dividir por cero.';
END IF;
-- Hacemos la división
result := a / b;
RETURN result;
END;
$$ LANGUAGE plpgsql;
- Si el denominador es 0, se llama a la función
log_error, que guarda el error en la tabla. - Después de guardar el error, se lanza una excepción con
RAISE EXCEPTIONpara avisar al usuario.
Ejemplo de llamada:
SELECT divide_numbers(10, 0);
Resultado:
- Llamar a esta función con división por cero va a guardar el error en la tabla
error_log. - El usuario verá el mensaje de error en su consola.
Ejemplo 2: logging al insertar datos no válidos
Vamos a ver un ejemplo con una función que añade un nuevo estudiante a la tabla students. Si el nombre del estudiante está vacío, registramos el evento y paramos la ejecución.
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('¡El nombre del estudiante es obligatorio!', 'add_student');
RAISE EXCEPTION 'El nombre del estudiante no puede estar vacío.';
END IF;
INSERT INTO students (name) VALUES (p_name);
END;
$$ LANGUAGE plpgsql;
Ejemplo de llamada:
SELECT add_student('');
Si intentamos añadir un estudiante sin nombre, la función creará un registro en error_log con el mensaje correspondiente.
Ejemplo 3: logging de advertencias
No siempre hace falta lanzar una excepción al usuario. A veces basta con registrar una advertencia. Vamos a hacer una función para comprobar la edad de un estudiante:
CREATE OR REPLACE FUNCTION check_age(p_age INT)
RETURNS VOID AS $$
BEGIN
IF p_age < 18 THEN
-- Registramos una advertencia, pero no paramos la ejecución
PERFORM log_error('La edad del estudiante es menor de 18.', 'check_age');
RAISE NOTICE 'Advertencia: La edad del estudiante es menor de 18.';
END IF;
RAISE NOTICE 'Comprobación de edad pasada.';
END;
$$ LANGUAGE plpgsql;
Ejemplo de llamada:
SELECT check_age(16);
Resultado:
- Registro de la advertencia en la tabla
error_log. - Notificación en la consola de que la edad del estudiante es menor de 18.
Logging y manejo de excepciones
Vamos a juntar el registro de errores y el manejo de excepciones en una función más compleja. Imagina que tienes que recalcular las notas de los estudiantes en la tabla grades. Si el proceso falla para uno de los estudiantes, el error se registra pero la operación sigue para los demás.
CREATE OR REPLACE FUNCTION recalculate_grades()
RETURNS VOID AS $$
DECLARE
student RECORD;
BEGIN
FOR student IN SELECT * FROM students LOOP
BEGIN
-- Ejemplo de tarea: actualizar las notas del estudiante
UPDATE grades SET final_grade = final_grade + 1
WHERE student_id = student.id;
RAISE NOTICE 'Notas actualizadas para el estudiante %', student.name;
EXCEPTION WHEN OTHERS THEN
-- Registramos el error y seguimos ejecutando
PERFORM log_error('No se pudieron actualizar las notas para el estudiante ' || student.name, 'recalculate_grades');
END;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Ejemplo de llamada:
SELECT recalculate_grades();
Este enfoque hace que tu función sea mucho más resistente, porque los errores se registran por separado y no paran la ejecución para todos los datos.
GO TO FULL VERSION