Wyobraź sobie, że wysłałeś swojego bota na samodzielne pływanie po bazie danych, żeby robił skomplikowane operacje. Prędzej czy później potknie się, walnie babola albo natknie się na coś niespodziewanego. Bez logowania może po prostu zamilknąć, a Ty zostaniesz z pytaniem, co poszło nie tak. Automatyczne logowanie pomaga:
- Śledzić pojawianie się błędów i ostrzeżeń.
- Kumać naturę i przyczyny awarii.
- Poprawiać diagnostykę i wydajność kodu.
Dzięki automatycznemu logowaniu tworzysz taki "czarny skrzynkę", która zapisuje zdarzenia w bazie i pomaga Ci wyłapywać błędy, jakbyś był pierwszorzędnym detektywem.
Tworzenie automatycznego logowania przez funkcje
- Definiujemy tabelę na logi
Żeby logować błędy, potrzebujemy miejsca na ich przechowywanie. Już wcześniej stworzyliśmy tabelę error_log w poprzednim wykładzie:
CREATE TABLE error_log (
id SERIAL PRIMARY KEY, -- Unikalny identyfikator wpisu
error_message TEXT NOT NULL, -- Wiadomość o błędzie
error_time TIMESTAMP DEFAULT NOW(), -- Czas wystąpienia błędu
function_name TEXT -- Nazwa funkcji, która wywołała błąd
);
Ta tabela ma wszystko, co trzeba do zapisywania błędów: tekst błędu, czas i funkcję, z której wyskoczył.
- Tworzymy funkcję do zapisywania logów
Kolejny krok — stworzyć uniwersalną funkcję, która będzie wrzucać błędy do tabeli error_log. Ta funkcja będzie wywoływana zawsze, gdy chcemy zarejestrować błąd.
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);
-- Wiadomość o udanym logowaniu
RAISE NOTICE 'Błąd zalogowany: %', p_error_message;
END;
$$ LANGUAGE plpgsql;
Rozkminiamy ten kod:
p_error_messageip_function_name— parametry funkcji, które przyjmują wiadomość o błędzie i nazwę wywołującej funkcji.INSERT INTO error_logdodaje wpis do tabeli.RAISE NOTICEwyświetla info w konsoli, żeby programista wiedział, że logowanie działa.
Teraz mamy ogarnięte pierwsze zadanie: możemy wrzucać błędy do naszej tabeli praktycznie bez wysiłku.
Użycie funkcji log_error w realnych zadaniach
Przykład 1: Logowanie błędów przy dzieleniu przez zero
Stwórzmy funkcję, która robi proste dzielenie, ale loguje błąd, jeśli mianownik to 0.
CREATE OR REPLACE FUNCTION divide_numbers(a NUMERIC, b NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
result NUMERIC;
BEGIN
IF b = 0 THEN
-- Wywołujemy funkcję logowania
PERFORM log_error('Próba dzielenia przez zero!', 'divide_numbers');
-- Generujemy wyjątek
RAISE EXCEPTION 'Dzielenie przez zero jest niedozwolone.';
END IF;
-- Wykonujemy dzielenie
result := a / b;
RETURN result;
END;
$$ LANGUAGE plpgsql;
- Jeśli mianownik to 0, wywoływana jest funkcja
log_error, która zapisuje błąd do tabeli. - Po zapisaniu błędu generowany jest wyjątek
RAISE EXCEPTION, żeby użytkownik się dowiedział.
Przykład wywołania:
SELECT divide_numbers(10, 0);
Wynik:
- Wywołanie tej funkcji z dzieleniem przez zero wrzuci błąd do tabeli
error_log. - Użytkownik zobaczy komunikat o błędzie w swojej konsoli.
Przykład 2: logowanie przy wstawianiu nieprawidłowych danych
Weźmy przykład z funkcją, która dodaje nowego studenta do tabeli students. Jeśli imię studenta jest puste, logujemy zdarzenie i przerywamy wykonanie.
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('Imię studenta musi być podane!', 'add_student');
RAISE EXCEPTION 'Imię studenta nie może być puste.';
END IF;
INSERT INTO students (name) VALUES (p_name);
END;
$$ LANGUAGE plpgsql;
Przykład wywołania:
SELECT add_student('');
Jeśli spróbujemy dodać studenta bez imienia, funkcja stworzy wpis w error_log z odpowiednią wiadomością.
Przykład 3: logowanie ostrzeżeń
Nie zawsze trzeba rzucać wyjątek użytkownikowi. Czasem wystarczy zarejestrować ostrzeżenie. Zróbmy funkcję do sprawdzania wieku studenta:
CREATE OR REPLACE FUNCTION check_age(p_age INT)
RETURNS VOID AS $$
BEGIN
IF p_age < 18 THEN
-- Logujemy ostrzeżenie, ale nie przerywamy wykonania
PERFORM log_error('Wiek studenta jest poniżej 18.', 'check_age');
RAISE NOTICE 'Ostrzeżenie: wiek studenta jest poniżej 18.';
END IF;
RAISE NOTICE 'Sprawdzanie wieku zaliczone.';
END;
$$ LANGUAGE plpgsql;
Przykład wywołania:
SELECT check_age(16);
Wynik:
- Wpis ostrzeżenia w tabeli
error_log. - Powiadomienie w konsoli, że wiek studenta jest poniżej 18.
Logowanie i obsługa wyjątków
Połączmy teraz zapisywanie błędów i obsługę wyjątków w bardziej złożonej funkcji. Wyobraź sobie, że mamy zadanie przeliczyć oceny studentów w tabeli grades. Jeśli proces nie pójdzie dla któregoś studenta, błąd jest logowany, ale operacja leci dalej.
CREATE OR REPLACE FUNCTION recalculate_grades()
RETURNS VOID AS $$
DECLARE
student RECORD;
BEGIN
FOR student IN SELECT * FROM students LOOP
BEGIN
-- Przykład zadania: aktualizacja ocen studenta
UPDATE grades SET final_grade = final_grade + 1
WHERE student_id = student.id;
RAISE NOTICE 'Zaktualizowano oceny dla studenta %', student.name;
EXCEPTION WHEN OTHERS THEN
-- Logujemy błąd i lecimy dalej
PERFORM log_error('Nie udało się zaktualizować ocen dla studenta ' || student.name, 'recalculate_grades');
END;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Przykład wywołania:
SELECT recalculate_grades();
To podejście sprawia, że Twoja funkcja jest dużo bardziej odporna, bo błędy są logowane osobno i nie zatrzymują działania na wszystkich danych.
GO TO FULL VERSION