Logging in Clojure

Here’s the translation of the Go logging example to Clojure, along with explanations in Markdown format suitable for Hugo:

(ns logging-example
  (:require [clojure.tools.logging :as log]
            [clojure.data.json :as json]
            [clojure.string :as str]))

(defn main []
  ;; Simply invoking functions from the `clojure.tools.logging` library
  ;; uses a standard logger, which is already pre-configured for
  ;; reasonable logging output.
  (log/info "standard logger")

  ;; Loggers can be configured with different appenders and layouts
  ;; to set their output format. Here's an example of setting up a
  ;; custom appender with a specific pattern layout.
  (let [layout (org.apache.log4j.PatternLayout. "%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c - %m%n")
        appender (doto (org.apache.log4j.ConsoleAppender.)
                   (.setLayout layout))]
    (.addAppender (org.apache.log4j.Logger/getRootLogger) appender)
    (log/info "with custom layout"))

  ;; We can create custom loggers for different parts of our application
  (def mylog (log/get-logger "MyLogger"))
  (log/info mylog "from mylog")

  ;; We can change the name of existing loggers
  (alter-var-root #'mylog (constantly (log/get-logger "OhMyLogger")))
  (log/info mylog "from renamed mylog")

  ;; Loggers can have custom output targets
  (def string-writer (java.io.StringWriter.))
  (let [layout (org.apache.log4j.PatternLayout. "%d [%t] %-5p %c - %m%n")
        appender (doto (org.apache.log4j.WriterAppender.)
                   (.setLayout layout)
                   (.setWriter string-writer))]
    (.addAppender (org.apache.log4j.Logger/getRootLogger) appender)
    (log/info "hello")
    (println "from string-writer:" (str/trim-newline (.toString string-writer))))

  ;; For structured logging, we can use JSON format
  (defn log-structured [msg & kvs]
    (let [data (apply hash-map kvs)]
      (println (json/write-str (merge {:time (java.time.LocalDateTime/now)
                                       :level "INFO"
                                       :msg msg}
                                      data)))))

  (log-structured "hi there")
  (log-structured "hello again" :key "val" :age 25))

(main)

This Clojure code demonstrates various logging techniques similar to the original example. Here’s a breakdown of the key points:

  1. We use the clojure.tools.logging library for basic logging functionality.

  2. Custom log formats are achieved by configuring Log4j appenders and layouts.

  3. We can create and rename loggers for different parts of the application.

  4. Custom output targets are demonstrated using a StringWriter.

  5. For structured logging, we create a custom function that outputs JSON-formatted log entries.

To run this program, you would typically save it as a .clj file and execute it using a Clojure runtime environment. The output would look similar to this:

2023-08-22 10:45:16 INFO [main] logging-example - standard logger
2023-08-22 10:45:16.904 [main] INFO  logging-example - with custom layout
2023-08-22 10:45:16 INFO [main] MyLogger - from mylog
2023-08-22 10:45:16 INFO [main] OhMyLogger - from renamed mylog
from string-writer: 2023-08-22 10:45:16 [main] INFO  logging-example - hello
{"time":"2023-08-22T10:45:16.904166391","level":"INFO","msg":"hi there"}
{"time":"2023-08-22T10:45:16.904178985","level":"INFO","msg":"hello again","key":"val","age":25}

Note that the exact timestamps will depend on when you run the program. This example demonstrates various logging techniques in Clojure, including basic logging, custom formatting, logger management, custom output targets, and structured logging with JSON output.