Timber คืออะไร?
Timber เป็น Library Wrapper รอบ Android Log (android.util.Log) ทำให้ Logging ง่ายขึ้น ปลอดภัยกว่า และจัดการได้ดีขึ้น พัฒนาโดย Jake Wharton (Square)
ทำไมต้องใช้ Timber แทน Log ปกติ?
| ปัญหา Log ปกติ | Timber แก้ไขอย่างไร |
|---|---|
| ต้อง TAG เองทุกครั้ง | Auto Tag จาก Class Name |
| Release Build ยัง Log ออกไป | Auto Disable ใน Release (via BuildConfig.DEBUG) |
| Format ข้อความยุ่งยาก | String Formatting เหมือน String.format |
| Crash ไม่ได้ Log Stack Trace อัตโนมัติ | Log Exception ได้โดยตรง |
| ยากต่อการ Redirect ไป File/Server | Custom Tree สำหรับ Crashlytics, File, Remote |
การติดตั้ง
// build.gradle (Module: app)
dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
// สำหรับ Kotlin ใช้ได้เลย (มี Extension Functions)
}
การตั้งค่า (Initialization)
ต้อง Plant Tree ใน Application Class (ไม่ใช่ Activity) เพื่อให้ใช้งานได้ทั่วแอป
MyApplication.java
import android.app.Application;
import timber.log.Timber;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
// Debug Tree: Log ไป Logcat พร้อม Tag, Line Number
Timber.plant(new Timber.DebugTree());
} else {
// Release Tree: ส่งไป Crashlytics, Server, File
Timber.plant(new ReleaseTree());
}
}
}
AndroidManifest.xml
...
การใช้งานพื้นฐาน
import timber.log.Timber;
// Log Levels
Timber.v("Verbose: %s", "detail"); // Verbose
Timber.d("Debug: %d items", count); // Debug
Timber.i("Info: User %s logged in", name); // Info
Timber.w("Warning: %s", "low battery"); // Warning
Timber.e("Error: %s", errorMessage); // Error
// Exception/Stack Trace (สำคัญมาก!)
try {
riskyOperation();
} catch (Exception e) {
Timber.e(e, "Operation failed for user %s", userId);
// หรือ
Timber.e(e); // Log แค่ Exception
}
// Tag อัตโนมัติ = Class Name (MainActivity, UserRepository, etc.)
Advanced: Custom Tree สำหรับ Production
1. Crashlytics Tree
import com.google.firebase.crashlytics.FirebaseCrashlytics;
import timber.log.Timber;
public class CrashlyticsTree extends Timber.Tree {
@Override
protected void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable t) {
if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) {
return; // ไม่ส่ง Debug/Info ไป Crashlytics
}
if (t != null) {
FirebaseCrashlytics.getInstance().recordException(t);
} else {
FirebaseCrashlytics.getInstance().log(message);
// หรือ recordCustomKey
}
}
}
2. File Logging Tree (เก็บ Log ไฟล์)
import android.util.Log;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import timber.log.Timber;
public class FileLoggingTree extends Timber.Tree {
private final File logFile;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
public FileLoggingTree(File logDir) {
this.logFile = new File(logDir, "app_log_" + dateFormat.format(new Date()) + ".txt");
}
@Override
protected void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable t) {
if (message == null && t == null) return;
String level = priorityToString(priority);
String logLine = String.format(Locale.US, "%s %s/%s: %s%n",
dateFormat.format(new Date()), level, tag, message);
if (t != null) {
logLine +="\n" + Log.getStackTraceString(t) + "\n";
}
try (FileWriter writer = new FileWriter(logFile, true)) {
writer.write(logLine);
} catch (IOException e) {
// Fail silently
}
}
private String priorityToString(int priority) {
switch (priority) {
case Log.VERBOSE: return "V";
case Log.DEBUG: return "D";
case Log.INFO: return "I";
case Log.WARN: return "W";
case Log.ERROR: return "E";
default: return "?";
}
}
}
3. Plant หลาย Tree พร้อมกัน
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
// Plant File Tree ด้วยใน Debug ถ้าอยากเก็บไฟล์
Timber.plant(new FileLoggingTree(getCacheDir()));
} else {
Timber.plant(new CrashlyticsTree());
Timber.plant(new FileLoggingTree(getExternalFilesDir(null))); // หรือ Internal
}
Kotlin Extensions (ถ้าใช้ Kotlin)
// timber.kt (Extension) inline funT.logDebug(message: () -> String): T { Timber.tag(this::class.java.simpleName).d(message()) return this } inline fun T.logError(message: () -> String, throwable: Throwable? = null): T { Timber.tag(this::class.java.simpleName).e(throwable, message()) return this } // ใช้งาน userRepository.logDebug { "Fetching user $userId" } .fetchUser(userId) .logError { "Failed to fetch user $userId" }
Best Practices
- Plant ใน Application Class - ครอบคลุมทั้งแอป
- ใช้ String Formatting -
Timber.d("User %s has %d items", name, count)ไม่ใช้ + Concatenation - Log Exception ด้วย
Timber.e(e, ...)- Stack Trace ครบถ้วน - ไม่ Log Sensitive Data - Password, Token, PII ใน Production
- Custom Tree สำหรับ Release - ส่งไป Crashlytics, Sentry, Server Log
- ใช้ Tag สำหรับ Filter - Logcat กรองได้ตาม Class Name
Migration จาก Log ปกติ
// Before
private static final String TAG = "MainActivity";
Log.d(TAG, "onCreate: " + savedInstanceState);
Log.e(TAG, "Error: " + e.getMessage(), e);
// After
Timber.d("onCreate: %s", savedInstanceState);
Timber.e(e, "Error occurred");
สรุป
- Timber = Better Log API + Auto Tag + Release Safety
- Plant Tree ใน Application.onCreate()
- Debug:
Timber.DebugTree() - Release: Custom Tree (Crashlytics, File, Remote)
- ใช้
Timber.e(throwable, message)สำหรับ Exception - Kotlin ใช้ Extension Functions ทำให้สะดวกกว่า