Timber es una biblioteca de registro ligera para Android con una arquitectura extensible basada en árboles (Tree), que ha reemplazado al android.util.Log estándar en miles de proyectos. Según GitHub, 2024, la biblioteca ha superado las 10 000 estrellas y se utiliza en aplicaciones con más de 1 mil millones de instalaciones. Timber resuelve tres problemas principales de Log API: falta de tag automático, verificación obligatoria de isLoggable y naturaleza estática de las llamadas.
Puntos Clave
Timber es una biblioteca de código abierto para Android creada por Jake Wharton en 2013 como alternativa al android.util.Log estándar. La idea clave de Timber es reemplazar la API Log estática con tag manual obligatorio por un mecanismo automático que determina la fuente de la llamada mediante la pila.
La biblioteca está construida sobre el patrón arquitectónico Composite con Árboles (Tree). En lugar de una única clase Log con comportamiento fijo, Timber gestiona un "bosque" de árboles — cada árbol es responsable de su propio canal de salida: consola, archivo, Crashlytics, servidor remoto. El desarrollador puede agregar cualquier cantidad de árboles y combinarlos.
Según Google I/O 2019, Timber es recomendado por Google como una mejor práctica para el registro en aplicaciones Android. La biblioteca ocupa menos de 10 KB en APK y no tiene dependencias externas, lo que la convierte en una opción ideal para proyectos de cualquier escala.
Timber resuelve el problema de las tags inconsistentes en equipos grandes. Cuando cada desarrollador escribe tags manualmente, los errores tipográficos y las discrepancias son inevitables — una clase se registra como "MainActivity", otra como "MAIN_ACTIVITY". Timber deriva automáticamente el tag del nombre de la clase: MainActivity.kt → tag MainActivity.
Arquitectura de Timber consta de dos componentes: la clase estática central Timber y la clase abstracta Timber.Tree. Timber actúa como una fachada que delega cada llamada de registro a todos los árboles plantados. Cada árbol decide si procesar el mensaje y, si es así, a dónde enviarlo.
DebugTree es la implementación estándar de Tree incluida con la biblioteca. Determina el tag analizando la pila de llamadas: sube 8 marcos desde el punto de llamada Timber.d() y encuentra el nombre de la clase que invocó el método de registro. DebugTree se desactiva automáticamente (no produce salida) en compilaciones release porque verifica BuildConfig.DEBUG.
Forest (Bosque) — la colección de todos los árboles plantados. Cuando se llama al método Timber.d("mensaje"), la biblioteca pasa iterativamente el mensaje a todos los árboles en el orden en que fueron plantados. Cada árbol puede filtrar el mensaje por nivel, tag o contenido, y procesarlo a su manera.
El orden de plantación importa: el primer árbol plantado se procesa primero. Se recomienda plantar DebugTree al final, para que los árboles personalizados (por ejemplo, Crashlytics) procesen el mensaje antes de que llegue a Logcat.
Timber es seguro para hilos — todos los métodos están sincronizados mediante un bloqueo interno. Esto garantiza que los mensajes de diferentes hilos no se mezclen. Sin embargo, dentro de un árbol personalizado, la sincronización es responsabilidad del desarrollador: si el árbol escribe en un archivo, se debe usar synchronized o ReentrantLock.
// Inicialización del bosque de árboles en 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")
}
}
Instalación de Timber se realiza agregando una sola dependencia en build.gradle. La biblioteca está publicada en Maven Central bajo el artefacto com.jakewharton.timber:timber. La versión actual a 2024 es 5.0.1, la última actualización estable.
// build.gradle (Module: app)
dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
}
Configuración mínima después de la instalación — plantar DebugTree en Application.onCreate. Sin este paso, Timber ignorará todas las llamadas de registro sin lanzar excepciones. Este es un comportamiento seguro predeterminado: si no se planta ningún árbol, la biblioteca funciona inactiva con una sobrecarga mínima.
Según Jake Wharton, 2023, el 70% de los problemas de Timber para nuevos usuarios están relacionados con una inicialización olvidada o incorrecta. Timber no genera un error cuando no hay árboles — los desarrolladores esperan que los registros aparezcan en Logcat, pero no sucede nada.
Para pruebas, Timber proporciona Timber.asTree() — un método que devuelve el árbol actual o null. Esto es conveniente para pruebas unitarias: puedes reemplazar el árbol con un mock y verificar que el mensaje de registro se envió con el nivel y tag correctos.
Árbol personalizado — la razón principal para usar Timber en lugar de la API Log estándar. Sobrescribiendo los métodos de Tree, puedes enrutar registros de cualquier nivel a Crashlytics, sistema de archivos, Remote Config o tu propio servidor.
class CrashReportingTree : Timber.Tree() {
override fun isLoggable(tag: String?, priority: Int): Boolean {
// Solo Error y WTF para 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")
}
}
}
Métodos para sobrescribir: isLoggable(tag, priority) — un filtro que determina si procesar el mensaje (la implementación base devuelve true). log(priority, tag, message, t) — la lógica principal de procesamiento. prepareLog(priority, tag, throwable, message, args) — se llama antes del formateo, permite modificar el mensaje antes de procesarlo.
Una ventaja importante de los árboles personalizados es sin reflexión. A diferencia de muchos frameworks de registro, Timber no usa Reflection API para determinar el tag o el nivel. El tag se calcula analizando la pila de llamadas (Throwable.stackTrace), que funciona órdenes de magnitud más rápido.
Comparación de Timber y la API Log estándar muestra cuatro diferencias clave: tag automático, soporte de formato de cadena con varargs, múltiples canales de salida y comportamiento seguro cuando no está inicializado.
| Parámetro | android.util.Log | Timber |
|---|---|---|
| Detección de tag | Manual, constante de cadena | Automática, mediante pila de llamadas |
| Formateo | Concatenación o String.format | Varargs integrados + marcador %s |
| Canales de salida | Solo Logcat | Árboles: Logcat, archivo, Crashlytics, etc. |
| Comportamiento sin inicialización | Siempre funciona | No produce salida |
| Rendimiento | Nivel base | Formateo diferido mediante isLoggable |
Principal argumento en contra de Timber — dependencia de una biblioteca de terceros. Para un proyecto simple con registro mínimo, usar Timber puede ser excesivo. Sin embargo, según Google Play Console, 2024, más del 60% de las 1000 mejores aplicaciones en Google Play usan Timber, lo que confirma su fiabilidad y eficiencia.
El rendimiento de Timber en compilaciones release está a la par con la API Log estándar. Cuando no hay árboles plantados, el método Timber.d() verifica la presencia de árboles (un if) y regresa — sin formateo de cadenas. Esto es más rápido que Log.d() con concatenación, que siempre se ejecuta.
Primera regla — siempre verifica la inicialización de Timber en las pruebas. Usa Timber.asTree() para verificar que un árbol está plantado. En pruebas unitarias, planta TestTree que guarda mensajes en una lista para comprobaciones assert.
Segunda regla — no mezcles Timber y android.util.Log en el mismo proyecto. Si el proyecto ya usa Timber, todas las nuevas llamadas de registro deben pasar a través de él. La mezcla provoca mensajes duplicados y confusión durante el análisis.
Tercera regla — planta CrashReportingTree sin verificar BuildConfig.DEBUG. A diferencia de DebugTree, el árbol de crash debe funcionar tanto en debug como en release — esto asegura que los errores de prueba también sean capturados por el sistema de informes de crash.
Cuarta regla — usa los niveles integrados de Timber: Timber.v(), Timber.d(), Timber.i(), Timber.w(), Timber.e(), Timber.wtf(). Evita llamar a Timber.log() directamente con prioridad numérica — esto reduce la legibilidad del código y complica la refactorización.
Quinta regla — para bibliotecas y módulos, usa Timber.tag("CustomTag"). Este método devuelve un árbol temporal con un tag sobrescrito sin afectar la configuración global. Esto permite registrar desde código de biblioteca con un identificador personalizado.
Preguntas Frecuentes
Sí — Timber es seguro de usar en bibliotecas. Si no se planta ningún árbol en la aplicación, las llamadas a Timber no generan errores. Para bibliotecas, se recomienda usar Timber.tag("LibraryTag") para identificar la fuente del registro.
Mediante la pila de llamadas (stack trace) — DebugTree sube 8 marcos desde el punto de llamada Timber.d() y extrae el nombre de la clase. El método Throwable.stackTrace se utiliza para determinar la clase llamante sin gastos de Reflection API.
Logcat es una utilidad del sistema Android para ver registros. Timber es una biblioteca para escribir registros. Timber envía mensajes a Logcat a través de DebugTree, pero también puede enviarlos a archivos, Crashlytics, Sentry y otros canales mediante árboles personalizados.
No — Timber está vinculado al SDK de Android (android.util.Log). Para proyectos KMP, considera Kermit o Napier — bibliotecas de registro multiplataforma con una arquitectura de árbol similar, que funcionan en Android, iOS, JVM y JS.
Usa Timber.uprootAll() — el método elimina todos los árboles registrados. Timber.uproot(tree) elimina un árbol específico. Esto es útil en pruebas para restablecer el estado entre métodos de prueba.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también