Timber è una libreria di logging leggera per Android con architettura estensibile basata su alberi (Tree), che ha sostituito il standard android.util.Log in migliaia di progetti. Secondo GitHub, 2024, la libreria ha superato le 10.000 stelle ed è utilizzata in applicazioni con oltre 1 miliardo di installazioni. Timber risolve tre problemi principali dell’API Log: assenza di tag automatico, controllo obbligatorio isLoggable e natura statica delle chiamate.
Punti Chiave
Timber è una libreria open-source per Android creata da Jake Wharton nel 2013 come alternativa al android.util.Log standard. L’idea chiave di Timber è sostituire l’API Log statica con tag manuale obbligatorio con un meccanismo automatico che determina la fonte della chiamata tramite lo stack.
La libreria è costruita sul pattern architetturale Composite con Alberi (Tree). Invece di una singola classe Log con comportamento fisso, Timber gestisce una "foresta" di alberi — ogni albero è responsabile del proprio canale di output: console, file, Crashlytics, server remoto. Lo sviluppatore può aggiungere qualsiasi numero di alberi e combinarli.
Secondo Google I/O 2019, Timber è raccomandato da Google come best practice per il logging nelle applicazioni Android. La libreria occupa meno di 10 KB nell’APK e non ha dipendenze esterne, rendendola una scelta ideale per progetti di qualsiasi dimensione.
Timber risolve il problema dei tag incoerenti nei grandi team. Quando ogni sviluppatore scrive i tag manualmente, errori di battitura e discrepanze sono inevitabili — una classe viene registrata come "MainActivity", un’altra come "MAIN_ACTIVITY". Timber deriva automaticamente il tag dal nome della classe: MainActivity.kt → tag MainActivity.
Architettura di Timber è composta da due componenti: la classe statica centrale Timber e la classe astratta Timber.Tree. Timber agisce come una facciata che delega ogni chiamata di log a tutti gli alberi piantati. Ogni albero decide se elaborare il messaggio e, in caso affermativo, dove inviarlo.
DebugTree è l’implementazione standard di Tree fornita con la libreria. Determina il tag analizzando lo stack di chiamate: sale di 8 frame dal punto di chiamata Timber.d() e trova il nome della classe che ha invocato il metodo di log. DebugTree si disattiva automaticamente (non produce output) nelle build release perché controlla BuildConfig.DEBUG.
Forest (Foresta) — la collezione di tutti gli alberi piantati. Quando viene chiamato il metodo Timber.d("messaggio"), la libreria passa iterativamente il messaggio a tutti gli alberi nell’ordine in cui sono stati piantati. Ogni albero può filtrare il messaggio per livello, tag o contenuto e elaborarlo a modo suo.
L’ordine di piantagione è importante: il primo albero piantato viene elaborato per primo. Si raccomanda di piantare DebugTree per ultimo, in modo che gli alberi personalizzati (ad esempio, Crashlytics) elaborino il messaggio prima che raggiunga Logcat.
Timber è thread-safe — tutti i metodi sono sincronizzati tramite un blocco interno. Questo garantisce che i messaggi provenienti da thread diversi non si mescolino. Tuttavia, all’interno di un albero personalizzato, la sincronizzazione è responsabilità dello sviluppatore: se l’albero scrive su un file, è necessario utilizzare synchronized o ReentrantLock.
// Inizializzazione della foresta di alberi in 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")
}
}
Installazione di Timber si effettua aggiungendo una singola dipendenza a build.gradle. La libreria è pubblicata su Maven Central con l’artifact com.jakewharton.timber:timber. La versione attuale al 2024 è 5.0.1, l’ultimo aggiornamento stabile.
// build.gradle (Module: app)
dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
}
Configurazione minima dopo l’installazione — piantare DebugTree in Application.onCreate. Senza questo passaggio, Timber ignorerà tutte le chiamate di log senza lanciare eccezioni. Questo è un comportamento predefinito sicuro: se nessun albero viene piantato, la libreria funziona a vuoto con overhead minimo.
Secondo Jake Wharton, 2023, il 70% dei problemi di Timber per i nuovi utenti è legato a un’inizializzazione dimenticata o errata. Timber non genera un errore quando non ci sono alberi — gli sviluppatori si aspettano che i log appaiano in Logcat, ma non succede nulla.
Per i test, Timber fornisce Timber.asTree() — un metodo che restituisce l’albero corrente o null. Questo è comodo per i test unitari: è possibile sostituire l’albero con un mock e verificare che il messaggio di log sia stato inviato con il livello e il tag corretti.
Albero personalizzato — il motivo principale per utilizzare Timber invece dell’API Log standard. Sovrascrivendo i metodi di Tree, è possibile instradare i log di qualsiasi livello verso Crashlytics, il filesystem, Remote Config o il proprio server.
class CrashReportingTree : Timber.Tree() {
override fun isLoggable(tag: String?, priority: Int): Boolean {
// Solo Error e WTF per 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")
}
}
}
Metodi da sovrascrivere: isLoggable(tag, priority) — un filtro che determina se elaborare il messaggio (l’implementazione base restituisce true). log(priority, tag, message, t) — la logica di elaborazione principale. prepareLog(priority, tag, throwable, message, args) — chiamato prima della formattazione, permette di modificare il messaggio prima dell’elaborazione.
Un importante vantaggio degli alberi personalizzati è nessuna riflessione. A differenza di molti framework di logging, Timber non utilizza l’API Reflection per determinare il tag o il livello. Il tag viene calcolato analizzando lo stack di chiamate (Throwable.stackTrace), che è di ordini di grandezza più veloce.
Confronto tra Timber e l’API Log standard mostra quattro differenze chiave: tag automatico, supporto per la formattazione di stringhe con varargs, canali di output multipli e comportamento sicuro senza inizializzazione.
| Parametro | android.util.Log | Timber |
|---|---|---|
| Rilevamento tag | Manuale, costante stringa | Automatico, tramite stack di chiamate |
| Formattazione | Concatenazione o String.format | Varargs integrati + segnaposto %s |
| Canali di output | Solo Logcat | Alberi: Logcat, file, Crashlytics, ecc. |
| Comportamento senza inizializzazione | Funziona sempre | Non produce output |
| Prestazioni | Livello base | Formattazione pigra tramite isLoggable |
Principale argomento contro Timber — dipendenza da una libreria di terze parti. Per un progetto semplice con logging minimo, l’uso di Timber può essere eccessivo. Tuttavia, secondo Google Play Console, 2024, oltre il 60% delle prime 1000 applicazioni su Google Play utilizzano Timber, confermandone l’affidabilità e l’efficienza.
Le prestazioni di Timber nelle build release sono paragonabili all’API Log standard. Quando non ci sono alberi piantati, il metodo Timber.d() verifica la presenza di alberi (un if) e ritorna — senza formattazione della stringa. Questo è più veloce di Log.d() con concatenazione, che viene sempre eseguita.
Prima regola — controlla sempre l’inizializzazione di Timber nei test. Usa Timber.asTree() per verificare che un albero sia piantato. Nei test unitari, pianta TestTree che salva i messaggi in una lista per controlli assert.
Seconda regola — non mescolare Timber e android.util.Log nello stesso progetto. Se il progetto usa già Timber, tutte le nuove chiamate di log devono passare attraverso di esso. La miscelazione porta a messaggi duplicati e confusione durante l’analisi.
Terza regola — pianta CrashReportingTree senza controllare BuildConfig.DEBUG. A differenza di DebugTree, l’albero di crash deve funzionare sia in debug che in release — questo garantisce che anche gli errori di test vengano catturati dal sistema di crash-reporting.
Quarta regola — usa i livelli integrati di Timber: Timber.v(), Timber.d(), Timber.i(), Timber.w(), Timber.e(), Timber.wtf(). Evita di chiamare Timber.log() direttamente con priorità numerica — questo riduce la leggibilità del codice e complica il refactoring.
Quinta regola — per librerie e moduli, usa Timber.tag("CustomTag"). Questo metodo restituisce un albero temporaneo con un tag sovrascritto senza influenzare la configurazione globale. Ciò consente di registrare dal codice della libreria con un identificatore personalizzato.
Domande Frequenti
Sì — Timber è sicuro da usare nelle librerie. Se nessun albero viene piantato nell’applicazione, le chiamate Timber non causano errori. Per le librerie, si consiglia di utilizzare Timber.tag("LibraryTag") per identificare la fonte dei log.
Tramite lo stack di chiamate (stack trace) — DebugTree sale di 8 frame dal punto di chiamata Timber.d() ed estrae il nome della classe. Il metodo Throwable.stackTrace viene utilizzato per determinare la classe chiamante senza overhead dell’API Reflection.
Logcat è un’utilità di sistema Android per visualizzare i log. Timber è una libreria per scrivere log. Timber invia messaggi a Logcat tramite DebugTree, ma può anche inviarli a file, Crashlytics, Sentry e altri canali tramite alberi personalizzati.
No — Timber è legato all’Android SDK (android.util.Log). Per progetti KMP, considera Kermit o Napier — librerie di logging multipiattaforma con architettura ad albero simile, funzionanti su Android, iOS, JVM e JS.
Usa Timber.uprootAll() — il metodo rimuove tutti gli alberi registrati. Timber.uproot(tree) rimuove un albero specifico. Questo è utile nei test per resettare lo stato tra i metodi di test.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche