Log Level — phân loại thông báo ghi log theo mức độ nghiêm trọng, cho phép nhà phát triển kiểm soát khối lượng thông tin đầu ra ở các giai đoạn khác nhau của ứng dụng. Theo Google Android Developers, 2024, việc chọn đúng cấp độ ghi log giảm khối lượng log trong môi trường production xuống 85–95% và tăng tốc chẩn đoán lỗi. Mỗi cấp độ giải quyết nhiệm vụ riêng — từ gỡ lỗi trong giai đoạn phát triển đến giám sát sự cố nghiêm trọng trong production.
Những điểm chính
Log Level là một thuộc tính của mỗi thông báo log, xác định tầm quan trọng và mức độ khẩn cấp của việc xử lý. Các nền tảng iOS và Android hiện đại hỗ trợ thang đo thống nhất gồm 6–7 cấp độ: từ chi tiết nhất (Verbose/Trace) đến nghiêm trọng (Error/Assert). Việc chọn cấp độ quyết định liệu thông báo có được ghi vào log với cấu hình ứng dụng hiện tại hay không.
Khái niệm Log Level dựa trên nguyên tắc kim tự tháp mức độ nghiêm trọng: cấp độ càng cao, càng ít thông báo được xuất ra ở cấp độ đó. Theo Semaphore CI, 2024, trong ứng dụng production, phân bố như sau: Info — 60% thông báo, Warn — 25%, Error — 10%, Debug — 5%. Các thông báo Verbose phải được tắt hoàn toàn trong production.
Mỗi nền tảng triển khai Log Level thông qua API riêng. Android sử dụng android.util.Log với các phương thức v(), d(), i(), w(), e(). Apple sử dụng OSLog với các cấp độ default, info, debug, error, fault. Các thư viện như Timber và CocoaLumberjack thêm chức năng bổ sung trên các API tiêu chuẩn này.
Theo Google I/O 2023, việc chọn sai Log Level là nguyên nhân của 40% vấn đề hiệu suất trong production. Nhà phát triển để lại log Debug trong bản build phát hành, dẫn đến ghi đĩa quá mức và hao pin nhanh.
Verbose (TRACE) — cấp độ chi tiết nhất, chỉ dành riêng cho phát triển. Ở cấp độ này, tất cả các tính toán trung gian, vòng lặp và kết quả của từng bước thuật toán đều được xuất ra. Trên Android, cấp độ này tương ứng với Log.v(), trên iOS — OSLog loại debug (trước iOS 14, os_trace được sử dụng).
Debug — thông báo gỡ lỗi hữu ích trong quá trình phát triển và kiểm thử. Chúng chứa thông tin về trạng thái của các đối tượng chính, kết quả truy vấn SQL và tham số gọi API. Không giống như Verbose, thông báo Debug có cấu trúc và có ý nghĩa ngữ nghĩa. Trên iOS, cấp độ này tương ứng với OSLogType.debug.
Info — thông báo về các sự kiện thông thường của ứng dụng: khởi tạo SDK, xác thực thành công, mở màn hình, nhận dữ liệu từ máy chủ. Thông báo Info không được chứa dữ liệu cá nhân của người dùng và phải an toàn cho phân tích production. Trên iOS, OSLogType.info được sử dụng, trên Android — Log.i().
Warn — cảnh báo về vấn đề tiềm ẩn. Ứng dụng tiếp tục hoạt động, nhưng tình huống cần chú ý: kích thước bộ nhớ đệm gần giới hạn, phiên bản API cũ, phản hồi mạng chậm, thử kết nối lại. Trên Android — Log.w(), trên iOS — OSLogType.default (cho cảnh báo).
Error — lỗi nghiêm trọng khi ứng dụng không thể thực hiện thao tác được yêu cầu nhưng vẫn tiếp tục hoạt động: yêu cầu API thất bại, mất kết nối, lỗi ghi cơ sở dữ liệu, thiếu quyền. Trên iOS, OSLogType.error được sử dụng cho lỗi, trên Android — Log.e().
Assert (WTF) — cấp độ cao nhất, biểu thị tình huống “không thể xảy ra”. Được sử dụng để ghi log các lỗi vi phạm bất biến cơ bản của hệ thống. Trên Android, thông báo Assert không được hiển thị trong bản build phát hành theo mặc định. Trên iOS, WTF (What a Terrible Failure) được xử lý thông qua OSLogType.fault.
Android Log API — cơ chế ghi log tích hợp từ gói android.util.Log. Nó cung cấp 6 phương thức tĩnh: Log.v(), Log.d(), Log.i(), Log.w(), Log.e() và Log.wtf(). Mỗi phương thức nhận tag (chuỗi định danh nguồn) và msg (văn bản thông báo).
class UserRepository {
companion object {
private val TAG = "UserRepo"
}
suspend fun loadUser(id: String): User {
Log.d(TAG, "Đang tải người dùng có id: $id")
return try {
val response = api.fetchUser(id)
Log.i(TAG, "Người dùng đã được tải thành công")
response.toUser()
} catch (e: Exception) {
Log.e(TAG, "Không thể tải người dùng: ${e.message}")
throw e
}
}
}
Lọc theo cấp độ trong Android Logcat được thực hiện qua ADB: adb logcat *:E sẽ chỉ hiển thị thông báo Error. Trong bản build production, tất cả các lệnh gọi Log.v() và Log.d() bị xóa bởi ProGuard/R8 khi tính năng thu nhỏ được bật. Log.i(), Log.w() và Log.e() vẫn còn, vì vậy điều quan trọng là không xuất dữ liệu nhạy cảm qua các phương thức này.
Để lọc tùy chỉnh trong thời gian chạy, Android cung cấp Log.isLoggable(tag, level) — phương thức kiểm tra xem cấp độ được chỉ định có được bật cho tag nhất định hay không. Điều này cho phép bật động ghi log chi tiết cho một mô-đun cụ thể mà không cần xây dựng lại ứng dụng.
OSLog — hệ thống ghi log hợp nhất của Apple, thay thế NSLog đã lỗi thời. OSLog cung cấp 5 cấp độ: debug, info, default (notice), error và fault. Ưu điểm chính là ghi log có cấu trúc với hỗ trợ chuỗi định dạng và lọc động qua bảng điều khiển.
import OSLog
let logger = Logger(
subsystem: "com.example.app",
category: "network"
)
func fetchData(from url: URL) {
logger.debug("Starting request to \(url.absoluteString)")
do {
let data = try Data(contentsOf: url)
logger.info("Received \(data.count) bytes")
} catch {
logger.error("Request failed: \(error.localizedDescription)")
}
}
Hệ thống lọc của OSLog hoạt động ở cấp độ hệ điều hành. Thông báo Debug chỉ được ghi khi trình gỡ lỗi được kết nối hoặc khi đối số -com.apple.CoreData.Logging.debug 1 được bật. Thông báo Info được thu thập trong bộ nhớ thiết bị (lên đến 512 KB) và có thể truy cập qua Console.app. Thông báo Error và fault được ghi liên tục và có sẵn để thu thập qua hệ thống báo cáo sự cố.
Một tính năng quan trọng của OSLog: chuỗi định dạng với các trình giữ chỗ. Thay vì nội suy chuỗi Swift (luôn được đánh giá, bất kể cấp độ), OSLog sử dụng định dạng os_log với %{public}@ và %{private}@ để phân biệt dữ liệu nhạy cảm. Các tham số riêng tư được che giấu trong log production.
Quy tắc chính — một tập hợp tối thiểu các cấp độ trong production: Info, Warn, Error, Assert. Debug và Verbose phải bị vô hiệu hóa. Lý do không chỉ là bảo mật mà còn là hiệu suất: mỗi lệnh gọi log tiêu tốn thời gian CPU để định dạng chuỗi, ngay cả khi thông báo không được xuất ra.
Tối ưu hóa quan trọng — không bao giờ sử dụng nội suy chuỗi trong lệnh gọi log. Nếu chuỗi được xây dựng trước lệnh gọi log(), thời gian CPU bị lãng phí ngay cả khi cấp độ bị vô hiệu hóa. Sử dụng định dạng lười biếng thông qua lambda hoặc điều kiện bảo vệ.
Trên Android, phương thức Log.isLoggable() phục vụ mục đích này; trên OSLog, chuỗi định dạng gốc với trình giữ chỗ được hỗ trợ. Timber cho Android giải quyết vấn đề thông qua timber.log.Tree với kiểm tra cấp độ bên trong cây.
Remote Log Level — phương pháp mà cấp độ ghi log được điều khiển từ máy chủ thông qua Firebase Remote Config hoặc dịch vụ tương tự. Nếu xảy ra lỗi phức tạp trong production, nhà phát triển có thể bật từ xa ghi log Debug cho một mô-đun cụ thể trên thiết bị của nhóm người dùng được chọn.
Theo Firebase, 2024, phương pháp này giảm thời gian chẩn đoán lỗi hiếm gặp xuống 60% và cho phép có được bức tranh toàn cảnh về vấn đề mà không cần cài đặt bản build gỡ lỗi. Hạn chế chính — ghi log chỉ được bật ở lần khởi động ứng dụng tiếp theo sau khi nhận được cấu hình.
BuildConfig.DEBUG trên Android và #if DEBUG trên Swift là các cơ chế biên dịch có điều kiện tiêu chuẩn, vô hiệu hóa cấp độ gỡ lỗi trong bản build phát hành. Đối với kiến trúc sạch, nên chuyển việc chọn Log Level vào bộ chứa DI hoặc nhà máy logger để không làm lộn xộn logic kinh doanh với các chỉ thị có điều kiện.
Quy tắc đầu tiên — mỗi lệnh gọi log phải trả lời câu hỏi “ai, cái gì, khi nào”. Ai — thành phần hoặc mô-đun (tag trên Android, category trên iOS). Cái gì — sự kiện cụ thể hoặc thay đổi trạng thái. Khi nào — dấu thời gian, được hệ thống ghi log tự động thêm vào.
Quy tắc thứ hai — không ghi dữ liệu nhạy cảm qua Info và cao hơn. Mật khẩu, mã thông báo, email, số điện thoại, tọa độ địa lý chính xác bị nghiêm cấm trong bất kỳ log nào đi vào production. Nếu cần, hãy sử dụng che giấu: “email: us***@example.com.”
Quy tắc thứ ba — cấp độ Warn là trách nhiệm của nhà phát triển, Error — của nhóm. Warn có nghĩa là “đây có vấn đề tiềm ẩn, hãy theo dõi”. Error có nghĩa là “đây có vấn đề, hãy sửa nó”. Không sử dụng Error cho các tình huống được mong đợi và đã xử lý (ví dụ: lỗi API 404).
Quy tắc thứ tư — nhất quán. Toàn bộ dự án phải sử dụng quy ước đặt tên thống nhất cho tag và danh mục. Khuyến nghị sử dụng ClassName.methodName cho tag Android và module.subsystem cho category iOS. Điều này cho phép lọc nhanh log theo thành phần.
Quy tắc thứ năm — kiểm tra log của bạn. Trong kiểm thử đơn vị, hãy xác minh rằng cấp độ Log Level chính xác được gọi trong các kịch bản cụ thể. Có các thư viện giả lập ghi log cho mục đích này: Mockito cho Android, Cuckoo cho iOS. Kiểm tra cấp độ trong kiểm thử ngăn chặn rò rỉ thông báo gỡ lỗi vào production.
Câu hỏi thường gặp
Hao pin nhanh và ghi đĩa quá mức. Mỗi log Debug định dạng một chuỗi và ghi dữ liệu vào bộ đệm. Trên thiết bị có bộ nhớ Flash, điều này làm tăng tốc độ hao mòn bộ nhớ. Ngoài ra, log Debug có thể chứa dữ liệu nhạy cảm không được phép xem trong production.
Debug — cho nội dung yêu cầu và phản hồi, tiêu đề và mã trạng thái. Info — cho sự kiện yêu cầu đã hoàn thành (URL, phương thức, thời lượng). Error — cho yêu cầu thất bại với mã 4xx/5xx. Không bao giờ sử dụng Verbose cho log mạng trong production.
OSLogType.default (cấp độ notice) — thông báo có tầm quan trọng trung bình, được lưu trong log hệ thống và hiển thị trong Console.app. OSLogType.info — thông báo kỹ thuật, không được lưu vĩnh viễn, chỉ khả dụng trong quá trình phân tích hiệu suất chủ động qua Instruments.
R8/ProGuard xóa Log.v() và Log.d() khi tính năng thu nhỏ được bật trong bản build phát hành. Log.i(), Log.w() và Log.e() được giữ lại. Để xóa hoàn toàn tất cả log, cần có quy tắc tùy chỉnh -assumenosideeffects class android.util.Log với tất cả các cấp độ được chỉ định.
Không — ghi log quá mức làm giảm khả năng đọc và hiệu suất. Chỉ ghi log đầu vào trong các phương thức phức tạp hoặc bất đồng bộ. Đối với phương thức đồng bộ, một log tại điểm trả về hoặc lỗi là đủ. Sử dụng cấp độ Debug để theo dõi cuộc gọi.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm