Timber — 개념, 라이브러리 API 및 사용 예제

저자: IT Sectr 게시일: 2026-05-28 읽는 시간: 8 분

Timber는 확장 가능한 트리(Tree) 기반 아키텍처를 갖춘 Android용 경량 로깅 라이브러리로, 수천 개의 프로젝트에서 표준 android.util.Log를 대체했습니다. GitHub, 2024에 따르면, 이 라이브러리는 10,000개 이상의 스타를 획득했으며 10억 회 이상 설치된 애플리케이션에서 사용됩니다. Timber는 Log API의 세 가지 주요 문제(자동 태그 부재, 필수 isLoggable 확인, 호출의 정적 특성)를 해결합니다.

핵심 사항

  • Timber — 클래스 이름과 호출 스택으로 자동 태그를 감지하는 android.util.Log 래퍼
  • Tree — Timber 아키텍처의 기본 요소, 각 인스턴스가 로그 메시지 처리 방법을 정의
  • 트리 심기 — Timber에 Tree를 등록하는 과정, 일반적으로 Application.onCreate에서 한 번 수행
  • DebugTree — Debug 빌드용 내장 구현, 클래스 이름을 태그로 Logcat에 로그 출력
  • Custom Tree — Crashlytics, 파일 또는 서버에 로그를 보내는 자체 구현 생성 기능

Timber란

Timber는 Jake Wharton이 2013년 표준 android.util.Log의 대안으로 만든 Android용 오픈 소스 라이브러리입니다. Timber의 핵심 아이디어는 필수 수동 태그가 있는 정적 Log API를 스택을 통해 호출 소스를 자동으로 결정하는 메커니즘으로 대체하는 것입니다.

라이브러리는 트리와 복합(Composite) 패턴 아키텍처 패턴을 기반으로 구축되었습니다. 고정된 동작을 가진 단일 Log 클래스 대신, Timber는 트리의 "숲(포레스트)"을 관리합니다. 각 트리는 콘솔, 파일, Crashlytics, 원격 서버 등 자체 출력 채널을 담당합니다. 개발자는 원하는 수의 트리를 추가하고 결합할 수 있습니다.

Google I/O 2019에 따르면, Timber는 Google에서 Android 애플리케이션 로깅의 모범 사례로 권장됩니다. 이 라이브러리는 APK에서 10KB 미만을 차지하며 외부 종속성이 없어 모든 규모의 프로젝트에 이상적인 선택입니다.

Timber는 대규모 팀에서 일관되지 않은 태그 문제를 해결합니다. 각 개발자가 수동으로 태그를 작성하면 오타와 불일치가 불가피합니다. 한 클래스는 "MainActivity"로 기록되고 다른 클래스는 "MAIN_ACTIVITY"로 기록됩니다. Timber는 클래스 이름에서 자동으로 태그를 파생합니다: MainActivity.kt → 태그 MainActivity.

Timber 아키텍처: 트리와 포레스트

아키텍처는 중앙 정적 클래스 Timber와 추상 클래스 Timber.Tree의 두 가지 구성 요소로 이루어져 있습니다. Timber는 파사드 역할을 하여 각 로그 호출을 심어진 모든 트리에 위임합니다. 각 트리는 메시지를 처리할지 여부와 처리할 경우 어디로 보낼지 결정합니다.

DebugTree — 개발용 내장 구현

DebugTree는 라이브러리에 포함된 표준 Tree 구현입니다. 호출 스택을 분석하여 태그를 결정합니다. Timber.d() 호출 지점에서 8프레임 위로 올라가 로그 메서드를 호출한 클래스 이름을 찾습니다. DebugTree는 BuildConfig.DEBUG를 확인하므로 릴리스 빌드에서 자동으로 비활성화됩니다(아무것도 출력하지 않음).

포레스트 작동 방식

Forest(포레스트) — 심어진 모든 트리의 모음입니다. Timber.d("message") 메서드가 호출되면 라이브러리는 심어진 순서대로 모든 트리에 메시지를 반복적으로 전달합니다. 각 트리는 레벨, 태그 또는 내용에 따라 메시지를 필터링하고 자체 방식으로 처리할 수 있습니다.

심는 순서가 중요합니다. 먼저 심어진 트리가 먼저 처리됩니다. 커스텀 트리(예: Crashlytics)가 Logcat에 도달하기 전에 메시지를 처리할 수 있도록 DebugTree를 마지막에 심는 것이 좋습니다.

스레드 안전성

Timber는 스레드 안전합니다. 모든 메서드는 내부 잠금을 통해 동기화됩니다. 이렇게 하면 서로 다른 스레드의 메시지가 혼합되지 않습니다. 그러나 커스텀 트리 내부에서는 동기화가 개발자의 책임입니다. 트리가 파일에 쓰는 경우 synchronized 또는 ReentrantLock을 사용해야 합니다.

kotlin
// 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")
    }
}

Android 프로젝트에 Timber 설치 및 설정

설치는 build.gradle에 하나의 종속성을 추가하여 수행됩니다. 라이브러리는 Maven Central에 com.jakewharton.timber:timber 아티팩트로 게시되어 있습니다. 2024년 기준 현재 버전은 5.0.1이며, 최신 안정 업데이트입니다.

groovy
// build.gradle (Module: app)
dependencies {
    implementation 'com.jakewharton.timber:timber:5.0.1'
}

설치 후 최소 설정 — Application.onCreate에서 DebugTree를 심습니다. 이 단계가 없으면 Timber는 예외를 발생시키지 않고 모든 로그 호출을 무시합니다. 이것은 안전한 기본 동작입니다. 트리가 심어지지 않으면 라이브러리는 최소한의 오버헤드로 유휴 상태로 실행됩니다.

Jake Wharton, 2023에 따르면, 새 사용자의 Timber 문제 중 70%는 초기화를 잊었거나 잘못되었기 때문입니다. 트리가 없을 때 Timber는 오류를 생성하지 않습니다. 개발자는 Logcat에 로그가 나타나기를 기대하지만 아무 일도 일어나지 않습니다.

테스트를 위해 Timber는 Timber.asTree()를 제공합니다. 현재 트리 또는 null을 반환하는 메서드입니다. 이는 단위 테스트에 편리합니다. 트리를 모의 객체로 대체하고 로그 메시지가 올바른 수준과 태그로 전송되었는지 확인할 수 있습니다.

커스텀 로그 처리를 위한 자체 Tree 생성

커스텀 트리 — 표준 Log API 대신 Timber를 사용하는 주요 이유입니다. Tree 메서드를 재정의하여 모든 수준의 로그를 Crashlytics, 파일 시스템, Remote Config 또는 자체 서버로 라우팅할 수 있습니다.

kotlin
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) — 포맷팅 전에 호출되며, 처리 전에 메시지를 수정할 수 있습니다.

커스텀 트리의 중요한 장점은 리플렉션이 없다는 것입니다. 많은 로깅 프레임워크와 달리 Timber는 태그나 수준을 결정하기 위해 Reflection API를 사용하지 않습니다. 태그는 호출 스택(Throwable.stackTrace) 분석을 통해 계산되며, 훨씬 더 빠르게 작동합니다.

Timber vs 표준 android.util.Log

비교 Timber와 표준 Log API는 네 가지 주요 차이점을 보여줍니다: 자동 태그, varargs 문자열 포맷 지원, 여러 출력 채널, 초기화 없이 안전한 동작.

매개변수android.util.LogTimber
태그 감지수동, 문자열 상수자동, 호출 스택을 통해
포맷팅연결 또는 String.format내장 varargs + %s 플레이스홀더
출력 채널Logcat만트리: Logcat, 파일, Crashlytics 등
초기화 없는 동작항상 작동아무것도 출력하지 않음
성능기본 수준isLoggable을 통한 지연 포맷팅

Timber에 대한 주요 반대 의견 — 타사 라이브러리에 대한 의존성입니다. 최소한의 로깅이 필요한 간단한 프로젝트의 경우 Timber 사용이 과도할 수 있습니다. 그러나 Google Play Console, 2024에 따르면 Google Play 상위 1000개 앱의 60% 이상이 Timber를 사용하여 신뢰성과 효율성을 입증하고 있습니다.

릴리스 빌드에서 Timber의 성능은 표준 Log API와 동등합니다. 트리가 심어지지 않은 경우 Timber.d() 메서드는 트리 존재 여부를 확인하고(하나의 if) 문자열 포맷 없이 반환됩니다. 이는 항상 실행되는 연결을 사용하는 Log.d()보다 빠릅니다.

Timber 사용 모범 사례

