Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.gradle
build
build
.idea
12 changes: 8 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ kotlin {
// For MacOS, preset should be changed to e.g. presets.macosX64
//fromPreset(presets.linuxX64, 'linux')
}
macosX64("macos") {}
sourceSets {
commonMain {
dependencies {
Expand Down Expand Up @@ -59,9 +60,12 @@ kotlin {
implementation 'org.jetbrains.kotlin:kotlin-test-js'
}
}
// linuxMain {
// }
// linuxTest {
// }
macosMain {
}
macosTest {
dependencies {
implementation(kotlin("test-annotations-common"))
}
}
}
}
151 changes: 151 additions & 0 deletions src/macosMain/kotlin/klog/Formatter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package klog

import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.pointed
import kotlinx.cinterop.ptr
import kotlinx.cinterop.value
import platform.posix.gmtime
import platform.posix.time
import platform.posix.time_tVar
import platform.posix.tm

class Formatter() {
companion object {
private const val loggerMainClass = "kfun:klog.KLogger"
}

constructor(body: Formatter.() -> Unit): this() {
body.invoke(this)
}

private val formatters: MutableList<(KLoggingLevels, Throwable?, msg: Any?) -> String> = ArrayList()

fun formatMessage(
level: KLoggingLevels,
t: Throwable?,
msg: Any?
): String {
return formatters.joinToString(separator = "") { it.invoke(level, t, msg) }
}

fun level() {
formatters.add{ value, _, _ -> value.name}
}

fun dateTime() {
formatters.add{ _, _, _ ->
getDateStruct()?.let {
"${year(it)}.${month(it)}.${day(it)}-${hour(it)}:${minute(it)}:${second(it)}"
} ?:""
}
}

private fun year(t: tm) = (t.tm_year + 1900).toString()
private fun month(t: tm) = (t.tm_mon + 1).toString().let { if (it.length > 1) it else "0$it" }
private fun day(t: tm) = t.tm_mday.toString()
private fun hour(t: tm) = t.tm_hour.toString().let { if (it.length > 1) it else "0$it" }
private fun minute(t: tm) = t.tm_min.toString().let { if (it.length > 1) it else "0$it" }
private fun second(t: tm) = t.tm_sec.toString().let { if (it.length > 1) it else "0$it" }

private fun getDateStruct() = memScoped {
val t = alloc<time_tVar>()
t.value = time(null)
val local = gmtime(t.ptr)?.pointed
local
}

fun message() {
formatters.add{ _, _, value -> value.toString()}
}

fun text(value: String) {
formatters.add{ _, _, _ -> value}
}

fun method(withLine: Boolean = false, compactClassName: Boolean = true) {
formatters.add { _, _, _ ->
val stackLine = getMethodBeforeLogger(Throwable().getStackTrace())
val funLine = if (stackLine.contains("kfun:")) {
stackLine.substringAfter("kfun:")
} else {
stackLine
}
var methodName = if (funLine.contains("(")) {
funLine.substringBefore("(")
} else {
funLine
}
methodName = if (methodName.contains("#")) {
methodName.substringBefore("#")
} else {
methodName
}
methodName = if (methodName.contains("$")) {
methodName.substringBefore("$")
} else {
methodName
}
if (compactClassName) {
val methodParts = methodName.split(".")
val partsCount = methodParts.size
if (partsCount > 2) {
val b = StringBuilder()
for (i in 0 until partsCount - 2) {
b.append(methodParts[i][0]).append('.')
}
b.append(methodParts[partsCount - 2]).append('.').append(methodParts[partsCount - 1])
methodName = b.toString()
}
}
if (withLine) {
val parts = stackLine.split(":")
if (parts.size > 2) {
"$methodName(${parts[parts.size-2]})"
} else {
"$methodName(?)"
}
} else {
methodName
}
}
}

private fun getMethodBeforeLogger(strings: Array<String>): String {
var kotlinNativeArrived = false
for (string in strings) {
if (kotlinNativeArrived) {
if (!string.contains(loggerMainClass)) {
return string
}
} else {
kotlinNativeArrived = string.contains(loggerMainClass)
}
}
return ""
}

fun throwable(prefix: String = "\n ", separator: String = "\n, ") {
formatters.add { _, value, _ ->
if (value != null) {
var msg = "${prefix}${throwableLabel(value)}"
var previous = value
var current = previous.cause
while (current != null && previous != current) {
msg += "${separator}Caused by: ${throwableLabel(current)}"
previous = current
current = previous.cause
}
msg
} else {
""
}
}
}

private fun throwableLabel(current: Throwable): String {
val className = current::class.qualifiedName ?: current::class.simpleName ?: "<object>"
return "$className: ${current.message}"
}

}
103 changes: 103 additions & 0 deletions src/macosMain/kotlin/klog/KLoggers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package klog

import platform.Foundation.NSLog
import kotlin.native.concurrent.AtomicReference
import kotlin.native.concurrent.freeze
import kotlin.reflect.KClass

actual object KLoggers {
val isDebug = Throwable().getStackTrace()[0].contains("Throwable")

private val levels = HashMap<Regex, KLoggingLevels>()
private val formatters = HashMap<Regex, Formatter>()
private val defaultLoggingLevelAtomic
= AtomicReference(if (isDebug) KLoggingLevels.DEBUG else KLoggingLevels.INFO)

// case with println
private val defaultFormatterAtomic =
AtomicReference(
Formatter {
dateTime()
text(" - ")
level()
text(":")
if (isDebug) {
text(" ")
method(true)
}
text(" ")
message()
throwable()
}.freeze()
)
private fun log(line: String) { println(line) }

// case with NSLog
// private val defaultFormatterAtomic =
// AtomicReference(
// Formatter {
// level()
// text(":")
// if (isDebug) {
// text(" ")
// method(true)
// }
// text(" ")
// message()
// throwable()
// }.freeze()
// )
// private fun log(line: String) { NSLog(line) }

private val writerAtomic: AtomicReference<(String) -> Any> =
AtomicReference(::log.freeze())

var defaultLoggingLevel: KLoggingLevels
get() = defaultLoggingLevelAtomic.value
set(value) { defaultLoggingLevelAtomic.value = value }

var defaultFormatter: Formatter
get() = defaultFormatterAtomic.value
set(value) { defaultFormatterAtomic.value = value.freeze() }

var writer: (String) -> Any
get() = writerAtomic.value
set(value) { writerAtomic.value = value.freeze() }

actual fun logger(owner: Any): KLogger = when (owner) {
is String -> KLogger(NativeLogger(calcLevel(owner), calcFormatter(owner), writer))
is KClass<*> -> logger(name(owner::class))
else -> logger(name(owner::class))
}

fun name(forClass: KClass<out Any>): String {
val name = forClass.qualifiedName ?: forClass.simpleName ?: "<object>"
val slicedName = when {
name.contains("Kt$") -> name.substringBefore("Kt$")
name.contains("$") -> name.substringBefore("$")
else -> name
}
return when {
slicedName.endsWith(".") -> slicedName.substring(0, slicedName.length - 1)
else -> slicedName
}
}

fun loggingLevel(regex: Regex, level: KLoggingLevels) {
levels.put(regex, level)
}

private fun calcLevel(name: String) =
levels
.filter { it.key.matches(name) }
.maxBy { it.value }
?.value
?: defaultLoggingLevel

private fun calcFormatter(name: String) =
formatters
.filter { it.key.matches(name) }
.maxBy { it.value.toString() }
?.value
?: defaultFormatter
}
6 changes: 6 additions & 0 deletions src/macosMain/kotlin/klog/KLoggingLevels.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package klog

@Suppress("unused")
enum class KLoggingLevels {
NONE, ERROR, WARN, INFO, DEBUG, TRACE
}
71 changes: 71 additions & 0 deletions src/macosMain/kotlin/klog/NativeLogger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package klog

class NativeLogger(private val level: KLoggingLevels,
private val formatter: Formatter,
private val writer: (String) -> Any) : BaseLogger {
override val isTraceEnabled: Boolean get() = level >= KLoggingLevels.TRACE
override val isDebugEnabled: Boolean get() = level >= KLoggingLevels.DEBUG
override val isInfoEnabled: Boolean get() = level >= KLoggingLevels.INFO
override val isWarnEnabled: Boolean get() = level >= KLoggingLevels.WARN
override val isErrorEnabled: Boolean get() = level >= KLoggingLevels.ERROR

override fun trace(message: Any?) {
if (isTraceEnabled) {
writer.invoke(formatter.formatMessage(KLoggingLevels.TRACE, null, message))
}
}

override fun debug(message: Any?) {
if (isDebugEnabled) {
writer.invoke(formatter.formatMessage(KLoggingLevels.DEBUG, null, message))
}
}

override fun info(message: Any?) {
if (isInfoEnabled) {
writer.invoke(formatter.formatMessage(KLoggingLevels.INFO, null, message))
}
}

override fun warn(message: Any?) {
if (isWarnEnabled) {
writer.invoke(formatter.formatMessage(KLoggingLevels.WARN, null, message))
}
}

override fun error(message: Any?) {
if (isErrorEnabled) {
writer.invoke(formatter.formatMessage(KLoggingLevels.ERROR, null, message))
}
}

override fun trace(t: Throwable, message: Any?) {
if (isTraceEnabled) {
writer.invoke(formatter.formatMessage(KLoggingLevels.TRACE, t, message))
}
}

override fun debug(t: Throwable, message: Any?) {
if (isDebugEnabled) {
writer.invoke(formatter.formatMessage(KLoggingLevels.DEBUG, t, message))
}
}

override fun info(t: Throwable, message: Any?) {
if (isInfoEnabled) {
writer.invoke(formatter.formatMessage(KLoggingLevels.INFO, t, message))
}
}

override fun warn(t: Throwable, message: Any?) {
if (isWarnEnabled) {
writer.invoke(formatter.formatMessage(KLoggingLevels.WARN, t, message))
}
}

override fun error(t: Throwable, message: Any?) {
if (isErrorEnabled) {
writer.invoke(formatter.formatMessage(KLoggingLevels.ERROR, t, message))
}
}
}
Loading