Timber هي مكتبة تسجيل خفيفة الوزن لنظام Android بهيكل قابل للتوسع يعتمد على الأشجار (Tree)، لتحل محل android.util.Log القياسي في آلاف المشاريع. وفقًا لـ GitHub، 2024، حصلت المكتبة على أكثر من 10,000 نجمة وتُستخدم في تطبيقات بأكثر من مليار عملية تثبيت. يحل Timber ثلاث مشاكل رئيسية في Log API: عدم وجود tag تلقائي، الفحص الإلزامي isLoggable، والطبيعة الثابتة للاستدعاءات.
النقاط الرئيسية
Timber هي مكتبة مفتوحة المصدر لنظام Android أنشأها Jake Wharton في عام 2013 كبديل لـ android.util.Log القياسي. الفكرة الرئيسية لـ Timber هي استبدال واجهة Log الثابتة مع tag يدوي إلزامي بآلية تلقائية تحدد مصدر الاستدعاء عبر المكدس.
المكتبة مبنية على النمط المعماري Composite مع الأشجار (Tree). بدلاً من class Log واحد بسلوك ثابت، يدير Timber «غابة» من الأشجار — كل شجرة مسؤولة عن قناة الإخراج الخاصة بها: وحدة التحكم، ملف، Crashlytics، خادم بعيد. يمكن للمطور إضافة أي عدد من الأشجار ودمجها.
وفقًا لـ Google I/O 2019، توصي Google بـ Timber كأفضل ممارسة للتسجيل في تطبيقات Android. تشغل المكتبة أقل من 10 كيلوبايت في APK وليس لها تبعيات خارجية، مما يجعلها خيارًا مثاليًا للمشاريع مهما كان حجمها.
يحل Timber مشكلة العلامات (tag) غير المتسقة في الفرق الكبيرة. عندما يكتب كل مطور tag يدويًا، تكون الأخطاء المطبعية والتناقضات حتمية — يتم تسجيل class واحد باسم «MainActivity» وآخر باسم «MAIN_ACTIVITY». يستخرج Timber tag تلقائيًا من اسم class: MainActivity.kt → tag MainActivity.
الهيكل الخاص بـ Timber يتكون من مكونين: class الثابت المركزي Timber و class المجرد Timber.Tree. يعمل Timber كواجهة (facade) تُفوض كل استدعاء سجل لجميع الأشجار المزروعة. كل شجرة تقرر ما إذا كانت ستعالج الرسالة، وإذا كان الأمر كذلك، إلى أين ترسلها.
DebugTree هو التطبيق القياسي لـ Tree المرفق مع المكتبة. يحدد tag عن طريق تحليل مكدس الاستدعاءات: يصعد 8 إطارات من نقطة استدعاء Timber.d() ويجد اسم class الذي استدعى طريقة التسجيل. يتم تعطيل DebugTree تلقائيًا (لا يُخرج شيئًا) في إصدارات release لأنه يتحقق من BuildConfig.DEBUG.
Forest (غابة) — مجموعة جميع الأشجار المزروعة. عند استدعاء طريقة Timber.d("رسالة")، تمرر المكتبة الرسالة بشكل تكراري لجميع الأشجار بترتيب زراعتها. يمكن لكل شجرة تصفية الرسالة حسب المستوى أو 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 تحت القطعة 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 جميع استدعاءات السجل دون إلقاء استثناءات. هذا سلوك افتراضي آمن: إذا لم تُزرع أي شجرة، تعمل المكتبة بشكل خامل مع حمل زائد ضئيل.
وفقًا لـ Jake Wharton، 2023، 70% من مشاكل Timber لدى المستخدمين الجدد مرتبطة بالتهيئة المنسية أو غير الصحيحة. لا يُصدر Timber خطأً عند عدم وجود أشجار — يتوقع المطورون ظهور السجلات في Logcat، لكن لا يحدث شيء.
للاختبار، يوفر Timber Timber.asTree() — طريقة تُرجع الشجرة الحالية أو null. هذا مناسب لاختبارات الوحدة: يمكنك استبدال الشجرة بـ mock والتحقق من إرسال رسالة السجل بالمستوى و tag الصحيحين.
الشجرة المخصصة — السبب الرئيسي لاستخدام Timber بدلاً من Log API القياسي. من خلال تجاوز طرق Tree، يمكنك توجيه السجلات من أي مستوى إلى Crashlytics أو نظام الملفات أو Remote Config أو الخادم الخاص بك.
class CrashReportingTree : Timber.Tree() {
override fun isLoggable(tag: String?, priority: Int): Boolean {
// فقط Error و WTF للإبلاغ عن الأعطال
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")
}
}
}
الطرق القابلة للتجاوز: 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 — وهذا يضمن أن أخطاء الاختبار يتم التقاطها أيضًا بواسطة نظام الإبلاغ عن الأعطال.
القاعدة الرابعة — استخدم مستويات Timber المدمجة: Timber.v()، Timber.d()، Timber.i()، Timber.w()، Timber.e()، Timber.wtf(). تجنب استدعاء Timber.log() مباشرة بأولوية رقمية — فهذا يقلل من قابلية قراءة الكود ويعقد إعادة الهيكلة.
القاعدة الخامسة — للمكتبات والوحدات، استخدم Timber.tag("CustomTag"). هذه الطريقة تُرجع شجرة مؤقتة بـ tag مُعاد تعريفه دون التأثير على التكوين العام. هذا يسمح بالتسجيل من كود المكتبة بمعرف مخصص.
الأسئلة الشائعة
نعم — Timber آمن للاستخدام في المكتبات. إذا لم تُزرع أي شجرة في التطبيق، استدعاءات Timber لا تسبب أخطاء. للمكتبات، يُوصى باستخدام Timber.tag("LibraryTag") لتحديد مصدر السجلات.
عبر مكدس الاستدعاءات (stack trace) — يصعد DebugTree 8 إطارات من نقطة استدعاء Timber.d() ويستخرج اسم class. يتم استخدام طريقة Throwable.stackTrace لتحديد class المستدعي دون تكلفة Reflection API.
Logcat هي أداة نظام Android لعرض السجلات. Timber هي مكتبة لكتابة السجلات. يُخرج Timber الرسائل إلى Logcat عبر DebugTree، لكن يمكنه أيضًا إرسالها إلى ملفات و Crashlytics و Sentry وقنوات أخرى عبر أشجار مخصصة.
لا — Timber مرتبط بـ Android SDK (android.util.Log). لمشاريع KMP، فكر في Kermit أو Napier — مكتبات تسجيل متعددة المنصات بهندسة أشجار مماثلة، تعمل على Android و iOS و JVM و JS.
استخدم Timber.uprootAll() — الطريقة تزيل جميع الأشجار المسجلة. Timber.uproot(tree) تزيل شجرة محددة. هذا مفيد في الاختبارات لإعادة تعيين الحالة بين طرق الاختبار.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا