Timber là một thư viện ghi log nhẹ cho Android với kiến trúc mở rộng dựa trên cây (Tree), đã thay thế android.util.Log tiêu chuẩn trong hàng nghìn dự án. Theo GitHub, 2024, thư viện đã đạt hơn 10.000 sao và được sử dụng trong các ứng dụng với hơn 1 tỷ lượt cài đặt. Timber giải quyết ba vấn đề chính của Log API: thiếu tag tự động, kiểm tra isLoggable bắt buộc và tính chất tĩnh của các lệnh gọi.
Những điểm chính
Timber là một thư viện mã nguồn mở cho Android được tạo bởi Jake Wharton vào năm 2013 như một giải pháp thay thế cho android.util.Log tiêu chuẩn. Ý tưởng chính của Timber là thay thế Log API tĩnh với tag thủ công bắt buộc bằng một cơ chế tự động xác định nguồn gốc lệnh gọi thông qua ngăn xếp.
Thư viện được xây dựng trên mô hình kiến trúc Composite với Cây (Tree). Thay vì một lớp Log duy nhất với hành vi cố định, Timber quản lý một "khu rừng" gồm nhiều cây — mỗi cây chịu trách nhiệm về kênh đầu ra riêng: bảng điều khiển, tệp, Crashlytics, máy chủ từ xa. Nhà phát triển có thể thêm bất kỳ số lượng cây nào và kết hợp chúng.
Theo Google I/O 2019, Timber được Google khuyến nghị như một thực hành tốt nhất cho việc ghi log trong các ứng dụng Android. Thư viện chiếm ít hơn 10 KB trong APK và không có phụ thuộc bên ngoài, khiến nó trở thành lựa chọn lý tưởng cho các dự án ở mọi quy mô.
Timber giải quyết vấn đề tag không nhất quán trong các nhóm lớn. Khi mỗi nhà phát triển viết tag thủ công, lỗi chính tả và sai lệch là khó tránh khỏi — một lớp được ghi là "MainActivity", lớp khác là "MAIN_ACTIVITY". Timber tự động lấy tag từ tên lớp: MainActivity.kt → tag MainActivity.
Kiến trúc của Timber gồm hai thành phần: lớp tĩnh trung tâm Timber và lớp trừu tượng Timber.Tree. Timber đóng vai trò như một mặt tiền (facade) ủy quyền mỗi lệnh gọi log cho tất cả các cây đã trồng. Mỗi cây quyết định có xử lý thông báo hay không và nếu có thì gửi nó đi đâu.
DebugTree là triển khai Tree tiêu chuẩn đi kèm với thư viện. Nó xác định tag bằng cách phân tích ngăn xếp lệnh gọi: đi lên 8 khung từ điểm gọi Timber.d() và tìm tên lớp đã gọi phương thức log. DebugTree tự động vô hiệu hóa (không xuất gì) trong bản dựng release vì nó kiểm tra BuildConfig.DEBUG.
Forest (Rừng) — tập hợp tất cả các cây đã trồng. Khi phương thức Timber.d("thông báo") được gọi, thư viện lần lượt chuyển thông báo đến tất cả các cây theo thứ tự trồng. Mỗi cây có thể lọc thông báo theo cấp độ, tag hoặc nội dung và xử lý theo cách riêng.
Thứ tự trồng rất quan trọng: cây trồng đầu tiên được xử lý trước. Nên trồng DebugTree cuối cùng để các cây tùy chỉnh (ví dụ: Crashlytics) xử lý thông báo trước khi nó đến Logcat.
Timber an toàn luồng — tất cả các phương thức được đồng bộ hóa thông qua một khóa nội bộ. Điều này đảm bảo rằng các thông báo từ các luồng khác nhau không bị trộn lẫn. Tuy nhiên, bên trong một cây tùy chỉnh, việc đồng bộ hóa thuộc trách nhiệm của nhà phát triển: nếu cây ghi vào tệp, phải sử dụng synchronized hoặc ReentrantLock.
// Khởi tạo rừng cây trong Application.onCreate
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
Timber.plant(CrashReportingTree())
Timber.plant(FileLoggingTree())
Timber.i("Timber planted with 3 trees")
}
}
Cài đặt Timber được thực hiện bằng cách thêm một phụ thuộc duy nhất vào build.gradle. Thư viện được xuất bản trên Maven Central dưới artifact com.jakewharton.timber:timber. Phiên bản hiện tại vào năm 2024 là 5.0.1, bản cập nhật ổn định mới nhất.
// build.gradle (Module: app)
dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
}
Thiết lập tối thiểu sau khi cài đặt — trồng DebugTree trong Application.onCreate. Nếu không có bước này, Timber sẽ bỏ qua tất cả các lệnh gọi log mà không ném ngoại lệ. Đây là hành vi mặc định an toàn: nếu không có cây nào được trồng, thư viện chạy không tải với chi phí tối thiểu.
Theo Jake Wharton, 2023, 70% vấn đề Timber của người dùng mới liên quan đến việc quên hoặc khởi tạo không đúng. Timber không tạo lỗi khi không có cây — các nhà phát triển mong đợi log xuất hiện trong Logcat nhưng không có gì xảy ra.
Để kiểm tra, Timber cung cấp Timber.asTree() — một phương thức trả về cây hiện tại hoặc null. Điều này thuận tiện cho kiểm thử đơn vị: bạn có thể thay thế cây bằng mock và xác minh rằng thông báo log đã được gửi với cấp độ và tag chính xác.
Cây tùy chỉnh — lý do chính để sử dụng Timber thay vì Log API tiêu chuẩn. Bằng cách ghi đè các phương thức của Tree, bạn có thể định tuyến log ở bất kỳ cấp độ nào đến Crashlytics, hệ thống tệp, Remote Config hoặc máy chủ của riêng bạn.
class CrashReportingTree : Timber.Tree() {
override fun isLoggable(tag: String?, priority: Int): Boolean {
// Chỉ Error và WTF cho báo cáo lỗi
return priority >= Log.ERROR
}
override fun log(priority: Int, tag: String?,
message: String, t: Throwable?) {
if (t != null) {
FirebaseCrashlytics.getInstance()
.recordException(t)
} else {
FirebaseCrashlytics.getInstance()
.log("[$tag] $message")
}
}
}
Các phương thức cần ghi đè: isLoggable(tag, priority) — bộ lọc xác định có xử lý thông báo hay không (triển khai cơ bản trả về true). log(priority, tag, message, t) — logic xử lý chính. prepareLog(priority, tag, throwable, message, args) — được gọi trước khi định dạng, cho phép sửa đổi thông báo trước khi xử lý.
Một lợi thế quan trọng của cây tùy chỉnh là không có phản chiếu (reflection). Không giống như nhiều framework ghi log, Timber không sử dụng Reflection API để xác định tag hoặc cấp độ. Tag được tính toán thông qua phân tích ngăn xếp lệnh gọi (Throwable.stackTrace), nhanh hơn nhiều cấp độ.
So sánh Timber và Log API tiêu chuẩn cho thấy bốn điểm khác biệt chính: tag tự động, hỗ trợ định dạng chuỗi với varargs, nhiều kênh đầu ra và hành vi an toàn khi không được khởi tạo.
| Tham số | android.util.Log | Timber |
|---|---|---|
| Phát hiện tag | Thủ công, hằng chuỗi | Tự động, qua ngăn xếp lệnh gọi |
| Định dạng | Nối chuỗi hoặc String.format | Varargs tích hợp + placeholder %s |
| Kênh đầu ra | Chỉ Logcat | Cây: Logcat, tệp, Crashlytics, v.v. |
| Hành vi không khởi tạo | Luôn hoạt động | Không xuất gì |
| Hiệu suất | Mức cơ bản | Định dạng lười qua isLoggable |
Lập luận chính chống lại Timber — phụ thuộc vào thư viện bên thứ ba. Đối với một dự án đơn giản với ghi log tối thiểu, sử dụng Timber có thể là quá mức cần thiết. Tuy nhiên, theo Google Play Console, 2024, hơn 60% trong số 1000 ứng dụng hàng đầu trên Google Play sử dụng Timber, xác nhận độ tin cậy và hiệu quả của nó.
Hiệu suất của Timber trong bản dựng release tương đương với Log API tiêu chuẩn. Khi không có cây nào được trồng, phương thức Timber.d() kiểm tra sự hiện diện của cây (một if) và trả về — không cần định dạng chuỗi. Điều này nhanh hơn Log.d() với nối chuỗi luôn được thực thi.
Quy tắc đầu tiên — luôn kiểm tra khởi tạo Timber trong các bài kiểm thử. Sử dụng Timber.asTree() để xác minh rằng một cây đã được trồng. Trong kiểm thử đơn vị, hãy trồng TestTree lưu thông báo vào danh sách để kiểm tra assert.
Quy tắc thứ hai — không trộn lẫn Timber và android.util.Log trong cùng một dự án. Nếu dự án đã sử dụng Timber, tất cả các lệnh gọi log mới phải thông qua nó. Việc trộn lẫn dẫn đến thông báo trùng lặp và nhầm lẫn khi phân tích.
Quy tắc thứ ba — trồng CrashReportingTree mà không kiểm tra BuildConfig.DEBUG. Không giống như DebugTree, cây báo cáo lỗi phải hoạt động cả trong debug và release — điều này đảm bảo rằng lỗi kiểm thử cũng được hệ thống báo cáo lỗi ghi nhận.
Quy tắc thứ tư — sử dụng các cấp độ tích hợp của Timber: Timber.v(), Timber.d(), Timber.i(), Timber.w(), Timber.e(), Timber.wtf(). Tránh gọi Timber.log() trực tiếp với ưu tiên số — điều này làm giảm khả năng đọc mã và phức tạp hóa việc tái cấu trúc.
Quy tắc thứ năm — cho thư viện và mô-đun, sử dụng Timber.tag("CustomTag"). Phương thức này trả về một cây tạm thời với tag được ghi đè mà không ảnh hưởng đến cấu hình toàn cục. Điều này cho phép ghi log từ mã thư viện với định danh tùy chỉnh.
Câu hỏi thường gặp
Có — Timber an toàn khi sử dụng trong thư viện. Nếu không có cây nào được trồng trong ứng dụng, các lệnh gọi Timber không gây ra lỗi. Đối với thư viện, nên sử dụng Timber.tag("LibraryTag") để xác định nguồn log.
Thông qua ngăn xếp lệnh gọi (stack trace) — DebugTree đi lên 8 khung từ điểm gọi Timber.d() và trích xuất tên lớp. Phương thức Throwable.stackTrace được sử dụng để xác định lớp gọi mà không tốn chi phí Reflection API.
Logcat là tiện ích hệ thống Android để xem log. Timber là thư viện để viết log. Timber gửi thông báo đến Logcat qua DebugTree, nhưng cũng có thể gửi chúng đến tệp, Crashlytics, Sentry và các kênh khác thông qua cây tùy chỉnh.
Không — Timber phụ thuộc vào Android SDK (android.util.Log). Đối với dự án KMP, hãy xem xét Kermit hoặc Napier — thư viện ghi log đa nền tảng với kiến trúc cây tương tự, hoạt động trên Android, iOS, JVM và JS.
Sử dụng Timber.uprootAll() — phương thức xóa tất cả các cây đã đăng ký. Timber.uproot(tree) xóa một cây cụ thể. Điều này hữu ích trong kiểm thử để đặt lại trạng thái giữa các phương thức kiểm thử.
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