diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f41c207..345fafa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,16 @@ For a list of breaking changes, check [here](#breaking-changes). Babashka proper: +- Add `clojure.tools.logging` with `taoensso.timbre` as the default implementation - Passing form on Windows with question mark breaks evaluation [#889](https://github.com/babashka/babashka/issues/889) - `(read-line)` is buggy in REPL [#899](https://github.com/babashka/babashka/issues/899) - Add `java.io.FileInputStream`. This fixes compatibility with [replikativ/hasch](https://github.com/replikativ/hasch). - `babashka.tasks/clojure` with `:dir` option doesn't resolve deps in `:dir` [#914](https://github.com/babashka/babashka/issues/914) - Support `pprint/formatter-out` [#922](https://github.com/babashka/babashka/issues/922) - Support `pprint/cl-format` with `with-out-str` [#930](https://github.com/babashka/babashka/issues/930) +- Compatibility with `org.clojure/data.json {:mvn/version "2.4.0}"` +- Support passing `GITLIBS` via `:extra-env` in `clojure` to set git lib dir: + `(clojure {:extra-env {"GITLIBS" ".gitlib"}} ,,,) [#934](https://github.com/babashka/babashka/issues/934)` Deps.clj: diff --git a/deps.edn b/deps.edn index e68e04de..275b302e 100644 --- a/deps.edn +++ b/deps.edn @@ -37,7 +37,9 @@ org.clojure/core.match {:mvn/version "1.0.0"} hiccup/hiccup {:mvn/version "2.0.0-alpha2"} rewrite-clj/rewrite-clj {:mvn/version "1.0.605-alpha"} - selmer/selmer {:mvn/version "1.12.40"}} + selmer/selmer {:mvn/version "1.12.40"} + com.taoensso/timbre {:mvn/version "5.1.2"} + org.clojure/tools.logging {:mvn/version "1.1.0"}} :aliases {:babashka/dev {:main-opts ["-m" "babashka.main"]} :profile diff --git a/project.clj b/project.clj index 72f22857..2b2c2787 100644 --- a/project.clj +++ b/project.clj @@ -23,7 +23,9 @@ [cheshire "5.10.0"] [nrepl/bencode "1.1.0"] [borkdude/sci.impl.reflector "0.0.1"] - [org.clojure/test.check "1.1.0"]] + [org.clojure/test.check "1.1.0"] + [com.taoensso/timbre "5.1.2"] + [org.clojure/tools.logging "1.1.0"]] :profiles {:feature/xml {:source-paths ["feature-xml"] :dependencies [[org.clojure/data.xml "0.2.0-alpha6"]]} :feature/yaml {:source-paths ["feature-yaml"] diff --git a/src/babashka/impl/timbre.clj b/src/babashka/impl/timbre.clj new file mode 100644 index 00000000..803cf80b --- /dev/null +++ b/src/babashka/impl/timbre.clj @@ -0,0 +1,137 @@ +(ns babashka.impl.timbre + (:require [clojure.tools.logging] + [clojure.tools.logging.impl :as impl] + [sci.core :as sci] + [taoensso.encore :as enc :refer [have]] + [taoensso.timbre :as timbre])) + +;;;; timbre + +(def tns (sci/create-ns 'taoensso.timbre nil)) + +(defn- fline [and-form] (:line (meta and-form))) + +(defmacro log! ; Public wrapper around `-log!` + "Core low-level log macro. Useful for tooling, etc. + * `level` - must eval to a valid logging level + * `msg-type` - must eval to e/o #{:p :f nil} + * `opts` - ks e/o #{:config :?err :?ns-str :?file :?line :?base-data :spying?} + Supports compile-time elision when compile-time const vals + provided for `level` and/or `?ns-str`." + [level msg-type args & [opts]] + (have [:or nil? sequential?] args) ; To allow -> (delay [~@args]) + (let [{:keys [?ns-str] :or {?ns-str (str @sci/ns)}} opts] + ;; level, ns may/not be compile-time consts: + (when-not (timbre/-elide? level ?ns-str) + (let [{:keys [config ?err ?file ?line ?base-data spying?] + :or {config 'taoensso.timbre/*config* + ?err :auto ; => Extract as err-type v0 + ?file @sci/file + ;; NB waiting on CLJ-865: + ?line (fline &form)}} opts + + ?file (when (not= ?file "NO_SOURCE_PATH") ?file) + + ;; Identifies this particular macro expansion; note that this'll + ;; be fixed for any fns wrapping `log!` (notably `tools.logging`, + ;; `slf4j-timbre`, etc.): + callsite-id + (hash [level msg-type args ; Unevaluated args (arg forms) + ?ns-str ?file ?line (rand)])] + + `(taoensso.timbre/-log! ~config ~level ~?ns-str ~?file ~?line ~msg-type ~?err + (delay [~@args]) ~?base-data ~callsite-id ~spying?))))) + +(defn make-ns [ns sci-ns ks] + (reduce (fn [ns-map [var-name var]] + (let [m (meta var) + no-doc (:no-doc m) + doc (:doc m) + arglists (:arglists m)] + (if no-doc ns-map + (assoc ns-map var-name + (sci/new-var (symbol var-name) @var + (cond-> {:ns sci-ns + :name (:name m)} + (:macro m) (assoc :macro true) + doc (assoc :doc doc) + arglists (assoc :arglists arglists))))))) + {} + (select-keys (ns-publics ns) ks))) + +(def config (sci/new-dynamic-var '*config* timbre/*config* {:ns tns})) + +(defn swap-config! [f & args] + (apply sci/alter-var-root config f args)) + +(defn set-level! [level] (swap-config! (fn [m] (assoc m :min-level level)))) + +(def timbre-namespace + (assoc (make-ns 'taoensso.timbre tns ['trace 'tracef 'debug 'debugf + 'info 'infof 'warn 'warnf + 'error 'errorf + '-log! 'with-level + 'println-appender 'spit-appender]) + 'log! (sci/copy-var log! tns) + '*config* config + 'swap-config! (sci/copy-var swap-config! tns) + 'set-level! (sci/copy-var set-level! tns))) + +;;;; clojure.tools.logging + +(defn- force-var "To support dynamic vars, etc." + [x] (if (instance? clojure.lang.IDeref x) (deref x) x)) + +(deftype Logger [logger-ns-str timbre-config] + clojure.tools.logging.impl/Logger + + (enabled? [_ level] + ;; No support for per-call config + (timbre/may-log? level logger-ns-str + (force-var timbre-config))) + + (write! [_ level throwable message] + (log! level :p + [message] ; No support for pre-msg raw args + {:config (force-var timbre-config) ; No support for per-call config + :?ns-str logger-ns-str + :?file nil ; No support + :?line nil ; '' + :?err throwable}))) + +(deftype LoggerFactory [get-logger-fn] + clojure.tools.logging.impl/LoggerFactory + (name [_] "Timbre") + (get-logger [_ logger-ns] (get-logger-fn logger-ns))) + +(alter-var-root + #'clojure.tools.logging/*logger-factory* + (fn [_] + (LoggerFactory. + (enc/memoize (fn [logger-ns] (Logger. (str logger-ns) config)))))) + +(def lns (sci/create-ns 'clojure.tools.logging nil)) + +(defmacro log + "Evaluates and logs a message only if the specified level is enabled. See log* + for more details." + ([level message] + `(clojure.tools.logging/log ~level nil ~message)) + ([level throwable message] + `(clojure.tools.logging/log ~(deref sci/ns) ~level ~throwable ~message)) + ([logger-ns level throwable message] + `(clojure.tools.logging/log clojure.tools.logging/*logger-factory* ~logger-ns ~level ~throwable ~message)) + ([logger-factory logger-ns level throwable message] + `(let [logger# (impl/get-logger ~logger-factory ~logger-ns)] + (if (impl/enabled? logger# ~level) + (clojure.tools.logging/log* logger# ~level ~throwable ~message))))) + +(def tools-logging-namespace + (assoc (make-ns 'clojure.tools.logging lns ['debug 'debugf 'info 'infof 'warn 'warnf 'error 'errorf + 'logp 'logf '*logger-factory* 'log*]) + 'log (sci/copy-var log lns))) + +(def lins (sci/create-ns 'clojure.tools.logging.impl nil)) + +(def tools-logging-impl-namespace + (make-ns 'clojure.tools.logging.impl lins ['get-logger 'enabled?])) diff --git a/src/babashka/main.clj b/src/babashka/main.clj index b96ae97c..30cd7a77 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -33,6 +33,8 @@ [babashka.impl.socket-repl :as socket-repl] [babashka.impl.tasks :as tasks :refer [tasks-namespace]] [babashka.impl.test :as t] + [babashka.impl.timbre :refer [timbre-namespace tools-logging-namespace + tools-logging-impl-namespace]] [babashka.impl.tools.cli :refer [tools-cli-namespace]] [babashka.nrepl.server :as nrepl-server] [babashka.wait :as wait] @@ -350,7 +352,10 @@ Use bb run --help to show this help output. 'babashka.process process-namespace 'clojure.core.server clojure-core-server 'babashka.deps deps-namespace - 'babashka.tasks tasks-namespace} + 'babashka.tasks tasks-namespace + 'taoensso.timbre timbre-namespace + 'clojure.tools.logging tools-logging-namespace + 'clojure.tools.logging.impl tools-logging-impl-namespace} features/xml? (assoc 'clojure.data.xml @(resolve 'babashka.impl.xml/xml-namespace)) features/yaml? (assoc 'clj-yaml.core @(resolve 'babashka.impl.yaml/yaml-namespace) 'flatland.ordered.map @(resolve 'babashka.impl.ordered/ordered-map-ns))