Common logging interface that wraps clojure.tools.logging in JVM Clojure and Glogi in CLJS.

The interface is the same as [[clojure.tools.logging]].

(ns metabase.util.log
  (:require
   [clojure.pprint :as pprint]
   [clojure.string :as str]
   ^{:clj-kondo/ignore [:discouraged-namespace]}
   [clojure.tools.logging]
   [clojure.tools.logging.impl]
   [metabase.config :as config]
   [metabase.util.log.capture]
   [net.cgrand.macrovich :as macros]))

Macro helper for [[logp]] in CLJS.

--------------------------------------------- CLJ-side macro helpers ---------------------------------------------

(defn- glogi-logp
  [logger-name level x more]
  `(let [level#  (glogi-level ~level)
         logger# ~logger-name]
     (when (is-loggable? logger# level#)
       (let [x# ~x]
         (if (instance? js/Error x#)
           (lambdaisland.glogi/log logger# level# (print-str ~@more) x#)
           (lambdaisland.glogi/log logger# level# (print-str x# ~@more) nil))))))

Macro helper for [[logf]] in CLJS.

(defn- glogi-logf
  [logger-name level x more]
  `(let [level#  (glogi-level ~level)
         logger# ~logger-name]
     (when (is-loggable? logger# level#)
       (let [x# ~x]
         (if (instance? js/Error x#)
           (lambdaisland.glogi/log logger# level# (format-msg ~@more) x#)
           (lambdaisland.glogi/log logger# level# (format-msg x# ~@more) nil))))))

Macro helper for [[spy]] and [[spyf]] in CLJS.

(defn- glogi-spy
  [logger-name level expr formatter]
  `(let [level#  (glogi-level ~level)
         logger# ~logger-name]
     (when (is-loggable? logger# level#)
       (let [a# ~expr
             s# (~formatter a#)]
         (lambdaisland.glogi/log logger# level# nil s#)
         a#))))

Macro helper for [[logp]] in CLJ.

(defn- tools-logp
  [logger-ns level x more]
  `(let [logger# (clojure.tools.logging.impl/get-logger clojure.tools.logging/*logger-factory* ~logger-ns)]
     (when (clojure.tools.logging.impl/enabled? logger# ~level)
       (let [x# ~x]
         (if (instance? Throwable x#)
           (clojure.tools.logging/log* logger# ~level x#  ~(if (nil? more)
                                                             ""
                                                             `(print-str ~@more)))
           (clojure.tools.logging/log* logger# ~level nil (print-str x# ~@more)))))))

Macro helper for [[logf]] in CLJ.

(defn- tools-logf
  [logger-ns level x more]
  (if (and (instance? String x) (nil? more))
    ;; Simple case: just a String and no args.
    `(let [logger# (clojure.tools.logging.impl/get-logger clojure.tools.logging/*logger-factory* ~logger-ns)]
       (when (clojure.tools.logging.impl/enabled? logger# ~level)
         (clojure.tools.logging/log* logger# ~level nil ~x)))
    ;; Full case, with formatting.
    `(let [logger# (clojure.tools.logging.impl/get-logger clojure.tools.logging/*logger-factory* ~logger-ns)]
       (when (clojure.tools.logging.impl/enabled? logger# ~level)
         (let [x# ~x]
           (if (instance? Throwable x#)
             (clojure.tools.logging/log* logger# ~level x#  (format ~@more))
             (clojure.tools.logging/log* logger# ~level nil (format x# ~@more))))))))

Implementation for prn-style logp. You shouldn't have to use this directly; prefer the level-specific macros like [[info]].

------------------------------------------------ Internal macros -------------------------------------------------

(defmacro logp
  {:arglists '([level message & more] [level throwable message & more])}
  [level x & more]
  `(do
     ~(config/build-type-case
        :dev
        `(metabase.util.log.capture/capture-logp ~(str *ns*) ~level ~x ~@more))
     ~(macros/case
        :cljs (glogi-logp (str *ns*) level x more)
        :clj  (tools-logp *ns*       level x more))))

Implementation for printf-style logf. You shouldn't have to use this directly; prefer the level-specific macros like [[infof]].

(defmacro logf
  [level x & args]
  `(do
     ~(config/build-type-case
        :dev
        `(metabase.util.log.capture/capture-logf ~(str *ns*) ~level ~x ~@args))
     ~(macros/case
        :cljs (glogi-logf (str *ns*) level x args)
        :clj  (tools-logf *ns*       level x args))))

Log one or more args at the :trace level.

--------------------------------------------------- Public API ---------------------------------------------------

(defmacro trace
  {:arglists '([& args] [e & args])}
  [& args]
  `(logp :trace ~@args))

Log a message at the :trace level by applying format to a format string and args.

(defmacro tracef
  {:arglists '([format-string & args] [e format-string & args])}
  [& args]
  `(logf :trace ~@args))

Log one or more args at the :debug level.

(defmacro debug
  {:arglists '([& args] [e & args])}
  [& args]
  `(logp :debug ~@args))

Log a message at the :debug level by applying format to a format string and args.

(defmacro debugf
  {:arglists '([format-string & args] [e format-string & args])}
  [& args]
  `(logf :debug ~@args))

Log one or more args at the :info level.

(defmacro info
  {:arglists '([& args] [e & args])}
  [& args]
  `(logp :info ~@args))

Log a message at the :info level by applying format to a format string and args.

(defmacro infof
  {:arglists '([format-string & args] [e format-string & args])}
  [& args]
  `(logf :info ~@args))

Log one or more args at the :warn level.

(defmacro warn
  {:arglists '([& args] [e & args])}
  [& args]
  `(logp :warn ~@args))

Log a message at the :warn level by applying format to a format string and args.

(defmacro warnf
  {:arglists '([format-string & args] [e format-string & args])}
  [& args]
  `(logf :warn ~@args))

Log one or more args at the :error level.

(defmacro error
  {:arglists '([& args] [e & args])}
  [& args]
  `(logp :error ~@args))

Log a message at the :error level by applying format to a format string and args.

(defmacro errorf
  {:arglists '([format-string & args] [e format-string & args])}
  [& args]
  `(logf :error ~@args))

Log one or more args at the :fatal level.

#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defmacro fatal
  {:arglists '([& args] [e & args])}
  [& args]
  `(logp :fatal ~@args))

Log a message at the :fatal level by applying format to a format string and args.

(defmacro fatalf
  {:arglists '([format-string & args] [e format-string & args])}
  [& args]
  `(logf :fatal ~@args))

Evaluates an expression, and may write both the form and its result to the log. Returns the result of expr. Defaults to the :debug level.

(defmacro spy
  ([expr] `(spy :debug ~expr))
  ([level expr]
   (macros/case
     :cljs (glogi-spy (str *ns*) level expr
                      #(str/trim-newline
                        (with-out-str
                          #_{:clj-kondo/ignore [:discouraged-var]}
                          (pprint/with-pprint-dispatch pprint/code-dispatch
                            (pprint/pprint '~expr)
                            (print "=> ")
                            (pprint/pprint %)))))
     :clj  `(clojure.tools.logging/spy ~level ~expr))))

Evaluates an expression, and may write both the form and its formatted result to the log. Defaults to the :debug level.

#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defmacro spyf
  ([fmt expr]
   `(spyf :debug ~fmt ~expr))
  ([level fmt expr]
   (macros/case
     :cljs (glogi-spy (str *ns*) level expr #(format ~fmt %))
     :clj  `(spyf ~level ~fmt ~expr))))

Turns off logs in body.

(defmacro with-no-logs
  [& body]
  `(binding [clojure.tools.logging/*logger-factory* clojure.tools.logging.impl/disabled-logger-factory]
     ~@body))