diff --git a/CHANGES.txt b/CHANGES.txt index ecf1e77..722c3a1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +0.2.11 - May 27, 2024 +• Fix a bug when a log file is stored in a non-existing folder. + 0.2.6 - April 11, 2024 • Fix a bug when extracting the date from a log file name. diff --git a/src/commonMain/kotlin/com/airthings/lib/logging/TypeAlias.kt b/src/commonMain/kotlin/com/airthings/lib/logging/TypeAlias.kt index 8813ef4..50a1dcd 100644 --- a/src/commonMain/kotlin/com/airthings/lib/logging/TypeAlias.kt +++ b/src/commonMain/kotlin/com/airthings/lib/logging/TypeAlias.kt @@ -33,3 +33,8 @@ internal typealias LoggerFacilitiesMap = Map * An alias for a [MutableMap] of [LoggerFacility] instances associated by their unique names. */ internal typealias LoggerFacilitiesMutableMap = MutableMap + +/** + * An alias for a function that handles a missing folder incident. + */ +internal typealias LogFolderMissingHandler = (String) -> Unit diff --git a/src/commonMain/kotlin/com/airthings/lib/logging/facility/FileLoggerFacility.kt b/src/commonMain/kotlin/com/airthings/lib/logging/facility/FileLoggerFacility.kt index 8f93aaa..429259e 100644 --- a/src/commonMain/kotlin/com/airthings/lib/logging/facility/FileLoggerFacility.kt +++ b/src/commonMain/kotlin/com/airthings/lib/logging/facility/FileLoggerFacility.kt @@ -30,6 +30,7 @@ import com.airthings.lib.logging.LogMessage import com.airthings.lib.logging.LoggerFacility import com.airthings.lib.logging.dateStamp import com.airthings.lib.logging.datetimeStampPrefix +import com.airthings.lib.logging.platform.DelegateFileInputOutput import com.airthings.lib.logging.platform.PlatformDirectoryListing import com.airthings.lib.logging.platform.PlatformFileInputOutput import com.airthings.lib.logging.platform.PlatformFileInputOutputImpl @@ -128,19 +129,15 @@ class FileLoggerFacility( notifier = null, ) - private val io: PlatformFileInputOutput = PlatformFileInputOutputImpl() + private val io: PlatformFileInputOutput = DelegateFileInputOutput( + folder = baseFolder, + io = PlatformFileInputOutputImpl(), + onFolderMissing = { + notifier?.onLogFolderInvalid(it) ?: DelegateFileInputOutput.reportMissingFolder(it) + }, + ) private val currentLogFile = AtomicReference(null) - init { - coroutineScope.launch { - // Please note: The call to `io.mkdirs()` returns true if the directory exists, which may be - // different from the platform's implementation. - if (!io.mkdirs(baseFolder)) { - throw IllegalArgumentException("Base log folder is invalid: $baseFolder") - } - } - } - /** * Returns the platform-dependent [PlatformDirectoryListing] instance. */ diff --git a/src/commonMain/kotlin/com/airthings/lib/logging/facility/JsonLoggerFacility.kt b/src/commonMain/kotlin/com/airthings/lib/logging/facility/JsonLoggerFacility.kt index 8d702d8..6414c18 100644 --- a/src/commonMain/kotlin/com/airthings/lib/logging/facility/JsonLoggerFacility.kt +++ b/src/commonMain/kotlin/com/airthings/lib/logging/facility/JsonLoggerFacility.kt @@ -29,6 +29,7 @@ import com.airthings.lib.logging.LogMessage import com.airthings.lib.logging.LoggerFacility import com.airthings.lib.logging.dateStamp import com.airthings.lib.logging.datetimeStamp +import com.airthings.lib.logging.platform.DelegateFileInputOutput import com.airthings.lib.logging.platform.PlatformDirectoryListing import com.airthings.lib.logging.platform.PlatformFileInputOutput import com.airthings.lib.logging.platform.PlatformFileInputOutputImpl @@ -128,19 +129,15 @@ class JsonLoggerFacility( notifier = null, ) - private val io: PlatformFileInputOutput = PlatformFileInputOutputImpl() + private val io: PlatformFileInputOutput = DelegateFileInputOutput( + folder = baseFolder, + io = PlatformFileInputOutputImpl(), + onFolderMissing = { + notifier?.onLogFolderInvalid(it) ?: DelegateFileInputOutput.reportMissingFolder(it) + }, + ) private val currentLogFile = AtomicReference(null) - init { - coroutineScope.launch { - // Please note: The call to `io.mkdirs()` returns true if the directory exists, which may be - // different from the platform's implementation. - if (!io.mkdirs(baseFolder)) { - throw IllegalArgumentException("Base log folder is invalid: $baseFolder") - } - } - } - /** * Returns the platform-dependent [PlatformDirectoryListing] instance. */ diff --git a/src/commonMain/kotlin/com/airthings/lib/logging/platform/DelegateFileInputOutput.kt b/src/commonMain/kotlin/com/airthings/lib/logging/platform/DelegateFileInputOutput.kt new file mode 100644 index 0000000..e6ba4a4 --- /dev/null +++ b/src/commonMain/kotlin/com/airthings/lib/logging/platform/DelegateFileInputOutput.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2024 Airthings ASA. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.airthings.lib.logging.platform + +import com.airthings.lib.logging.LogDate +import com.airthings.lib.logging.LogFolderMissingHandler + +internal class DelegateFileInputOutput( + private val folder: String, + private val io: PlatformFileInputOutput, + private val onFolderMissing: LogFolderMissingHandler? = null, +) : PlatformFileInputOutput { + override val pathSeparator: Char = io.pathSeparator + + override suspend fun mkdirs(path: String): Boolean = ensureFolder { + mkdirs(path) + } + + override suspend fun size(path: String): Long = ensureFolder { + size(path) + } + + override suspend fun write( + path: String, + position: Long, + contents: String, + ) = ensureFolder { + write( + path = path, + position = position, + contents = contents, + ) + } + + override suspend fun append( + path: String, + contents: String, + ) = ensureFolder { + append( + path = path, + contents = contents, + ) + } + + override suspend fun ensure(path: String) = ensureFolder { + ensure(path) + } + + override suspend fun delete(path: String) = ensureFolder { + delete(path) + } + + override suspend fun of(path: String): Collection = ensureFolder { + of(path) + } + + override suspend fun of( + path: String, + date: LogDate, + ): Collection = ensureFolder { + of( + path = path, + date = date, + ) + } + + private suspend fun ensureFolder( + action: suspend PlatformFileInputOutput.() -> T, + ): T = with(io) { + val isDirectoryExists = mkdirs(folder) + try { + io.action() + } finally { + if (!isDirectoryExists) { + onFolderMissing?.invoke(folder) + } + } + } + + companion object { + /** + * Default handler when a folder is invalid. + * + * @param folder The invalid folder's path. + */ + fun reportMissingFolder(folder: String) { + throw IllegalStateException("Log folder is invalid: $folder") + } + } +} diff --git a/src/commonMain/kotlin/com/airthings/lib/logging/platform/PlatformFileInputOutputNotifier.kt b/src/commonMain/kotlin/com/airthings/lib/logging/platform/PlatformFileInputOutputNotifier.kt index 7d616bb..66185b3 100644 --- a/src/commonMain/kotlin/com/airthings/lib/logging/platform/PlatformFileInputOutputNotifier.kt +++ b/src/commonMain/kotlin/com/airthings/lib/logging/platform/PlatformFileInputOutputNotifier.kt @@ -20,7 +20,7 @@ package com.airthings.lib.logging.platform /** - * Defines a contract that notifies about opening and closing log files. + * Defines a contract that notifies about opening and closing log files and folders. */ interface PlatformFileInputOutputNotifier { /** @@ -36,4 +36,11 @@ interface PlatformFileInputOutputNotifier { * @param path The location of the log file. */ fun onLogFileClosed(path: String) + + /** + * Invoked when the log folder cannot be prepared and is effectively invalid. + * + * @param path The location of the log folder. + */ + fun onLogFolderInvalid(path: String) } diff --git a/src/jvmMain/kotlin/com/airthings/lib/logging/platform/PlatformFileInputOutputImpl.kt b/src/jvmMain/kotlin/com/airthings/lib/logging/platform/PlatformFileInputOutputImpl.kt index ca72cf7..8074044 100644 --- a/src/jvmMain/kotlin/com/airthings/lib/logging/platform/PlatformFileInputOutputImpl.kt +++ b/src/jvmMain/kotlin/com/airthings/lib/logging/platform/PlatformFileInputOutputImpl.kt @@ -63,8 +63,12 @@ internal actual class PlatformFileInputOutputImpl : PlatformFileInputOutput { } override suspend fun ensure(path: String) { - synchronized(writeLock) { - File(path).createNewFile() + val parentPath = File(path).parentFile.canonicalPath + + if (mkdirs(parentPath)) { + synchronized(writeLock) { + File(path).createNewFile() + } } }