{"id":21706847,"url":"https://github.com/smyrgeorge/log4k","last_synced_at":"2026-04-06T13:01:50.918Z","repository":{"id":258408605,"uuid":"873865353","full_name":"smyrgeorge/log4k","owner":"smyrgeorge","description":"A Comprehensive Logging and Tracing Solution for Kotlin Multiplatform.","archived":false,"fork":false,"pushed_at":"2025-04-07T06:49:11.000Z","size":2778,"stargazers_count":47,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-12T16:14:44.352Z","etag":null,"topics":["kotlin","kotlin-jvm","kotlin-logger","kotlin-native","logger","logging","logging-library","tracing","tracing-collector","tracing-library"],"latest_commit_sha":null,"homepage":"https://smyrgeorge.github.io","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smyrgeorge.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"smyrgeorge"}},"created_at":"2024-10-16T21:15:00.000Z","updated_at":"2025-04-07T06:49:11.000Z","dependencies_parsed_at":"2024-10-25T09:11:30.669Z","dependency_job_id":"58227600-d327-4ef2-832b-6ed7d4809259","html_url":"https://github.com/smyrgeorge/log4k","commit_stats":null,"previous_names":["smyrgeorge/log4k"],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smyrgeorge%2Flog4k","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smyrgeorge%2Flog4k/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smyrgeorge%2Flog4k/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smyrgeorge%2Flog4k/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smyrgeorge","download_url":"https://codeload.github.com/smyrgeorge/log4k/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248594190,"owners_count":21130316,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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-jvm","kotlin-logger","kotlin-native","logger","logging","logging-library","tracing","tracing-collector","tracing-library"],"created_at":"2024-11-25T22:14:31.217Z","updated_at":"2026-04-06T13:01:50.906Z","avatar_url":"https://github.com/smyrgeorge.png","language":"Kotlin","funding_links":["https://github.com/sponsors/smyrgeorge"],"categories":["日志库"],"sub_categories":[],"readme":"# log4k\n\n![Build](https://github.com/smyrgeorge/log4k/actions/workflows/ci.yml/badge.svg)\n![Maven Central](https://img.shields.io/maven-central/v/io.github.smyrgeorge/log4k)\n![GitHub License](https://img.shields.io/github/license/smyrgeorge/log4k)\n![GitHub commit activity](https://img.shields.io/github/commit-activity/w/smyrgeorge/log4k)\n![GitHub issues](https://img.shields.io/github/issues/smyrgeorge/log4k)\n[![Kotlin](https://img.shields.io/badge/kotlin-2.3.20-blue.svg?logo=kotlin)](http://kotlinlang.org)\n\n![](https://img.shields.io/static/v1?label=\u0026message=Platforms\u0026color=grey)\n![](https://img.shields.io/static/v1?label=\u0026message=Jvm\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=Linux\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=macOS\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=Windows\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=iOS\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=Android\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=Js\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=wasmJs\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=wasmWasi\u0026color=blue)\n\nA Comprehensive Logging and Tracing Solution for Kotlin Multiplatform.\n\nThis project provides a robust, event-driven logging and tracing platform specifically designed for Kotlin\nMultiplatform (also compatible with the Java ecosystem). Built with coroutines and channels at its core, it offers\nasynchronous, scalable logging across multiple platforms.\n\nThis project also tries to be fully compatible with `OpenTelemetry` standard.\n\n📖 [Documentation](https://smyrgeorge.github.io/log4k/)\n\n🏠 [Homepage](https://smyrgeorge.github.io/) (under construction)\n\n## Usage\n\n```kotlin\n// https://central.sonatype.com/artifact/io.github.smyrgeorge/log4k\nimplementation(\"io.github.smyrgeorge:log4k:x.y.z\")\n```\n\n### Extension Modules\n\nStarting with Kotlin 2.3.20, what was previously a call-ambiguity warning between context-aware and non-context\nextension functions became a compilation error. To resolve this, the lambda-based extension functions have been\nsplit into two separate modules:\n\n**`log4k-classic`** — Lambda extensions **without** context receivers (standard usage):\n\n```kotlin\n// https://central.sonatype.com/artifact/io.github.smyrgeorge/log4k-classic\nimplementation(\"io.github.smyrgeorge:log4k-classic:x.y.z\")\n```\n\n```kotlin\nlog.debug { \"ignore\" }\nlog.debug { \"ignore + ${5}\" } // Will be evaluated only if DEBUG logs are enabled.\nlog.error(e) { e.message }\n```\n\n**`log4k-context`** — Lambda extensions **with** context receivers (`TracingContext`) for automatic span propagation:\n\n```kotlin\n// https://central.sonatype.com/artifact/io.github.smyrgeorge/log4k-context\nimplementation(\"io.github.smyrgeorge:log4k-context:x.y.z\")\n```\n\n```kotlin\ntrace.span(\"my-operation\") {\n    // TracingContext is in scope — span is automatically attached to log events.\n    log.info { \"Processing started\" }\n    log.error(exception) { \"Operation failed\" }\n}\n```\n\nYou can depend on both modules simultaneously if you need both styles in the same source set.\n\n## Architecture\n\n\u003c!--suppress HtmlDeprecatedAttribute --\u003e\n\u003cp align=\"center\"\u003e\n  \u003c!--suppress CheckImageSize --\u003e\n\u003cimg src=\"img/arch.png\" alt=\"Architecture\" width=\"158\" height=\"338\"\u003e\n\u003c/p\u003e\n\nAt the core of the logging system is the `RootLogger`, which manages a `Channel\u003cLoggingEvent\u003e`. All logging events are\nenqueued in this channel, and the `RootLogger` is responsible for distributing them to the registered appenders (refer\nto `RootLogger` for more details).\n\nEach `Appender` may also maintain its own `Channel`, which is particularly beneficial in scenarios that require\nbatching—such as sending batched log or trace events over the network or appending them to a file. For instance, the\n`FlowAppender` leverages `kotlinx.coroutines.flow.Flow` to process incoming events efficiently.\n\nOn the other hand, some appenders can be simpler and do not require a `Channel` for event processing. For example, the\n`SimpleConsoleLoggingAppender` directly prints each incoming event to the console without queuing, offering a\nstraightforward logging\nsolution.\n\nThe tracing module shares exactly the same principals.\n\n## Logging API\n\nBy default, a platform-specific appender is automatically registered:\n\n- **Android**: `AndroidLoggingAppender` (routes to Android Logcat)\n- **iOS/macOS**: `AppleLoggingAppender` (routes to Apple's Unified Logging)\n- **All other platforms**: `SimpleConsoleLoggingAppender` (color-coded console output)\n\nYou can change the default behavior by unregistering all appenders early in your program:\n\n```kotlin\nRootLogger.Logging.appenders.unregisterAll()\n```\n\nAfter unregistering, you can register any appender you want like this:\n\n```kotlin\nRootLogger.Logging.appenders.register(SimpleJsonConsoleLoggingAppender())\n```\n\n```kotlin\n// Create a Logger.\nprivate val log: Logger = Logger.of(this::class)\n\nlog.info(\"this is test log\")\nlog.info(\"this is test with 1 arg: {}\", \"hello\")\nlog.error(e.message, e)\n```\n\nWe also support a more kotlin style API:\n\n```kotlin\nlog.debug { \"ignore\" }\nlog.debug { \"ignore + ${5}\" } // Will be evaluated only if DEBUG logs are enabled.\nlog.error { e.message }\nlog.error(e) { e.message } // e: Throwable\n```\n\n### Context Parameters Support\n\nThe logging API supports Kotlin's context receivers for automatic span propagation. When logging within a\n`TracingContext`,\nthe current span is automatically attached to log events without explicitly passing it:\n\n```kotlin\ntrace.span(\"my-operation\") {\n    // Inside this block, TracingContext is available as a context receiver.\n    // Log statements automatically include the current span information.\n    log.info { \"Processing started\" }  // Span context automatically attached\n    log.debug { \"Details: $data\" }\n    log.error(exception) { \"Operation failed\" }\n}\n```\n\nThis eliminates the need to manually pass the span to each log call:\n\n```kotlin\n// Without context receivers (explicit span passing):\ntrace.span(\"my-operation\") {\n    log.info(this, \"Processing started\")\n}\n\n// With context receivers (automatic span propagation):\ntrace.span(\"my-operation\") {\n    log.info { \"Processing started\" }  // Span is automatically included\n}\n```\n\nAll log levels (`trace`, `debug`, `info`, `warn`, `error`) support context receivers, both with message strings\nand with lambdas for lazy evaluation.\n\nSee the [Logger](./log4k/src/commonMain/kotlin/io/github/smyrgeorge/log4k/Logger.kt)\nand [TracingContext](./log4k/src/commonMain/kotlin/io/github/smyrgeorge/log4k/TracingContext.kt) classes for more\ndetails.\n\n### Full SLF4J Integration Supported\n\nWe’ve ensured complete compatibility with SLF4J, allowing seamless integration into projects that already use SLF4J as a\nlogging abstraction layer. By providing SLF4J support, `log4k` can be easily adopted in both new and existing\napplications\nwithout requiring significant changes to your current logging setup. This means you can leverage log4k’s powerful,\nmultiplatform capabilities while maintaining compatibility with other SLF4J-compatible libraries and frameworks.\n\nTo enable SLF4J integration, simply add the following dependency to your project:\n\n```kotlin\n// https://central.sonatype.com/artifact/io.github.smyrgeorge/log4k-slf4j\nimplementation(\"io.github.smyrgeorge:log4k-slf4j:x.y.z\")\n```\n\nFor detailed setup instructions and usage, see the project’s [README.md](./log4k-slf4j/README.md)\n\n### Json Appender\n\n```kotlin\n// You can register the `SimpleJsonConsoleLoggingAppender` for json logs in the console.\nRootLogger.Logging.appenders.register(SimpleJsonConsoleLoggingAppender())\n```\n\n## Tracing API\n\nThe tracing API is fully compatible with the `OpenTelemetry` standard, enabling seamless distributed tracing, metric\ncollection, and context propagation across services.\n\n```kotlin\nprivate val trace: Tracer = Tracer.of(this::class)\n// We need to manually register an appender.\n// The [SimpleConsoleTracingAppender] will print the traces in the console\n// (is just an example, should not be used as a real example).\nRootLogger.Tracing.appenders.register(SimpleConsoleTracingAppender())\n\n// Create the span and then start it.\nval span: TracingEvent.Span.Local = trace.span(\"test\").start()\nspan.event(name = \"test-event\")\n// Close the span manually.\nspan.end()\n```\n\nSimilarly to the logging API, we also support a more kotlin style API:\n\n```kotlin\ntrace.span(\"test\", parent) {\n    log.info(this, \"this is a test with span\") // The log will contain the span id.\n    // Set span tags.\n    tags[\"key\"] = \"value\"\n    // Send events that are related to the current span.\n    event(name = \"event-1\", level = Level.DEBUG)\n    debug(name = \"event-1\") // Same as event(name = \"event-1\", level = Level.DEBUG)\n    // Include tags in the event.\n    event(name = \"event-2\", tags = mapOf(\"key\" to \"value\"))\n    event(name = \"event-2\") { tags -\u003e\n        tags[\"key\"] = \"value\"\n    }\n    // Nested Span.\n    span(\"test-2\") {\n        event(name = \"event-3\", tags = mapOf(\"key\" to \"value\"))\n        log.info(this, \"this is a test with span\") // The log will contain the span id.\n    }\n    // Automatically closes at the end of te scope.\n}\n```\n\nAdditionally, you can instantiate a span that represents the parent span.\nThis is useful in cases that the parent span is created outside our application (e.g. received from an HTTP call).\n\n```kotlin\n// Create the parent span.\n// NOTICE: we do not start it, since it's already started.\nval parent: TracingEvent.Span.Remote = trace.span(id = \"ID_EXAMPLE\", traceId = \"TRACE_ID_EXAMPLE\")\ntrace.span(\"test\", parent) {\n    // Your logic here\n}\n```\n\nIn the examples above, we see two variations of the `Span` class:\n\n- **Span.Local**: Represents a span created locally within our application, exposing all methods such as `start`, `end`,\n  `event`, `debug`, `info`, and more.\n- **Span.Remote**: Represents a span created outside our application and propagated to us (e.g., from an HTTP call). It\n  does not expose any methods and serves only as a reference to the parent remote span.\n\n## Metering API\n\nA measurement captured at runtime.\n\nA metric is a measurement of a service captured at runtime. The moment of capturing a measurements is known as a metric\nevent, which consists not only of the measurement itself, but also the time at which it was captured and associated\nmetadata.\n\nSeveral types of metrics are supported:\n\n- **Counter**: A value that accumulates over time – you can think of this like an odometer on a car; it only ever goes\n  up.\n- **UpDownCounter**: A value that accumulates over time, but can also go down again. An example could be a queue length,\n  it will increase and decrease with the number of work items in the queue.\n- **Gauge**: Measures a current value at the time it is read. An example would be the fuel gauge in a vehicle. Gauges\n  are asynchronous.\n- **Histogram** (in progress): A client-side aggregation of values, such as request latencies. A histogram is a good\n  choice if you are interested in value statistics. For example: How many requests take fewer than 1s?\n\n```kotlin\n// Create a Counter that holds Int values.\nval c1 = meter.counter\u003cInt\u003e(\"event-a\")\ndelay(1000)\nc1.increment(1, \"label\" to \"pool-a\")\nc1.increment(1, \"label\" to \"pool-a\")\n\n// Create a UpDownCounter that holds Double values.\nval c2 = meter.upDownCounter\u003cDouble\u003e(\"event-b\")\ndelay(1000)\nc2.increment(2.0, \"label\" to \"pool-b\")\nc2.increment(2.0, \"label\" to \"pool-b\")\nc2.decrement(2.0, \"label\" to \"pool-b\")\n\n// Create a Gauge\nval g1 = meter.gauge\u003cInt\u003e(\"thread-pool-size\")\ndelay(1000)\ng1.record(3, \"pool\" to \"pool-a\")\ng1.record(6, \"pool\" to \"pool-b\")\n```\n\nEach time an operation is performed (i.e., a measurement is taken with a meter), an event is triggered and propagated to\nall registered appenders. For quick debugging you can register the `SimpleConsoleMeteringAppender`, which prints each\nmetric event directly to stdout. For aggregation and export, use the `SimpleMeteringCollectorAppender`:\n\n```kotlin\nval collector = SimpleMeteringCollectorAppender()\nRootLogger.Metering.appenders.register(collector)\n```\n\nThe `SimpleMeteringCollectorAppender` processes all events, updating the value for each registered instrument. It also\nprovides a method that returns a string with the collected data in the `OpenMetrics` line format.\n\n```kotlin\nval metrics = collector.toOpenMetricsLineFormatString()\nprintln(metrics)\n\n// The above example will print:\n//\n// # HELP event-a\n// # TYPE event-a counter\n// event-a {label=\"pool-a\"} 2 1730360802506\n//\n// # HELP thread-pool-size\n// # TYPE thread-pool-size gauge\n// thread-pool-size {pool=\"pool-a\"} 3 1730360802506\n// thread-pool-size {pool=\"pool-b\"} 6 1730360802506\n//\n// # HELP event-b\n// # TYPE event-b updowncounter\n// event-b {label=\"pool-b\"} 4.0 1730360802506\n```\n\n### Gauge\n\nWe also provide a convenient way to periodically poll and publish value changes, enabling automated and timely updates.\nThis approach ensures that values are recorded consistently, which is particularly useful for monitoring changes over\ntime and minimizing manual intervention.\n\n```kotlin\nmeter.gauge\u003cInt\u003e(\"thread-pool-size\").poll(every = 10.seconds) {\n    record(3, \"pool\" to \"pool-a\")\n    record(6, \"pool\" to \"pool-b\")\n}\n```\n\nUsing this method, values are automatically recorded at regular intervals, making it ideal for tracking metrics in\ndynamic environments.\n\n## Appenders\n\n### Logging\n\n| Appender                           | Platform    | Description                                             |\n|------------------------------------|-------------|---------------------------------------------------------|\n| `SimpleConsoleLoggingAppender`     | All         | Default. Prints color-coded log events to stdout.       |\n| `SimpleJsonConsoleLoggingAppender` | All         | Prints log events as JSON to stdout.                    |\n| `AndroidLoggingAppender`           | Android     | Routes events to Android Logcat via `android.util.Log`. |\n| `AppleLoggingAppender`             | iOS / macOS | Routes events to Apple's Unified Logging via `NSLog`.   |\n\n### Tracing\n\n| Appender                       | Platform | Description                    |\n|--------------------------------|----------|--------------------------------|\n| `SimpleConsoleTracingAppender` | All      | Prints trace events to stdout. |\n\n### Metering\n\n| Appender                          | Platform | Description                                                   |\n|-----------------------------------|----------|---------------------------------------------------------------|\n| `SimpleConsoleMeteringAppender`   | All      | Prints metric events to stdout.                               |\n| `SimpleMeteringCollectorAppender` | All      | Collects metrics and exposes them in OpenMetrics line format. |\n\n### Flow-based base appenders\n\nAbstract classes for building custom appenders with async, coroutine-backed processing.\n\n| Appender                     | Description                                                                            |\n|------------------------------|----------------------------------------------------------------------------------------|\n| `FlowAppender`               | Base class. Processes events asynchronously via a Kotlin Flow.                         |\n| `FlowBufferedAppender`       | Extends `FlowAppender` with configurable buffering and overflow strategy.              |\n| `FlowFloodProtectedAppender` | Extends `FlowAppender` with rate-limiting to drop excess events and prevent flooding.  |\n| `BatchAppender`              | Extends `FlowAppender` to accumulate events into fixed-size batches before processing. |\n\n### Prevent log/trace flooding.\n\n_Log rate spikes are common and often go unnoticed. They could be an indication that something went terribly wrong or\nthat a high-traffic system was unintentionally configured with verbose logging._\n\nAt times, it's crucial to reduce the volume of logs and traces to prevent unnecessary costs. In our solution, we can\nleverage Kotlin's Flow to manage log streams efficiently by dropping excess log messages when needed. For example, the\n`FlowFloodProtectedAppender` is designed specifically for this scenario. It not only limits the flood of log messages\nbut also reports the number of dropped messages, giving you visibility into how much data is being filtered out.\n\n```kotlin\nclass SimpleFloodProtectedAppender(\n    requestPerSecond: Int,\n    burstDurationMillis: Int\n) : FlowFloodProtectedAppender\u003cLoggingEvent\u003e(requestPerSecond, burstDurationMillis) {\n    override suspend fun handle(event: LoggingEvent) = event.print()\n}\n\nRootLogger.Logging.appenders.unregisterAll()\nRootLogger.Logging.appenders.register(\n    SimpleFloodProtectedAppender(requestPerSecond = 50, burstDurationMillis = 100)\n)\n\nrepeat(1_000_000) {\n    log.info(\"$it\")\n}\n\n// The above will produce the following output:\n// 115 2024-10-24T07:19:34.707789Z [native-1] - INFO  Main - 0\n// 116 2024-10-24T07:19:34.707869Z [native-1] - INFO  Main - 1\n// 117 2024-10-24T07:19:34.707884Z [native-1] - INFO  Main - 2\n// 118 2024-10-24T07:19:34.707899Z [native-1] - INFO  Main - 3\n// # ...\n// # After some ~4k logs starts to drop.\n// 991339 2024-10-24T07:19:38.294933Z [native-1] - INFO  Main - 991224\n// 2024-10-24T07:19:38.295050Z [native-13] - WARN  FlowFloodProtectedAppender - Dropped 6556 log messages due to flooding (total dropped: 987299).\n// 995897 2024-10-24T07:19:38.314454Z [native-1] - INFO  Main - 995782\n// 2024-10-24T07:19:38.315134Z [native-19] - WARN  FlowFloodProtectedAppender - Dropped 4557 log messages due to flooding (total dropped: 991856).\n```\n\nTo tackle similar issues, we can apply dynamic rate-limiting based on system load or log severity, prioritizing critical\nlogs while dropping less important ones during high-traffic periods. Batching or buffering logs can also help optimize\nprocessing, ensuring important logs are preserved without overwhelming the system. This reduces costs and maintains log\nintegrity.\n\n## Examples\n\nFor more detailed examples take also a look at the `examples` module.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmyrgeorge%2Flog4k","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmyrgeorge%2Flog4k","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmyrgeorge%2Flog4k/lists"}