Android Log
Android standard logging API (Log.d, Log.i, Log.w, Log.e, etc.). Widely used as simplest choice in Android application development. Suitable for rapid log output in debug builds, but limited advanced control in production builds.
Library
Android Log
Overview
Android Log (android.util.Log) is the Android standard logging API (Log.d, Log.i, Log.w, Log.e, etc.), widely used as the simplest and most essential choice for Android application development. With its lightweight design integrated with system logs, it's optimized for rapid log output in debug builds, and logs remain even when applications crash, providing crucial information for problem analysis. As a fundamental debugging tool during development and testing phases, it's an indispensable tool for Android developers.
Details
Android Log continues to maintain its essential presence as a fundamental debugging tool for Android development in 2025. Progressive information management is possible through five major log levels (Verbose, Debug, Info, Warning, Error), with improved development experience through complete LogCat integration. It supports ProGuard automatic removal in production builds, TAG-based filtering, and structured log message formatting. In Kotlin development, TAG definition patterns using companion objects have been standardized, enabling efficient debugging workflows in modern Android development.
Key Features
- 5-Level Log System: Importance classification through Verbose, Debug, Info, Warning, Error
- TAG-Based Filtering: Efficient log searching by class name or module name
- LogCat Integration: Real-time log display and analysis in Android Studio
- ProGuard Optimization: Automatic log removal in production builds for performance improvement
- System Integration: Complete integration with Android system logs and crash-time retention
- Zero Dependencies: Immediately available without external libraries
Advantages and Disadvantages
Advantages
- Available immediately without configuration as Android standard API
- Excellent development experience through complete LogCat integration
- Efficient debugging work through TAG-based filtering
- Automatic optimization in production builds through ProGuard
- Reliability with log retention even during system crashes
- Unified usage experience in both Kotlin and Java
Disadvantages
- Limited advanced log management features in production environments
- Basic level support for structured logs and asynchronous processing
- No enterprise-level features like log rotation or file output
- Performance constraints during high-volume log output
- Basic security-oriented log control features
- Requires separate tools for complex log analysis and metrics collection
Reference Pages
Code Examples
Installation and Basic Setup
// No additional installation required for Android standard API
// Available through import statement
import android.util.Log
// TAG definition using Kotlin companion object (recommended pattern)
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Filtering confirmation in LogCat
Log.d(TAG, "Activity created successfully")
}
}
// Java version TAG definition
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "Activity created successfully");
}
}
Basic Log Level Usage (Verbose/Debug/Info/Warning/Error)
import android.util.Log
class UserService {
companion object {
private const val TAG = "UserService"
}
fun authenticateUser(username: String, password: String): Boolean {
// Verbose: Most detailed information (development only)
Log.v(TAG, "Starting authentication process for user: $username")
// Debug: Debug information (development and test environments)
Log.d(TAG, "Validating credentials for username: $username")
// Info: General information (normal processing flow)
Log.i(TAG, "User authentication attempt: $username")
if (username.isEmpty() || password.isEmpty()) {
// Warning: Warning level issues
Log.w(TAG, "Authentication failed: Empty credentials provided")
return false
}
return try {
val result = performAuthentication(username, password)
if (result) {
Log.i(TAG, "Authentication successful for user: $username")
} else {
Log.w(TAG, "Authentication failed: Invalid credentials")
}
result
} catch (e: Exception) {
// Error: Error level (when exceptions occur)
Log.e(TAG, "Authentication error for user: $username", e)
false
}
}
private fun performAuthentication(username: String, password: String): Boolean {
// Authentication logic implementation
Log.d(TAG, "Performing network authentication request")
return username == "admin" && password == "password"
}
}
// Java version log level usage example
public class UserService {
private static final String TAG = "UserService";
public boolean authenticateUser(String username, String password) {
Log.v(TAG, "Starting authentication process for user: " + username);
Log.d(TAG, "Validating credentials");
Log.i(TAG, "User authentication attempt: " + username);
if (username.isEmpty() || password.isEmpty()) {
Log.w(TAG, "Authentication failed: Empty credentials");
return false;
}
try {
boolean result = performAuthentication(username, password);
if (result) {
Log.i(TAG, "Authentication successful");
} else {
Log.w(TAG, "Authentication failed: Invalid credentials");
}
return result;
} catch (Exception e) {
Log.e(TAG, "Authentication error", e);
return false;
}
}
}
Structured Logs and Data Formats (JSON/Key-Value Format)
import android.util.Log
import org.json.JSONObject
class AnalyticsLogger {
companion object {
private const val TAG = "Analytics"
}
// Structured log - JSON format
fun logUserAction(userId: String, action: String, metadata: Map<String, Any>) {
val logData = JSONObject().apply {
put("timestamp", System.currentTimeMillis())
put("userId", userId)
put("action", action)
put("sessionId", getCurrentSessionId())
metadata.forEach { (key, value) -> put(key, value) }
}
Log.i(TAG, "UserAction: $logData")
}
// Structured log - key-value format
fun logPerformanceMetrics(operation: String, duration: Long, success: Boolean) {
val logMessage = buildString {
append("operation=$operation ")
append("duration=${duration}ms ")
append("success=$success ")
append("memory=${getMemoryUsage()}MB ")
append("thread=${Thread.currentThread().name}")
}
Log.d(TAG, "Performance: $logMessage")
}
// Detailed error log with stack trace
fun logDetailedError(operation: String, error: Throwable, context: Map<String, String>) {
val contextInfo = context.entries.joinToString(", ") { "${it.key}=${it.value}" }
Log.e(TAG, "Error in $operation. Context: [$contextInfo]", error)
// Additional debug information
Log.d(TAG, "Error details: ${error.javaClass.simpleName} - ${error.message}")
Log.d(TAG, "Stack trace: ${Log.getStackTraceString(error)}")
}
private fun getCurrentSessionId(): String = "session_${System.currentTimeMillis()}"
private fun getMemoryUsage(): Long = Runtime.getRuntime().totalMemory() / 1024 / 1024
}
// Usage example
class ShoppingCartActivity : AppCompatActivity() {
private val analytics = AnalyticsLogger()
private fun addItemToCart(productId: String, quantity: Int) {
val startTime = System.currentTimeMillis()
try {
// Add item processing
performAddToCart(productId, quantity)
val duration = System.currentTimeMillis() - startTime
analytics.logPerformanceMetrics("add_to_cart", duration, true)
analytics.logUserAction(
userId = getCurrentUserId(),
action = "add_to_cart",
metadata = mapOf(
"productId" to productId,
"quantity" to quantity,
"cartTotal" to getCartTotal()
)
)
} catch (e: Exception) {
val duration = System.currentTimeMillis() - startTime
analytics.logPerformanceMetrics("add_to_cart", duration, false)
analytics.logDetailedError(
operation = "add_to_cart",
error = e,
context = mapOf(
"productId" to productId,
"quantity" to quantity.toString(),
"userId" to getCurrentUserId()
)
)
}
}
}
Performance Measurement and Benchmarking
import android.util.Log
class PerformanceLogger {
companion object {
private const val TAG = "Performance"
}
// Method execution time measurement
inline fun <T> measureTime(operation: String, block: () -> T): T {
val startTime = System.nanoTime()
return try {
val result = block()
val duration = (System.nanoTime() - startTime) / 1_000_000 // ms
Log.d(TAG, "$operation completed in ${duration}ms")
result
} catch (e: Exception) {
val duration = (System.nanoTime() - startTime) / 1_000_000
Log.e(TAG, "$operation failed after ${duration}ms", e)
throw e
}
}
// Memory usage monitoring
fun logMemoryUsage(operation: String) {
val runtime = Runtime.getRuntime()
val usedMemory = (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024
val maxMemory = runtime.maxMemory() / 1024 / 1024
val memoryPercentage = (usedMemory * 100) / maxMemory
Log.d(TAG, "$operation - Memory: ${usedMemory}MB/${maxMemory}MB (${memoryPercentage}%)")
if (memoryPercentage > 80) {
Log.w(TAG, "High memory usage detected: ${memoryPercentage}%")
}
}
// Network request time measurement
fun logNetworkRequest(url: String, method: String, responseTime: Long, responseCode: Int) {
val logMessage = "NetworkRequest: method=$method url=$url " +
"responseTime=${responseTime}ms responseCode=$responseCode"
when {
responseCode in 200..299 -> Log.i(TAG, logMessage)
responseCode in 400..499 -> Log.w(TAG, logMessage)
responseCode >= 500 -> Log.e(TAG, logMessage)
else -> Log.d(TAG, logMessage)
}
// Performance warning
if (responseTime > 5000) {
Log.w(TAG, "Slow network request detected: ${responseTime}ms for $url")
}
}
}
// Usage example
class DataRepository {
private val perfLogger = PerformanceLogger()
suspend fun fetchUserData(userId: String): UserData? {
return perfLogger.measureTime("fetchUserData") {
perfLogger.logMemoryUsage("Before API call")
val startTime = System.currentTimeMillis()
val response = apiService.getUser(userId)
val responseTime = System.currentTimeMillis() - startTime
perfLogger.logNetworkRequest(
url = "/api/users/$userId",
method = "GET",
responseTime = responseTime,
responseCode = response.code()
)
perfLogger.logMemoryUsage("After API call")
response.body()
}
}
}
Debug Tools and LogCat Integration
import android.util.Log
object DebugLogger {
private const val TAG = "DebugLogger"
private var isDebugMode = BuildConfig.DEBUG
// Debug mode control
fun setDebugMode(enabled: Boolean) {
isDebugMode = enabled
Log.i(TAG, "Debug mode ${if (enabled) "enabled" else "disabled"}")
}
// Conditional logging (output only in debug mode)
fun d(tag: String, message: String) {
if (isDebugMode) {
Log.d(tag, message)
}
}
fun v(tag: String, message: String) {
if (isDebugMode) {
Log.v(tag, message)
}
}
// Log with class information
fun logClassMethod(obj: Any, methodName: String, message: String = "") {
if (isDebugMode) {
val className = obj.javaClass.simpleName
val fullMessage = if (message.isNotEmpty()) {
"$className.$methodName(): $message"
} else {
"$className.$methodName() called"
}
Log.d(TAG, fullMessage)
}
}
// Stack trace analysis
fun logStackTrace(tag: String, message: String) {
if (isDebugMode) {
Log.d(tag, message)
val stackTrace = Thread.currentThread().stackTrace
stackTrace.forEachIndexed { index, element ->
if (index > 1) { // Skip Thread.getStackTrace() and this method
Log.d(tag, " at ${element.className}.${element.methodName}(${element.fileName}:${element.lineNumber})")
}
}
}
}
// Custom tag generation for LogCat filtering
fun createTag(className: String, feature: String): String {
return "${className}_$feature"
}
}
// Debug log usage example in Activity/Fragment
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
private val UI_TAG = DebugLogger.createTag("MainActivity", "UI")
private val NETWORK_TAG = DebugLogger.createTag("MainActivity", "Network")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DebugLogger.logClassMethod(this, "onCreate")
setContentView(R.layout.activity_main)
DebugLogger.d(UI_TAG, "Layout inflated successfully")
setupClickListeners()
}
override fun onResume() {
super.onResume()
DebugLogger.logClassMethod(this, "onResume", "Activity becoming visible")
// LogCat filter search examples:
// tag:MainActivity_UI - Display only UI-related logs
// tag:MainActivity_Network - Display only network-related logs
// tag:MainActivity - Display all logs
}
private fun setupClickListeners() {
DebugLogger.d(UI_TAG, "Setting up click listeners")
findViewById<Button>(R.id.button).setOnClickListener {
DebugLogger.logStackTrace(UI_TAG, "Button clicked")
performNetworkRequest()
}
}
private fun performNetworkRequest() {
DebugLogger.d(NETWORK_TAG, "Starting network request")
// Network processing
}
}
// ProGuard configuration example (log removal in release build)
// proguard-rules.pro:
/*
-assumenosideeffects class android.util.Log {
public static int d(...);
public static int v(...);
public static int i(...);
public static int w(...);
public static int e(...);
}
-assumenosideeffects class com.yourpackage.DebugLogger {
public static void d(...);
public static void v(...);
public static void logClassMethod(...);
public static void logStackTrace(...);
}
*/
Error Handling and Crash Reports
import android.util.Log
import java.io.PrintWriter
import java.io.StringWriter
class CrashReportLogger {
companion object {
private const val TAG = "CrashReport"
}
// Setup global exception handler
fun setupGlobalExceptionHandler() {
val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, exception ->
logCrashReport(thread, exception)
defaultHandler?.uncaughtException(thread, exception)
}
Log.i(TAG, "Global exception handler configured")
}
// Detailed crash report
private fun logCrashReport(thread: Thread, exception: Throwable) {
Log.e(TAG, "=== CRASH REPORT ===")
Log.e(TAG, "Thread: ${thread.name}")
Log.e(TAG, "Exception: ${exception.javaClass.simpleName}")
Log.e(TAG, "Message: ${exception.message}")
Log.e(TAG, "Stack trace:", exception)
// Device information
logDeviceInfo()
// App state information
logAppState()
Log.e(TAG, "=== END CRASH REPORT ===")
}
// Try-catch exception handling
inline fun <T> safeExecute(
operation: String,
onError: (Throwable) -> T? = { null },
block: () -> T
): T? {
return try {
Log.d(TAG, "Executing: $operation")
val result = block()
Log.d(TAG, "Successfully completed: $operation")
result
} catch (e: Exception) {
Log.e(TAG, "Error in $operation", e)
logDetailedException(operation, e)
onError(e)
}
}
// Detailed exception information log
private fun logDetailedException(operation: String, exception: Throwable) {
Log.e(TAG, "=== DETAILED EXCEPTION ===")
Log.e(TAG, "Operation: $operation")
Log.e(TAG, "Exception type: ${exception.javaClass.canonicalName}")
Log.e(TAG, "Message: ${exception.message}")
Log.e(TAG, "Cause: ${exception.cause}")
// Custom stack trace output
val stringWriter = StringWriter()
exception.printStackTrace(PrintWriter(stringWriter))
Log.e(TAG, "Full stack trace:\n$stringWriter")
// Output related exceptions too
var cause = exception.cause
var level = 1
while (cause != null && level <= 5) {
Log.e(TAG, "Caused by ($level): ${cause.javaClass.simpleName} - ${cause.message}")
cause = cause.cause
level++
}
Log.e(TAG, "=== END DETAILED EXCEPTION ===")
}
private fun logDeviceInfo() {
Log.e(TAG, "Device info:")
Log.e(TAG, " Android version: ${android.os.Build.VERSION.RELEASE}")
Log.e(TAG, " API level: ${android.os.Build.VERSION.SDK_INT}")
Log.e(TAG, " Device: ${android.os.Build.DEVICE}")
Log.e(TAG, " Manufacturer: ${android.os.Build.MANUFACTURER}")
Log.e(TAG, " Model: ${android.os.Build.MODEL}")
}
private fun logAppState() {
val runtime = Runtime.getRuntime()
Log.e(TAG, "App state:")
Log.e(TAG, " Memory usage: ${(runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024}MB")
Log.e(TAG, " Max memory: ${runtime.maxMemory() / 1024 / 1024}MB")
Log.e(TAG, " Available processors: ${runtime.availableProcessors()}")
Log.e(TAG, " Current thread: ${Thread.currentThread().name}")
}
}
// Usage example
class NetworkManager {
private val crashLogger = CrashReportLogger()
fun fetchData(url: String): String? {
return crashLogger.safeExecute(
operation = "fetchData from $url",
onError = { exception ->
// Custom error handling
when (exception) {
is java.net.UnknownHostException -> {
Log.w("NetworkManager", "No internet connection")
"offline_data"
}
is java.net.SocketTimeoutException -> {
Log.w("NetworkManager", "Request timeout")
"cached_data"
}
else -> null
}
}
) {
// Network processing
performNetworkCall(url)
}
}
private fun performNetworkCall(url: String): String {
// Actual network processing
if (url.isEmpty()) throw IllegalArgumentException("URL cannot be empty")
return "response_data"
}
}
// Initialization in Application class
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
CrashReportLogger().setupGlobalExceptionHandler()
}
}
}
Production Environment Optimization and Security
import android.util.Log
// Production environment log manager
object ProductionLogger {
private const val TAG = "ProductionLogger"
// Log level control
private val isReleaseMode = !BuildConfig.DEBUG
private val allowedLogLevels = if (isReleaseMode) {
setOf(Log.ERROR, Log.WARN) // Only Error and Warning in production
} else {
setOf(Log.VERBOSE, Log.DEBUG, Log.INFO, Log.WARN, Log.ERROR) // All levels in development
}
// Secure log (sensitive information removal)
fun secureLog(tag: String, level: Int, message: String) {
if (level !in allowedLogLevels) return
val sanitizedMessage = sanitizeMessage(message)
when (level) {
Log.VERBOSE -> Log.v(tag, sanitizedMessage)
Log.DEBUG -> Log.d(tag, sanitizedMessage)
Log.INFO -> Log.i(tag, sanitizedMessage)
Log.WARN -> Log.w(tag, sanitizedMessage)
Log.ERROR -> Log.e(tag, sanitizedMessage)
}
}
// Sensitive information removal and masking
private fun sanitizeMessage(message: String): String {
var sanitized = message
// Password removal
sanitized = sanitized.replace(Regex("password[=:\"']\\s*\\S+", RegexOption.IGNORE_CASE), "password=***")
// Partial email address masking
sanitized = sanitized.replace(Regex("([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})")) { matchResult ->
val email = matchResult.value
val atIndex = email.indexOf('@')
val username = email.substring(0, atIndex)
val domain = email.substring(atIndex)
val maskedUsername = if (username.length > 2) {
username.take(2) + "*".repeat(username.length - 2)
} else {
"*".repeat(username.length)
}
"$maskedUsername$domain"
}
// Phone number masking
sanitized = sanitized.replace(Regex("\\b\\d{3}-?\\d{4}-?\\d{4}\\b"), "***-****-****")
// API key and token removal
sanitized = sanitized.replace(Regex("(api[_-]?key|token|secret)[=:\"']\\s*\\S+", RegexOption.IGNORE_CASE), "$1=***")
return sanitized
}
// Usage-limited logging (spam prevention)
private val logCounters = mutableMapOf<String, LogCounter>()
data class LogCounter(
var count: Int = 0,
var lastLogTime: Long = 0L
)
fun throttledLog(tag: String, level: Int, message: String, maxLogsPerMinute: Int = 10) {
val key = "$tag:$message"
val currentTime = System.currentTimeMillis()
val counter = logCounters.getOrPut(key) { LogCounter() }
// Reset counter after 1 minute
if (currentTime - counter.lastLogTime > 60_000) {
counter.count = 0
counter.lastLogTime = currentTime
}
if (counter.count < maxLogsPerMinute) {
secureLog(tag, level, message)
counter.count++
} else if (counter.count == maxLogsPerMinute) {
secureLog(tag, Log.WARN, "Log throttling activated for: $message")
counter.count++
}
// Ignore if exceeds maxLogsPerMinute
}
// Production environment error-only logging
fun logProductionError(tag: String, error: Throwable, context: Map<String, String> = emptyMap()) {
if (!isReleaseMode) {
// Detailed log in development environment
Log.e(tag, "Error: ${error.message}", error)
context.forEach { (key, value) ->
Log.e(tag, "Context $key: $value")
}
} else {
// Minimal information only in production environment
val sanitizedContext = context.mapValues { (_, value) -> sanitizeMessage(value) }
val errorInfo = "ErrorType: ${error.javaClass.simpleName}, Context: $sanitizedContext"
Log.e(tag, sanitizeMessage(errorInfo))
}
}
}
// Usage example
class UserAuthenticator {
companion object {
private const val TAG = "UserAuth"
}
fun login(email: String, password: String): Boolean {
// Detailed log in development environment, sensitive information removed in production
ProductionLogger.secureLog(TAG, Log.INFO, "Login attempt for: $email")
return try {
val result = performLogin(email, password)
if (result) {
ProductionLogger.secureLog(TAG, Log.INFO, "Login successful for: $email")
} else {
// Throttling for spam prevention
ProductionLogger.throttledLog(TAG, Log.WARN, "Login failed for: $email", maxLogsPerMinute = 5)
}
result
} catch (e: Exception) {
ProductionLogger.logProductionError(
tag = TAG,
error = e,
context = mapOf(
"operation" to "login",
"email" to email,
"timestamp" to System.currentTimeMillis().toString()
)
)
false
}
}
private fun performLogin(email: String, password: String): Boolean {
// Authentication logic
return email.isNotEmpty() && password.isNotEmpty()
}
}
// Production optimization through ProGuard configuration
// app/proguard-rules.pro:
/*
# Complete debug log removal in production builds
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int d(...);
public static int i(...);
}
# ProductionLogger development-only method removal
-assumenosideeffects class com.yourpackage.ProductionLogger {
# Remove Info level and below logs in production
public static void secureLog(java.lang.String, int, java.lang.String)
return false;
# Remove development-only part of throttledLog
public static void throttledLog(...)
return false;
}
# BuildConfig.DEBUG optimization
-assumenosideeffects class com.yourpackage.BuildConfig {
public static final boolean DEBUG return false;
}
*/