After discussions with fellow Penetration Testers, I discovered that many production apps still expose sensitive data (like API Keys, PII, etc.) in their logs. This essentially provides attackers with a golden ticket to enter our systems and cause business damage. In this article, I'll share some safe logging practices to help protect your applications.
Developers use logs 🪵 for various purposes including troubleshooting, debugging, and performance monitoring. Logging is an inseparable part of our day-to-day coding routine, but it needs to be handled with care.
Logging in Android Apps
The logging system in Android is fairly complex, but we'll focus specifically on the android.util.Log
class. As you may know, the basic usage looks like this:
if (BuildConfig.DEBUG) {
Log.v("StunningPotato", "Is this code being called?")
}
In the code above, we're using Log.v
or verbose logging.
verbose /və(ː)ˈbəʊs/ - using or expressed in more words than are needed.
Beyond verbose logs, Android provides different log levels for different purposes:
- Log.wtf ➡ "What a Terrible Failure" - indicates a serious failure or critical conditions
- Log.e ➡ Error conditions
- Log.w ➡ Warnings or potential problems
- Log.i ➡ Informational messages
- Log.d ➡ Debug-level messages
- Log.v ➡ Verbose messages
WTF, error, and warning logs are typically sent directly to system monitoring tools like Firebase or incident management tools like PagerDuty for prompt handling.
Information logs are collected for troubleshooting more subtle issues where obvious problems aren't immediately apparent.
Debug and verbose logs are primarily used during development to identify issues in your code. You can automatically remove these log levels during compile-time by adding these rules to your ProGuard file:
-assumenosideeffects class android.util.Log {
public static int d(...);
public static int v(...);
}
Timber — Logging for Lazy People
Why "for lazy people"? Because Timber handles necessary safeguards to prevent even junior programmers from making obvious mistakes.
Check out this simple Android demo app that demonstrates Timber usage.
Installation
implementation("com.jakewharton.timber:timber:5.0.1")
Typically, Timber is initialized in your Application class by calling Timber.plant
:
if (BuildConfig.DEBUG) {
// Plant the debug tree in debug builds
Timber.plant(Timber.DebugTree())
} else {
// Plant the release tree in release builds
Timber.plant(ReleaseTree())
}
Log behavior is defined through instances of Tree
.
The default Timber.DebugTree()
automatically identifies which class called the log method and uses that class name as the "TAG" for the log entry.
For ReleaseTree
, we can create a custom implementation that filters out verbose and debug logs and potentially sends critical logs to crash reporting services:
/**
* Custom Timber tree for release builds.
* In a real app, you might use this to send logs to a crash reporting service.
*/
class ReleaseTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (priority < Log.INFO) {
// Don't log VERBOSE or DEBUG logs in release build
return
}
// You could send important logs to a crash reporting service here
// For example: Crashlytics.log(priority, tag, message)
// Also log exception if present
if (t != null && priority >= Log.ERROR) {
// For example: Crashlytics.logException(t)
}
}
}
Usage
Using Timber is straightforward:
Timber.d("MainActivity onCreate called")
Timber.i("Debug button clicked")
Timber.d("User %s logged in with ID %d", username, userId)
try {
// Some code that might throw an exception
} catch (e: Exception) {
Timber.e(e, "Something went wrong")
}
With this approach, you don't need to worry about debug-level logs making it into production, as they won't be included when building the APK for production environments.
Additionally, the library includes Lint rules to detect:
- Direct usage of the
Log
class (encouraging developers to use Timber instead) - Tags exceeding Android's maximum length of 23 characters
- Various formatting issues in log messages
By following these practices, you can ensure your logs don't become security vulnerabilities while still maintaining their usefulness during development and troubleshooting.