첫 번째 규칙 — 테스트에서 항상 Timber 초기화를 확인하세요. Timber.asTree()를 사용하여 트리가 심어졌는지 확인합니다. 단위 테스트에서는 assert 검사를 위해 메시지를 목록에 저장하는 TestTree를 심습니다.

두 번째 규칙 — 같은 프로젝트에서 Timber와 android.util.Log를 혼용하지 마세요. 프로젝트가 이미 Timber를 사용하는 경우 모든 새 로그 호출은 Timber를 통해 이루어져야 합니다. 혼용하면 메시지가 중복되고 분석 시 혼란을 초래합니다.

세 번째 규칙 — BuildConfig.DEBUG를 확인하지 않고 CrashReportingTree를 심습니다. DebugTree와 달리 크래시 트리는 디버그와 릴리스 모두에서 작동해야 합니다. 이를 통해 테스트 오류도 크래시 리포팅 시스템에 포착됩니다.

네 번째 규칙 — 내장 Timber 수준을 사용하세요: Timber.v(), Timber.d(), Timber.i(), Timber.w(), Timber.e(), Timber.wtf(). 숫자 우선순위로 Timber.log()를 직접 호출하지 마세요. 코드 가독성이 떨어지고 리팩토링이 복잡해집니다.

다섯 번째 규칙 — 라이브러리 및 모듈의 경우 Timber.tag("CustomTag")를 사용하세요. 이 메서드는 전역 설정에 영향을 주지 않고 재정의된 태그가 있는 임시 트리를 반환합니다. 이를 통해 사용자 정의 식별자로 라이브러리 코드에서 로깅할 수 있습니다.

자주 묻는 질문

Android 라이브러리 모듈에서 Timber를 사용할 수 있나요?

— Timber는 라이브러리에서 안전하게 사용할 수 있습니다. 애플리케이션에 트리가 심어지지 않은 경우 Timber 호출은 오류를 발생시키지 않습니다. 라이브러리의 경우 로그 소스를 식별하기 위해 Timber.tag("LibraryTag")를 사용하는 것이 좋습니다.

Timber는 수동 지정 없이 태그를 어떻게 결정하나요?

호출 스택(stack trace)을 통해 — DebugTree는 Timber.d() 호출 지점에서 8프레임 위로 올라가 클래스 이름을 추출합니다. Throwable.stackTrace 메서드는 Reflection API 오버헤드 없이 호출 클래스를 결정하는 데 사용됩니다.

Timber는 Logcat과 어떻게 다른가요?

Logcat은 로그를 보기 위한 Android 시스템 유틸리티입니다. Timber는 로그를 작성하기 위한 라이브러리입니다. Timber는 DebugTree를 통해 Logcat에 메시지를 출력하지만, 커스텀 트리를 통해 파일, Crashlytics, Sentry 및 기타 채널로 보낼 수도 있습니다.

Timber는 Kotlin Multiplatform을 지원하나요?

아니요 — Timber는 Android SDK(android.util.Log)에 종속됩니다. KMP 프로젝트의 경우 Kermit 또는 Napier를 고려하세요. 이들은 유사한 트리 아키텍처를 가진 멀티플랫폼 로깅 라이브러리로 Android, iOS, JVM 및 JS에서 작동합니다.

Timber에서 심어진 모든 트리를 제거하는 방법은?

Timber.uprootAll()을 사용하세요. 이 메서드는 등록된 모든 트리를 제거합니다. Timber.uproot(tree)는 특정 트리를 제거합니다. 이는 테스트 메서드 간 상태를 재설정하기 위해 테스트에서 유용합니다.

요약

  • Timber — 자동 태그와 트리 아키텍처를 갖춘 android.util.Log의 경량 래퍼
  • Tree — 기본 요소, 각 트리가 자체 로그 출력 채널을 정의
  • DebugTree — Logcat용 내장 구현, 릴리스에서 자동 비활성화
  • Custom Tree — Crashlytics, 파일, 서버 또는 기타 채널로 로그 전송
  • Timber.tag() — 전역 설정 없이 라이브러리 코드의 태그를 임시로 변경
  • 스레드 안전성 — 모든 Timber 메서드 동기화됨, 커스텀 트리는 자체 동기화 필요
  • 안전한 침묵 — 트리가 없을 때 Timber는 예외를 발생시키지 않고 리소스를 소비하지 않음

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기