feat: clojure.tools.logging shim for migratus (jolt-nzg)
Faithful shim of the real clojure/tools.logging public API rather than a bespoke logger. clojure.tools.logging.impl provides the Logger/LoggerFactory protocols (matching upstream signatures) plus a jolt stderr-backed factory; find-factory returns it instead of probing slf4j/log4j/jul. The macro surface (logp/logf, level macros + f-variants, log, enabled?, spy) dispatches through those protocols. Departures forced by no-JVM: log* writes directly (no agent/ LockingTransaction), and the throwable-first-arg branch is dropped since (instance? Throwable x) is always false for jolt exception values. Workarounds for two jolt gaps found en route (filed as jolt-9av, jolt-6ym): macro bodies fully-qualify impl refs because syntax-quote does not resolve ns aliases, and def docstrings are kept in comments because (def name doc value) is mishandled. stderr via __eprint. trace/debug suppressed by default (impl/*level* :info). Conformance 335/335 x3 modes.
This commit is contained in:
parent
6abcb11e92
commit
e2d33df484
2 changed files with 123 additions and 0 deletions
73
src/jolt/clojure/tools/logging.clj
Normal file
73
src/jolt/clojure/tools/logging.clj
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
;; clojure.tools.logging — jolt shim of the public API.
|
||||
;;
|
||||
;; Faithful to the upstream macro surface (logp/logf, the level macros and their
|
||||
;; f-variants, log, enabled?, spy), dispatching through the Logger/LoggerFactory
|
||||
;; protocols in clojure.tools.logging.impl. Two deliberate departures from
|
||||
;; upstream, both forced by jolt not being a JVM:
|
||||
;;
|
||||
;; - log* writes directly. The real log* may hand off to an agent when inside a
|
||||
;; running clojure.lang.LockingTransaction; jolt has neither agents nor STM.
|
||||
;; - The throwable-first-argument branch is dropped. Upstream splits args on
|
||||
;; (instance? Throwable x); on jolt that is always false for exception values,
|
||||
;; so the would-be throwable simply becomes part of the printed message. This
|
||||
;; also avoids the double-evaluation the branch would otherwise cause here.
|
||||
(ns clojure.tools.logging
|
||||
(:require [clojure.tools.logging.impl :as impl]))
|
||||
|
||||
;; The LoggerFactory used to obtain loggers. Defaults to the jolt stderr
|
||||
;; factory; rebind to plug in a different backend.
|
||||
(def ^:dynamic *logger-factory* (impl/find-factory))
|
||||
|
||||
(defn log*
|
||||
"Write a log entry through the logger. Direct, unlike upstream's agent path."
|
||||
[logger level throwable message]
|
||||
(impl/write! logger level throwable message)
|
||||
nil)
|
||||
|
||||
(defmacro logp
|
||||
"Log a message built by print-str of args, at the given level. Args are
|
||||
evaluated only when the level is enabled."
|
||||
[level & args]
|
||||
`(let [logger# (clojure.tools.logging.impl/get-logger *logger-factory* ~(str *ns*))]
|
||||
(when (clojure.tools.logging.impl/enabled? logger# ~level)
|
||||
(log* logger# ~level nil (print-str ~@args)))))
|
||||
|
||||
(defmacro logf
|
||||
"Log a clojure.core/format message at the given level. Args are evaluated
|
||||
only when the level is enabled."
|
||||
[level & args]
|
||||
`(let [logger# (clojure.tools.logging.impl/get-logger *logger-factory* ~(str *ns*))]
|
||||
(when (clojure.tools.logging.impl/enabled? logger# ~level)
|
||||
(log* logger# ~level nil (format ~@args)))))
|
||||
|
||||
(defmacro log
|
||||
([level message] `(logp ~level ~message))
|
||||
([level throwable message] `(logp ~level ~throwable ~message)))
|
||||
|
||||
(defmacro trace [& args] `(logp :trace ~@args))
|
||||
(defmacro debug [& args] `(logp :debug ~@args))
|
||||
(defmacro info [& args] `(logp :info ~@args))
|
||||
(defmacro warn [& args] `(logp :warn ~@args))
|
||||
(defmacro error [& args] `(logp :error ~@args))
|
||||
(defmacro fatal [& args] `(logp :fatal ~@args))
|
||||
|
||||
(defmacro tracef [& args] `(logf :trace ~@args))
|
||||
(defmacro debugf [& args] `(logf :debug ~@args))
|
||||
(defmacro infof [& args] `(logf :info ~@args))
|
||||
(defmacro warnf [& args] `(logf :warn ~@args))
|
||||
(defmacro errorf [& args] `(logf :error ~@args))
|
||||
(defmacro fatalf [& args] `(logf :fatal ~@args))
|
||||
|
||||
(defmacro enabled?
|
||||
"True if the given level is enabled for the (optionally given) logger ns."
|
||||
([level] `(clojure.tools.logging.impl/enabled? (clojure.tools.logging.impl/get-logger *logger-factory* ~(str *ns*)) ~level))
|
||||
([level log-ns] `(clojure.tools.logging.impl/enabled? (clojure.tools.logging.impl/get-logger *logger-factory* ~log-ns) ~level)))
|
||||
|
||||
(defmacro spy
|
||||
"Evaluate expr, log it at the given level (default :debug) as expr => value,
|
||||
and return the value."
|
||||
([expr] `(spy :debug ~expr))
|
||||
([level expr]
|
||||
`(let [a# ~expr]
|
||||
(logp ~level (print-str '~expr "=>" (pr-str a#)))
|
||||
a#)))
|
||||
50
src/jolt/clojure/tools/logging/impl.clj
Normal file
50
src/jolt/clojure/tools/logging/impl.clj
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
;; clojure.tools.logging.impl — jolt shim.
|
||||
;;
|
||||
;; The real impl probes slf4j / log4j / log4j2 / commons-logging / java.util.logging
|
||||
;; via Class/forName and reflection. jolt has no JVM, so find-factory returns a
|
||||
;; native stderr-backed factory instead. The Logger and LoggerFactory protocols
|
||||
;; match the real signatures, so consumers (and any custom factory bound through
|
||||
;; clojure.tools.logging/*logger-factory*) work unchanged.
|
||||
(ns clojure.tools.logging.impl)
|
||||
|
||||
(defprotocol Logger
|
||||
(enabled? [logger level])
|
||||
(write! [logger level throwable message]))
|
||||
|
||||
(defprotocol LoggerFactory
|
||||
(name [factory])
|
||||
(get-logger [factory logger-ns]))
|
||||
|
||||
;; Minimum level the default jolt stderr logger emits. trace and debug are
|
||||
;; suppressed by default; bind or alter-var-root this to change the threshold.
|
||||
(def ^:dynamic *level* :info)
|
||||
|
||||
(def ^:private level-order
|
||||
{:trace 0 :debug 1 :info 2 :warn 3 :error 4 :fatal 5})
|
||||
|
||||
;; The protocol method `name` shadows clojure.core/name in this namespace, so a
|
||||
;; keyword->label uses an explicit table rather than (name level).
|
||||
(defn- level-str [level]
|
||||
(case level
|
||||
:trace "TRACE" :debug "DEBUG" :info "INFO"
|
||||
:warn "WARN" :error "ERROR" :fatal "FATAL"
|
||||
(str level)))
|
||||
|
||||
(defn- stderr-logger [logger-ns]
|
||||
(reify Logger
|
||||
(enabled? [_ level]
|
||||
(>= (get level-order level 2) (get level-order *level* 2)))
|
||||
(write! [_ level throwable message]
|
||||
(__eprint
|
||||
(str (level-str level) " " logger-ns " - " message
|
||||
(when throwable (str " " throwable)))))))
|
||||
|
||||
(def ^:private jolt-stderr-factory
|
||||
(reify LoggerFactory
|
||||
(name [_] "jolt/stderr")
|
||||
(get-logger [_ logger-ns] (stderr-logger (str logger-ns)))))
|
||||
|
||||
(defn find-factory
|
||||
"jolt has no JVM logging backends; return the native stderr factory."
|
||||
[]
|
||||
jolt-stderr-factory)
|
||||
Loading…
Add table
Add a link
Reference in a new issue