{"id":15062830,"url":"https://github.com/brunobonacci/mulog","last_synced_at":"2025-05-14T07:09:54.291Z","repository":{"id":37431423,"uuid":"219197715","full_name":"BrunoBonacci/mulog","owner":"BrunoBonacci","description":"μ/log is a micro-logging library that logs events and data, not words!","archived":false,"fork":false,"pushed_at":"2025-03-27T10:34:54.000Z","size":4144,"stargazers_count":502,"open_issues_count":8,"forks_count":47,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-05-13T01:37:56.347Z","etag":null,"topics":["clojure","events","logging","mulog","observability","tracing"],"latest_commit_sha":null,"homepage":"https://cljdoc.org/d/com.brunobonacci/mulog/","language":"Clojure","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/BrunoBonacci.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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}},"created_at":"2019-11-02T18:39:20.000Z","updated_at":"2025-04-22T20:56:34.000Z","dependencies_parsed_at":"2022-07-12T13:19:44.331Z","dependency_job_id":"36e430a7-fb4c-4e11-a7d9-2467d893fd25","html_url":"https://github.com/BrunoBonacci/mulog","commit_stats":{"total_commits":400,"total_committers":23,"mean_commits":"17.391304347826086","dds":0.07999999999999996,"last_synced_commit":"f93c789af0820b1f06c904ce257511d136c49360"},"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BrunoBonacci%2Fmulog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BrunoBonacci%2Fmulog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BrunoBonacci%2Fmulog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BrunoBonacci%2Fmulog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BrunoBonacci","download_url":"https://codeload.github.com/BrunoBonacci/mulog/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254092789,"owners_count":22013290,"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":["clojure","events","logging","mulog","observability","tracing"],"created_at":"2024-09-24T23:47:09.318Z","updated_at":"2025-05-14T07:09:49.281Z","avatar_url":"https://github.com/BrunoBonacci.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# μ/log\n[![Clojars Project](https://img.shields.io/clojars/v/com.brunobonacci/mulog.svg)](https://clojars.org/com.brunobonacci/mulog)\n[![cljdoc badge](https://cljdoc.org/badge/com.brunobonacci/mulog)](https://cljdoc.org/d/com.brunobonacci/mulog/)\n[![CircleCi](https://img.shields.io/circleci/project/BrunoBonacci/mulog.svg)](https://circleci.com/gh/BrunoBonacci/mulog)\n![last-commit](https://img.shields.io/github/last-commit/BrunoBonacci/mulog.svg)\n\n![mulog](./doc/images/mulog.png)\n\n***μ/log*** *(Pronounced: /mjuːlog/)* is a micro-logging library that logs events and data, not words!\n\n\n\u003e From the Greek letter **μ**, **mu** *(Pronunciation: /mjuː/)* \u003cbr/\u003e\n\u003e The twelfth letter of the\n\u003e Greek alphabet (Μ, μ), often used as a prefix for *micro-* which is\n\u003e 10\u003csup\u003e-6\u003c/sup\u003e in the SI (System of Units). Lowercase letter \"`u`\" is often\n\u003e substituted for \"`μ`\" when the Greek character is not typographically\n\u003e available.\u003cp/\u003e\n\u003e *(source: [https://en.wikipedia.org/wiki/Mu_(letter)](https://en.wikipedia.org/wiki/Mu_(letter)))*\n\u003e \u003cbr/\u003e\n\n\n## Features\n\nHere some features and key design decisions that make ***μ/log*** special:\n\n  * Effortlessly, logs events as data points.\n  * No need to construct strings that then need to be deconstructed later.\n  * Fast, extremely fast, under **300 nanoseconds** per log entry\n  * Memory bound; no unbounded use of memory\n  * All the processing and rendering happens asynchronously.\n  * Ability to add contextual logging.\n  * Adding publishers won't affect logging performances\n  * Extremely easy to create *stateful* publishers for new systems\n  * Wide range of publishers available ([see available list](./doc/publishers))\n  * *Event logs are useful, but not as important as process flow\n    (therefore preferable to drop events rather than crashing the\n    process)*\n  * Because it is cheap to log events, you can freely log plenty.\n  * And events are **just data** so can process, enrich, filter,\n    aggregate, visualise the data with your own tools.\n\n\n## Motivation\n\nIt is not the intention of ***µ/log*** to be a logging system in the\nsense of Log4j et al. In any significant project I worked in the last\n15 years, logging text messages resulted in a large amount of strings\nwhich was hard to make sense of, thus mostly ignored. ***µ/log***'s\nidea is to replace the \"*3 Pillars of Observability*\" with a more\nfundamental concept: \"**the event**\". Event-based data is easy to\nindex, search, augment, aggregate and visualise therefore can easily\nreplace traditional logs, metrics and traces.\n\nExisting logging libraries are based on a design from the 80s and\nearly 90s.  Most of the systems at the time where developed in\nstandalone servers where logging messages to console or file was the\npredominant thing to do. Logging was mostly providing debugging\ninformation and system behavioural introspection.\n\nMost of modern systems are distributed in virtualized machines that\nlive in the cloud. These machines could disappear any time. In this\ncontext logging on the local file system isn't useful as logs are\neasily lost if virtual machines are destroyed. Therefore it is common\npractice to use log collectors and centralized log processors. The ELK\nstack it has been predominant in this space for years, but there are a\nmultitude of other commercial and open-source products.\n\nMost of these systems have to deal with non structured data\nrepresented as formatted strings in files. The process of extracting\ninformation out of these strings is very tedious, error prone, and\ndefinitely not fun. But the question is: **why did we encode these as\nstrings in the first place?** This is just because existing log\nframeworks, which have been redesigned in various decades follow the\nsame structure as when systems lived on the same single server for\ndecades.\n\nI believe we need the break free of these anachronistic designs and use\nevent loggers, *not message loggers*, which are designed for dynamic\ndistributed systems living in cloud and using centralized log\naggregators. *So here is ***μ/log*** designed for this very purpose.*\n\nWatch my talk on ***μ/log*** at the *London Clojurians Meetup*:\n\n[![μ/log and the next 100 logging systems](https://img.youtube.com/vi/P1149dWnl3k/0.jpg)](https://www.youtube.com/watch?v=P1149dWnl3k)\n\n## Table of contents\n\n\u003c!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --\u003e\n\n - [Features](#features)\n - [Motivation](#motivation)\n - [Table of contents](#table-of-contents)\n - [Usage](#usage)\n     - [Use of context](#use-of-context)\n - [Best practices](#best-practices)\n - [***μ/trace***](#μtrace)\n - [Publishers](#publishers)\n - [Additional topics](#additional-topics)\n - [Contributions](#contributions)\n - [Related projects](#related-projects)\n - [License](#license)\n\n\u003c!-- markdown-toc end --\u003e\n\n\n## Usage\n\nIn order to use the library add the dependency to your `project.clj`\n\n``` clojure\n;; Leiningen project\n[com.brunobonacci/mulog \"0.9.0\"]\n\n;; deps.edn format\n{:deps { com.brunobonacci/mulog {:mvn/version \"0.9.0\"}}}\n```\n\nCurrent version: [![Clojars Project](https://img.shields.io/clojars/v/com.brunobonacci/mulog.svg)](https://clojars.org/com.brunobonacci/mulog)\n\n\nThen require the namespace:\n\n``` clojure\n(ns your-ns\n  (:require [com.brunobonacci.mulog :as μ]))\n\n;; or for the more ASCII traditionalists\n(ns your-ns\n  (:require [com.brunobonacci.mulog :as u]))\n```\n\nCheck the [online documentation](https://cljdoc.org/d/com.brunobonacci/mulog/CURRENT)\n\nThen instrument your code with the log you deem useful. The general structure is\n\n``` clojure\n(μ/log event-name, key1 value1, key2 value2, ... keyN valueN)\n```\n\nYou can add as many key-value pairs as you deem useful to express the event in your system.\n\nFor example:\n``` clojure\n;; good to use namespaced keywords for the event-name\n(μ/log ::hello :to \"New World!\")\n```\n\nHowever you will NOT be able to see any events until you add a\npublisher which will take your events and send them to a distributed\nlogger or your local console (if you are developing).\n\n``` clojure\n(μ/start-publisher! {:type :console})\n```\n\nAt this point you should be able to see the previous event in your\nREPL terminal and it will look as follows:\n\n``` clojure\n{:mulog/trace-id #mulog/flake \"4VTBeu2scrIEMle9us8StnmvRrj9ThWP\", :mulog/timestamp 1587500402972, :mulog/event-name :your-ns/hello, :mulog/namespace \"your-ns\", :to \"New World!\"}\n```\n\nHere are some example of events you might want to log.\n\n``` clojure\n;; The general form is\n(μ/log ::event-name, :key1 \"value1\", :key2 :value2, :keyN \"valueN\")\n\n;; examples\n(μ/log ::system-started :version \"0.1.0\" :init-time 32)\n\n(μ/log ::user-logged :user-id \"1234567\" :remote-ip \"1.2.3.4\" :auth-method :password-login)\n\n(μ/log ::http-request :path \"/orders\", :method :post, :remote-ip \"1.2.3.4\", :http-status 201, :request-time 129)\n\n(def x (RuntimeException. \"Boom!\"))\n(μ/log ::invalid-request :exception x, :user-id \"123456789\", :items-requested 47)\n\n(μ/log ::position-updated :poi \"1234567\" :location {:lat 51.4978128, :lng -0.1767122} )\n```\n\nAll above are examples of events you might want to track, collect and\naggregate on it in a specialized timeseries database.\n\n### Use of context\n\nAdding events which are rich in attributes and dimensions is extremely\nuseful, however it is not easy to have all the attributes and\ndimensions at your disposal everywhere in the code. To get around\nthis problem ***μ/log*** supports the use of context.\n\nThere are two levels of context, a global level and a local one.\n\nThe global context allows you to define properties and values which\nwill be added to all the events logged afterwards.\n\nFor example:\n\n``` clojure\n(μ/log ::system-started :init-time 32)\n;; {:mulog/timestamp 1572709206048, :mulog/event-name :your-ns/system-started, :mulog/namespace \"your-ns\", :init-time 32}\n\n;; set global context\n(μ/set-global-context! {:app-name \"mulog-demo\", :version \"0.1.0\", :env \"local\"})\n\n(μ/log ::system-started :init-time 32)\n;; {:mulog/event-name :your-ns/system-started,\n;;  :mulog/timestamp  1587501375129,\n;;  :mulog/trace-id   #mulog/flake \"4VTCYUcCs5KRbiRibgulnns3l6ZW_yxk\",\n;;  :mulog/namespace  \"your-ns\",\n;;  :app-name         \"mulog-demo\",\n;;  :env              \"local\",\n;;  :init-time        32,\n;;  :version          \"0.1.0\"}\n```\n\nTypically, you will set the global context once in your main function\nat the starting of your application with properties which are valid\nfor all events emitted by the process. Use `set-global-context!` to\nspecify a given value, or `update-global-context!` with a update\nfunction to change some of the values. Examples of properties you\nshould consider adding in the global context are `app-name`,\n`version`, `environment`, `process-id`, `host-ip`, `os-type`,\n`jvm-version` etc etc\n\n\nThe second type of context is the (thread) local context. It can be\nused to inject information about the current processing and all the\nevents within the scope of the context will inherit the properties\nand their values.\n\nFor example the following line will contain all the properties of the\n*global context*, all the properties of the *local context* and all\n*inline properties*.\n\n``` clojure\n(μ/with-context {:order \"abc123\"}\n  (μ/log ::item-processed :item-id \"sku-123\" :qt 2))\n\n;; {:mulog/event-name :your-ns/item-processed,\n;;  :mulog/timestamp  1587501473472,\n;;  :mulog/trace-id   #mulog/flake \"4VTCdCz6T_TTM9bS5LCwqMG0FhvSybkN\",\n;;  :mulog/namespace  \"your-ns\",\n;;  :app-name         \"mulog-demo\",\n;;  :env              \"local\",\n;;  :item-id          \"sku-123\",\n;;  :order            \"abc123\",\n;;  :qt               2,\n;;  :version          \"0.1.0\"}\n```\n\nThe local context can be nested:\n\n``` clojure\n(μ/with-context {:transaction-id \"tx-098765\"}\n  (μ/with-context {:order \"abc123\"}\n    (μ/log ::item-processed :item-id \"sku-123\" :qt 2)))\n\n;; {:mulog/event-name :your-ns/item-processed,\n;;  :mulog/timestamp  1587501492168,\n;;  :mulog/trace-id   #mulog/flake \"4VTCeIc_FNzCjegzQ0cMSLI09RqqC2FR\",\n;;  :mulog/namespace  \"your-ns\",\n;;  :app-name         \"mulog-demo\",\n;;  :env              \"local\",\n;;  :item-id          \"sku-123\",\n;;  :order            \"abc123\",\n;;  :qt               2,\n;;  :transaction-id   \"tx-098765\",\n;;  :version          \"0.1.0\"}\n```\n\nLocal context works across function boundaries:\n\n``` clojure\n(defn process-item [sku quantity]\n    ;; ... do something\n    (μ/log ::item-processed :item-id sku :qt quantity)\n    ;; ... do something\n    )\n\n(μ/with-context {:order \"abc123\"}\n    (process-item \"sku-123\" 2))\n\n;; {:mulog/event-name :your-ns/item-processed,\n;;  :mulog/timestamp  1587501555926,\n;;  :mulog/trace-id   #mulog/flake \"4VTCi08XrCWQLrR8vS2nP8sG1zDTGuY_\",\n;;  :mulog/namespace  \"your-ns\",\n;;  :app-name         \"mulog-demo\",\n;;  :env              \"local\",\n;;  :item-id          \"sku-123\",\n;;  :order            \"abc123\",\n;;  :qt               2,\n;;  :version          \"0.1.0\"}\n\n```\n\n## Best practices\n\nHere some best practices to follow while logging events:\n\n  * Use namespaced keywords or qualified strings for the `event-name`\n  * Log plain values, not opaque objects, objects will be turned into strings\n    which diminishes their value\n  * Do not log mutable values, since rendering is done asynchronously\n    you could be logging a different state. If values are mutable\n    capture the current state (deref) and log it.\n  * Avoid logging deeply nested maps, they are hard to query.\n  * Log timestamps with milliseconds precision.\n  * Use global context to enrich events with application name\n    (`:app-name`), version (`:version`), environment (`:env`), host,\n    OS pid, and other useful information so that it is always possible\n    to determine the source of the event.\n    See [example here](https://github.com/BrunoBonacci/mulog/blob/master/examples/roads-disruptions/src/com/brunobonacci/disruptions/main.clj#L44-L46).\n  * If you have to log an error/exception put the exception object\n    with a `:exception` key. For example:\n    ```clojure\n    (try\n      (something)\n      (catch Exception x\n        (μ/log ::actionX :exception x :status :failed)))\n    ```\n    It will be easier to search for all the error in Elasticsearch\n    just by looking the presence of the `exception` key\n    (Elasticsearch query example `exception:*`)\n\n## ***μ/trace***\n![since v0.2.0](https://img.shields.io/badge/since-v0.2.0-brightgreen)\n\n![mutrace](./doc/images/mutrace.png)\n\n***μ/trace*** *(Pronounced: /mjuːtrace/)* is a micro distributed\ntracing library with the focus on tracking data with custom attributes.\n\n***μ/trace*** is a subsystem of ***μ/log*** and it relies heavily on\nit.  While the objective of ***μ/log*** is to record and publish a\nevent which happens in a single point in time, the objective of\n***μ/trace*** is to record and publish an event that spans over a\nshort period of time, and potentially, spans across multiple systems.\n\n***μ/trace*** can be used within a single system and it will provide\naccurate data around instrumented operation of that system. ***μ/trace***\ncan also be used in a distributed setup and in conjunction with other\ndistributed tracers such as [Zipkin](https://zipkin.io/) and participate\ninto distributed traces.\n\n***μ/trace*** data points are not confined to distributed tracers,\nbut the data can be used and interpreted in Elasticsearch, in real-time\nstreaming system which use Apache Kafka etc.\n\nAssume that you have a complex operation which you want to track the\nrate, the outcome, the latency and have contextual information about\nthe call.\n\nOne example of such calls is the call to an external service or database\nto retrieve the current product availability.\n\nHere an example of such call:\n\n``` clojure\n;; example call to external service\n(defn product-availability [product-id]\n  (http/get availability-service {:product-id product-id}))\n```\n\nWe want to track how long this operation takes and if it fails, what's\nthe reason. With ***μ/trace*** we can instrument the request as follow:\n\n``` clojure\n;; same require as mulog\n;; (require '[com.brunobonacci.mulog :as μ])\n\n;; wrap the call to the `product-availability` function with μ/trace\n(μ/trace ::availability\n  []\n  (product-availability product-id))\n```\n\n***μ/trace*** will start a timer before calling `(product-availability\nproduct-id)` and when the execution completes it will log an event\nusing ***μ/log***.  To the caller it will be like calling\n`(product-availability product-id)` directly as the caller will receive the\nevaluation result of the body. However, ***μ/log*** will publish the following\nevent:\n\n``` clojure\n;; {:mulog/event-name :your-ns/availability,\n;;  :mulog/timestamp 1587504242983,\n;;  :mulog/trace-id #mulog/flake \"4VTF9QBbnef57vxVy-b4uKzh7dG7r7y4\",\n;;  :mulog/root-trace #mulog/flake \"4VTF9QBbnef57vxVy-b4uKzh7dG7r7y4\",\n;;  :mulog/duration 254402837,\n;;  :mulog/namespace \"your-ns\",\n;;  :mulog/outcome :ok,\n;;  :app-name \"mulog-demo\",\n;;  :env \"local\",\n;;  :version \"0.1.0\"}\n```\n\nThere are a few things to notice here:\n  - Firstly, it inherited the global context which we set for\n    ***μ/log*** (`:app-name`, `:version` and `:env`)\n  - Next, we have the same keys which are available in ***μ/log***\n    events, such as: `:mulog/event-name`, `:mulog/timestamp`,\n    `:mulog/namespace` and `:mulog/trace-id`.\n  - In addition to the `:mulog/trace-id`, which identified this\n    particular trace event, we have two more IDs. One called\n    `:mulog/root-trace` and the second one called\n    `:mulog/parent-trace`. The latter one is missing because this\n    trace doesn't have a parent ***μ/trace*** block. The\n    `:mulog/root-trace` is the id of the originating trace which could\n    be coming from another system.  The `:mulog/root-trace` is the\n    same as the `:mulog/trace-id` because, in this example, this trace\n    is the first one (and the only one) of the stack.\n  - Next, we have `:mulog/duration` which is the duration of the evaluation of\n    the body ( the `product-availability` call) expressed in *nanoseconds*\n  - Whether the call succeeded or failed, this is specified in\n    `:mulog/outcome` which it can be `:ok` or `:error`. The latter\n    will be set in case an exception is raised, and in this case, an\n    additional `:exception` property will be added with the actual\n    exception. In case of errors, the exception will be thrown back to\n    the caller for further handling.\n\nIn the above example we are missing some contextual information.\nFor example, we know that someone is enquiring about product availability\nbut we don't know about which product. This information is available\nat the point of call, it would be nice to be able to see this information\nin the trace as well. That's easily done.\n\nLike ***μ/log*** events, we can add key/value `pairs` to the trace as well:\n\n``` clojure\n(μ/trace ::availability\n  [:product-id product-id]\n  (product-availability product-id))\n```\n\nNote that within square brackets we have added the info we need.\nBut we can go one step further. Let's assume that we had the `order-id`\nand the `user-id` who is enquiring about the availability as local context\nthen we would have the following trace event.\n\n``` clojure\n(def product-id \"2345-23-545\")\n(def order-id   \"34896-34556\")\n(def user-id    \"709-6567567\")\n\n(μ/with-context {:order order-id, :user user-id}\n  (μ/trace ::availability\n    [:product-id product-id]\n    (product-availability product-id)))\n\n;; {:mulog/event-name :your-ns/availability,\n;;  :mulog/timestamp 1587506497789,\n;;  :mulog/trace-id #mulog/flake \"4VTHCez0rr3TpaBmUQrTb2DZaYmaWFkH\",\n;;  :mulog/root-trace #mulog/flake \"4VTHCez0rr3TpaBmUQrTb2DZaYmaWFkH\",\n;;  :mulog/duration 280510026,\n;;  :mulog/namespace \"your-ns\",\n;;  :mulog/outcome :ok,\n;;  :app-name \"mulog-demo\",\n;;  :env \"local\",\n;;  :order \"34896-34556\",\n;;  :product-id \"2345-23-545\",\n;;  :user \"709-6567567\",\n;;  :version \"0.1.0\"}\n\n```\n\nOne important difference between `with-context` and the `μ/trace`\n`pairs` is that `with-context` will *propagate that information to\nall nested calls* while the `μ/trace` pairs will be only added to that\nspecific trace event and not the nested ones.\n\nIf we had the following set of nested calls:\n\n``` clojure\n(process-order)\n└── (availability)\n    ├── (warehouse-availability)\n    ├── (shopping-carts)\n    └── (availability-estimator)\n```\n\nWhere `process-order` check the `availability` of each product, and to\ncheck the availability of each product you need to verify what is\navailable in the `warehouse` as well as how many items are locked in\nin-flight shopping carts and have this information provided to an\n`estimator` you would end-up with a trace which looks like the\nfollowing:\n\n![nested traces](./doc/images/nested-traces.png)\n\n\n\n\n## Publishers\n\nPublishers allow to send the events to external system where they can\nbe stored, indexed, transformed or visualised.\n\nMost of the publishers are in separated modules to reduce the risk of\ndependencies clash. Please see the specific publisher documentation\nfor the name of the module to add in your dependencies.\n\nModules can be started as follow:\n\n``` clojure\n(def pub (μ/start-publisher! {:type :console :pretty? true}))\n```\n\nThe map contains the configuration which is specific to the publisher.\n\nIt returns a function with no arguments which when called stops the\npublisher and flushes the records currently present in the buffer.\nFinally, if the publisher implements the `java.io.Closeable` it will\ncall the `close` method to release/close external resources.\n\nHere the list of all available publishers:\n\n  - Publishers\n    - [Simple Console Publisher](./doc/publishers/simple-console-publisher.md)\n    - [Simple File Publisher](./doc/publishers/simple-file-publisher.md)\n    - [Advanced Console Publisher](./doc/publishers/advanced-console-publisher.md)\n    - [Cloudwatch Logs Publisher](./doc/publishers/cloudwatch-logs-publisher.md)\n    - [Elasticsearch Publisher](./doc/publishers/elasticsearch-publisher.md)\n    - [Jaeger Publisher](./doc/publishers/jaeger-publisher.md)\n    - [Kafka Publisher](./doc/publishers/kafka-publisher.md)\n    - [Kinesis Publisher](./doc/publishers/kinesis-publisher.md)\n    - [OpenTelemetry Publisher](./doc/publishers/open-telemetry-publisher.md)\n    - [Prometheus Publisher](./doc/publishers/prometheus-publisher.md)\n    - [Slack Publisher](./doc/publishers/slack-publisher.md)\n    - [Zipkin Publisher](./doc/publishers/zipkin-publisher.md)\n  - Special publishers\n    - [Inline Publishers](./doc/publishers/inline-publishers.md)\n    - [Custom Publishers](./doc/publishers/custom-publishers.md)\n    - [Multi Publisher](./doc/publishers/multi-publisher.md)\n  - Samplers\n    - [JVM Metrics Sampling](./doc/publishers/jvm-metrics-sampling.md)\n    - [Filesystem Metrics Sampling](./doc/publishers/filesystem-metrics-sampling.md)\n\n\n## Additional topics\n\n  * Read about [μ/log internals](./doc/mulog-internals.md)\n  * Check the [Frequently Asked Questions](./doc/faq.md)\n  * [How to write custom publishers](./doc/custom-publishers.md)\n  * [How to transfer the local-context](./doc/transfer-context.md) into a different thread.\n\n\n## Contributions\n\nI do consider the core pretty much feature complete, therefore I won't\naccept changes in the core module. However, there is loads of work\nto be done on supporting libraries and publishers for various systems,\nHere your help if welcome, you can have a look at the list of open issues\nmarked as [help wanted](https://github.com/BrunoBonacci/mulog/labels/help%20wanted).\n\n*PRs are welcome `;-)`*\n\nTo contribute:\n\n  - pick an issue you would like to work on.\n  - drop a message to the issue so that I know someone else is working on it\n  - follow the guidelines in the ticket\n  - in doubt, just ask!\n\n## Need help?\n\nIf you have questions or you need help please open an issue or post\nyour questions into [Github Discussions](https://github.com/BrunoBonacci/mulog/discussions)\nboard.\n\nAlternatively you can post a question to the [#mulog channel in the Clojurians Slack team](https://clojurians.slack.com/channels/mulog).\n\n## Related projects\n\nHere there are some other open-source projects which are related to ***μ/log***:\n\n  * [slf4j-mulog](https://gitlab.com/nonseldiha/slf4j-mulog) - a SLF4j backend for ***μ/log***.\n\n    It enables you to send your traditional logs from your existing\n    projects via ***μ/log*** and leverage all ***μ/log***'s capability\n    to filter/transform/enrich events before publishing.\n\n## Articles\n\n - [***μ/log*** hidden superpower, by Bruno Bonacci](https://redefine.io/blog/mulog-hidden-superpower/)\n - [Structured logging with mulog, by Francesco Pischedda](https://fpsd.codes/clojure-bites---structured-logging-with-mulog.html)\n\n## License\n\nCopyright © 2019-2025 Bruno Bonacci - Distributed under the [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrunobonacci%2Fmulog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrunobonacci%2Fmulog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrunobonacci%2Fmulog/lists"}