From c6b359c3718a20607384df4010859e9972ea08bb Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Feb 2026 07:23:35 +0000 Subject: [PATCH 1/4] refactor: make KetchLogger a class with tag constructor parameter Convert KetchLogger from a singleton object to a normal class that takes a tag parameter in its constructor. Each component now creates its own KetchLogger instance, eliminating the redundant tag parameter from every logging call. Before: KetchLogger.d("Coordinator") { "message" } After: log.d { "message" } // where log = KetchLogger("Coordinator") The global Logger backend remains in the companion object, set once by the Ketch instance and shared across all KetchLogger instances. https://claude.ai/code/session_01NxdYLTBc1qtKTGDL823wNz --- .../linroid/ketch/app/android/KetchService.kt | 12 +++--- .../kotlin/com/linroid/ketch/core/Ketch.kt | 40 ++++++++++--------- .../ketch/core/engine/DownloadCoordinator.kt | 39 +++++++++--------- .../ketch/core/engine/DownloadScheduler.kt | 29 +++++++------- .../ketch/core/engine/HttpDownloadSource.kt | 33 +++++++-------- .../ketch/core/engine/RangeSupportDetector.kt | 6 ++- .../ketch/core/engine/ScheduleManager.kt | 19 ++++----- .../ketch/core/engine/SourceResolver.kt | 10 +++-- .../linroid/ketch/core/engine/TokenBucket.kt | 5 ++- .../com/linroid/ketch/core/log/KetchLogger.kt | 40 ++++++++++++------- .../ketch/core/segment/SegmentDownloader.kt | 7 ++-- .../linroid/ketch/engine/KtorHttpEngine.kt | 19 ++++----- .../com/linroid/ketch/server/KetchServer.kt | 10 ++--- .../ketch/server/mdns/NativeMdnsRegistrar.kt | 7 +--- 14 files changed, 149 insertions(+), 127 deletions(-) diff --git a/app/android/src/main/kotlin/com/linroid/ketch/app/android/KetchService.kt b/app/android/src/main/kotlin/com/linroid/ketch/app/android/KetchService.kt index 3b5281c9..f77fc689 100644 --- a/app/android/src/main/kotlin/com/linroid/ketch/app/android/KetchService.kt +++ b/app/android/src/main/kotlin/com/linroid/ketch/app/android/KetchService.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch class KetchService : Service() { + private val log = KetchLogger("KetchService") inner class LocalBinder : Binder() { val service: KetchService get() = this@KetchService @@ -71,7 +72,7 @@ class KetchService : Service() { downloadConfig = downloadConfig, deviceName = instanceName, localServerFactory = { port, apiToken, ketchApi -> - KetchLogger.i(TAG) { "Starting local server on port $port" } + log.i { "Starting local server on port $port" } val server = KetchServer( ketchApi, ServerConfig( @@ -82,12 +83,12 @@ class KetchService : Service() { mdnsServiceName = instanceName, ) server.start(wait = false) - KetchLogger.i(TAG) { "Local server started on port $port" } + log.i { "Local server started on port $port" } object : LocalServerHandle { override fun stop() { - KetchLogger.i(TAG) { "Stopping local server" } + log.i { "Stopping local server" } server.stop() - KetchLogger.i(TAG) { "Local server stopped" } + log.i { "Local server stopped" } } } }, @@ -160,7 +161,7 @@ class KetchService : Service() { if (shouldBeForeground) { val notification = buildNotification(activeCount, serverState) if (!isForeground) { - KetchLogger.i(TAG) { "Start notification" } + log.i { "Start notification" } } ServiceCompat.startForeground( this@KetchService, @@ -223,7 +224,6 @@ class KetchService : Service() { } companion object { - private const val TAG = "KetchService" private const val CHANNEL_ID = "ketch_service" private const val NOTIFICATION_ID = 1 private const val ACTION_REPOST_NOTIFICATION = "com.linroid.ketch.app.android.action.REPOST_NOTIFICATION" diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/Ketch.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/Ketch.kt index 29635b46..7a8627ca 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/Ketch.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/Ketch.kt @@ -92,17 +92,19 @@ class Ketch( override val backendLabel: String = "Core" + private val log = KetchLogger("Ketch") + init { KetchLogger.setLogger(logger) - KetchLogger.i("Ketch") { "Ketch v${KetchApi.VERSION} initialized" } + log.i { "Ketch v${KetchApi.VERSION} initialized" } if (!config.speedLimit.isUnlimited) { - KetchLogger.i("Ketch") { + log.i { "Global speed limit: " + "${config.speedLimit.bytesPerSecond} bytes/sec" } } if (additionalSources.isNotEmpty()) { - KetchLogger.i("Ketch") { + log.i { "Additional sources: " + additionalSources.joinToString { it.type } } @@ -149,7 +151,7 @@ class Ketch( val isScheduled = request.schedule !is DownloadSchedule.Immediate || request.conditions.isNotEmpty() - KetchLogger.i("Ketch") { + log.i { "Downloading: taskId=$taskId, url=${request.url}, " + "connections=${request.connections}, " + "priority=${request.priority}" + @@ -180,7 +182,7 @@ class Ketch( if (stateFlow.value.isActive) { coordinator.pause(taskId) } else { - KetchLogger.d("Ketch") { + log.d { "Ignoring pause for taskId=$taskId " + "in state ${stateFlow.value}" } @@ -200,7 +202,7 @@ class Ketch( ) } } else { - KetchLogger.d("Ketch") { + log.d { "Ignoring resume for taskId=$taskId in state $state" } } @@ -215,7 +217,7 @@ class Ketch( stateFlow.value = DownloadState.Canceled } } else { - KetchLogger.d("Ketch") { + log.d { "Ignoring cancel for taskId=$taskId in state $s" } } @@ -233,13 +235,13 @@ class Ketch( rescheduleAction = { schedule, conditions -> val s = stateFlow.value if (s.isTerminal) { - KetchLogger.d("Ketch") { + log.d { "Ignoring reschedule for taskId=$taskId in " + "terminal state $s" } return@DownloadTaskImpl } - KetchLogger.i("Ketch") { + log.i { "Rescheduling taskId=$taskId, schedule=$schedule, " + "conditions=${conditions.size}" } @@ -264,7 +266,7 @@ class Ketch( url: String, headers: Map, ): ResolvedSource { - KetchLogger.i("Ketch") { "Resolving URL: $url" } + log.i { "Resolving URL: $url" } val source = sourceResolver.resolve(url) return source.resolve(url, headers) } @@ -300,11 +302,11 @@ class Ketch( * - `CANCELED` -> [DownloadState.Canceled] */ suspend fun loadTasks() { - KetchLogger.i("Ketch") { + log.i { "Loading tasks from persistent storage" } val records = taskStore.loadAll() - KetchLogger.i("Ketch") { "Found ${records.size} task(s)" } + log.i { "Found ${records.size} task(s)" } tasksMutex.withLock { val currentTasks = _tasks.value @@ -342,7 +344,7 @@ class Ketch( if (stateFlow.value.isActive) { coordinator.pause(record.taskId) } else { - KetchLogger.d("Ketch") { + log.d { "Ignoring pause for taskId=${record.taskId} " + "in state ${stateFlow.value}" } @@ -362,7 +364,7 @@ class Ketch( ) } } else { - KetchLogger.d("Ketch") { + log.d { "Ignoring resume for taskId=${record.taskId} " + "in state $state" } @@ -378,7 +380,7 @@ class Ketch( stateFlow.value = DownloadState.Canceled } } else { - KetchLogger.d("Ketch") { + log.d { "Ignoring cancel for taskId=${record.taskId} " + "in state $s" } @@ -397,13 +399,13 @@ class Ketch( rescheduleAction = { schedule, conditions -> val s = stateFlow.value if (s.isTerminal) { - KetchLogger.d("Ketch") { + log.d { "Ignoring reschedule for taskId=${record.taskId} in " + "terminal state $s" } return@DownloadTaskImpl } - KetchLogger.i("Ketch") { + log.i { "Rescheduling taskId=${record.taskId}, " + "schedule=$schedule, " + "conditions=${conditions.size}" @@ -501,11 +503,11 @@ class Ketch( // Apply queue config scheduler.queueConfig = config.queueConfig - KetchLogger.i("Ketch") { "Config updated: $config" } + log.i { "Config updated: $config" } } override fun close() { - KetchLogger.i("Ketch") { "Closing Ketch" } + log.i { "Closing Ketch" } httpEngine.close() scope.cancel() } diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadCoordinator.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadCoordinator.kt index 69002a93..df05dafb 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadCoordinator.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadCoordinator.kt @@ -42,6 +42,7 @@ internal class DownloadCoordinator( private val fileNameResolver: FileNameResolver, private val globalLimiter: SpeedLimiter = SpeedLimiter.Unlimited, ) { + private val log = KetchLogger("Coordinator") private val mutex = Mutex() private val activeDownloads = mutableMapOf() @@ -76,7 +77,7 @@ internal class DownloadCoordinator( mutex.withLock { if (activeDownloads.containsKey(taskId)) { - KetchLogger.d("Coordinator") { + log.d { "Download already active for taskId=$taskId, skipping start" } return @@ -129,7 +130,7 @@ internal class DownloadCoordinator( mutex.withLock { if (activeDownloads.containsKey(record.taskId)) { - KetchLogger.d("Coordinator") { + log.d { "Download already active for taskId=${record.taskId}, " + "skipping startFromRecord" } @@ -183,7 +184,7 @@ internal class DownloadCoordinator( val resolvedUrl: ResolvedSource if (resolved != null) { - KetchLogger.d("Coordinator") { + log.d { "Using pre-resolved info for ${request.url} " + "(source=${resolved.sourceType})" } @@ -191,7 +192,7 @@ internal class DownloadCoordinator( resolvedUrl = resolved } else { source = sourceResolver.resolve(request.url) - KetchLogger.d("Coordinator") { + log.d { "Resolved source '${source.type}' for ${request.url}" } resolvedUrl = source.resolve(request.url, request.headers) @@ -210,7 +211,7 @@ internal class DownloadCoordinator( serverFileName = fileName, deduplicate = true, ) - KetchLogger.d("Coordinator") { + log.d { "Resolved outputPath=$outputPath" } @@ -278,7 +279,7 @@ internal class DownloadCoordinator( ) } - KetchLogger.i("Coordinator") { + log.i { "Download completed successfully for taskId=$taskId" } stateFlow.value = @@ -296,7 +297,7 @@ internal class DownloadCoordinator( suspend fun pause(taskId: String) { mutex.withLock { val active = activeDownloads[taskId] ?: return - KetchLogger.i("Coordinator") { + log.i { "Pausing download for taskId=$taskId" } @@ -319,7 +320,7 @@ internal class DownloadCoordinator( active.job.cancel() currentSegments?.let { segments -> - KetchLogger.d("Coordinator") { + log.d { "Saving pause state for taskId=$taskId" } val downloadedBytes = @@ -338,7 +339,7 @@ internal class DownloadCoordinator( try { accessor.flush() } catch (e: Exception) { - KetchLogger.w("Coordinator", e) { + log.w(e) { "Failed to flush file during pause for taskId=$taskId" } } @@ -357,7 +358,7 @@ internal class DownloadCoordinator( ): Boolean { mutex.withLock { if (activeDownloads.containsKey(taskId)) { - KetchLogger.d("Coordinator") { + log.d { "Download already active for taskId=$taskId, " + "skipping resume" } @@ -425,7 +426,7 @@ internal class DownloadCoordinator( ) { val sourceType = taskRecord.sourceType ?: HttpDownloadSource.TYPE val source = sourceResolver.resolveByType(sourceType) - KetchLogger.i("Coordinator") { + log.i { "Resuming download for taskId=$taskId via " + "source '${source.type}'" } @@ -520,7 +521,7 @@ internal class DownloadCoordinator( } if (!error.isRetryable || retryCount >= config.retryCount) { - KetchLogger.e("Coordinator", error) { + log.e(error) { "Download failed after $retryCount retries: " + "${error.message}" } @@ -538,14 +539,14 @@ internal class DownloadCoordinator( ) delayMs = error.retryAfterSeconds?.let { it * 1000L } ?: (config.retryDelayMs * (1 shl (retryCount - 1))) - KetchLogger.w("Coordinator") { + log.w { "Rate limited (429). Retry attempt $retryCount " + "after ${delayMs}ms delay, connections=" + "${context.maxConnections.value}" } } else { delayMs = config.retryDelayMs * (1 shl (retryCount - 1)) - KetchLogger.w("Coordinator") { + log.w { "Retry attempt $retryCount after ${delayMs}ms " + "delay: ${error.message}" } @@ -584,7 +585,7 @@ internal class DownloadCoordinator( (current / 2).coerceAtLeast(1) } context.maxConnections.value = reduced - KetchLogger.w("Coordinator") { + log.w { "Reducing connections for taskId=$taskId: " + "$current -> $reduced" + (rateLimitRemaining?.let { @@ -594,7 +595,7 @@ internal class DownloadCoordinator( } suspend fun cancel(taskId: String) { - KetchLogger.i("Coordinator") { + log.i { "Canceling download for taskId=$taskId" } mutex.withLock { @@ -638,7 +639,7 @@ internal class DownloadCoordinator( ) { val existing = taskStore.load(taskId) if (existing == null) { - KetchLogger.w("Coordinator") { + log.w { "TaskRecord not found for taskId=$taskId, skipping update" } return @@ -659,7 +660,7 @@ internal class DownloadCoordinator( activeDownloads[taskId]?.context ?.maxConnections?.value = connections } - KetchLogger.i("Coordinator") { + log.i { "Task connections updated for taskId=$taskId: $connections" } } @@ -675,7 +676,7 @@ internal class DownloadCoordinator( } else { active.taskLimiter.delegate = TokenBucket(limit.bytesPerSecond) } - KetchLogger.i("Coordinator") { + log.i { "Task speed limit updated for taskId=$taskId: " + "${limit.bytesPerSecond} bytes/sec" } diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadScheduler.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadScheduler.kt index 350069dc..ccf2ceed 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadScheduler.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadScheduler.kt @@ -17,6 +17,7 @@ internal class DownloadScheduler( private val coordinator: DownloadCoordinator, private val scope: CoroutineScope, ) { + private val log = KetchLogger("Scheduler") private val mutex = Mutex() private val activeEntries = mutableMapOf() private val queuedEntries = mutableListOf() @@ -58,7 +59,7 @@ internal class DownloadScheduler( activeEntries.size < queueConfig.maxConcurrentDownloads && hostCount < queueConfig.maxConnectionsPerHost ) { - KetchLogger.i("Scheduler") { + log.i { "Starting download immediately: taskId=$taskId, " + "active=${activeEntries.size}/" + "${queueConfig.maxConcurrentDownloads}" @@ -71,7 +72,7 @@ internal class DownloadScheduler( } else { insertSorted(entry) stateFlow.value = DownloadState.Queued - KetchLogger.i("Scheduler") { + log.i { "Download queued: taskId=$taskId, " + "priority=${request.priority}, " + "position=${queuedEntries.indexOfFirst { @@ -98,14 +99,14 @@ internal class DownloadScheduler( if (victim == null) { insertSorted(entry) entry.stateFlow.value = DownloadState.Queued - KetchLogger.i("Scheduler") { + log.i { "Cannot preempt: all active tasks are URGENT. " + "Queuing taskId=${entry.taskId}" } return } - KetchLogger.i("Scheduler") { + log.i { "Preempting taskId=${victim.taskId} " + "(priority=${victim.priority}) for URGENT " + "taskId=${entry.taskId}" @@ -122,7 +123,7 @@ internal class DownloadScheduler( if (activeEntries.size < queueConfig.maxConcurrentDownloads && hostCount < queueConfig.maxConnectionsPerHost ) { - KetchLogger.i("Scheduler") { + log.i { "Starting URGENT download: taskId=${entry.taskId}, " + "active=${activeEntries.size + 1}/" + "${queueConfig.maxConcurrentDownloads}" @@ -131,7 +132,7 @@ internal class DownloadScheduler( } else { insertSorted(entry) entry.stateFlow.value = DownloadState.Queued - KetchLogger.i("Scheduler") { + log.i { "URGENT taskId=${entry.taskId} queued " + "(host limit still exceeded)" } @@ -141,7 +142,7 @@ internal class DownloadScheduler( suspend fun onTaskCompleted(taskId: String) { mutex.withLock { removeActive(taskId) - KetchLogger.d("Scheduler") { + log.d { "Task completed: taskId=$taskId, " + "active=${activeEntries.size}/" + "${queueConfig.maxConcurrentDownloads}" @@ -153,7 +154,7 @@ internal class DownloadScheduler( suspend fun onTaskFailed(taskId: String) { mutex.withLock { removeActive(taskId) - KetchLogger.d("Scheduler") { + log.d { "Task failed: taskId=$taskId, " + "active=${activeEntries.size}/" + "${queueConfig.maxConcurrentDownloads}" @@ -165,7 +166,7 @@ internal class DownloadScheduler( suspend fun onTaskCanceled(taskId: String) { mutex.withLock { removeActive(taskId) - KetchLogger.d("Scheduler") { + log.d { "Task canceled: taskId=$taskId, " + "active=${activeEntries.size}/" + "${queueConfig.maxConcurrentDownloads}" @@ -178,7 +179,7 @@ internal class DownloadScheduler( mutex.withLock { val index = queuedEntries.indexOfFirst { it.taskId == taskId } if (index < 0) { - KetchLogger.d("Scheduler") { + log.d { "setPriority: taskId=$taskId not in queue " + "(may be active)" } @@ -187,7 +188,7 @@ internal class DownloadScheduler( val entry = queuedEntries.removeAt(index) entry.priority = priority insertSorted(entry) - KetchLogger.i("Scheduler") { + log.i { "Priority updated: taskId=$taskId, " + "priority=$priority, " + "newPosition=${queuedEntries.indexOfFirst { @@ -202,12 +203,12 @@ internal class DownloadScheduler( mutex.withLock { val removed = queuedEntries.removeAll { it.taskId == taskId } if (removed) { - KetchLogger.i("Scheduler") { + log.i { "Dequeued: taskId=$taskId" } } else if (activeEntries.containsKey(taskId)) { removeActive(taskId) - KetchLogger.i("Scheduler") { + log.i { "Removed active download from tracking: " + "taskId=$taskId" } @@ -269,7 +270,7 @@ internal class DownloadScheduler( val entry = findNextEligible() ?: break queuedEntries.remove(entry) val host = extractHost(entry.request.url) - KetchLogger.i("Scheduler") { + log.i { "Promoting queued task: taskId=${entry.taskId}, " + "priority=${entry.priority}, " + "active=${activeEntries.size + 1}/" + diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/HttpDownloadSource.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/HttpDownloadSource.kt index dcd9957f..d927ee24 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/HttpDownloadSource.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/HttpDownloadSource.kt @@ -37,6 +37,7 @@ internal class HttpDownloadSource( private val progressUpdateIntervalMs: Long = 200, private val segmentSaveIntervalMs: Long = 5000, ) : DownloadSource { + private val log = KetchLogger("HttpSource") override val type: String = TYPE @@ -96,19 +97,19 @@ internal class HttpDownloadSource( existing.isNotEmpty() && existing.any { it.downloadedBytes > 0 } ) { - KetchLogger.i("HttpSource") { + log.i { "Reusing segments with existing progress, " + "resegmenting to $connections connections" } SegmentCalculator.resegment(existing, connections) } else if (resolved.supportsResume && connections > 1) { - KetchLogger.i("HttpSource") { + log.i { "Server supports ranges. Using $connections " + "connections, totalBytes=$totalBytes" } SegmentCalculator.calculateSegments(totalBytes, connections) } else { - KetchLogger.i("HttpSource") { + log.i { "Single connection, totalBytes=$totalBytes" } SegmentCalculator.singleSegment(totalBytes) @@ -135,7 +136,7 @@ internal class HttpDownloadSource( ) { val state = Json.decodeFromString(resumeState.data) - KetchLogger.i("HttpSource") { + log.i { "Resuming download for taskId=${context.taskId}" } @@ -143,7 +144,7 @@ internal class HttpDownloadSource( val serverInfo = detector.detect(context.url, context.headers) if (state.etag != null && serverInfo.etag != state.etag) { - KetchLogger.w("HttpSource") { + log.w { "ETag mismatch - file has changed on server" } throw KetchError.ValidationFailed( @@ -154,7 +155,7 @@ internal class HttpDownloadSource( if (state.lastModified != null && serverInfo.lastModified != state.lastModified ) { - KetchLogger.w("HttpSource") { + log.w { "Last-Modified mismatch - file has changed on server" } throw KetchError.ValidationFailed( @@ -172,7 +173,7 @@ internal class HttpDownloadSource( ) val incompleteCount = segments.count { !it.isComplete } if (incompleteCount > 0 && connections != incompleteCount) { - KetchLogger.i("HttpSource") { + log.i { "Resegmenting for taskId=${context.taskId}: " + "$incompleteCount -> $connections connections" } @@ -202,7 +203,7 @@ internal class HttpDownloadSource( context.fileAccessor.size() } catch (e: Exception) { if (e is CancellationException) throw e - KetchLogger.w("HttpSource") { + log.w { "Cannot read file size for taskId=${context.taskId}, " + "resetting segments" } @@ -211,7 +212,7 @@ internal class HttpDownloadSource( val claimedProgress = segments.sumOf { it.downloadedBytes } if (fileSize < claimedProgress || fileSize < totalBytes) { - KetchLogger.w("HttpSource") { + log.w { "Local file integrity check failed for " + "taskId=${context.taskId}: fileSize=$fileSize, " + "claimedProgress=$claimedProgress, totalBytes=$totalBytes. " + @@ -226,7 +227,7 @@ internal class HttpDownloadSource( } return segments.map { it.copy(downloadedBytes = 0) } } - KetchLogger.d("HttpSource") { + log.d { "Local file integrity check passed for " + "taskId=${context.taskId}: fileSize=$fileSize, " + "claimedProgress=$claimedProgress" @@ -267,7 +268,7 @@ internal class HttpDownloadSource( context.segments.value, newCount ) context.segments.value = currentSegments - KetchLogger.i("HttpSource") { + log.i { "Resegmented to $newCount connections for " + "taskId=${context.taskId}" } @@ -336,7 +337,7 @@ internal class HttpDownloadSource( while (true) { delay(segmentSaveIntervalMs) context.segments.value = currentSegments() - KetchLogger.v("HttpSource") { + log.v { "Periodic segment save for taskId=${context.taskId}" } } @@ -353,7 +354,7 @@ internal class HttpDownloadSource( } context.pendingResegment = context.maxConnections.value - KetchLogger.i("HttpSource") { + log.i { "Connection change detected for " + "taskId=${context.taskId}: " + "$lastSeen -> ${context.pendingResegment}" @@ -386,7 +387,7 @@ internal class HttpDownloadSource( updatedSegments[completed.index] = completed } context.segments.value = currentSegments() - KetchLogger.d("HttpSource") { + log.d { "Segment ${completed.index} completed for " + "taskId=${context.taskId}" } @@ -450,7 +451,7 @@ internal class HttpDownloadSource( if (remaining == null) return connections if (remaining == 0L) { val delaySec = reset?.coerceAtLeast(1) ?: 1L - KetchLogger.i("HttpSource") { + log.i { "Rate limit exhausted (remaining=0), " + "delaying ${delaySec}s before download" } @@ -459,7 +460,7 @@ internal class HttpDownloadSource( } if (remaining < connections) { val capped = remaining.toInt().coerceAtLeast(1) - KetchLogger.i("HttpSource") { + log.i { "Capping connections from $connections to $capped " + "based on RateLimit-Remaining=$remaining" } diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/RangeSupportDetector.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/RangeSupportDetector.kt index f44957e2..300f84df 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/RangeSupportDetector.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/RangeSupportDetector.kt @@ -5,10 +5,12 @@ import com.linroid.ketch.core.log.KetchLogger internal class RangeSupportDetector( private val httpEngine: HttpEngine, ) { + private val log = KetchLogger("RangeDetector") + suspend fun detect(url: String, headers: Map = emptyMap()): ServerInfo { - KetchLogger.d("RangeDetector") { "Sending HEAD request to $url" } + log.d { "Sending HEAD request to $url" } val serverInfo = httpEngine.head(url, headers) - KetchLogger.i("RangeDetector") { + log.i { "Server info: contentLength=${serverInfo.contentLength}, " + "acceptRanges=${serverInfo.acceptRanges}, " + "supportsResume=${serverInfo.supportsResume}, " + diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/ScheduleManager.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/ScheduleManager.kt index 33daee97..99378c03 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/ScheduleManager.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/ScheduleManager.kt @@ -22,6 +22,7 @@ internal class ScheduleManager( private val scheduler: DownloadScheduler, private val scope: CoroutineScope, ) { + private val log = KetchLogger("ScheduleManager") private val mutex = Mutex() private val scheduledJobs = mutableMapOf() @@ -36,7 +37,7 @@ internal class ScheduleManager( val conditions = request.conditions stateFlow.value = DownloadState.Scheduled(schedule) - KetchLogger.i("ScheduleManager") { + log.i { "Scheduling download: taskId=$taskId, schedule=$schedule, " + "conditions=${conditions.size}" } @@ -46,7 +47,7 @@ internal class ScheduleManager( waitForSchedule(taskId, schedule) waitForConditions(taskId, conditions) - KetchLogger.i("ScheduleManager") { + log.i { "Schedule and conditions met for taskId=$taskId, enqueuing" } scheduler.enqueue( @@ -73,7 +74,7 @@ internal class ScheduleManager( } stateFlow.value = DownloadState.Scheduled(schedule) - KetchLogger.i("ScheduleManager") { + log.i { "Rescheduling download: taskId=$taskId, schedule=$schedule, " + "conditions=${conditions.size}" } @@ -83,7 +84,7 @@ internal class ScheduleManager( waitForSchedule(taskId, schedule) waitForConditions(taskId, conditions) - KetchLogger.i("ScheduleManager") { + log.i { "Reschedule conditions met for taskId=$taskId, enqueuing " + "with preferResume=true" } @@ -102,7 +103,7 @@ internal class ScheduleManager( mutex.withLock { scheduledJobs.remove(taskId)?.let { job -> job.cancel() - KetchLogger.d("ScheduleManager") { + log.d { "Canceled scheduled task: taskId=$taskId" } } @@ -119,7 +120,7 @@ internal class ScheduleManager( val now = Clock.System.now() val waitDuration = schedule.startAt - now if (waitDuration.isPositive()) { - KetchLogger.d("ScheduleManager") { + log.d { "Waiting ${waitDuration.inWholeSeconds}s for " + "taskId=$taskId (startAt=${schedule.startAt})" } @@ -127,7 +128,7 @@ internal class ScheduleManager( } } is DownloadSchedule.AfterDelay -> { - KetchLogger.d("ScheduleManager") { + log.d { "Waiting ${schedule.delay.inWholeSeconds}s delay " + "for taskId=$taskId" } @@ -142,7 +143,7 @@ internal class ScheduleManager( ) { if (conditions.isEmpty()) return - KetchLogger.d("ScheduleManager") { + log.d { "Waiting for ${conditions.size} condition(s) for taskId=$taskId" } @@ -154,7 +155,7 @@ internal class ScheduleManager( combined.first { it } - KetchLogger.d("ScheduleManager") { + log.d { "All conditions met for taskId=$taskId" } } diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/SourceResolver.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/SourceResolver.kt index 03b8b54f..e56b2eb6 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/SourceResolver.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/SourceResolver.kt @@ -12,8 +12,10 @@ import com.linroid.ketch.core.log.KetchLogger * fallback. */ internal class SourceResolver(private val sources: List) { + private val log = KetchLogger("SourceResolver") + init { - KetchLogger.d("SourceResolver") { + log.d { "Initialized with ${sources.size} source(s): " + sources.joinToString { it.type } } @@ -22,12 +24,12 @@ internal class SourceResolver(private val sources: List) { fun resolve(url: String): DownloadSource { val source = sources.firstOrNull { it.canHandle(url) } if (source != null) { - KetchLogger.d("SourceResolver") { + log.d { "Resolved source '${source.type}' for URL: $url" } return source } - KetchLogger.e("SourceResolver") { + log.e { "No source found for URL: $url" } throw KetchError.Unsupported @@ -36,7 +38,7 @@ internal class SourceResolver(private val sources: List) { fun resolveByType(type: String): DownloadSource { val source = sources.firstOrNull { it.type == type } if (source != null) return source - KetchLogger.e("SourceResolver") { + log.e { "No source found for type: $type" } throw KetchError.Unsupported diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/TokenBucket.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/TokenBucket.kt index 17c9bab6..222e28dd 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/TokenBucket.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/TokenBucket.kt @@ -11,6 +11,7 @@ internal class TokenBucket( bytesPerSecond: Long, private val burstSize: Long = 65536, ) : SpeedLimiter { + private val log = KetchLogger("TokenBucket") private val mutex = Mutex() private var tokens: Long = burstSize private var lastRefillTime: Instant = Clock.System.now() @@ -33,7 +34,7 @@ internal class TokenBucket( } } if (waitMs > 0) { - KetchLogger.v("TokenBucket") { + log.v { "Throttling: waiting ${waitMs}ms for $remaining bytes" } delay(waitMs) @@ -43,7 +44,7 @@ internal class TokenBucket( fun updateRate(newBytesPerSecond: Long) { rate = newBytesPerSecond.toDouble() - KetchLogger.d("TokenBucket") { + log.d { "Rate updated to $newBytesPerSecond bytes/sec" } } diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt index d8591efb..117406ec 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt @@ -1,36 +1,48 @@ package com.linroid.ketch.core.log /** - * Logger holder for Ketch components. - * This is set by the Ketch instance and used by all Ketch modules. + * Tagged logger for Ketch components. * + * Each component creates its own instance with a descriptive [tag]. + * The global [Logger] backend is set once by the [Ketch][com.linroid.ketch.core.Ketch] + * instance and shared across all [KetchLogger] instances. + * + * ```kotlin + * private val logger = KetchLogger("Coordinator") + * logger.d { "state changed" } // emits "[Coordinator] state changed" + * ``` + * + * @param tag component name prepended to every log message * @suppress This is internal API and should not be used directly by library users. */ -object KetchLogger { - var logger: Logger = Logger.None - private set - - fun setLogger(logger: Logger) { - this.logger = logger - } +class KetchLogger(private val tag: String) { - fun v(tag: String, message: () -> String) { + fun v(message: () -> String) { logger.v { "[$tag] ${message()}" } } - fun d(tag: String, message: () -> String) { + fun d(message: () -> String) { logger.d { "[$tag] ${message()}" } } - fun i(tag: String, message: () -> String) { + fun i(message: () -> String) { logger.i { "[$tag] ${message()}" } } - fun w(tag: String, throwable: Throwable? = null, message: () -> String) { + fun w(throwable: Throwable? = null, message: () -> String) { logger.w(message = { "[$tag] ${message()}" }, throwable = throwable) } - fun e(tag: String, throwable: Throwable? = null, message: () -> String) { + fun e(throwable: Throwable? = null, message: () -> String) { logger.e(message = { "[$tag] ${message()}" }, throwable = throwable) } + + companion object { + var logger: Logger = Logger.None + private set + + fun setLogger(logger: Logger) { + this.logger = logger + } + } } diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/segment/SegmentDownloader.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/segment/SegmentDownloader.kt index b7710652..9155324a 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/segment/SegmentDownloader.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/segment/SegmentDownloader.kt @@ -16,6 +16,7 @@ internal class SegmentDownloader( private val taskLimiter: SpeedLimiter = SpeedLimiter.Unlimited, private val globalLimiter: SpeedLimiter = SpeedLimiter.Unlimited, ) { + private val log = KetchLogger("SegmentDownloader") suspend fun download( url: String, segment: Segment, @@ -27,7 +28,7 @@ internal class SegmentDownloader( } val remainingBytes = segment.totalBytes - segment.downloadedBytes - KetchLogger.d("SegmentDownloader") { + log.d { "Starting segment ${segment.index}: range ${segment.start}..${segment.end} ($remainingBytes bytes remaining)" } @@ -53,7 +54,7 @@ internal class SegmentDownloader( } if (downloadedBytes < segment.totalBytes) { - KetchLogger.w("SegmentDownloader") { + log.w { "Incomplete segment ${segment.index}: " + "downloaded $downloadedBytes/${segment.totalBytes} bytes" } @@ -65,7 +66,7 @@ internal class SegmentDownloader( ) } - KetchLogger.d("SegmentDownloader") { + log.d { "Completed segment ${segment.index}: " + "downloaded ${downloadedBytes - initialBytes} bytes" } diff --git a/library/ktor/src/commonMain/kotlin/com/linroid/ketch/engine/KtorHttpEngine.kt b/library/ktor/src/commonMain/kotlin/com/linroid/ketch/engine/KtorHttpEngine.kt index df5f3014..14f8ab07 100644 --- a/library/ktor/src/commonMain/kotlin/com/linroid/ketch/engine/KtorHttpEngine.kt +++ b/library/ktor/src/commonMain/kotlin/com/linroid/ketch/engine/KtorHttpEngine.kt @@ -29,16 +29,17 @@ import kotlin.coroutines.cancellation.CancellationException class KtorHttpEngine( private val client: HttpClient = defaultClient(), ) : HttpEngine { + private val log = KetchLogger("KtorHttpEngine") override suspend fun head(url: String, headers: Map): ServerInfo { try { - KetchLogger.d("KtorHttpEngine") { "HEAD request: $url" } + log.d { "HEAD request: $url" } val customHeaders = headers val response = client.head(url) { customHeaders.forEach { (name, value) -> header(name, value) } } - KetchLogger.d("KtorHttpEngine") { + log.d { "HEAD ${response.status.value} headers: " + response.headers.entries().joinToString { (k, v) -> "$k=${v.joinToString(",")}" @@ -46,7 +47,7 @@ class KtorHttpEngine( } if (!response.status.isSuccess()) { - KetchLogger.e("KtorHttpEngine") { + log.e { "HTTP error ${response.status.value}: ${response.status.description}" } val is429 = response.status.value == 429 @@ -90,7 +91,7 @@ class KtorHttpEngine( } catch (e: KetchError) { throw e } catch (e: Exception) { - KetchLogger.e("KtorHttpEngine") { "Network error: ${e.message}" } + log.e { "Network error: ${e.message}" } throw KetchError.Network(e) } } @@ -103,9 +104,9 @@ class KtorHttpEngine( ) { try { if (range != null) { - KetchLogger.d("KtorHttpEngine") { "GET request: $url, range=${range.first}-${range.last}" } + log.d { "GET request: $url, range=${range.first}-${range.last}" } } else { - KetchLogger.d("KtorHttpEngine") { "GET request: $url (no range)" } + log.d { "GET request: $url (no range)" } } val customHeaders = headers client.prepareGet(url) { @@ -116,7 +117,7 @@ class KtorHttpEngine( }.execute { response -> val status = response.status - KetchLogger.d("KtorHttpEngine") { + log.d { "GET ${status.value} headers: " + response.headers.entries().joinToString { (k, v) -> "$k=${v.joinToString(",")}" @@ -124,7 +125,7 @@ class KtorHttpEngine( } if (!status.isSuccess()) { - KetchLogger.e("KtorHttpEngine") { + log.e { "HTTP error ${status.value}: ${status.description}" } val is429 = status.value == 429 @@ -156,7 +157,7 @@ class KtorHttpEngine( } catch (e: KetchError) { throw e } catch (e: Exception) { - KetchLogger.e("KtorHttpEngine") { "Network error: ${e.message}" } + log.e { "Network error: ${e.message}" } throw KetchError.Network(e) } } diff --git a/library/server/src/main/kotlin/com/linroid/ketch/server/KetchServer.kt b/library/server/src/main/kotlin/com/linroid/ketch/server/KetchServer.kt index 743ce88a..2559e831 100644 --- a/library/server/src/main/kotlin/com/linroid/ketch/server/KetchServer.kt +++ b/library/server/src/main/kotlin/com/linroid/ketch/server/KetchServer.kt @@ -90,6 +90,7 @@ class KetchServer( private val mdnsServiceName: String = "Ketch", private val mdnsRegistrar: MdnsRegistrar = defaultMdnsRegistrar(), ) { + private val log = KetchLogger("KetchServer") private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private var engine: EmbeddedServer = embeddedServer( CIO, @@ -125,7 +126,7 @@ class KetchServer( val tokenValue = if (config.apiToken.isNullOrBlank()) "none" else "required" - KetchLogger.d(TAG) { + log.d { "Registering mDNS service:" + " name=$mdnsServiceName," + " type=${ServerConfig.MDNS_SERVICE_TYPE}," + @@ -139,7 +140,7 @@ class KetchServer( port = config.port, metadata = mapOf("token" to tokenValue), ) - KetchLogger.i(TAG) { + log.i { "mDNS registered: $mdnsServiceName" + " (${ServerConfig.MDNS_SERVICE_TYPE})" } @@ -147,14 +148,14 @@ class KetchServer( } catch (e: CancellationException) { throw e } catch (e: Exception) { - KetchLogger.w(TAG, throwable = e) { + log.w(throwable = e) { "mDNS registration failed: ${e.message}" } } finally { runCatching { mdnsRegistrar.unregister() }.onFailure { e -> - KetchLogger.w(TAG, throwable = e) { + log.w(throwable = e) { "mDNS unregister failed: ${e.message}" } } @@ -242,7 +243,6 @@ class KetchServer( } companion object { - private const val TAG = "KetchServer" private const val AUTH_API = "api-bearer" } } diff --git a/library/server/src/main/kotlin/com/linroid/ketch/server/mdns/NativeMdnsRegistrar.kt b/library/server/src/main/kotlin/com/linroid/ketch/server/mdns/NativeMdnsRegistrar.kt index e24c0f55..11a4e3b3 100644 --- a/library/server/src/main/kotlin/com/linroid/ketch/server/mdns/NativeMdnsRegistrar.kt +++ b/library/server/src/main/kotlin/com/linroid/ketch/server/mdns/NativeMdnsRegistrar.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.withContext * advertised and is destroyed on [unregister]. */ internal class NativeMdnsRegistrar : MdnsRegistrar { + private val log = KetchLogger("NativeMdnsRegistrar") @Volatile private var process: Process? = null override suspend fun register( @@ -28,7 +29,7 @@ internal class NativeMdnsRegistrar : MdnsRegistrar { serviceName, serviceType, ".", port.toString(), ) metadata.forEach { (key, value) -> cmd.add("$key=$value") } - KetchLogger.d(TAG) { "Exec: ${cmd.joinToString(" ")}" } + log.d { "Exec: ${cmd.joinToString(" ")}" } process = withContext(Dispatchers.IO) { ProcessBuilder(cmd) .redirectErrorStream(true) @@ -42,8 +43,4 @@ internal class NativeMdnsRegistrar : MdnsRegistrar { } process = null } - - companion object { - private const val TAG = "NativeMdnsRegistrar" - } } From 1d34d5c8f8af32fbe35087895d7740982b611e1d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Feb 2026 07:31:28 +0000 Subject: [PATCH 2/4] style: collapse short log calls to single line https://claude.ai/code/session_01NxdYLTBc1qtKTGDL823wNz --- .../kotlin/com/linroid/ketch/core/Ketch.kt | 12 ++----- .../ketch/core/engine/DownloadCoordinator.kt | 36 +++++-------------- .../ketch/core/engine/DownloadScheduler.kt | 4 +-- .../ketch/core/engine/HttpDownloadSource.kt | 20 +++-------- .../ketch/core/engine/ScheduleManager.kt | 16 +++------ .../ketch/core/engine/SourceResolver.kt | 12 ++----- .../linroid/ketch/core/engine/TokenBucket.kt | 8 ++--- .../linroid/ketch/engine/KtorHttpEngine.kt | 8 ++--- 8 files changed, 29 insertions(+), 87 deletions(-) diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/Ketch.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/Ketch.kt index 7a8627ca..da0ef865 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/Ketch.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/Ketch.kt @@ -202,9 +202,7 @@ class Ketch( ) } } else { - log.d { - "Ignoring resume for taskId=$taskId in state $state" - } + log.d { "Ignoring resume for taskId=$taskId in state $state" } } }, cancelAction = { @@ -217,9 +215,7 @@ class Ketch( stateFlow.value = DownloadState.Canceled } } else { - log.d { - "Ignoring cancel for taskId=$taskId in state $s" - } + log.d { "Ignoring cancel for taskId=$taskId in state $s" } } }, removeAction = { removeTaskInternal(taskId) }, @@ -302,9 +298,7 @@ class Ketch( * - `CANCELED` -> [DownloadState.Canceled] */ suspend fun loadTasks() { - log.i { - "Loading tasks from persistent storage" - } + log.i { "Loading tasks from persistent storage" } val records = taskStore.loadAll() log.i { "Found ${records.size} task(s)" } diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadCoordinator.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadCoordinator.kt index df05dafb..4b5f3532 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadCoordinator.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadCoordinator.kt @@ -77,9 +77,7 @@ internal class DownloadCoordinator( mutex.withLock { if (activeDownloads.containsKey(taskId)) { - log.d { - "Download already active for taskId=$taskId, skipping start" - } + log.d { "Download already active for taskId=$taskId, skipping start" } return } @@ -192,9 +190,7 @@ internal class DownloadCoordinator( resolvedUrl = resolved } else { source = sourceResolver.resolve(request.url) - log.d { - "Resolved source '${source.type}' for ${request.url}" - } + log.d { "Resolved source '${source.type}' for ${request.url}" } resolvedUrl = source.resolve(request.url, request.headers) } @@ -211,9 +207,7 @@ internal class DownloadCoordinator( serverFileName = fileName, deduplicate = true, ) - log.d { - "Resolved outputPath=$outputPath" - } + log.d { "Resolved outputPath=$outputPath" } val now = Clock.System.now() updateTaskRecord(taskId) { @@ -279,9 +273,7 @@ internal class DownloadCoordinator( ) } - log.i { - "Download completed successfully for taskId=$taskId" - } + log.i { "Download completed successfully for taskId=$taskId" } stateFlow.value = DownloadState.Completed(outputPath) } finally { @@ -297,9 +289,7 @@ internal class DownloadCoordinator( suspend fun pause(taskId: String) { mutex.withLock { val active = activeDownloads[taskId] ?: return - log.i { - "Pausing download for taskId=$taskId" - } + log.i { "Pausing download for taskId=$taskId" } // Use segmentsFlow as the source of truth — it is // continuously updated by the download source with the @@ -320,9 +310,7 @@ internal class DownloadCoordinator( active.job.cancel() currentSegments?.let { segments -> - log.d { - "Saving pause state for taskId=$taskId" - } + log.d { "Saving pause state for taskId=$taskId" } val downloadedBytes = segments.sumOf { it.downloadedBytes } updateTaskRecord(taskId) { @@ -595,9 +583,7 @@ internal class DownloadCoordinator( } suspend fun cancel(taskId: String) { - log.i { - "Canceling download for taskId=$taskId" - } + log.i { "Canceling download for taskId=$taskId" } mutex.withLock { val active = activeDownloads[taskId] active?.job?.cancel() @@ -639,9 +625,7 @@ internal class DownloadCoordinator( ) { val existing = taskStore.load(taskId) if (existing == null) { - log.w { - "TaskRecord not found for taskId=$taskId, skipping update" - } + log.w { "TaskRecord not found for taskId=$taskId, skipping update" } return } taskStore.save(update(existing)) @@ -660,9 +644,7 @@ internal class DownloadCoordinator( activeDownloads[taskId]?.context ?.maxConnections?.value = connections } - log.i { - "Task connections updated for taskId=$taskId: $connections" - } + log.i { "Task connections updated for taskId=$taskId: $connections" } } suspend fun setTaskSpeedLimit(taskId: String, limit: SpeedLimit) { diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadScheduler.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadScheduler.kt index ccf2ceed..f9500cd8 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadScheduler.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/DownloadScheduler.kt @@ -203,9 +203,7 @@ internal class DownloadScheduler( mutex.withLock { val removed = queuedEntries.removeAll { it.taskId == taskId } if (removed) { - log.i { - "Dequeued: taskId=$taskId" - } + log.i { "Dequeued: taskId=$taskId" } } else if (activeEntries.containsKey(taskId)) { removeActive(taskId) log.i { diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/HttpDownloadSource.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/HttpDownloadSource.kt index d927ee24..26f87a0c 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/HttpDownloadSource.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/HttpDownloadSource.kt @@ -109,9 +109,7 @@ internal class HttpDownloadSource( } SegmentCalculator.calculateSegments(totalBytes, connections) } else { - log.i { - "Single connection, totalBytes=$totalBytes" - } + log.i { "Single connection, totalBytes=$totalBytes" } SegmentCalculator.singleSegment(totalBytes) } @@ -136,17 +134,13 @@ internal class HttpDownloadSource( ) { val state = Json.decodeFromString(resumeState.data) - log.i { - "Resuming download for taskId=${context.taskId}" - } + log.i { "Resuming download for taskId=${context.taskId}" } val detector = RangeSupportDetector(httpEngine) val serverInfo = detector.detect(context.url, context.headers) if (state.etag != null && serverInfo.etag != state.etag) { - log.w { - "ETag mismatch - file has changed on server" - } + log.w { "ETag mismatch - file has changed on server" } throw KetchError.ValidationFailed( "ETag mismatch - file has changed on server" ) @@ -155,9 +149,7 @@ internal class HttpDownloadSource( if (state.lastModified != null && serverInfo.lastModified != state.lastModified ) { - log.w { - "Last-Modified mismatch - file has changed on server" - } + log.w { "Last-Modified mismatch - file has changed on server" } throw KetchError.ValidationFailed( "Last-Modified mismatch - file has changed on server" ) @@ -337,9 +329,7 @@ internal class HttpDownloadSource( while (true) { delay(segmentSaveIntervalMs) context.segments.value = currentSegments() - log.v { - "Periodic segment save for taskId=${context.taskId}" - } + log.v { "Periodic segment save for taskId=${context.taskId}" } } } diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/ScheduleManager.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/ScheduleManager.kt index 99378c03..b687a622 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/ScheduleManager.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/ScheduleManager.kt @@ -47,9 +47,7 @@ internal class ScheduleManager( waitForSchedule(taskId, schedule) waitForConditions(taskId, conditions) - log.i { - "Schedule and conditions met for taskId=$taskId, enqueuing" - } + log.i { "Schedule and conditions met for taskId=$taskId, enqueuing" } scheduler.enqueue( taskId, request, createdAt, stateFlow, segmentsFlow ) @@ -103,9 +101,7 @@ internal class ScheduleManager( mutex.withLock { scheduledJobs.remove(taskId)?.let { job -> job.cancel() - log.d { - "Canceled scheduled task: taskId=$taskId" - } + log.d { "Canceled scheduled task: taskId=$taskId" } } } } @@ -143,9 +139,7 @@ internal class ScheduleManager( ) { if (conditions.isEmpty()) return - log.d { - "Waiting for ${conditions.size} condition(s) for taskId=$taskId" - } + log.d { "Waiting for ${conditions.size} condition(s) for taskId=$taskId" } val flows = conditions.map { it.isMet() } val combined = when (flows.size) { @@ -155,9 +149,7 @@ internal class ScheduleManager( combined.first { it } - log.d { - "All conditions met for taskId=$taskId" - } + log.d { "All conditions met for taskId=$taskId" } } /** Whether a task is currently waiting for its schedule/conditions. */ diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/SourceResolver.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/SourceResolver.kt index e56b2eb6..520039cc 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/SourceResolver.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/SourceResolver.kt @@ -24,23 +24,17 @@ internal class SourceResolver(private val sources: List) { fun resolve(url: String): DownloadSource { val source = sources.firstOrNull { it.canHandle(url) } if (source != null) { - log.d { - "Resolved source '${source.type}' for URL: $url" - } + log.d { "Resolved source '${source.type}' for URL: $url" } return source } - log.e { - "No source found for URL: $url" - } + log.e { "No source found for URL: $url" } throw KetchError.Unsupported } fun resolveByType(type: String): DownloadSource { val source = sources.firstOrNull { it.type == type } if (source != null) return source - log.e { - "No source found for type: $type" - } + log.e { "No source found for type: $type" } throw KetchError.Unsupported } } diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/TokenBucket.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/TokenBucket.kt index 222e28dd..7c9165c8 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/TokenBucket.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/engine/TokenBucket.kt @@ -34,9 +34,7 @@ internal class TokenBucket( } } if (waitMs > 0) { - log.v { - "Throttling: waiting ${waitMs}ms for $remaining bytes" - } + log.v { "Throttling: waiting ${waitMs}ms for $remaining bytes" } delay(waitMs) } } @@ -44,9 +42,7 @@ internal class TokenBucket( fun updateRate(newBytesPerSecond: Long) { rate = newBytesPerSecond.toDouble() - log.d { - "Rate updated to $newBytesPerSecond bytes/sec" - } + log.d { "Rate updated to $newBytesPerSecond bytes/sec" } } private fun refill() { diff --git a/library/ktor/src/commonMain/kotlin/com/linroid/ketch/engine/KtorHttpEngine.kt b/library/ktor/src/commonMain/kotlin/com/linroid/ketch/engine/KtorHttpEngine.kt index 14f8ab07..2ef27e54 100644 --- a/library/ktor/src/commonMain/kotlin/com/linroid/ketch/engine/KtorHttpEngine.kt +++ b/library/ktor/src/commonMain/kotlin/com/linroid/ketch/engine/KtorHttpEngine.kt @@ -47,9 +47,7 @@ class KtorHttpEngine( } if (!response.status.isSuccess()) { - log.e { - "HTTP error ${response.status.value}: ${response.status.description}" - } + log.e { "HTTP error ${response.status.value}: ${response.status.description}" } val is429 = response.status.value == 429 val retryAfter = if (is429) { parseRetryAfter(response.headers["Retry-After"]) @@ -125,9 +123,7 @@ class KtorHttpEngine( } if (!status.isSuccess()) { - log.e { - "HTTP error ${status.value}: ${status.description}" - } + log.e { "HTTP error ${status.value}: ${status.description}" } val is429 = status.value == 429 val retryAfter = if (is429) { parseRetryAfter(response.headers["Retry-After"]) From 39a495405348dcf0db4173c9dda9717f6735a0c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Feb 2026 07:34:32 +0000 Subject: [PATCH 3/4] perf: make KetchLogger functions inline with Logger.None fast path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All logging methods are now `inline` with a reference-equality check against Logger.None. When logging is disabled (the default): - The outer message lambda is eliminated by inlining (no allocation) - The inner tag-wrapper lambda is skipped by the fast-path check - Result: true zero-cost logging — no allocations, no virtual calls When logging is enabled, the only overhead vs before is one cheap reference comparison per call. https://claude.ai/code/session_01NxdYLTBc1qtKTGDL823wNz --- .../com/linroid/ketch/core/log/KetchLogger.kt | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt index 117406ec..0cb78bcb 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt @@ -7,6 +7,10 @@ package com.linroid.ketch.core.log * The global [Logger] backend is set once by the [Ketch][com.linroid.ketch.core.Ketch] * instance and shared across all [KetchLogger] instances. * + * All logging methods are `inline` with a fast-path check for [Logger.None], + * so when logging is disabled (the default) there is zero allocation overhead — + * neither the message lambda nor the tag-wrapper lambda is created. + * * ```kotlin * private val logger = KetchLogger("Coordinator") * logger.d { "state changed" } // emits "[Coordinator] state changed" @@ -15,30 +19,52 @@ package com.linroid.ketch.core.log * @param tag component name prepended to every log message * @suppress This is internal API and should not be used directly by library users. */ -class KetchLogger(private val tag: String) { +class KetchLogger(@PublishedApi internal val tag: String) { - fun v(message: () -> String) { - logger.v { "[$tag] ${message()}" } + inline fun v(crossinline message: () -> String) { + val l = logger + if (l !== Logger.None) { + l.v { "[$tag] ${message()}" } + } } - fun d(message: () -> String) { - logger.d { "[$tag] ${message()}" } + inline fun d(crossinline message: () -> String) { + val l = logger + if (l !== Logger.None) { + l.d { "[$tag] ${message()}" } + } } - fun i(message: () -> String) { - logger.i { "[$tag] ${message()}" } + inline fun i(crossinline message: () -> String) { + val l = logger + if (l !== Logger.None) { + l.i { "[$tag] ${message()}" } + } } - fun w(throwable: Throwable? = null, message: () -> String) { - logger.w(message = { "[$tag] ${message()}" }, throwable = throwable) + inline fun w( + throwable: Throwable? = null, + crossinline message: () -> String, + ) { + val l = logger + if (l !== Logger.None) { + l.w(message = { "[$tag] ${message()}" }, throwable = throwable) + } } - fun e(throwable: Throwable? = null, message: () -> String) { - logger.e(message = { "[$tag] ${message()}" }, throwable = throwable) + inline fun e( + throwable: Throwable? = null, + crossinline message: () -> String, + ) { + val l = logger + if (l !== Logger.None) { + l.e(message = { "[$tag] ${message()}" }, throwable = throwable) + } } companion object { - var logger: Logger = Logger.None + @PublishedApi + internal var logger: Logger = Logger.None private set fun setLogger(logger: Logger) { From b47b14892ef7a8d33e792dd315089cf88a988307 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Feb 2026 07:46:34 +0000 Subject: [PATCH 4/4] refactor: change Logger interface from lambda to String message KetchLogger's inline functions with Logger.None fast-path already handle lazy evaluation, so Logger implementations don't need lambdas. This simplifies the Logger interface and eliminates an inner lambda allocation per log call. https://claude.ai/code/session_01NxdYLTBc1qtKTGDL823wNz --- .claude/CLAUDE.md | 11 ++-- docs/logging.md | 31 +++++----- .../linroid/ketch/core/log/Logger.android.kt | 20 +++---- .../com/linroid/ketch/core/log/KetchLogger.kt | 38 ++++-------- .../com/linroid/ketch/core/log/Logger.kt | 58 ++++++++----------- .../com/linroid/ketch/core/log/Logger.ios.kt | 20 +++---- .../com/linroid/ketch/core/log/Logger.jvm.kt | 20 +++---- .../linroid/ketch/core/log/Logger.wasmJs.kt | 20 +++---- .../com/linroid/ketch/log/KermitLogger.kt | 20 +++---- 9 files changed, 107 insertions(+), 131 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index a645db4f..cf5ea9b2 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -94,7 +94,8 @@ cli/ # JVM CLI entry point ### Logging System - `Logger.None` (default, zero overhead), `Logger.console()`, `KermitLogger` - Platform-specific console: Logcat (Android), NSLog (iOS), println/stderr (JVM), println (Wasm) -- Lazy lambda evaluation for zero cost when disabled +- `KetchLogger` uses `inline` functions with `Logger.None` fast-path for zero-cost disabled logging +- `Logger` interface accepts `String` messages; lazy evaluation handled by `KetchLogger` ### Error Handling (sealed `KetchError`) - `Network` (retryable), `Http(code)` (5xx retryable), `Disk`, `Unsupported`, @@ -146,12 +147,14 @@ cli/ # JVM CLI entry point - Test edge cases: 0-byte files, 1-byte files, uneven segment splits ### Logging -- Use `KetchLogger` for all internal logging +- Use `KetchLogger` for all internal logging — instantiate per component: + `private val log = KetchLogger("Coordinator")` - Tags: "Ketch", "Coordinator", "SegmentDownloader", "RangeDetector", "KtorHttpEngine", - "Scheduler", "ScheduleManager", "SourceResolver" + "Scheduler", "ScheduleManager", "SourceResolver", "HttpSource", "TokenBucket" - Levels: verbose (segment detail), debug (state changes), info (user events), warn (retries), error (fatal) -- Use lazy lambdas: `logger.d { "expensive $computation" }` +- Use lazy lambdas: `log.d { "expensive $computation" }` +- Keep log calls on one line when the message is short enough (within 100 chars) ## Current Limitations diff --git a/docs/logging.md b/docs/logging.md index d404f702..a7526504 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -133,30 +133,25 @@ You can implement your own logger by implementing the `Logger` interface: ```kotlin class CustomLogger : Logger { - override fun v(message: () -> String) { - // Verbose logging - println("[VERBOSE] ${message()}") + override fun v(message: String) { + println("[VERBOSE] $message") } - override fun d(message: () -> String) { - // Debug logging - println("[DEBUG] ${message()}") + override fun d(message: String) { + println("[DEBUG] $message") } - override fun i(message: () -> String) { - // Info logging - println("[INFO] ${message()}") + override fun i(message: String) { + println("[INFO] $message") } - override fun w(message: () -> String, throwable: Throwable?) { - // Warning logging - println("[WARN] ${message()}") + override fun w(message: String, throwable: Throwable?) { + println("[WARN] $message") throwable?.printStackTrace() } - override fun e(message: () -> String, throwable: Throwable?) { - // Error logging - System.err.println("[ERROR] ${message()}") + override fun e(message: String, throwable: Throwable?) { + System.err.println("[ERROR] $message") throwable?.printStackTrace() } } @@ -167,7 +162,11 @@ val ketch = Ketch( ) ``` -**Note:** Tags are included in the message itself by KetchLogger. Each log message is formatted as `[ComponentTag] message`, so you don't need to handle tags separately. +**Note:** `Logger` receives pre-built `String` messages. Lazy evaluation is handled by +`KetchLogger`'s `inline` functions, which skip message construction entirely when +`Logger.None` is active (the default). Tags are included in the message by `KetchLogger` — +each message is formatted as `[ComponentTag] message`, so you don't need to handle tags +separately. ## Platform-Specific Behavior diff --git a/library/core/src/androidMain/kotlin/com/linroid/ketch/core/log/Logger.android.kt b/library/core/src/androidMain/kotlin/com/linroid/ketch/core/log/Logger.android.kt index 14b52543..c700c9f8 100644 --- a/library/core/src/androidMain/kotlin/com/linroid/ketch/core/log/Logger.android.kt +++ b/library/core/src/androidMain/kotlin/com/linroid/ketch/core/log/Logger.android.kt @@ -4,23 +4,23 @@ import android.util.Log internal actual fun consoleLogger(minLevel: LogLevel): Logger = object : Logger { - override fun v(message: () -> String) { - if (minLevel <= LogLevel.VERBOSE) Log.v("Ketch", message()) + override fun v(message: String) { + if (minLevel <= LogLevel.VERBOSE) Log.v("Ketch", message) } - override fun d(message: () -> String) { - if (minLevel <= LogLevel.DEBUG) Log.d("Ketch", message()) + override fun d(message: String) { + if (minLevel <= LogLevel.DEBUG) Log.d("Ketch", message) } - override fun i(message: () -> String) { - if (minLevel <= LogLevel.INFO) Log.i("Ketch", message()) + override fun i(message: String) { + if (minLevel <= LogLevel.INFO) Log.i("Ketch", message) } - override fun w(message: () -> String, throwable: Throwable?) { - if (minLevel <= LogLevel.WARN) Log.w("Ketch", message(), throwable) + override fun w(message: String, throwable: Throwable?) { + if (minLevel <= LogLevel.WARN) Log.w("Ketch", message, throwable) } - override fun e(message: () -> String, throwable: Throwable?) { - if (minLevel <= LogLevel.ERROR) Log.e("Ketch", message(), throwable) + override fun e(message: String, throwable: Throwable?) { + if (minLevel <= LogLevel.ERROR) Log.e("Ketch", message, throwable) } } diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt index 0cb78bcb..93dce3eb 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/KetchLogger.kt @@ -9,7 +9,7 @@ package com.linroid.ketch.core.log * * All logging methods are `inline` with a fast-path check for [Logger.None], * so when logging is disabled (the default) there is zero allocation overhead — - * neither the message lambda nor the tag-wrapper lambda is created. + * the message lambda is never created and the string is never constructed. * * ```kotlin * private val logger = KetchLogger("Coordinator") @@ -21,45 +21,29 @@ package com.linroid.ketch.core.log */ class KetchLogger(@PublishedApi internal val tag: String) { - inline fun v(crossinline message: () -> String) { + inline fun v(message: () -> String) { val l = logger - if (l !== Logger.None) { - l.v { "[$tag] ${message()}" } - } + if (l !== Logger.None) l.v("[$tag] ${message()}") } - inline fun d(crossinline message: () -> String) { + inline fun d(message: () -> String) { val l = logger - if (l !== Logger.None) { - l.d { "[$tag] ${message()}" } - } + if (l !== Logger.None) l.d("[$tag] ${message()}") } - inline fun i(crossinline message: () -> String) { + inline fun i(message: () -> String) { val l = logger - if (l !== Logger.None) { - l.i { "[$tag] ${message()}" } - } + if (l !== Logger.None) l.i("[$tag] ${message()}") } - inline fun w( - throwable: Throwable? = null, - crossinline message: () -> String, - ) { + inline fun w(throwable: Throwable? = null, message: () -> String) { val l = logger - if (l !== Logger.None) { - l.w(message = { "[$tag] ${message()}" }, throwable = throwable) - } + if (l !== Logger.None) l.w("[$tag] ${message()}", throwable) } - inline fun e( - throwable: Throwable? = null, - crossinline message: () -> String, - ) { + inline fun e(throwable: Throwable? = null, message: () -> String) { val l = logger - if (l !== Logger.None) { - l.e(message = { "[$tag] ${message()}" }, throwable = throwable) - } + if (l !== Logger.None) l.e("[$tag] ${message()}", throwable) } companion object { diff --git a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/Logger.kt b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/Logger.kt index 549e4861..50a4454b 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/Logger.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/ketch/core/log/Logger.kt @@ -4,7 +4,7 @@ package com.linroid.ketch.core.log * Log level for filtering log output. * * Messages at or above the configured level are emitted; - * messages below are discarded without evaluating the lambda. + * messages below are silently discarded. */ enum class LogLevel { VERBOSE, DEBUG, INFO, WARN, ERROR @@ -14,7 +14,9 @@ enum class LogLevel { * Logger abstraction for Ketch. * * Ketch supports pluggable logging with zero overhead when disabled (default). - * All log methods accept lambda parameters for lazy evaluation. + * All log methods accept a pre-built [String] message. Lazy evaluation is + * handled by [KetchLogger]'s `inline` functions, which skip the message + * construction entirely when [Logger.None] is active. * * ## Usage * @@ -50,11 +52,11 @@ enum class LogLevel { * ### Custom Logger * ```kotlin * class MyLogger : Logger { - * override fun v(message: () -> String) { println(message()) } - * override fun d(message: () -> String) { println(message()) } - * override fun i(message: () -> String) { println(message()) } - * override fun w(message: () -> String, throwable: Throwable?) { println(message()) } - * override fun e(message: () -> String, throwable: Throwable?) { System.err.println(message()) } + * override fun v(message: String) { println(message) } + * override fun d(message: String) { println(message) } + * override fun i(message: String) { println(message) } + * override fun w(message: String, throwable: Throwable?) { println(message) } + * override fun e(message: String, throwable: Throwable?) { System.err.println(message) } * } * ``` * @@ -70,42 +72,30 @@ enum class LogLevel { * @see Logger.None */ interface Logger { - /** - * Log a verbose message. Used for detailed diagnostic information. - * - * @param message Lazy message provider (only evaluated if logging is enabled) - */ - fun v(message: () -> String) + /** Log a verbose message. Used for detailed diagnostic information. */ + fun v(message: String) - /** - * Log a debug message. Used for debugging information. - * - * @param message Lazy message provider (only evaluated if logging is enabled) - */ - fun d(message: () -> String) + /** Log a debug message. Used for debugging information. */ + fun d(message: String) - /** - * Log an info message. Used for general informational messages. - * - * @param message Lazy message provider (only evaluated if logging is enabled) - */ - fun i(message: () -> String) + /** Log an info message. Used for general informational messages. */ + fun i(message: String) /** * Log a warning message. Used for potentially harmful situations. * - * @param message Lazy message provider (only evaluated if logging is enabled) + * @param message Log message * @param throwable Optional throwable to log */ - fun w(message: () -> String, throwable: Throwable? = null) + fun w(message: String, throwable: Throwable? = null) /** * Log an error message. Used for error events. * - * @param message Lazy message provider (only evaluated if logging is enabled) + * @param message Log message * @param throwable Optional throwable to log */ - fun e(message: () -> String, throwable: Throwable? = null) + fun e(message: String, throwable: Throwable? = null) companion object { /** @@ -113,11 +103,11 @@ interface Logger { * This is the default logger if none is provided. */ val None: Logger = object : Logger { - override fun v(message: () -> String) {} - override fun d(message: () -> String) {} - override fun i(message: () -> String) {} - override fun w(message: () -> String, throwable: Throwable?) {} - override fun e(message: () -> String, throwable: Throwable?) {} + override fun v(message: String) {} + override fun d(message: String) {} + override fun i(message: String) {} + override fun w(message: String, throwable: Throwable?) {} + override fun e(message: String, throwable: Throwable?) {} } /** diff --git a/library/core/src/iosMain/kotlin/com/linroid/ketch/core/log/Logger.ios.kt b/library/core/src/iosMain/kotlin/com/linroid/ketch/core/log/Logger.ios.kt index 1ba0c4f9..a57c0571 100644 --- a/library/core/src/iosMain/kotlin/com/linroid/ketch/core/log/Logger.ios.kt +++ b/library/core/src/iosMain/kotlin/com/linroid/ketch/core/log/Logger.ios.kt @@ -2,21 +2,21 @@ package com.linroid.ketch.core.log internal actual fun consoleLogger(minLevel: LogLevel): Logger = object : Logger { - override fun v(message: () -> String) { - if (minLevel <= LogLevel.VERBOSE) println("[VERBOSE] ${message()}") + override fun v(message: String) { + if (minLevel <= LogLevel.VERBOSE) println("[VERBOSE] $message") } - override fun d(message: () -> String) { - if (minLevel <= LogLevel.DEBUG) println("[DEBUG] ${message()}") + override fun d(message: String) { + if (minLevel <= LogLevel.DEBUG) println("[DEBUG] $message") } - override fun i(message: () -> String) { - if (minLevel <= LogLevel.INFO) println("[INFO] ${message()}") + override fun i(message: String) { + if (minLevel <= LogLevel.INFO) println("[INFO] $message") } - override fun w(message: () -> String, throwable: Throwable?) { + override fun w(message: String, throwable: Throwable?) { if (minLevel <= LogLevel.WARN) { - println("[WARN] ${message()}") + println("[WARN] $message") throwable?.let { println(" Exception: ${it.message}") println(" ${it.stackTraceToString()}") @@ -24,9 +24,9 @@ internal actual fun consoleLogger(minLevel: LogLevel): Logger = } } - override fun e(message: () -> String, throwable: Throwable?) { + override fun e(message: String, throwable: Throwable?) { if (minLevel <= LogLevel.ERROR) { - println("[ERROR] ${message()}") + println("[ERROR] $message") throwable?.let { println(" Exception: ${it.message}") println(" ${it.stackTraceToString()}") diff --git a/library/core/src/jvmMain/kotlin/com/linroid/ketch/core/log/Logger.jvm.kt b/library/core/src/jvmMain/kotlin/com/linroid/ketch/core/log/Logger.jvm.kt index 604ba79b..f9fe200a 100644 --- a/library/core/src/jvmMain/kotlin/com/linroid/ketch/core/log/Logger.jvm.kt +++ b/library/core/src/jvmMain/kotlin/com/linroid/ketch/core/log/Logger.jvm.kt @@ -2,28 +2,28 @@ package com.linroid.ketch.core.log internal actual fun consoleLogger(minLevel: LogLevel): Logger = object : Logger { - override fun v(message: () -> String) { - if (minLevel <= LogLevel.VERBOSE) println("[VERBOSE] ${message()}") + override fun v(message: String) { + if (minLevel <= LogLevel.VERBOSE) println("[VERBOSE] $message") } - override fun d(message: () -> String) { - if (minLevel <= LogLevel.DEBUG) println("[DEBUG] ${message()}") + override fun d(message: String) { + if (minLevel <= LogLevel.DEBUG) println("[DEBUG] $message") } - override fun i(message: () -> String) { - if (minLevel <= LogLevel.INFO) println("[INFO] ${message()}") + override fun i(message: String) { + if (minLevel <= LogLevel.INFO) println("[INFO] $message") } - override fun w(message: () -> String, throwable: Throwable?) { + override fun w(message: String, throwable: Throwable?) { if (minLevel <= LogLevel.WARN) { - println("[WARN] ${message()}") + println("[WARN] $message") throwable?.printStackTrace() } } - override fun e(message: () -> String, throwable: Throwable?) { + override fun e(message: String, throwable: Throwable?) { if (minLevel <= LogLevel.ERROR) { - System.err.println("[ERROR] ${message()}") + System.err.println("[ERROR] $message") throwable?.printStackTrace() } } diff --git a/library/core/src/wasmJsMain/kotlin/com/linroid/ketch/core/log/Logger.wasmJs.kt b/library/core/src/wasmJsMain/kotlin/com/linroid/ketch/core/log/Logger.wasmJs.kt index 1ba0c4f9..a57c0571 100644 --- a/library/core/src/wasmJsMain/kotlin/com/linroid/ketch/core/log/Logger.wasmJs.kt +++ b/library/core/src/wasmJsMain/kotlin/com/linroid/ketch/core/log/Logger.wasmJs.kt @@ -2,21 +2,21 @@ package com.linroid.ketch.core.log internal actual fun consoleLogger(minLevel: LogLevel): Logger = object : Logger { - override fun v(message: () -> String) { - if (minLevel <= LogLevel.VERBOSE) println("[VERBOSE] ${message()}") + override fun v(message: String) { + if (minLevel <= LogLevel.VERBOSE) println("[VERBOSE] $message") } - override fun d(message: () -> String) { - if (minLevel <= LogLevel.DEBUG) println("[DEBUG] ${message()}") + override fun d(message: String) { + if (minLevel <= LogLevel.DEBUG) println("[DEBUG] $message") } - override fun i(message: () -> String) { - if (minLevel <= LogLevel.INFO) println("[INFO] ${message()}") + override fun i(message: String) { + if (minLevel <= LogLevel.INFO) println("[INFO] $message") } - override fun w(message: () -> String, throwable: Throwable?) { + override fun w(message: String, throwable: Throwable?) { if (minLevel <= LogLevel.WARN) { - println("[WARN] ${message()}") + println("[WARN] $message") throwable?.let { println(" Exception: ${it.message}") println(" ${it.stackTraceToString()}") @@ -24,9 +24,9 @@ internal actual fun consoleLogger(minLevel: LogLevel): Logger = } } - override fun e(message: () -> String, throwable: Throwable?) { + override fun e(message: String, throwable: Throwable?) { if (minLevel <= LogLevel.ERROR) { - println("[ERROR] ${message()}") + println("[ERROR] $message") throwable?.let { println(" Exception: ${it.message}") println(" ${it.stackTraceToString()}") diff --git a/library/kermit/src/commonMain/kotlin/com/linroid/ketch/log/KermitLogger.kt b/library/kermit/src/commonMain/kotlin/com/linroid/ketch/log/KermitLogger.kt index 72dacb33..d690e263 100644 --- a/library/kermit/src/commonMain/kotlin/com/linroid/ketch/log/KermitLogger.kt +++ b/library/kermit/src/commonMain/kotlin/com/linroid/ketch/log/KermitLogger.kt @@ -42,23 +42,23 @@ class KermitLogger( tag = tag, ) - override fun v(message: () -> String) { - kermit.v(messageString = message()) + override fun v(message: String) { + kermit.v(messageString = message) } - override fun d(message: () -> String) { - kermit.d(messageString = message()) + override fun d(message: String) { + kermit.d(messageString = message) } - override fun i(message: () -> String) { - kermit.i(messageString = message()) + override fun i(message: String) { + kermit.i(messageString = message) } - override fun w(message: () -> String, throwable: Throwable?) { - kermit.w(messageString = message(), throwable = throwable) + override fun w(message: String, throwable: Throwable?) { + kermit.w(messageString = message, throwable = throwable) } - override fun e(message: () -> String, throwable: Throwable?) { - kermit.e(messageString = message(), throwable = throwable) + override fun e(message: String, throwable: Throwable?) { + kermit.e(messageString = message, throwable = throwable) } }