Timber — کتابخانه سبک لاگگیری برای Android با معماری قابل گسترش مبتنی بر درختان (Tree) است که android.util.Log استاندارد را در هزاران پروژه جایگزین کرده است. طبق دادههای GitHub, 2024، این کتابخانه بیش از 10٬000 ستاره جمعآوری کرده و در برنامههایی با مخاطب بیش از 1 میلیارد نصب استفاده میشود. Timber سه مشکل اصلی Log API را حل میکند: عدم وجود tag خودکار، بررسی اجباری isLoggable و ماهیت ایستای فراخوانیها.
نکات اصلی
Timber — یک کتابخانه متنباز (open-source) برای Android است که توسط جیک وارتون (Jake Wharton) در سال 2013 به عنوان جایگزینی برای android.util.Log استاندارد ایجاد شد. ایده کلیدی Timber — جایگزینی Log API ایستا با tag دستی اجباری با مکانیزم خودکاری است که منبع فراخوانی را از طریق پشته تعیین میکند.
این کتابخانه بر اساس الگوی معماری Composite با درختان (Tree) ساخته شده است. به جای یک کلاس Log واحد با رفتار ثابت، Timber یک «جنگل» از درختان را مدیریت میکند — هر درخت مسئول کانال خروجی خود است: کنسول، فایل، Crashlytics، سرور راه دور. توسعهدهنده میتواند هر تعداد درخت اضافه کرده و آنها را ترکیب کند.
طبق دادههای Google I/O 2019، Timber توسط Google به عنوان بهترین روش برای لاگگیری در برنامههای Android توصیه شده است. این کتابخانه کمتر از 10 کیلوبایت در APK اشغال میکند و وابستگی خارجی ندارد که آن را به گزینهای ایدهآل برای پروژههای هر مقیاسی تبدیل میکند.
Timber مشکل tagهای ناسازگار در تیمهای بزرگ را حل میکند. وقتی هر توسعهدهنده tag را دستی مینویسد، اشتباهات تایپی و تفاوتها اجتنابناپذیر است — یک کلاس به عنوان «MainActivity» لاگ میشود، دیگری به عنوان «MAIN_ACTIVITY». Timber به طور خودکار tag را از نام کلاس استخراج میکند:MainActivity.kt → tag MainActivity.
معماری Timber از دو مؤلفه تشکیل شده است: کلاس ایستای مرکزی Timber و کلاس انتزاعی Timber.Tree. Timber به عنوان facade عمل میکند که هر فراخوانی لاگ را به همه درختان کاشته شده (planted) واگذار میکند. هر درخت تصمیم میگیرد که آیا باید پیام را پردازش کند و اگر بله — به کجا هدایت کند.
DebugTree — پیادهسازی استاندارد Tree که با کتابخانه ارائه میشود. این پیادهسازی tag را از طریق تحلیل پشته فراخوانی تعیین میکند: 8 فریم از نقطه فراخوانی Timber.d() بالا رفته و نام کلاسی که متد لاگ را فراخوانی کرده پیدا میکند. DebugTree به طور خودکار در نسخههای release غیرفعال میشود (چیزی خروجی نمیدهد) زیرا BuildConfig.DEBUG را بررسی میکند.
Forest (جنگل) — مجموعه تمام درختان کاشته شده. وقتی متد Timber.d(«message») فراخوانی میشود، کتابخانه به صورت تکراری پیام را به همه درختان به ترتیب کاشت منتقل میکند. هر درخت میتواند پیام را بر اساس سطح، tag یا محتوا فیلتر کرده و به روش خود پردازش کند.
ترتیب کاشت مهم است: اولین درخت کاشته شده اول پردازش میشود. توصیه میشود DebugTree را آخرین بار بکارید تا درختان سفارشی (مثلاً Crashlytics) پیام را قبل از رسیدن به Logcat پردازش کنند.
Timber ایمن از نظر نخ است — همه متدها از طریق قفل داخلی همگامسازی شدهاند. این تضمین میکند که پیامهای نخهای مختلف با هم مخلوط نشوند. با این حال، در داخل درخت سفارشی همگامسازی بر عهده توسعهدهنده است: اگر درخت در فایل مینویسد، باید از synchronized یا ReentrantLock استفاده کرد.
// راهاندازی جنگل درختان در 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")
}
}
نصب Timber با افزودن یک وابستگی به build.gradle انجام میشود. کتابخانه در Maven Central با artifact com.jakewharton.timber:timber منتشر شده است. نسخه فعلی در سال 2024 — 5.0.1، آخرین بهروزرسانی پایدار.
// build.gradle (Module: app)
dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
}
حداقل پیکربندی پس از نصب — کاشت DebugTree در Application.onCreate. بدون این مرحله، Timber تمام فراخوانیهای لاگ را نادیده میگیرد بدون اینکه استثنا پرتاب کند. این رفتار پیشفرض ایمن است: اگر درختی کاشته نشده باشد، کتابخانه با حداقل overhead بیکار کار میکند.
طبق دادههای Jake Wharton, 2023، 70٪ مشکلات Timber در کاربران جدید مربوط به فراموشی یا راهاندازی نادرست است. Timber در صورت نبود درخت خطا تولید نمیکند — توسعهدهندگان انتظار دارند لاگها در Logcat ظاهر شوند، اما هیچ اتفاقی نمیافتد.
برای آزمایش، Timber متد Timber.asTree() را ارائه میدهد — متدی که درخت فعلی یا null را برمیگرداند. این برای بررسی در تستهای واحد مفید است: میتوان درخت را با mock جایگزین کرد و بررسی کرد که پیام لاگ با سطح و tag صحیح ارسال شده است.
درخت سفارشی — دلیل اصلی استفاده از Timber به جای Log API استاندارد. با override کردن متدهای Tree میتوان لاگهای هر سطحی را به Crashlytics، سیستم فایل، Remote Config یا سرور خود هدایت کرد.
class CrashReportingTree : Timber.Tree() {
override fun isLoggable(tag: String?, priority: Int): Boolean {
// فقط Error و WTF برای crash-reporting
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")
}
}
}
متدهای قابل override: isLoggable(tag, priority) — فیلتری که تعیین میکند آیا باید پیام پردازش شود (پیادهسازی پایه true برمیگرداند). log(priority, tag, message, t) — منطق اصلی پردازش. prepareLog(priority, tag, throwable, message, args) — قبل از قالببندی فراخوانی میشود، امکان تغییر پیام قبل از پردازش را میدهد.
مزیت مهم درختان سفارشی — عدم استفاده از reflection. بر خلاف بسیاری از فریمورکهای لاگگیری، Timber از Reflection API برای تعیین tag یا سطح استفاده نمیکند. Tag از طریق تحلیل پشته فراخوانی (Throwable.stackTrace) محاسبه میشود که مرتبهای سریعتر کار میکند.
مقایسه Timber و Log API استاندارد چهار تفاوت کلیدی را نشان میدهد: tag خودکار، پشتیبانی از قالببندی رشته با varargs، امکان کانالهای خروجی چندگانه و رفتار ایمن در صورت عدم راهاندازی.
| پارامتر | android.util.Log | Timber |
|---|---|---|
| تعیین tag | دستی، ثابت رشتهای | خودکار، بر اساس پشته فراخوانی |
| قالببندی | الحاق یا String.format | varargs داخلی + جایگیر %s |
| کانالهای خروجی | فقط Logcat | درختان: Logcat، فایل، Crashlytics و غیره |
| رفتار بدون راهاندازی | همیشه کار میکند | چیزی خروجی نمیدهد |
| عملکرد | سطح پایه | قالببندی تنبل از طریق isLoggable |
استدلال اصلی علیه Timber — وابستگی به کتابخانه شخص ثالث. برای یک پروژه ساده با لاگگیری حداقلی، استفاده از Timber میتواند اضافی باشد. با این حال، طبق دادههای Google Play Console, 2024، بیش از 60٪ از 1000 برنامه برتر در Google Play از Timber استفاده میکنند که قابلیت اطمینان و کارایی آن را تأیید میکند.
عملکرد Timber در نسخههای release از Log API استاندارد کمتر نیست. در صورت نبود درختان کاشته شده، متد Timber.d() وجود درختان را بررسی میکند (یک if) و برمیگردد — بدون قالببندی رشته. این سریعتر از Log.d() با الحاق است که همیشه انجام میشود.
قانون اول — همیشه راهاندازی Timber را در تستها بررسی کنید. از Timber.asTree() برای تأیید کاشت درخت استفاده کنید. در تستهای واحد، TestTree بکارید که پیامها را برای بررسیهای assert در یک لیست ذخیره میکند.
قانون دوم — Timber و android.util.Log را در یک پروژه مخلوط نکنید. اگر پروژه قبلاً از Timber استفاده میکند، تمام فراخوانیهای لاگ جدید باید از آن عبور کنند. مخلوط کردن منجر به تکرار پیامها و سردرگمی در تحلیل میشود.
قانون سوم — CrashReportingTree را بدون بررسی BuildConfig.DEBUG بکارید. بر خلاف DebugTree، درخت crash باید هم در debug و هم در release کار کند — این تضمین میکند که خطاهای تست نیز به سیستم crash-reporting وارد شوند.
قانون چهارم — از سطوح داخلی Timber استفاده کنید: Timber.v(), Timber.d(), Timber.i(), Timber.w(), Timber.e(), Timber.wtf(). از فراخوانی مستقیم Timber.log() با priority عددی خودداری کنید — این کار خوانایی کد را کاهش میدهد و refactoring را پیچیده میکند.
قانون پنجم — برای کتابخانهها و ماژولها از Timber.tag(«CustomTag») استفاده کنید. این متد یک درخت موقت با tag بازنویسی شده برمیگرداند بدون تأثیر بر پیکربندی سراسری. این امکان لاگگیری از کد کتابخانه با شناسه سفارشی را فراهم میکند.
سؤالات متداول
بله — Timber برای استفاده در کتابخانهها ایمن است. اگر درختی در برنامه کاشته نشده باشد، فراخوانیهای Timber خطا ایجاد نمیکنند. برای کتابخانهها توصیه میشود از Timber.tag(«LibraryTag») برای شناسایی منبع لاگها استفاده شود.
از طریق پشته فراخوانی (stack trace) — DebugTree 8 فریم از نقطه فراخوانی Timber.d() بالا رفته و نام کلاس را استخراج میکند. متد Throwable.stackTrace برای تعیین کلاس فراخوان بدون هزینه Reflection API استفاده میشود.
Logcat — ابزار سیستمی Android برای مشاهده لاگها. Timber — کتابخانه برای نوشتن لاگها. Timber پیامها را از طریق DebugTree به Logcat خروجی میدهد، اما همچنین میتواند آنها را از طریق درختان سفارشی به فایلها، Crashlytics، Sentry و سایر کانالها ارسال کند.
خیر — Timber به Android SDK (android.util.Log) وابسته است. برای پروژههای KMP، Kermit یا Napier را در نظر بگیرید — کتابخانههای لاگگیری چندسکویی با معماری درختی مشابه که روی Android، iOS، JVM و JS کار میکنند.
از Timber.uprootAll() استفاده کنید — این متد تمام درختان ثبتشده را حذف میکند. Timber.uproot(tree) یک درخت خاص را حذف میکند. این در تستها برای بازنشانی وضعیت بین متدهای تست مفید است.
نتیجهگیری
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید