{"id":37021512,"url":"https://github.com/codinux-gmbh/klf","last_synced_at":"2026-01-14T02:32:50.448Z","repository":{"id":267536722,"uuid":"859396203","full_name":"codinux-gmbh/klf","owner":"codinux-gmbh","description":"Kotlin (Multiplatform) logging facade for idiomatic logging in Kotlin with appenders for all supported KMP platforms","archived":false,"fork":false,"pushed_at":"2025-06-01T23:00:22.000Z","size":675,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-02T09:14:14.971Z","etag":null,"topics":["kotlin","kotlin-multiplatform","logging"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codinux-gmbh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-09-18T15:34:52.000Z","updated_at":"2025-05-25T15:19:47.000Z","dependencies_parsed_at":"2024-12-10T23:25:28.424Z","dependency_job_id":"4fa6b655-1e8a-443c-92d2-1b08a56bd249","html_url":"https://github.com/codinux-gmbh/klf","commit_stats":null,"previous_names":["codinux-gmbh/klf"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/codinux-gmbh/klf","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codinux-gmbh%2Fklf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codinux-gmbh%2Fklf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codinux-gmbh%2Fklf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codinux-gmbh%2Fklf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codinux-gmbh","download_url":"https://codeload.github.com/codinux-gmbh/klf/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codinux-gmbh%2Fklf/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408711,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kotlin","kotlin-multiplatform","logging"],"created_at":"2026-01-14T02:32:49.875Z","updated_at":"2026-01-14T02:32:50.440Z","avatar_url":"https://github.com/codinux-gmbh.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kotlin Logging Facade (klf)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.codinux.log/klf/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.codinux.log/klf)\n[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\nKotlin (Multiplatform) logging facade for idiomatic logging in Kotlin with appenders for all supported KMP platforms.\n\n## Setup\n\n### Gradle\n\n```\nimplementation(\"net.codinux.log:klf:1.8.3\")\n```\n\n### Native Images (e.g. Quarkus native)\n\nFor native images use\n\n```\nimplementation(\"net.codinux.log:klf-graal:1.8.3\")\n```\n\nwhich substitutes calls to reflection. (As otherwise each class for which a Logger gets created would need to be annotated with `@RegisterForReflection`.)\n\n### Compatibility note\n\nWith version 1.6.0 the project has been renamed from `kmp-log` to `klf` and the Kotlin version changed from 1.8 to 1.9.\nProjects targeting Kotlin 1.8 therefore should use `net.codinux.log:kmp-log:1.5.1`.\n\n## Usage\n\n### Kotlin style\n\n```kotlin\n    package com.example.service\n    \n    import net.codinux.log.logger\n    \n    class OrderService {\n        private val log by logger() // automatically uses declaring class / OrderService::class.name as logger name\n      \n        fun showUsage() {\n            log.info { \"Message with ${heavyCalculation()}\" } // lambda and therefor heavyCalculation() gets only called if INFO level is enabled\n    \n            log.error(e) { \"An error occurred\" } // e is a throwable\n        }\n    }\n```\n\nThe log lambdas like `info { }`, `warn { }`, ... are only called if the according log level is enabled.  \nTherefor there's no need for String formats like `log.info(\"Message with %s\", heavyCalculation())` as `\"Message with ${heavyCalculation()}\"` of example above gets only called if INFO level is enabled.\n\nWith `by logger()` the logger name gets automatically derived from declaring class. In above example it would by `\"com.example.service.OrderService\"`.  \nAnd the logger instance gets instantiated lazily on its first usage (or never if it never gets called).\n\nIf the logger is declared in a companion object automatically the enclosing class gets used as logger name\n(like in the following example `OrderService` instead of `OrderService.Companion`).  \nExcept on JavaScript, there the logger name will always be `\"Companion\"`.\n\n```kotlin\n    import net.codinux.log.logger\n    \n    class OrderService {\n      companion object {\n        private val log by logger() // automatically uses enclosing class = OrderService::class.name as logger name (except on JavaScript)\n      }\n    }\n``` \n\n### Classically:\n\n```kotlin\npackage com.example.service\n\nimport net.codinux.log.LoggerFactory\n\nclass OrderService {\n    private val log = LoggerFactory.getLogger(OrderService::class)\n    // or by setting logger name (tag) via a string\n    private val logByName = LoggerFactory.getLogger(\"OrderService\")\n  \n    fun showUsage() {\n        log.info(\"An info message: $detailedMessage\") // string get directly concatenated whether INFO level is enabled or not\n\n        logByName.error(\"An error occurred\", e) // e is a throwable\n    }\n}\n```\n\nYou might ask why is there no overload with format arguments like `fun info(message: String, exception: Throwable? = null, vararg arguments: Any)`.  \nThis is due to the restrictions of Kotlin multiplatform as there's no `String.format()` available (also the format specifier differ, e.g. `%s` on the JVM and `%@` on iOS and macOS).  \nSo if you like to format the log message, please use lambda variants.\n\n## Static Loggers\n\nSince version 1.5.0 klf also supports static Loggers:\n\n```kotlin\npackage com.example.service\n\nimport net.codinux.log.LoggerFactory\n\nclass OrderService {\n  \n    fun showUsage() {\n        Log.info\u003cOrderService\u003e { \"Message with ${heavyCalculation()}\" }\n\n        Log.error\u003cOrderService\u003e(e) { \"An error occurred\" } // e is a throwable\n\n        // or set the logger tag via String:\n        Log.info(loggerName = \"OrderService\") { \"Message with ${heavyCalculation()}\" }\n        \n        // The logger tag can also be omitted; in this case, the default logger name resolution will apply, as described below.\n        Log.info { \"Message with ${heavyCalculation()}\" }\n    }\n}\n```\n\n## Logger name resolution if no logger tag is provided\n\nIf no logger tag is provided, e.g. with `Log.info { \"Info\" }`, the following logger name resolution will be applied:\n\n1. If `LoggerFactory.config.useCallerMethodIfLoggerNameNotSet` or in debug mode  `LoggerFactory.debugConfig.useMethodNameIfLogTagIsNotSet` is set to true, then the method that executed the log statement will be used as logger name (which is quite handy e.g. for @Composable functions).       \nIt's a bit resource intensive, see below.   \nCurrently only works on `JVM` and `Android`.\n\n2. Otherwise if set, the value of `LoggerFactory.defaultLoggerName` will be used.\n\n3. If `defaultLoggerName` is not set, we try to determine the app name (e.g. Main Bundle name on iOS, app's package name and name on Android (but see Configuration -\u003e [Android](#android)), jar name on JVM and URL's last path name on JavaScript).\n\n4. If this does not work, `\"net.codinux.log.klf\"` will be used as logger name.\n\n\n## Configuration\n\n### Android\n\nFor some features to work on Android, like logging the app name or determining if running in debug mode, Android \nContext is required. Set it at app start, e.g. in MainActivity:\n\n```kotlin\nclass MainActivity : ComponentActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        net.codinux.log.android.AndroidContext.applicationContext = this.applicationContext\n\n        // ...\n    }\n}\n```\n\n### Options\n\n### rootLevel\n\nExperimental: Sets the default log level for all loggers that will be used\nif no logger specific level is set with [Logger.level].\n\nBe aware, this does not work reliably for all logging backends, e.g. we don't\nhave implementations for all slf4j logging backends and the Android min log\nlevel is unchangeable.\n\nIf slf4j is on the classpath, configure log level via logging backend (logback, log4j, ...).\n\nDefaults to `LogLevel.Info` and in debug mode to `LogLevel.Debug`.\n\n#### useCallerMethodIfLoggerNameNotSet\n\nIf no logger tag is passed to log statement, e.g. with `Log.info { \"..\" }`, \nand `LoggerFactory.config.useCallerMethodIfLoggerNameNotSet` or in debug mode \n`LoggerFactory.debugConfig.useMethodNameIfLogTagIsNotSet` is set to true, \nthen the method that executed the log statement will be used as logger name, \nin the format `\u003cclass name\u003e.\u003cmethod name\u003e`.\n\nThis comes in handy in Compose as in composable functions there's (usually) no class to reference with `by logger()` or `Log.info\u003cClassName\u003e { }`. \n\nIt's also trying to remove auto generated class name parts like they are applied to Coroutine functions\n(like \"org.example.AppKt$App$2$1$2.invokeSuspend\" -\u003e \"org.example.App\").\nThis may not work reliably under all circumstances as we have to extract the class name and method name from strings and we may are not aware of all possible formats yet.\n\nBut be aware that this is a bit resource intensive as it walks up the call \nstack for each log call to find the calling method, so preferably only enable it in debug mode.     \nFor UI applications this should be fine anyway, just don't enable it on high load servers.\n\nThis currently works only on `JVM` and `Android`.\n\n#### defaultLoggerName\n\nIf no logger tag is passed to log statement, e.g. with `Log.info { \"..\" }`,\nand [useCallerMethodIfLoggerNameNotSet](#useCallerMethodIfLoggerNameNotSet)\nis set to false, then the value configured with `defaultLoggerName` will be used.\n\nIf also `defaultLoggerName` is not set, we are trying to determine the app name\nand use that as logger name.        \nIf this does not work, `\"net.codinux.log.klf\"` will be used as logger name.\n\n### Debug mode vs. normal / release mode\n\nFor all options in `LoggerFactory.config` there's a same named option in `LoggerFactory.debugConfig`.\n\nValues from `debugConfig` will only be applied when running in debug mode / a debugger is attached.     \nSo you can set values that only get applied during development / when running in debug mode\nand don't touch your normal user's configuration like:\n\n```kotlin\nLoggerFactory.debugConfig.useCallerMethodIfLoggerNameNotSet = true\n```\n\nThis does currently not work on `JS` and `WASM` (hints how to detect attached debugger in JS are welcome).        \nFor `Android` it means that the app is compiled in `debug build variant`.\n\n\n## Log appenders\n\nklf by ships with a lot of log appenders. Depending on the platform by default these appenders are used:\n\n### JVM\n\nIf slf4j is on the classpath: **slf4j**.\n\nOtherwise: **Console** (`println()`)\n\n### Android\n\n**Logcat**\n\n### Apple system (macOS, iOS, iPadOS, watchOS, tvOS)\n\nIf available (iOS 10.0+, iPadOS 10.0+, macOS 10.12+, Mac Catalyst 13.1+, tvOS 10.0+, watchOS 3.0+): **OSLog**\n\nOtherwise: **NSLog**\n\n### JavaScript\n\n**JavaScript Console** (`console.log()` / `console.error()` / ...)\n\n### Native\n\n**Console** (`println()`)\n\n### Getting notified about log events\n\nIf you want to get notified about each log event, add a `NotifyAboutLogEventsAppender`:\n\n```kotlin\nLoggerFactory.addAppender(NotifyAboutLogEventsAppender(includeThreadName = true, includeException = true) { event -\u003e\n    // handle received log event\n})\n```\n\nFor `includeException` `true` is the default value, for `includeThreadName` `false`.\n\n### Loki\n\nIf you want to apply centralized logging, you can push all logs collected with klf directly to [Loki](https://github.com/grafana/loki) with `klf-loki-appender`:\n\nAdd to Gradle:\n```\nimplementation(\"net.codinux.log:klf-loki-appender:1.8.3\")\n```\n\nAnd on app start add LokiAppender:\n\n```kotlin\n// configure Loki url and credentials:\nval config = LokiLogAppenderConfig(writer = WriterConfig(\"http://192.168.0.27:3100\", username = null, password = null))\n\n// then add LokiAppender to klf LoggerFactory\nLoggerFactory.addAppender(LokiAppender(config))\n```\n\nIf using http (e.g. for tests in local network) on Android add `android:usesCleartextTraffic=\"true\"` to AndroidManifest.xml -\u003e \u003capplication\u003e tag.\n\n### Custom\n\nYou can also use add your custom log appender by implementing Appender interface:\n\n```kotlin\n  LoggerFactory.addAppender(CustomAppender()) // CustomAppender implements Appender interface\n```\n\nIf you dislike above default appenders and want to control logger creation entirely implement ILoggerFactory and call:\n```kotlin\n  LoggerFactory.setLoggerFactory(MyCustomLoggerFactory())\n```\n\n\n## WebAssembly (experimental)\n\nVersion 1.1.3 added experimental support for WASM Browser. While it should work in most circumstances, there is explicitly\ncurrently no support for multithreaded applications due to lack of synchronization mechanisms in Kotlin WASM.\n\n\n## MDC (Java only)\n\nklf has some convenience functions if values should get added to MDC (Mapped Diagnostic Context) only for the next log message and should then automatically get cleared again:\n\n```kotlin\n  log.withMdc(\"key1\" to \"value1\", \"key2\" to \"value2\").info { \"Info\" }\n```\n\nOr\n```kotlin\n  runWithMdc(\"key1\" to \"value1\") {\n    log.info { \"Info\" }\n  }\n```\n\n## Terminology\n\nThe terminology is mostly identical with slf4j / logback.\n\n### LoggerFactory\n\nPrimary facade to create a [Logger](#logger), e.g.  \n`private val log = LoggerFactory.getLogger(OrderService::class)`.\n\nklf ships with two LoggerFactories:\n- [Slf4jLoggerFactory](klf/src/javaCommonMain/kotlin/net/codinux/log/slf4j/Slf4jLoggerFactory.kt) that delegates logging to slf4j, so that on JVM any logging framework that implements slf4j can be used. \nGets automatically used if slf4j is on the classpath and its ILoggerFactory implementation is any other than [NOPLoggerFactory](https://www.slf4j.org/apidocs/org/slf4j/helpers/NOPLoggerFactory.html).\n- [DefaultLoggerFactory](klf/src/commonMain/kotlin/net.codinux.log/DefaultLoggerFactory.kt) that delegates logging to system's default log appender (see [Log appenders](#log-appenders)).\n\n### Logger\n\nInterface to log messages at different levels (Info, Error, ...) , e.g.  \n`log.error(e) { \"Calculating Fibonacci numbers for $number failed\" }`.\n\n### Appender\n\n- If a messages gets accepted by a Logger, Appender do the actual work of 'writing' log messages.\n- Multiple appender can be defined so that one logged messages e.g. gets written to console, to a file (only available via a slf4j compatible logging framework), to [Elasticsearch](https://github.com/codinux-gmbh/ElasticsearchLogger), [Loki](https://github.com/codinux-gmbh/LokiLogAppender), ...\n- All Loggers use the same appenders. (It's not possible to set an appender for a specific Logger like as logback.)\n- Except ConsoleAppender klf doesn't implement any Appender itself but delegates on to native loggers like Logcat, OSLog, JavaScript Console, slf4j, ...\n\n\n# License\n\n    Copyright 2023 codinux GmbH \u0026 Co. KG\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodinux-gmbh%2Fklf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodinux-gmbh%2Fklf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodinux-gmbh%2Fklf/lists"}