SkillAgentSearch skills...

Mulog

μ/log is a micro-logging library that logs events and data, not words!

Install / Use

/learn @BrunoBonacci/Mulog
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

μ/log

Clojars Project cljdoc badge CircleCi last-commit

mulog

μ/log (Pronounced: /mjuːlog/) is a micro-logging library that logs events and data, not words!

From the Greek letter μ, mu (Pronunciation: /mjuː/) <br/> The twelfth letter of the Greek alphabet (Μ, μ), often used as a prefix for micro- which is 10<sup>-6</sup> in the SI (System of Units). Lowercase letter "u" is often substituted for "μ" when the Greek character is not typographically available.<p/> (source: https://en.wikipedia.org/wiki/Mu_(letter)) <br/>

Features

Here some features and key design decisions that make μ/log special:

  • Effortlessly, logs events as data points.
  • No need to construct strings that then need to be deconstructed later.
  • Fast, extremely fast, under 300 nanoseconds per log entry
  • Memory bound; no unbounded use of memory
  • All the processing and rendering happens asynchronously.
  • Ability to add contextual logging.
  • Adding publishers won't affect logging performances
  • Extremely easy to create stateful publishers for new systems
  • Wide range of publishers available (see available list)
  • Event logs are useful, but not as important as process flow (therefore preferable to drop events rather than crashing the process)
  • Because it is cheap to log events, you can freely log plenty.
  • And events are just data so can process, enrich, filter, aggregate, visualise the data with your own tools.

Motivation

It is not the intention of µ/log to be a logging system in the sense of Log4j et al. In any significant project I worked in the last 15 years, logging text messages resulted in a large amount of strings which was hard to make sense of, thus mostly ignored. µ/log's idea is to replace the "3 Pillars of Observability" with a more fundamental concept: "the event". Event-based data is easy to index, search, augment, aggregate and visualise therefore can easily replace traditional logs, metrics and traces.

Existing logging libraries are based on a design from the 80s and early 90s. Most of the systems at the time where developed in standalone servers where logging messages to console or file was the predominant thing to do. Logging was mostly providing debugging information and system behavioural introspection.

Most of modern systems are distributed in virtualized machines that live in the cloud. These machines could disappear any time. In this context logging on the local file system isn't useful as logs are easily lost if virtual machines are destroyed. Therefore it is common practice to use log collectors and centralized log processors. The ELK stack it has been predominant in this space for years, but there are a multitude of other commercial and open-source products.

Most of these systems have to deal with non structured data represented as formatted strings in files. The process of extracting information out of these strings is very tedious, error prone, and definitely not fun. But the question is: why did we encode these as strings in the first place? This is just because existing log frameworks, which have been redesigned in various decades follow the same structure as when systems lived on the same single server for decades.

I believe we need the break free of these anachronistic designs and use event loggers, not message loggers, which are designed for dynamic distributed systems living in cloud and using centralized log aggregators. So here is μ/log designed for this very purpose.

Watch my talk on μ/log at the London Clojurians Meetup:

μ/log and the next 100 logging systems

Table of contents

<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> <!-- markdown-toc end -->

Usage

In order to use the library add the dependency to your project.clj

;; Leiningen project
[com.brunobonacci/mulog "0.10.1"]

;; deps.edn format
{:deps { com.brunobonacci/mulog {:mvn/version "0.10.1"}}}

Current version: Clojars Project

Then require the namespace:

(ns your-ns
  (:require [com.brunobonacci.mulog :as μ]))

;; or for the more ASCII traditionalists
(ns your-ns
  (:require [com.brunobonacci.mulog :as u]))

Check the online documentation

Then instrument your code with the log you deem useful. The general structure is

(μ/log event-name, key1 value1, key2 value2, ... keyN valueN)

You can add as many key-value pairs as you deem useful to express the event in your system.

For example:

;; good to use namespaced keywords for the event-name
(μ/log ::hello :to "New World!")

However you will NOT be able to see any events until you add a publisher which will take your events and send them to a distributed logger or your local console (if you are developing).

(μ/start-publisher! {:type :console})

At this point you should be able to see the previous event in your REPL terminal and it will look as follows:

{:mulog/trace-id #mulog/flake "4VTBeu2scrIEMle9us8StnmvRrj9ThWP", :mulog/timestamp 1587500402972, :mulog/event-name :your-ns/hello, :mulog/namespace "your-ns", :to "New World!"}

Here are some example of events you might want to log.

;; The general form is
(μ/log ::event-name, :key1 "value1", :key2 :value2, :keyN "valueN")

;; examples
(μ/log ::system-started :version "0.1.0" :init-time 32)

(μ/log ::user-logged :user-id "1234567" :remote-ip "1.2.3.4" :auth-method :password-login)

(μ/log ::http-request :path "/orders", :method :post, :remote-ip "1.2.3.4", :http-status 201, :request-time 129)

(def x (RuntimeException. "Boom!"))
(μ/log ::invalid-request :exception x, :user-id "123456789", :items-requested 47)

(μ/log ::position-updated :poi "1234567" :location {:lat 51.4978128, :lng -0.1767122} )

All above are examples of events you might want to track, collect and aggregate on it in a specialized timeseries database.

Use of context

Adding events which are rich in attributes and dimensions is extremely useful, however it is not easy to have all the attributes and dimensions at your disposal everywhere in the code. To get around this problem μ/log supports the use of context.

There are two levels of context, a global level and a local one.

The global context allows you to define properties and values which will be added to all the events logged afterwards.

For example:

(μ/log ::system-started :init-time 32)
;; {:mulog/timestamp 1572709206048, :mulog/event-name :your-ns/system-started, :mulog/namespace "your-ns", :init-time 32}

;; set global context
(μ/set-global-context! {:app-name "mulog-demo", :version "0.1.0", :env "local"})

(μ/log ::system-started :init-time 32)
;; {:mulog/event-name :your-ns/system-started,
;;  :mulog/timestamp  1587501375129,
;;  :mulog/trace-id   #mulog/flake "4VTCYUcCs5KRbiRibgulnns3l6ZW_yxk",
;;  :mulog/namespace  "your-ns",
;;  :app-name         "mulog-demo",
;;  :env              "local",
;;  :init-time        32,
;;  :version          "0.1.0"}

Typically, you will set the global context once in your main function at the starting of your application with properties which are valid for all events emitted by the process. Use set-global-context! to specify a given value, or update-global-context! with a update function to change some of the values. Examples of properties you should consider adding in the global context are app-name, version, environment, process-id, host-ip, os-type, jvm-version etc etc

The second type of context is the (thread) local context. It can be used to inject information about the current processing and all the events within the scope of the context will inherit the properties and their values.

For example the following line will contain all the properties of the global context, all the properties of the local context and all inline properties.

(μ/with-context {:order "abc123"}
  (μ/log ::item-processed :item-id "sku-123" :qt 2))

;; {:mulog/event-name :your-ns/item-processed,
;;  :mulog/timestamp  1587501473472,
;;  :mulog/trace-id   #mulog/flake "4VTCdCz6T_TTM9bS5LCwqMG0FhvSybkN",
;;  :mulog/namespace  "your-ns",
;;  :app-name         "mulog-demo",
;;  :env              "local",
;;  :item-id          "sku-123",
;;  :order            "abc123",
;;  :qt               2,
;;  :version          "0.1.0"}

The local context can be nested:

(μ/with-context {:transaction-id "tx-098765"}
  (μ/with-context {:order "abc123"}
    (μ/log ::item-processed :item-id "sku-123" :qt 2)))

;; {:mulog/event-name :your-ns/item-processed,
;;  :mulog/timestamp  1587501492168,
;;  :mulog/trace-id   #mulog/flake "4VTCeIc_FNzCjegzQ0cMSLI09RqqC2FR",
;;  :mulog/namespace  "your-ns",
;;  :app-name  

Related Skills

View on GitHub
GitHub Stars529
CategoryDevelopment
Updated14h ago
Forks48

Languages

Clojure

Security Score

100/100

Audited on Apr 1, 2026

No findings