Skip to content

Commit 36e56c3

Browse files
authored
Fix Darwin OSLog composition failure in historical logs (#594)
Fixes #588 ## Description This PR fixes an issue where logs on macOS (using `DarwinKLogger`) would show `<compose failure>` when viewed in historical logs (via `log show` or Console.app), despite appearing correctly in live logs (`log stream`). The issue was caused by passing a dynamically allocated format string to `os_log`. Apple's `os_log` system requires the format string to be a C string literal located in the `__TEXT,__oslogstring` section of the binary for security and privacy (redaction) reasons. Wrappers or dynamic strings do not satisfy this requirement by default, causing the logging system to fail composition during archival. ## Solution Implemented a C-interop solution to provide a valid static format string to `os_log`: 1. **Added C-Interop Definition (`oslog.def`)**: Created a C header/def file that defines a static inline function `kotlin_logging_os_log`. ```c static inline void kotlin_logging_os_log(os_log_t log, os_log_type_t type, const char* message) { os_log_with_type(log, type, "%{public}s", message); } ``` This function uses `"%{public}s"` as a constant C string literal, which `clang` places in the correct section. 2. **Updated Build Configuration (`build.gradle.kts`)**: Configured all Darwin targets to include this C-interop definition. 3. **Updated `DarwinKLogger`**: Modified `DarwinKLogger.kt` to call `kotlin_logging_os_log` instead of `_os_log_internal`. ## Verification - Added `Issue588Test` to reproduce the issue. - Verified that logs are successfully persisted and readable using `log show`: ```bash log show --predicate 'processImagePath CONTAINS "test.kexe"' --info --style syslog --last 5m ``` - Confirmed output contains the log message instead of `<compose failure>`.
1 parent 4ff0b7d commit 36e56c3

9 files changed

Lines changed: 56 additions & 16 deletions

File tree

build.gradle.kts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ kotlin {
271271
getByName("${it.targetName}Test") {
272272
dependsOn(darwinTest)
273273
}
274+
val main by it.compilations.getting
275+
val oslog by main.cinterops.creating {
276+
defFile(project.file("src/darwinMain/cinterop/oslog.def"))
277+
}
274278
}
275279
}
276280
}
@@ -407,3 +411,11 @@ tasks.withType<AbstractPublishToMaven>().configureEach {
407411
mustRunAfter(signingTasks)
408412
}
409413
//endregion
414+
415+
// Fix implicit dependency issue for native MetadataElements tasks
416+
// "Declare an explicit dependency on ':commonizeCInterop' from ':*MetadataElements' using Task#dependsOn"
417+
tasks.matching {
418+
it.name.matches("^(linux|mingw|androidNative|macos|ios|watchos|tvos).*MetadataElements$".toRegex())
419+
}.configureEach {
420+
dependsOn("commonizeCInterop")
421+
}

gradle.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ org.gradle.jvmargs=-Xmx2048m
55
kotlin.mpp.androidSourceSetLayoutVersion=2
66
kotlin.mpp.applyDefaultHierarchyTemplate=false
77
# Allow inconsistent JVM targets: Kotlin targets 1.8 (for compatibility), but module-info.java requires 9+.
8-
kotlin.jvm.target.validation.mode=warning
8+
kotlin.jvm.target.validation.mode=ignore
9+
kotlin.mpp.enableCInteropCommonization=true

src/darwinMain/cinterop/oslog.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package = io.github.oshai.kotlinlogging.cinterop
2+
language = Objective-C
3+
headers = os/log.h
4+
5+
---
6+
#include <os/log.h>
7+
8+
static inline void kotlin_logging_os_log(os_log_t log, os_log_type_t type, const char* message) {
9+
os_log_with_type(log, type, "%{public}s", message);
10+
}

src/darwinMain/kotlin/io/github/oshai/kotlinlogging/DarwinKLogger.kt

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22

33
package io.github.oshai.kotlinlogging
44

