Hãy tưởng tượng bạn thả con bot của mình đi lang thang trong database để nó làm mấy thao tác phức tạp. Sớm hay muộn nó cũng sẽ vấp, bị lỗi hoặc gặp tình huống bất ngờ. Nếu không có log, nó sẽ im re luôn, còn bạn thì ngồi đoán không biết chuyện gì vừa xảy ra. Ghi log tự động giúp bạn:
- Theo dõi khi nào có lỗi hoặc cảnh báo xuất hiện.
- Hiểu được bản chất và lý do của lỗi.
- Cải thiện việc chẩn đoán và hiệu năng code.
Với ghi log tự động, bạn tạo ra một "hộp đen" ghi lại mọi sự kiện trong database, giúp bạn tìm lỗi như một thám tử xịn sò.
Tạo ghi log tự động bằng function
- Định nghĩa bảng log
Để ghi log lỗi, mình cần chỗ để lưu lại. Mình đã tạo bảng error_log ở bài trước rồi nè:
CREATE TABLE error_log (
id SERIAL PRIMARY KEY, -- Định danh duy nhất cho bản ghi
error_message TEXT NOT NULL, -- Thông báo lỗi
error_time TIMESTAMP DEFAULT NOW(), -- Thời điểm lỗi xảy ra
function_name TEXT -- Tên function gây ra lỗi
);
Bảng này có đủ thứ cần thiết để ghi lỗi: nội dung lỗi, thời gian và tên function phát sinh lỗi.
- Tạo function để ghi log
Bước tiếp theo — tạo một function đa năng để ghi lỗi vào bảng error_log. Function này sẽ được gọi bất cứ khi nào bạn muốn lưu lại lỗi.
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);
-- Thông báo ghi log thành công
RAISE NOTICE 'Lỗi đã được ghi log: %', p_error_message;
END;
$$ LANGUAGE plpgsql;
Giải thích đoạn code này:
p_error_messagevàp_function_name— tham số truyền vào function, nhận thông báo lỗi và tên function gọi lỗi.INSERT INTO error_logthêm bản ghi vào bảng.RAISE NOTICEin ra thông báo trên console để dev biết quá trình ghi log đang diễn ra.
Giờ mình đã giải quyết xong bài toán đầu tiên: có thể ghi lỗi vào bảng log cực kỳ dễ dàng.
Dùng function log_error trong các bài toán thực tế
Ví dụ 1: Ghi log lỗi khi chia cho 0
Tạo function thực hiện phép chia đơn giản, nhưng sẽ ghi log nếu mẫu số bằng 0.
CREATE OR REPLACE FUNCTION divide_numbers(a NUMERIC, b NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
result NUMERIC;
BEGIN
IF b = 0 THEN
-- Gọi function ghi log
PERFORM log_error('Đã thử chia cho 0!', 'divide_numbers');
-- Sinh exception
RAISE EXCEPTION 'Không được phép chia cho 0.';
END IF;
-- Thực hiện phép chia
result := a / b;
RETURN result;
END;
$$ LANGUAGE plpgsql;
- Nếu mẫu số là 0, function
log_errorsẽ được gọi để ghi lỗi vào bảng. - Sau khi ghi log, sẽ sinh ra exception
RAISE EXCEPTIONđể báo cho user.
Ví dụ gọi hàm:
SELECT divide_numbers(10, 0);
Kết quả:
- Gọi function này với phép chia cho 0 sẽ ghi lỗi vào bảng
error_log. - User sẽ thấy thông báo lỗi trên console.
Ví dụ 2: ghi log khi chèn dữ liệu không hợp lệ
Xem thử function thêm sinh viên mới vào bảng students. Nếu tên sinh viên bị trống, mình sẽ ghi log và dừng luô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('Phải nhập tên sinh viên!', 'add_student');
RAISE EXCEPTION 'Tên sinh viên không được để trống.';
END IF;
INSERT INTO students (name) VALUES (p_name);
END;
$$ LANGUAGE plpgsql;
Ví dụ gọi hàm:
SELECT add_student('');
Nếu thử thêm sinh viên mà không có tên, function sẽ tạo bản ghi trong error_log với thông báo tương ứng.
Ví dụ 3: ghi log cảnh báo
Không phải lúc nào cũng cần sinh exception cho user. Đôi khi chỉ cần ghi lại cảnh báo là đủ. Làm thử function kiểm tra tuổi sinh viên nhé:
CREATE OR REPLACE FUNCTION check_age(p_age INT)
RETURNS VOID AS $$
BEGIN
IF p_age < 18 THEN
-- Ghi log cảnh báo, không dừng chương trình
PERFORM log_error('Tuổi sinh viên nhỏ hơn 18.', 'check_age');
RAISE NOTICE 'Cảnh báo: Tuổi sinh viên nhỏ hơn 18.';
END IF;
RAISE NOTICE 'Kiểm tra tuổi ok.';
END;
$$ LANGUAGE plpgsql;
Ví dụ gọi hàm:
SELECT check_age(16);
Kết quả:
- Ghi cảnh báo vào bảng
error_log. - Thông báo trên console rằng tuổi sinh viên nhỏ hơn 18.
Ghi log và xử lý exception
Giờ mình sẽ kết hợp ghi log lỗi và xử lý exception trong một function phức tạp hơn. Giả sử bạn cần tính lại điểm cho sinh viên trong bảng grades. Nếu có lỗi với một sinh viên nào đó, lỗi sẽ được ghi log, nhưng chương trình vẫn tiếp tục với các sinh viên khác.
CREATE OR REPLACE FUNCTION recalculate_grades()
RETURNS VOID AS $$
DECLARE
student RECORD;
BEGIN
FOR student IN SELECT * FROM students LOOP
BEGIN
-- Ví dụ: cập nhật điểm cho sinh viên
UPDATE grades SET final_grade = final_grade + 1
WHERE student_id = student.id;
RAISE NOTICE 'Đã cập nhật điểm cho sinh viên %', student.name;
EXCEPTION WHEN OTHERS THEN
-- Ghi log lỗi và tiếp tục
PERFORM log_error('Không cập nhật được điểm cho sinh viên ' || student.name, 'recalculate_grades');
END;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Ví dụ gọi hàm:
SELECT recalculate_grades();
Cách này giúp function của bạn "trâu bò" hơn, vì lỗi sẽ được ghi log riêng mà không làm dừng toàn bộ quá trình xử lý dữ liệu.
GO TO FULL VERSION