Android Log
Android標準のロギングAPI(Log.d、Log.i、Log.w、Log.e等)。Androidアプリケーション開発で最もシンプルな選択肢として広く使用。デバッグビルドでの迅速なログ出力に適しているが、本番ビルドでの高度な制御は制限的。
ライブラリ
Android Log
概要
Android Log(android.util.Log)は、Android標準のロギングAPI(Log.d、Log.i、Log.w、Log.e等)で、Androidアプリケーション開発で最もシンプルかつ必須の選択肢として広く使用されています。システムログと統合された軽量な設計により、デバッグビルドでの迅速なログ出力に最適化されており、アプリケーションがクラッシュした場合でもログが残り、問題分析に重要な情報を提供。開発・テスト段階での基本的なデバッグ手段として、Android開発者にとって欠かせないツールです。
詳細
Android Log は2025年でもAndroid開発の基本的なデバッグ手段として不可欠な存在を維持しています。5つの主要ログレベル(Verbose、Debug、Info、Warning、Error)により段階的な情報管理が可能で、LogCatとの完全統合により開発体験が向上。ProGuardによる本番ビルドでの自動除去機能、TAGベースのフィルタリング、構造化されたログメッセージフォーマットをサポート。Kotlin開発においてはcompanion objectでのTAG定義パターンが標準化され、モダンなAndroid開発における効率的なデバッグワークフローを実現しています。
主な特徴
- 5段階ログレベル: Verbose、Debug、Info、Warning、Errorによる重要度分類
- TAGベースフィルタリング: クラス名やモジュール名による効率的なログ検索
- LogCat統合: Android Studioでのリアルタイムログ表示と解析
- ProGuard最適化: 本番ビルドでの自動ログ除去による性能向上
- システム統合: Androidシステムログとの完全統合とクラッシュ時保持
- ゼロ依存: 外部ライブラリ不要で即座に利用可能
メリット・デメリット
メリット
- Android標準APIのため設定不要で即座に利用可能
- LogCatとの完全統合により優れた開発体験を提供
- TAGベースのフィルタリングによる効率的なデバッグ作業
- ProGuardによる本番ビルドでの自動最適化
- システムクラッシュ時でもログが保持される信頼性
- Kotlin、Java両言語での統一的な使用感
デメリット
- 本番環境での高度なログ管理機能が限定的
- 構造化ログや非同期処理への対応が基本的レベル
- ログローテーションやファイル出力などの企業レベル機能なし
- 大量ログ出力時のパフォーマンス制約
- セキュリティ面でのログ制御機能が基本的
- 複雑なログ解析やメトリクス収集には別途ツールが必要
参考ページ
書き方の例
インストールと基本セットアップ
// Android標準APIのため追加インストール不要
// import文で使用可能
import android.util.Log
// Kotlin companion objectでTAG定義(推奨パターン)
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// LogCatでのフィルタリング確認
Log.d(TAG, "Activity created successfully")
}
}
// Java版のTAG定義
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");
}
}
基本的なログレベル使用(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: 最も詳細な情報(開発時のみ)
Log.v(TAG, "Starting authentication process for user: $username")
// Debug: デバッグ情報(開発・テスト環境)
Log.d(TAG, "Validating credentials for username: $username")
// Info: 一般的な情報(通常の処理フロー)
Log.i(TAG, "User authentication attempt: $username")
if (username.isEmpty() || password.isEmpty()) {
// Warning: 警告レベルの問題
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: エラーレベル(例外発生時)
Log.e(TAG, "Authentication error for user: $username", e)
false
}
}
private fun performAuthentication(username: String, password: String): Boolean {
// 認証ロジックの実装
Log.d(TAG, "Performing network authentication request")
return username == "admin" && password == "password"
}
}
// Java版のログレベル使用例
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;
}
}
}
構造化ログとデータ形式(JSON/キーバリュー形式)
import android.util.Log
import org.json.JSONObject
class AnalyticsLogger {
companion object {
private const val TAG = "Analytics"
}
// 構造化ログ - JSON形式
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")
}
// 構造化ログ - キーバリュー形式
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")
}
// エラー詳細ログ with スタックトレース
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)
// 追加デバッグ情報
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
}
// 使用例
class ShoppingCartActivity : AppCompatActivity() {
private val analytics = AnalyticsLogger()
private fun addItemToCart(productId: String, quantity: Int) {
val startTime = System.currentTimeMillis()
try {
// 商品追加処理
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()
)
)
}
}
}
パフォーマンス測定とベンチマーク
import android.util.Log
class PerformanceLogger {
companion object {
private const val TAG = "Performance"
}
// メソッド実行時間測定
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
}
}
// メモリ使用量監視
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}%")
}
}
// ネットワーク処理時間測定
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)
}
// パフォーマンス警告
if (responseTime > 5000) {
Log.w(TAG, "Slow network request detected: ${responseTime}ms for $url")
}
}
}
// 使用例
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()
}
}
}
デバッグツールとLogCat統合
import android.util.Log
object DebugLogger {
private const val TAG = "DebugLogger"
private var isDebugMode = BuildConfig.DEBUG
// デバッグモード制御
fun setDebugMode(enabled: Boolean) {
isDebugMode = enabled
Log.i(TAG, "Debug mode ${if (enabled) "enabled" else "disabled"}")
}
// 条件付きログ(デバッグモードでのみ出力)
fun d(tag: String, message: String) {
if (isDebugMode) {
Log.d(tag, message)
}
}
fun v(tag: String, message: String) {
if (isDebugMode) {
Log.v(tag, message)
}
}
// クラス情報付きログ
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)
}
}
// スタックトレース分析
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})")
}
}
}
}
// LogCat フィルター用のカスタムタグ生成
fun createTag(className: String, feature: String): String {
return "${className}_$feature"
}
}
// 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 フィルターでの検索例:
// tag:MainActivity_UI - UI関連ログのみ表示
// tag:MainActivity_Network - ネットワーク関連ログのみ表示
// tag:MainActivity - 全体のログ表示
}
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")
// ネットワーク処理
}
}
// ProGuard設定例(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(...);
}
*/
エラーハンドリングとクラッシュレポート
import android.util.Log
import java.io.PrintWriter
import java.io.StringWriter
class CrashReportLogger {
companion object {
private const val TAG = "CrashReport"
}
// 未捕捉例外ハンドラー設定
fun setupGlobalExceptionHandler() {
val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, exception ->
logCrashReport(thread, exception)
defaultHandler?.uncaughtException(thread, exception)
}
Log.i(TAG, "Global exception handler configured")
}
// 詳細なクラッシュレポート
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)
// デバイス情報
logDeviceInfo()
// アプリ状態情報
logAppState()
Log.e(TAG, "=== END CRASH REPORT ===")
}
// Try-catch での例外処理
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)
}
}
// 詳細例外情報ログ
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}")
// カスタムスタックトレース出力
val stringWriter = StringWriter()
exception.printStackTrace(PrintWriter(stringWriter))
Log.e(TAG, "Full stack trace:\n$stringWriter")
// 関連例外も出力
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}")
}
}
// 使用例
class NetworkManager {
private val crashLogger = CrashReportLogger()
fun fetchData(url: String): String? {
return crashLogger.safeExecute(
operation = "fetchData from $url",
onError = { exception ->
// カスタムエラー処理
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
}
}
) {
// ネットワーク処理
performNetworkCall(url)
}
}
private fun performNetworkCall(url: String): String {
// 実際のネットワーク処理
if (url.isEmpty()) throw IllegalArgumentException("URL cannot be empty")
return "response_data"
}
}
// Application クラスでの初期化
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
CrashReportLogger().setupGlobalExceptionHandler()
}
}
}
本番環境最適化とセキュリティ
import android.util.Log
// 本番環境対応ログマネージャー
object ProductionLogger {
private const val TAG = "ProductionLogger"
// ログレベル制御
private val isReleaseMode = !BuildConfig.DEBUG
private val allowedLogLevels = if (isReleaseMode) {
setOf(Log.ERROR, Log.WARN) // 本番では Error と Warning のみ
} else {
setOf(Log.VERBOSE, Log.DEBUG, Log.INFO, Log.WARN, Log.ERROR) // 開発では全レベル
}
// セキュアログ(機密情報除去)
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)
}
}
// 機密情報の除去・マスキング
private fun sanitizeMessage(message: String): String {
var sanitized = message
// パスワード除去
sanitized = sanitized.replace(Regex("password[=:\"']\\s*\\S+", RegexOption.IGNORE_CASE), "password=***")
// メールアドレスの部分マスキング
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"
}
// 電話番号マスキング
sanitized = sanitized.replace(Regex("\\b\\d{3}-?\\d{4}-?\\d{4}\\b"), "***-****-****")
// APIキー・トークン除去
sanitized = sanitized.replace(Regex("(api[_-]?key|token|secret)[=:\"']\\s*\\S+", RegexOption.IGNORE_CASE), "$1=***")
return sanitized
}
// 使用量制限付きログ(スパム防止)
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() }
// 1分経過でカウンターリセット
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++
}
// maxLogsPerMinute を超えた場合は無視
}
// 本番環境でのエラー専用ログ
fun logProductionError(tag: String, error: Throwable, context: Map<String, String> = emptyMap()) {
if (!isReleaseMode) {
// 開発環境では詳細ログ
Log.e(tag, "Error: ${error.message}", error)
context.forEach { (key, value) ->
Log.e(tag, "Context $key: $value")
}
} else {
// 本番環境では最小限の情報のみ
val sanitizedContext = context.mapValues { (_, value) -> sanitizeMessage(value) }
val errorInfo = "ErrorType: ${error.javaClass.simpleName}, Context: $sanitizedContext"
Log.e(tag, sanitizeMessage(errorInfo))
}
}
}
// 使用例
class UserAuthenticator {
companion object {
private const val TAG = "UserAuth"
}
fun login(email: String, password: String): Boolean {
// 開発環境では詳細ログ、本番環境では機密情報除去
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 {
// スパム防止でスロットリング
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 {
// 認証ロジック
return email.isNotEmpty() && password.isNotEmpty()
}
}
// ProGuard設定による本番最適化
// app/proguard-rules.pro:
/*
# 本番ビルドでデバッグログを完全除去
-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の開発専用メソッド除去
-assumenosideeffects class com.yourpackage.ProductionLogger {
# 本番では Info レベル以下のログを除去
public static void secureLog(java.lang.String, int, java.lang.String)
return false;
# throttledLog の開発専用部分除去
public static void throttledLog(...)
return false;
}
# BuildConfig.DEBUG の最適化
-assumenosideeffects class com.yourpackage.BuildConfig {
public static final boolean DEBUG return false;
}
*/