5+
import io.github.oshai.kotlinlogging.cinterop.kotlin_logging_os_log
56
import io.github.oshai.kotlinlogging.internal.DarwinFormatter
67
import kotlinx.cinterop.ExperimentalForeignApi
7-
import kotlinx.cinterop.ptr
88
import platform.darwin.OS_LOG_TYPE_DEBUG
99
import platform.darwin.OS_LOG_TYPE_DEFAULT
1010
import platform.darwin.OS_LOG_TYPE_ERROR
1111
import platform.darwin.OS_LOG_TYPE_INFO
12-
import platform.darwin.__dso_handle
13-
import platform.darwin._os_log_internal
1412
import platform.darwin.os_log_t
1513
import platform.darwin.os_log_type_enabled
1614
import platform.darwin.os_log_type_t
@@ -21,16 +19,17 @@ public class DarwinKLogger(override val name: String, override val underlyingLog
2119
override fun at(level: Level, marker: Marker?, block: KLoggingEventBuilder.() -> Unit) {
2220
if (isLoggingEnabledFor(level, marker)) {
2321
KLoggingEventBuilder().apply(block).run {
24-
val formattedMessage: String = DarwinFormatter.getFormattedMessage(this, marker)
25-
_os_log_internal(
26-
__dso_handle.ptr,
27-
underlyingLogger,
28-
level.toDarwinLevel(),
29-
// Use %{public}s to prevent redaction of the log message in historical logs (log show)
30-
// See https://github.com/oshai/kotlin-logging/issues/588
31-
"%{public}s",
32-
formattedMessage,
33-
)
22+
val message = DarwinFormatter.getFormattedMessage(this, marker)
23+
val formattedMessage =
24+
if (marker != null) {
25+
// Separating marker and message because OSLog doesn't have a way to pass marker as a
26+
// separate argument
27+
// formatting manually to string for now
28+
"$marker $message"
29+
} else {
30+
message
31+
}
32+
kotlin_logging_os_log(underlyingLogger, level.toDarwinLevel(), formattedMessage)
3433
}
3534
}
3635
}

src/darwinMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggingClock.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ package io.github.oshai.kotlinlogging.internal
22

33
import kotlin.system.getTimeMillis
44

5-
public actual fun getCurrentTime(): Long = getTimeMillis()
5+
@Suppress("DEPRECATION") public actual fun getCurrentTime(): Long = getTimeMillis()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.github.oshai.kotlinlogging
2+
3+
import kotlin.test.Test
4+
import platform.Foundation.NSUUID
5+
6+
class Issue588Test {
7+
@Test
8+
fun testHistoricalLogging() {
9+
val uuid = NSUUID().UUIDString
10+
val logger = KotlinLogging.logger("issue588.repro")
11+
12+
logger.info { "Test message execution $uuid" }
13+
logger.error { "Test error execution $uuid" }
14+
}
15+
}

src/jvmMain/kotlin/io/github/oshai/kotlinlogging/KLogging.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ package io.github.oshai.kotlinlogging
1212
* ```
1313
*/
1414
@Deprecated("Use KLogger instead", ReplaceWith("KLogger", "io.github.oshai.kotlinlogging.KLogger"))
15+
@Suppress("DEPRECATION")
1516
public open class KLogging : KLoggable {
1617
override val logger: KLogger = logger()
1718
}
1819

1920
/** A class with logging capabilities and explicit logger name */
2021
@Deprecated("Use KLogger instead", ReplaceWith("KLogger", "io.github.oshai.kotlinlogging.KLogger"))
22+
@Suppress("DEPRECATION")
2123
public open class NamedKLogging(name: String) : KLoggable {
2224
override val logger: KLogger = logger(name)
2325
}

src/jvmMain/kotlin/io/github/oshai/kotlinlogging/logback/internal/LogbackLogEvent.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ internal class LogbackLogEvent(
4141
override fun getMessage(): String =
4242
kLoggingEvent.internalCompilerData?.messageTemplate ?: kLoggingEvent.message ?: ""
4343

44+
@Suppress("UNCHECKED_CAST")
4445
override fun getArgumentArray(): Array<Object>? = kLoggingEvent.arguments as? Array<Object>
4546

4647
override fun getFormattedMessage(): String? = kLoggingEvent.message

src/nativeMain/kotlin/io/github/oshai/kotlinlogging/internal/KLoggingClock.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ package io.github.oshai.kotlinlogging.internal
22

33
import kotlin.system.getTimeMillis
44

5-
public actual fun getCurrentTime(): Long = getTimeMillis()
5+
@Suppress("DEPRECATION") public actual fun getCurrentTime(): Long = getTimeMillis()

0 commit comments

Comments
 (0)