{"id":14972040,"url":"https://github.com/mjfryc/mjaron-tinyloki-java","last_synced_at":"2025-10-26T17:31:23.712Z","repository":{"id":46312246,"uuid":"423144454","full_name":"mjfryc/mjaron-tinyloki-java","owner":"mjfryc","description":"Tiny Grafana Loki client (log sender) written in pure Java 1.8 without any dependencies.","archived":false,"fork":false,"pushed_at":"2024-12-27T13:25:02.000Z","size":395,"stargazers_count":14,"open_issues_count":7,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-31T21:52:37.303Z","etag":null,"topics":["android","android-library","grafana","grafana-loki","java","java-8","java-android","java-se","java8","library","logger","logging","logs","loki"],"latest_commit_sha":null,"homepage":"","language":"Java","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/mjfryc.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":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-31T12:40:51.000Z","updated_at":"2025-01-07T10:22:01.000Z","dependencies_parsed_at":"2024-12-13T00:31:26.560Z","dependency_job_id":null,"html_url":"https://github.com/mjfryc/mjaron-tinyloki-java","commit_stats":{"total_commits":162,"total_committers":5,"mean_commits":32.4,"dds":0.5432098765432098,"last_synced_commit":"f0a8cfd04f5a17b801b72646e468de4c19baa803"},"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjfryc%2Fmjaron-tinyloki-java","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjfryc%2Fmjaron-tinyloki-java/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjfryc%2Fmjaron-tinyloki-java/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjfryc%2Fmjaron-tinyloki-java/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mjfryc","download_url":"https://codeload.github.com/mjfryc/mjaron-tinyloki-java/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238375206,"owners_count":19461568,"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":["android","android-library","grafana","grafana-loki","java","java-8","java-android","java-se","java8","library","logger","logging","logs","loki"],"created_at":"2024-09-24T13:46:16.943Z","updated_at":"2025-10-26T17:31:23.699Z","avatar_url":"https://github.com/mjfryc.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mjaron-tinyloki-java\n\nBuilding  \n[![Java CI with Gradle](https://github.com/mjfryc/mjaron-tinyloki-java/actions/workflows/gradle.yml/badge.svg)](https://github.com/mjfryc/mjaron-tinyloki-java/actions/workflows/gradle.yml)\n[![Gradle Package](https://github.com/mjfryc/mjaron-tinyloki-java/actions/workflows/gradle-publish.yml/badge.svg)](https://github.com/mjfryc/mjaron-tinyloki-java/actions/workflows/gradle-publish.yml)\n[![Maven Central](https://img.shields.io/maven-central/v/io.github.mjfryc/mjaron-tinyloki-java?color=dark-green\u0026style=flat)](https://search.maven.org/artifact/io.github.mjfryc/mjaron-tinyloki-java/)\n\nCode quality  \n[![CodeQL](https://github.com/mjfryc/mjaron-tinyloki-java/actions/workflows/codeql.yml/badge.svg)](https://github.com/mjfryc/mjaron-tinyloki-java/actions/workflows/codeql.yml)\n![snyk.io](https://snyk.io/test/github/mjfryc/mjaron-tinyloki-java/badge.svg)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mjfryc_mjaron-tinyloki-java\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=mjfryc_mjaron-tinyloki-java)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=mjfryc_mjaron-tinyloki-java\u0026metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=mjfryc_mjaron-tinyloki-java)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=mjfryc_mjaron-tinyloki-java\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=mjfryc_mjaron-tinyloki-java)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=mjfryc_mjaron-tinyloki-java\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=mjfryc_mjaron-tinyloki-java)\n\nUnit tests  \n![Coverage](.github/badges/jacoco.svg)\n![Branches](.github/badges/branches.svg)\n\nTiny [Grafana Loki](https://grafana.com/oss/loki/) client (log sender) written in pure Java 1.8 without any external\ndependencies. One of Grafana Loki third-party clients mentioned\nin [documentation](https://grafana.com/docs/loki/v3.4.x/send-data/). It is customizable and easy to integrate\nbut is not optimized for performance.\nSee other reliable clients mentioned in documentation.\n\n* Implements JSON variant of [Loki API](https://grafana.com/docs/loki/latest/api/#post-lokiapiv1push)\n* Works with **Android** and **Java SE**\n* Thread safe\n* May added to [java.util.logging](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html)\n  as a log [Handler](https://docs.oracle.com/javase/8/docs/api/java/util/logging/Handler.html)\n  (and then may be called from [SLF4J](https://www.slf4j.org/)\n  using [slf4j-jdk14 provider](https://www.slf4j.org/manual.html)).\n\n## Examples\n\n* See [./integration-test-server/README.md](./integration-test-server/README.md) to start the Grafana Loki server\n  collecting the logs from examples.\n* See [IntegrationTest.java](./src/test/java/pl/mjaron/tinyloki/IntegrationTest.java) to see more examples.\n* Gradle: `implementation 'io.github.mjfryc:mjaron-tinyloki-java:1.1.6'`\n\n### Short example\n\n```java\nimport pl.mjaron.tinyloki.*;\n\npublic class Sample {\n    public static void main(String[] args) {\n        TinyLoki loki = TinyLoki.withUrl(\"http://localhost:3100\")\n                .withBasicAuth(\"user\", \"pass\")\n                .open();\n        ILogStream logStream = loki.stream().info().l(\"topic\", \"shortExample\").open();\n        logStream.log(\"Hello world!\");\n        loki.closeSync();\n    }\n}\n```\n\n### Verbose example\n\n```java\nimport pl.mjaron.tinyloki.*;\n\npublic class Sample {\n    public static void main(String[] args) {\n\n        // Initialize the log controller instance with URL.\n        // The endpoint loki/api/v1/push will be added by default if missing.\n        // Usually creating more than one TinyLoki instance doesn't of sense.\n        // TinyLoki (its default IExecutor implementation) owns separate thread which\n        // sends logs periodically.\n        // It may be called inside try-with-resources block, but the default close()\n        // method doesn't synchronize the logs, but just interrupts the background worker\n        // thread.\n        try (TinyLoki loki = TinyLoki.withUrl(\"http://localhost:3100/loki/api/v1/push\")\n\n                // Print all diagnostic information coming from the TinyLoki library.\n                // For diagnostic purposes only.\n                // The messages are printed only if there is no log encoder -\n                // let's comment out .withGzipLogEncoder() to skip encoding.\n                .withVerboseLogMonitor(true)\n\n                // Set the custom log processing interval time.\n                // So the executor will try to send the next logs 10 seconds after\n                // the previous logs sending operation.\n                .withThreadExecutor(10 * 1000)\n\n                // Set custom time of HTTP connection establishing timeout.\n                .withConnectTimeout(10 * 1000)\n\n                // Encode the logs to limit the size of data sent.\n                // .withGzipLogEncoder()\n\n                // The BasicBuffering is set by default, but here the (not encoded)\n                // message size limit may be customized.\n                .withBasicBuffering(3 * 1024 * 1024, 10)\n\n                // The timestamp provider allows deciding what to do with logs having\n                // same labels and same message.\n                // Grafana Loki treats such logs as duplicates and ignores them,\n                // even if structured metadata is different.\n                // To receive duplicated logs, call withIncrementingTimestampProvider()\n                // to set the timestamp provider which always increases the log timestamp\n                // nanosecond value.\n                .withIncrementingTimestampProvider()\n\n                // Let's define some labels common for few streams.\n                .withLabels(Labels.of(\"topic\", \"verboseExample\").l(Labels.SERVICE_NAME, \"example_service\"))\n\n                // Initialize the library with above settings.\n                // The ThreadExecutor will create a new thread and start waiting\n                // for the logs to be sent.\n                .open()) {\n\n            // Some logs here...\n\n            ILogStream topicStream = loki.stream().open();\n            topicStream.log(\"Hello world.\");\n\n            ILogStream whiteStream = loki.stream().l(\"color\", \"white\").open();\n            whiteStream.log(\"Hello white world.\");\n\n            // Blocking method, tries to send the logs ASAP and wait for sending completion.\n            // This method returns false when timeout occurs, but true when sending has completed with success or failure.\n            boolean allHttpSendingOperationsFinished = loki.sync();\n            System.out.println(\"Are all logs processed: \" + allHttpSendingOperationsFinished);\n\n            ILogStream redStream = loki.stream().l(\"color\", \"red\").open();\n\n            // Let's attach the Grafana Loki structured metadata.\n            // In current implementation, the duplicated logs with same log line and timestamp (structured metadata doesn't matter) - is sent but may be dropped by Grafana Loki.\n            redStream.log(\"Hello red world 0\", Labels.of(\"structured_metadata_label\", 0).l(\"other_structured_metadata_label\", 'a'));\n            redStream.log(\"Hello red world 1\", Labels.of(\"structured_metadata_label\", 9).l(\"other_structured_metadata_label\", 'z'));\n\n            StreamSet streamSet = loki.streamSet().l(\"stream_set_label\", \"value\").open();\n            streamSet.debug().log(\"The debug level line. It contain the following labels: topic, stream_set_label, level\");\n            streamSet.info().log(\"The info level line.\", Labels.of(\"structured_metadata_label\", \"Of info stream set log.\"));\n\n            // Blocking method, tries to synchronize the logs than interrupt and join the execution thread.\n            // Set the custom timeout time for this operation.\n            boolean closedWithSuccess = loki.closeSync(5 * 1000);\n\n            System.out.println(\"Synced and closed with success: \" + closedWithSuccess);\n        }\n    }\n}\n```\n\n### SLF4J with java.util.logging example\n\n```groovy\ndependencies {\n    implementation(\"org.slf4j:slf4j-jdk14:2.0.17\")\n    implementation(\"org.slf4j:slf4j-api:2.0.17\")\n    implementation(\"io.github.mjfryc:mjaron-tinyloki-java:1.1.6\")\n}\n```\n\n```java\nimport pl.mjaron.tinyloki.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class Sample {\n    public static void main(String[] args) {\n\n        TinyLokiJulHandler.install(TinyLoki\n                .withUrl(\"http://localhost:3100\")\n                .withBasicAuth(\"user\", \"pass\")\n                .withLabels(Labels.of(Labels.SERVICE_NAME, \"integration_test\")));\n\n        Logger logger = LoggerFactory.getLogger(\"julIntegrationTest\");\n        logger.info(\"Hello info.\");\n    }\n}\n```\n\n## Integration\n\n### Maven Central\n\n```gradle\n    implementation 'io.github.mjfryc:mjaron-tinyloki-java:1.1.6'\n```\n\n_[Maven Central page](https://search.maven.org/artifact/io.github.mjfryc/mjaron-tinyloki-java/),_\n_[Maven Central repository URL](https://repo1.maven.org/maven2/io/github/mjfryc/mjaron-tinyloki-java/)_\n\n### GitHub Packages\n\nClick the [Packages section](https://github.com/mjfryc?tab=packages\u0026repo_name=mjaron-tinyloki-java) on the right.\n\n### Download directly\n\n1. Click the [Packages section](https://github.com/mjfryc?tab=packages\u0026repo_name=mjaron-tinyloki-java) on the right.\n2. Find and download jar package from files list to e.g. `your_project_root/libs` dir.\n3. Add this jar to project dependencies in build.gradle, e.g:\n\n```gradle\n    implementation files(project.rootDir.absolutePath + '/libs/mjaron-tinyloki-java-1.1.6.jar')\n```\n\n## Features description\n\n### Structured metadata\n\nThe [structured metadata](https://grafana.com/docs/loki/v3.4.x/get-started/labels/structured-metadata/) has been enabled\nby default in [Grafana Loki 3.0.0](https://grafana.com/docs/loki/v3.4.x/setup/upgrade/#loki-300).\nTo put structured metadata to the log line, add it as the last argument of logging method.\n\n### Preserve duplicated logs\n\nWhen sending two same logs in same time, the Loki server ignores the second one:\n\n```java\nclass Sample {\n    public static void sample() {\n        TinyLoki loki = TinyLoki.withUrl(\"http://localhost:3100\")\n                .withCurrentTimestampProvider().open();\n        ILogStream stream = loki.stream().open();\n        stream.log(\"Hello world.\");\n        stream.log(\"Two same logs.\");\n\n        // This log may be ignored if the CurrentTimestampProvider\n        // will provide the same timestamp for both same logs.\n        stream.log(\"Two same logs.\");\n\n        loki.closeSync();\n    }\n}\n```\n\nTo preserve all logs, call the `withIncrementingTimestampProvider()` to set the sequential timestamps\n(one nanosecond later). Then the Grafana Loki will not ignore duplicated logs.\n\n### Logs buffering\n\nSince the TinyLoki `v1.0.0`, the logs are buffered to optimize network load.\nThe logs buffering times (duration between two log sending operations) may be configured with:\n\n```java\npublic Settings withThreadExecutor(final int processingIntervalTime);\n```\n\nIf any buffers are full, the log data may be sent immediately.\nGrafana Loki restricts the max message size and drops too big messages.\nIf this size is exceeded, the server may respond with HTTP error `500`, e.g:\n\n```\nrpc error: code = ResourceExhausted desc = grpc: received message larger than max (6331143 vs. 4194304)\n```\n\nBy default (`BasicBuffering`) The TinyLoki tries to fill the data buffers and if the next log would exceed the size\nlimit,\nnew buffer is allocated instead. Custom buffer size may be configured with:\n\n```java\npublic Settings withBasicBuffering(final int maxMessageSize, final int maxBuffersCount);\n```\n\n## API design\n\n```mermaid\nclassDiagram\n    class Labels {\n        l(name, value) // Add the label\n    }\n\n    class ILogStream {\n        \u003c\u003cInterface\u003e\u003e\n        log(content)\n        log(timestamp, content, structuredMetadata)\n        release()\n    }\n\n    class ILogCollector {\n        \u003c\u003cInterface\u003e\u003e\n        void configure(...);\n        createStream(labels)\n        collect()\n        collectAll()\n        contentType()\n        waitForLogs(timeout)\n    }\n\n    class ILogEncoder {\n        \u003c\u003cInterface\u003e\u003e\n        contentEncoding(): String\n        encode(final byte[] what): byte[]\n    }\n\n    class ILogMonitor {\n        \u003c\u003cInterface\u003e\u003e\n        logInfo(what)\n        logError(what)\n        onConfigured(contentType, contentEncoding)\n        onEncoded(in, out)\n        send(message)\n        sendOk(status)\n        sendErr(status, message)\n        onException(exception)\n        onSync(success)\n        onStart()\n        onStop(success)\n    }\n\n    class ILogSender {\n        \u003c\u003cInterface\u003e\u003e\n        configure(logSenderSettings, logMonitor)\n        send(bytes)\n    }\n\n    class TinyLoki {\n        stream(): StreamBuilder\n        openStream(labels)\n        streamSet(): StreamSetBuilder\n        openStreamSet(labels)\n        sync()\n        close()\n    }\n\n    class GzipLogEncoder\n\n    class VerboseLogMonitor\n    class ErrorLogMonitor\n    class JsonLogStream\n    class JsonLogCollector\n\n    class Settings {\n        +start(): TinyLoki\n    }\n\n    class TinyLoki_Static {\n        +withUrl(url)$\n    }\n\n    VerboseLogMonitor --|\u003e ILogMonitor: implements\n    ErrorLogMonitor --|\u003e ILogMonitor: implements\n    ILogStream \u003c.. ILogCollector: create\n    ILogCollector --* TinyLoki\n    Labels \u003c.. ILogCollector: use\n    ILogSender --* TinyLoki\n    GzipLogEncoder --|\u003e ILogEncoder: implements\n    ILogEncoder --* TinyLoki\n    ILogMonitor --* TinyLoki\n    JsonLogStream --|\u003e ILogStream: implements\n    JsonLogCollector --|\u003e ILogCollector: implements\n    JsonLogStream \u003c.. JsonLogCollector: create\n    HttpLogSender --|\u003e ILogSender: implements\n    DummyLogSender --|\u003e ILogSender: implements\n    Settings \u003c.. TinyLoki_Static: create\n    TinyLoki \u003c.. Settings: create\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmjfryc%2Fmjaron-tinyloki-java","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmjfryc%2Fmjaron-tinyloki-java","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmjfryc%2Fmjaron-tinyloki-java/lists"}