Ghi nhật ký kiểm toán cho công cụ nội bộ: mẫu lịch sử thay đổi gọn gàng
Ghi nhật ký kiểm toán cho công cụ nội bộ thực tế: theo dõi ai làm gì và khi nào trên mọi thay đổi CRUD, lưu diff an toàn, và hiển thị activity feed cho admin.

Tại sao công cụ nội bộ cần audit logs (và chỗ thường thất bại)
Hầu hết các đội chỉ thêm nhật ký kiểm toán sau khi có sự cố. Khách hàng tranh chấp một thay đổi, một con số tài chính thay đổi, hoặc kiểm toán hỏi: "Ai phê duyệt cái này?" Nếu bạn bắt đầu lúc đó, bạn sẽ cố phục dựng quá khứ từ manh mối rời rạc: timestamp trong DB, tin nhắn Slack, và phỏng đoán.
Với đa số ứng dụng nội bộ, "đủ cho tuân thủ" không có nghĩa là một hệ thống pháp y hoàn hảo. Nó nghĩa là bạn có thể trả lời nhanh và nhất quán một tập câu hỏi nhỏ: ai đã thay đổi, bản ghi nào bị ảnh hưởng, gì đã thay đổi, khi nào xảy ra, và đến từ đâu (UI, import, API, tự động). Sự rõ ràng đó làm cho nhật ký audit trở nên đáng tin.
Nơi audit logs thường thất bại không phải là database. Là bao phủ. Lịch sử trông ổn cho các sửa đơn giản, rồi xuất hiện khoảng trống ngay khi công việc diễn ra với tốc độ. Những thủ phạm phổ biến là chỉnh sửa hàng loạt, import, job có lịch, hành động admin bỏ qua màn hình thường (ví dụ reset mật khẩu hoặc đổi vai trò), và xoá (đặc biệt là hard delete).
Lỗi thường gặp khác là trộn lẫn log debug với audit log. Debug logs dùng cho dev: ồn, kỹ thuật và thường không nhất quán. Audit logs dùng cho trách nhiệm: trường nhất quán, ngôn ngữ rõ ràng, và định dạng ổn định bạn có thể đưa cho người không phải kỹ thuật.
Ví dụ thực tế: một quản lý hỗ trợ thay đổi gói khách hàng, rồi một automation cập nhật chi tiết thanh toán sau đó. Nếu bạn chỉ ghi "updated customer", bạn không thể biết người hay workflow đã làm, hay một file import đã ghi đè.
Các trường audit trả lời ai, cái gì, khi nào
Ghi nhật ký audit tốt bắt đầu với một mục tiêu: một người nên đọc một mục và hiểu chuyện gì xảy ra mà không phải đoán.
Ai làm
Lưu một actor rõ ràng cho mỗi thay đổi. Hầu hết đội dừng ở "user id", nhưng công cụ nội bộ thường thay đổi dữ liệu qua nhiều cánh cửa.
Bao gồm loại actor và định danh actor, để phân biệt nhân viên, service account, hay tích hợp bên ngoài. Nếu bạn có team hoặc tenant, cũng lưu organization hoặc workspace id để sự kiện không bị lẫn.
Cái gì đã xảy ra và với bản ghi nào
Ghi hành động (create, update, delete, restore) cùng mục tiêu. "Mục tiêu" nên vừa thân thiện với con người vừa chính xác: tên bảng hoặc thực thể, id bản ghi, và lý tưởng là một nhãn ngắn (ví dụ số order) để quét nhanh.
Một tập trường tối thiểu thiết thực:
- actor_type, actor_id (và actor_display_name nếu có)
- action và target_type, target_id
- happened_at_utc (timestamp lưu bằng UTC)
- source (screen, endpoint, job, import) và ip_address (chỉ khi cần)
- reason (lý do tuỳ chọn cho thay đổi nhạy cảm)
Khi nào xảy ra
Lưu timestamp ở UTC. Luôn luôn. Rồi hiển thị theo giờ địa phương của người xem trong UI quản trị. Điều này tránh tranh cãi "hai người thấy thời gian khác nhau" khi rà soát.
Nếu bạn xử lý hành động rủi ro cao như thay đổi vai trò, hoàn tiền, hoặc xuất dữ liệu, thêm trường "reason". Một ghi chú ngắn như "Phê duyệt bởi quản lý trong ticket 1842" có thể biến trail audit từ nhiễu trở thành bằng chứng.
Chọn mô hình dữ liệu: event log vs versioned history
Quyết định thiết kế đầu tiên là nơi "sự thật" của lịch sử thay đổi nằm. Hầu hết đội rơi vào một trong hai mô hình: bảng event append-only, hoặc bảng lịch sử theo entity.
Tuỳ chọn 1: Event log (bảng actions append-only)
Event log là một bảng duy nhất ghi mỗi hành động dưới dạng một hàng mới. Mỗi hàng lưu ai làm, khi nào, thực thể bị chạm tới, và payload (thường JSON) mô tả thay đổi.
Mô hình này dễ thêm vào và linh hoạt khi mô hình dữ liệu thay đổi. Nó cũng tự nhiên cho activity feed quản trị vì feed cơ bản là "sự kiện mới nhất trước nhất."
Tuỳ chọn 2: Versioned history (lịch sử theo entity)
Approach versioned history tạo bảng lịch sử cho từng entity, như Order_history hay User_versions, nơi mỗi lần update tạo snapshot đầy đủ mới (hoặc tập trường đã thay đổi có cấu trúc) với một số phiên bản.
Điều này giúp báo cáo theo thời điểm dễ hơn ("bản ghi này trông như thế nào vào thứ Ba tuần trước?"). Nó cũng có thể rõ ràng hơn cho kiểm toán viên, vì timeline của mỗi bản ghi tự chứa.
Cách chọn thực tế:
- Chọn event log nếu bạn muốn một nơi để tìm, feed hoạt động dễ, và ít ma sát khi entity mới xuất hiện.
- Chọn versioned history nếu bạn cần timeline theo bản ghi thường xuyên, view tại thời điểm cụ thể, hoặc diff per entity dễ dàng.
- Nếu lưu trữ là vấn đề, event log với diff theo trường thường nhẹ hơn snapshot đầy đủ.
- Nếu báo cáo là mục tiêu chính, bảng version có thể dễ truy vấn hơn so với phân tích payload sự kiện.
Dù chọn gì, giữ các mục audit bất biến: không cập nhật, không xoá. Nếu có sai sót, thêm một mục mới giải thích việc sửa.
Cân nhắc thêm correlation_id (hoặc operation id). Một hành động người dùng có thể kích hoạt nhiều thay đổi (ví dụ, "Deactivate user" cập nhật user, thu hồi session, huỷ tác vụ đang chờ). Một correlation id chung cho phép gom các hàng đó thành một thao tác dễ đọc.
Ghi lại hành động CRUD một cách đáng tin (bao gồm xoá và chỉnh sửa hàng loạt)
Ghi audit đáng tin bắt đầu với một quy tắc: mọi ghi phải đi qua một đường duy nhất mà cũng ghi event audit. Nếu một số cập nhật chạy trong job nền, import, hoặc quick-edit bỏ qua flow lưu thông thường, log sẽ có lỗ hổng.
Với create, ghi actor và nguồn (UI, API, import). Import là nơi các đội thường mất dấu "ai", nên lưu một giá trị "performed by" rõ ràng ngay cả khi dữ liệu đến từ file hay tích hợp. Cũng hữu ích khi lưu giá trị ban đầu (snapshot đầy đủ hoặc tập trường khoá) để giải thích vì sao bản ghi tồn tại.
Update phức tạp hơn. Bạn có thể chỉ ghi các trường thay đổi (nhỏ, dễ đọc, và nhanh), hoặc lưu snapshot đầy đủ sau mỗi lưu (đơn giản để truy vấn sau này nhưng tốn). Một giải pháp cân bằng là lưu diff cho sửa bình thường và lưu snapshot chỉ cho đối tượng nhạy cảm (như permissions, thông tin ngân hàng, hay quy tắc giá).
Xoá không nên xoá bằng chứng. Ưu tiên soft delete (flag is_deleted + một mục audit). Nếu bắt buộc hard delete, ghi event audit trước và kèm snapshot của bản ghi để chứng minh những gì đã bị xoá.
Xem phục hồi (undelete) là một hành động riêng. "Restore" không giống "Update", và tách ra giúp việc rà soát và kiểm tra tuân thủ dễ hơn.
Với chỉnh sửa hàng loạt, tránh một mục mơ hồ như "updated 500 records." Bạn cần đủ chi tiết để trả lời "những bản ghi nào thay đổi?" sau này. Mô hình thực tế là một parent event cộng với child event cho từng bản ghi:
- Parent event: actor, tool/screen, bộ lọc đã dùng, và kích thước batch
- Child event cho mỗi bản ghi: record id, before/after (hoặc trường đã thay đổi), và kết quả (thành công/thất bại)
- Tuỳ chọn: một trường reason chung (cập nhật chính sách, dọn dẹp, migration)
Ví dụ: một trưởng nhóm hỗ trợ đóng hàng loạt 120 ticket. Parent entry ghi bộ lọc "status=open, older than 30 days," và mỗi ticket có child entry cho thấy status open -> closed.
Lưu những gì đã thay đổi mà không tạo ra vấn đề về quyền riêng tư hay lưu trữ
Nhật ký audit nhanh biến thành rác khi họ lưu quá nhiều (mỗi bản ghi đầy đủ, mãi mãi) hoặc quá ít (chỉ "edited user"). Mục tiêu là một bản ghi có thể bào chữa cho tuân thủ và dễ đọc bởi admin.
Mặc định thực tế là lưu diff theo trường cho hầu hết cập nhật. Chỉ lưu các trường đã thay đổi, với giá trị "before" và "after". Điều này giữ dung lượng thấp và làm feed hoạt động dễ quét: "Status: Pending -> Approved" rõ ràng hơn một blob khổng lồ.
Giữ snapshot đầy đủ cho những thời điểm quan trọng: tạo, xoá, và chuyển đổi workflow lớn. Snapshot nặng hơn, nhưng bảo vệ bạn khi ai đó hỏi "Hồ sơ khách hàng trông như thế nào trước khi bị xoá?"
Dữ liệu nhạy cảm cần quy tắc che/ẩn, nếu không bảng audit sẽ trở thành một cơ sở dữ liệu thứ hai đầy bí mật. Quy tắc phổ biến:
- Không bao giờ lưu mật khẩu, token API, hoặc khóa riêng (chỉ log "changed")
- Che dữ liệu cá nhân như email/số điện thoại (lưu giá trị một phần hoặc hash)
- Với ghi chú hoặc trường văn bản tự do, lưu preview ngắn và một flag "changed"
- Log tham chiếu (user_id, order_id) thay vì sao chép toàn bộ object liên quan
Thay đổi schema cũng có thể phá vỡ lịch sử audit. Nếu một trường sau đó đổi tên hoặc bị xoá, lưu fallback an toàn như "unknown field" kèm key trường gốc. Với trường bị xoá, giữ giá trị cuối cùng đã biết nhưng gắn nhãn "field removed from schema" để feed giữ tính trung thực.
Cuối cùng, làm mục audit thân thiện với con người. Lưu nhãn hiển thị ("Assigned to") kèm với key thô ("assignee_id"), và format giá trị (ngày, tiền tệ, tên trạng thái).
Mẫu theo bước: triển khai audit logging trong luồng app của bạn
Trail audit đáng tin không phải là ghi càng nhiều càng tốt. Là dùng một mẫu lặp lại ở mọi nơi để không có lỗ như "import hàng loạt không được log" hoặc "chỉnh sửa di động trông ẩn danh."
1) Mô hình hoá dữ liệu audit một lần
Bắt đầu từ mô hình dữ liệu và tạo một tập bảng nhỏ mô tả mọi thay đổi.
Giữ đơn giản: một bảng cho event, một cho trường thay đổi, và một context actor nhỏ.
- audit_event: id, entity_type, entity_id, action (create/update/delete/restore), created_at, request_id
- audit_event_item: id, audit_event_id, field_name, old_value, new_value
- actor_context (hoặc các trường trên audit_event): actor_type (user/system), actor_id, actor_email, ip, user_agent
2) Thêm một sub-process chung "Write + Audit"
Tạo một sub-process tái sử dụng mà:
- Nhận tên entity, id entity, action, và giá trị before/after.
- Ghi thay đổi nghiệp vụ vào bảng chính.
- Tạo một bản ghi audit_event.
- Tính các trường thay đổi và chèn các hàng audit_event_item.
Quy tắc nghiêm ngặt: mọi đường ghi phải gọi cùng sub-process này. Bao gồm nút UI, endpoint API, automations đã lịch, và tích hợp.
3) Tạo actor và thời gian trên server
Đừng tin trình duyệt cho "who" và "when." Đọc actor từ session auth, và sinh timestamp trên server. Nếu một automation chạy, đặt actor_type là system và lưu tên job làm nhãn actor.
4) Test với một kịch bản cụ thể
Chọn một bản ghi (ví dụ một ticket khách hàng): tạo, sửa hai trường (status và assignee), xoá, rồi phục hồi. Feed audit của bạn nên hiển thị năm event, với hai item update dưới event chỉnh sửa, và actor cùng timestamp được điền cùng cách mỗi lần.
Xây dựng activity feed quản trị mà người ta thực sự dùng được
Một audit log chỉ hữu dụng nếu ai đó có thể đọc nó nhanh trong lúc rà soát hoặc xử lý sự cố. Mục tiêu của feed quản trị đơn giản: trả lời "chuyện gì đã xảy ra?" trong nháy mắt, rồi cho phép xem sâu hơn mà không bị ngập trong JSON thô.
Bắt đầu với layout timeline: mới nhất trước, một dòng cho mỗi event, và các động từ rõ ràng như Created, Updated, Deleted, Restored. Mỗi dòng nên hiển thị actor (người hay hệ thống), target (loại bản ghi cộng với tên thân thiện), và thời gian.
Một định dạng dòng thực tế:
- Verb + object: "Updated Customer: Acme Co."
- Actor: "Maya (Support)" hoặc "System: Nightly Sync"
- Time: timestamp tuyệt đối (kèm timezone)
- Tóm tắt thay đổi: "status: Pending -> Approved, limit: 5,000 -> 7,500"
- Tags: Updated, Deleted, Integration, Job
Giữ "cái gì đã thay đổi" cô đọng. Hiển thị 1–3 trường inline, rồi cung cấp panel khoan (drawer/modal) để xem chi tiết đầy đủ: giá trị trước/sau, nguồn request (web, mobile, API), và bất kỳ trường lý do/comment.
Lọc là điều làm feed có thể dùng được sau tuần đầu. Tập trung vào bộ lọc phù hợp câu hỏi thực tế:
- Actor (user hoặc system)
- Loại đối tượng (Customers, Orders, Permissions)
- Loại hành động (Create/Update/Delete/Restore)
- Khoảng thời gian
- Tìm kiếm văn bản (tên bản ghi hoặc ID)
Liên kết quan trọng nhưng chỉ khi được phép. Nếu người xem có quyền với bản ghi bị ảnh hưởng, hiển thị "View record". Nếu không, hiển thị placeholder an toàn (ví dụ "Restricted record") và vẫn giữ entry audit hiển thị.
Làm rõ hành động hệ thống. Gắn nhãn job định kỳ và tích hợp rõ để admin phân biệt "Dana xoá nó" và "Nightly billing sync cập nhật nó."
Quyền và quy tắc quyền riêng tư cho dữ liệu audit
Audit logs là bằng chứng, nhưng cũng là dữ liệu nhạy cảm. Đối xử với audit như một sản phẩm riêng trong app của bạn: quy tắc truy cập rõ ràng, giới hạn rõ ràng, và xử lý cẩn trọng thông tin cá nhân.
Quyết định ai xem được gì. Một phân chia phổ biến: system admins xem mọi thứ; quản lý bộ phận xem sự kiện cho team họ; chủ sở hữu bản ghi xem sự kiện liên quan đến bản ghi họ đã có quyền (và không hơn). Nếu bạn hiển thị activity feed, áp dụng cùng quy tắc cho mọi hàng, không chỉ trên màn hình.
Hiển thị theo hàng quan trọng nhất trong multi-tenant hoặc công cụ xuyên phòng ban. Bảng audit của bạn nên mang cùng khóa pham vi như dữ liệu nghiệp vụ (tenant_id, department_id, project_id), để có thể lọc nhất quán. Ví dụ: một quản lý hỗ trợ nên thấy thay đổi với ticket trong queue của họ, nhưng không thấy điều chỉnh lương trong HR, dù cả hai xảy ra trong cùng app.
Chính sách đơn giản thường hoạt động thực tế:
- Admin: truy cập audit đầy đủ qua tenant và phòng ban
- Manager: truy cập audit bị giới hạn theo department_id hoặc project_id
- Chủ sở hữu bản ghi: truy cập audit chỉ cho bản ghi họ có thể xem
- Auditor/tuân thủ: chỉ đọc, cho phép export, chặn edit
- Những người khác: mặc định không có quyền
Quyền riêng tư là nửa sau. Lưu đủ để chứng minh việc đã xảy ra, nhưng tránh biến log thành một bản sao DB. Với trường nhạy cảm (SSN, ghi chú y tế, chi tiết thanh toán), ưu tiên redact: ghi rằng trường thay đổi mà không lưu giá trị cũ/mới. Bạn có thể log "email changed" đồng thời che giá trị thực, hoặc lưu fingerprint đã hash để xác minh.
Giữ sự kiện bảo mật tách biệt khỏi thay đổi nghiệp vụ. Thử đăng nhập, reset MFA, tạo API key và thay đổi vai trò nên vào luồng security_audit với quyền chặt hơn và lưu lâu hơn. Chỉnh sửa nghiệp vụ (status, approvals, workflow) có thể ở luồng audit chung.
Khi ai đó yêu cầu xoá dữ liệu cá nhân, đừng xoá toàn bộ audit trail. Thay vào đó:
- Xoá hoặc ẩn danh dữ liệu hồ sơ người dùng
- Thay thế định danh actor trong log bằng một bí danh cố định (ví dụ "deleted-user-123")
- Che các giá trị trường là dữ liệu cá nhân
- Giữ timestamp, loại hành động, và tham chiếu bản ghi để tuân thủ
Lưu trữ, tính toàn vẹn và hiệu năng cho tuân thủ
Một nhật ký audit hữu dụng không chỉ là "chúng tôi ghi sự kiện." Đối với tuân thủ, bạn cần chứng minh ba điều: bạn giữ dữ liệu đủ lâu, nó không bị thay đổi sau khi tạo, và bạn có thể truy xuất nhanh khi cần.
Retention: quyết định chính sách bạn có thể giải thích
Bắt đầu với quy tắc đơn giản phù hợp rủi ro. Nhiều đội chọn 90 ngày cho xử lý hàng ngày, 1–3 năm cho tuân thủ nội bộ, và lâu hơn chỉ cho bản ghi có quy định. Ghi rõ điều gì đặt lại đồng hồ (thường: event time) và điều gì bị loại trừ (ví dụ: log có trường bạn không nên giữ).
Nếu bạn có nhiều môi trường, đặt retention khác nhau. Log production thường cần lưu lâu nhất; log test thường không cần.
Tính toàn vẹn: làm cho việc giả mạo khó
Đối xử audit như append-only. Không cập nhật hàng, và không cho admin bình thường xoá chúng. Nếu xoá thật sự cần thiết (yêu cầu pháp lý, dọn dẹp dữ liệu), ghi hành động đó như một event riêng.
Mẫu thực tế:
- Chỉ server ghi event audit, không phải client
- Không cấp quyền UPDATE/DELETE trên bảng audit cho role thường
- Một role "break glass" riêng cho purge hiếm hoi
- Snapshot export định kỳ lưu ngoài DB chính
Export, hiệu năng và giám sát
Kiểm toán viên thường yêu cầu CSV hoặc JSON. Lên kế hoạch export có thể lọc theo khoảng thời gian và loại đối tượng (Invoice, User, Ticket) để bạn không phải truy vấn DB vào lúc xấu nhất.
Về hiệu năng, tạo index cho cách bạn tìm kiếm:
- created_at (truy vấn theo khoảng thời gian)
- object_type + object_id (toàn bộ lịch sử một bản ghi)
- actor_id (ai đã làm gì)
Theo dõi lỗi im lặng. Nếu việc ghi audit thất bại, bạn mất bằng chứng và thường không biết. Thêm cảnh báo đơn giản: nếu app xử lý ghi mà event audit giảm xuống 0 trong một khoảng, thông báo chủ sở hữu và log lỗi to hơn.
Sai lầm phổ biến làm audit logs vô dụng
Cách nhanh nhất để phí thời gian là thu quá nhiều hàng không trả lời được câu hỏi thực sự: ai thay đổi gì, khi nào, và từ đâu.
Một bẫy phổ biến là dựa vào triggers DB thôi. Trigger có thể ghi rằng một hàng đã thay đổi, nhưng thường bỏ lỡ ngữ cảnh nghiệp vụ: người dùng dùng màn hình nào, request nào gây ra, vai trò họ có, và đó là chỉnh sửa bình thường hay quy tắc tự động.
Các lỗi thường xuyên phá compliance và khả năng dùng hàng ngày:
- Ghi toàn bộ payload nhạy cảm (reset mật khẩu, token, ghi chú riêng) thay vì diff tối thiểu và định danh an toàn.
- Cho phép người ta chỉnh sửa hoặc xoá bản ghi audit "để sửa lịch sử."
- Quên các đường ghi không qua UI như CSV import, tích hợp, và job nền.
- Dùng tên hành động không nhất quán như "Updated," "Edit," "Change," "Modify," khiến feed đọc như nhiễu.
- Chỉ log object ID, không lưu tên thân thiện tại thời điểm thay đổi (tên có thể đổi sau này).
Chuẩn hoá từ vựng event sớm (ví dụ: user.created, user.updated, invoice.voided, access.granted) và yêu cầu mọi đường ghi phát ra một event. Đối xử dữ liệu audit như write-once: nếu ai đó làm sai, log một hành động sửa mới thay vì viết lại lịch sử.
Checklist nhanh và bước tiếp theo
Trước khi gọi là xong, chạy vài kiểm tra nhanh. Một audit log tốt thì nhàm chán theo cách tốt: đầy đủ, nhất quán, và dễ đọc khi có chuyện xảy ra.
Dùng checklist này trong môi trường test với dữ liệu thực tế:
- Mỗi create, update, delete, restore, và bulk edit tạo đúng một event audit cho mỗi bản ghi bị ảnh hưởng (không có lỗ, không trùng).
- Mỗi event bao gồm actor (user hoặc system), timestamp (UTC), action, và tham chiếu đối tượng ổn định (type + ID).
- View "cái gì đã thay đổi" dễ đọc: tên trường rõ ràng, giá trị cũ/mới hiển thị, và trường nhạy cảm được che hoặc tóm tắt.
- Admin có thể lọc activity feed theo khoảng thời gian, actor, action và object, và có thể export kết quả để rà soát.
- Log khó bị giả mạo: write-only với hầu hết vai trò, và mọi thay đổi lên audit log đều bị chặn hoặc audit riêng.
Nếu bạn xây dựng công cụ nội bộ với AppMaster (appmaster.io), một cách thực tế để giữ bao phủ cao là định tuyến hành động UI, endpoint API, import và automations qua cùng một Business Process pattern vừa ghi thay đổi dữ liệu vừa ghi event audit. Bằng cách đó, trail CRUD audit của bạn giữ nhất quán ngay cả khi màn hình và workflow thay đổi.
Bắt đầu nhỏ với một workflow quan trọng (tickets, approvals, thay đổi billing), làm cho activity feed dễ đọc, rồi mở rộng cho đến khi mọi đường ghi phát ra event audit có thể dự đoán và có thể tìm kiếm.
Câu hỏi thường gặp
Thêm nhật ký audit ngay khi công cụ có thể thay đổi dữ liệu thực. Trường hợp tranh chấp hoặc yêu cầu audit thường đến sớm hơn bạn nghĩ, và sao chép lịch sử sau đó thường chỉ là suy đoán.
Một nhật ký audit hữu ích phải trả lời được ai đã làm, bản ghi nào bị ảnh hưởng, gì đã thay đổi, khi nào nó xảy ra, và nó đến từ đâu (UI, API, import hoặc job). Nếu bạn không thể trả lời một trong những câu đó nhanh chóng, thì người ta sẽ không tin vào log.
Debug logs dành cho lập trình viên, thường ồn và không nhất quán. Audit logs dành cho trách nhiệm giải trình, nên cần các trường ổn định, cách diễn đạt rõ ràng và định dạng giữ được độ đọc cho người không phải kỹ thuật viên theo thời gian.
Thiếu bao phủ xảy ra khi các thay đổi diễn ra ngoài màn hình chỉnh sửa thông thường. Chỉnh sửa hàng loạt, import, job định kỳ, phím tắt admin và xoá là những nơi thường quên phát sinh sự kiện audit.
Lưu loại actor và định danh actor, không chỉ user ID. Như vậy bạn sẽ phân biệt rõ nhân viên, job hệ thống, service account hay tích hợp bên ngoài và tránh tình trạng “ai đó đã làm” mơ hồ.
Lưu timestamp ở UTC trong database, rồi hiển thị theo múi giờ của người xem trong UI quản trị. Cách này tránh tranh cãi về thời gian và giúp export nhất quán giữa nhóm và hệ thống.
Dùng event log (append-only) khi bạn muốn có một nơi để tìm kiếm và feed hoạt động dễ làm. Dùng versioned history khi bạn thường cần xem trạng thái tại một thời điểm của một bản ghi; nhiều ứng dụng thì event log với diff theo trường đủ dùng và tốn ít lưu trữ hơn.
Ưu tiên soft delete và ghi hành động xoá một cách rõ ràng. Nếu phải hard delete, hãy ghi event audit trước và kèm snapshot hoặc các trường khoá để sau này có thể chứng minh gì đã bị xoá.
Mặc định: lưu diff theo trường cho các bản cập nhật và lưu snapshot cho tạo/xoá. Với các trường nhạy cảm, chỉ ghi rằng giá trị đã thay đổi mà không lưu bí mật, hoặc redact/che chắn dữ liệu cá nhân để nhật ký không trở thành bản sao dữ liệu.
Tạo một luồng “write + audit” dùng chung và bắt mọi đường ghi phải gọi nó: UI, API, import, job nền. Trong AppMaster, đội thường hiện thực hoá điều này dưới dạng Business Process tái sử dụng thực hiện thay đổi dữ liệu và ghi event audit trong cùng một luồng để tránh lỗ hổng.
Mỗi hành động tạo, sửa, xoá, phục hồi nên sinh ra một event audit cho bản ghi bị ảnh hưởng (một event per record). Kiểm tra một luồng cụ thể: tạo, sửa hai trường, xoá, rồi phục hồi — feed audit nên cho thấy các event tương ứng với mục đích và actor/timestamp hợp lệ.
Tách các sự kiện bảo mật (đăng nhập, reset MFA, tạo API key, thay đổi vai trò) vào một luồng security_audit với quyền truy cập chặt hơn và thời hạn lưu khác; các chỉnh sửa nghiệp vụ (status, approvals, workflow) để trong luồng audit chung.


