diff --git a/src/jolt/clojure/tools/logging.clj b/src/jolt/clojure/tools/logging.clj deleted file mode 100644 index e963f06..0000000 --- a/src/jolt/clojure/tools/logging.clj +++ /dev/null @@ -1,338 +0,0 @@ -;;; logging.clj -- delegated logging for Clojure - -;; by Alex Taggart -;; July 27, 2009 - -;; Copyright (c) Alex Taggart, July 2009. All rights reserved. The use -;; and distribution terms for this software are covered by the Eclipse -;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -;; which can be found in the file epl-v10.html at the root of this -;; distribution. By using this software in any fashion, you are -;; agreeing to be bound by the terms of this license. You must not -;; remove this notice, or any other, from this software. -(ns ^{:author "Alex Taggart" - :doc "Logging macros which delegate to a specific logging implementation. - - A logging implementation is selected at runtime when this namespace is first - loaded. For more details, see the documentation for *logger-factory*. - - If you want to test that your code emits specific log messages, see the - clojure.tools.logging.test namespace."} - clojure.tools.logging - (:use - [clojure.string :only [trim-newline]] - [clojure.pprint :only [code-dispatch pprint with-pprint-dispatch]]) - (:require [clojure.tools.logging.impl :as impl])) - -(def ^{:doc - "The default agent used for performing logging when direct logging is - disabled. See log* for details." :dynamic true} - *logging-agent* (agent nil :error-mode :continue)) - -(def ^{:doc - "The set of levels that will require using an agent when logging from within a - running transaction. Defaults to #{:info :warn}. See log* for details." :dynamic true} - *tx-agent-levels* #{:info :warn}) - -(def ^{:doc - "Overrides the default rules for choosing between logging directly or via an - agent. Defaults to nil. See log* for details." :dynamic true} - *force* nil) - -(defn log* - "Attempts to log a message, either directly or via an agent; does not check if - the level is enabled. - - For performance reasons, an agent will only be used when invoked within a - running transaction, and only for logging levels specified by - *tx-agent-levels*. This allows those entries to only be written once the - transaction commits, and are discarded if it is retried or aborted. As - corollary, other levels (e.g., :debug, :error) will be written even from - failed transactions though at the cost of repeat messages during retries. - - One can override the above by setting *force* to :direct or :agent; all - subsequent writes will be direct or via an agent, respectively." - [logger level throwable message] - (if (case *force* - :agent true - :direct false - (and (clojure.lang.LockingTransaction/isRunning) - (*tx-agent-levels* level))) - (send-off *logging-agent* - (fn [_#] (impl/write! logger level throwable message))) - (impl/write! logger level throwable message)) - nil) - -(declare ^{:dynamic true} *logger-factory*) ; default LoggerFactory instance for calling impl/get-logger - -(defmacro log - "Evaluates and logs a message only if the specified level is enabled. See log* - for more details." - ([level message] - `(log ~level nil ~message)) - ([level throwable message] - `(log ~*ns* ~level ~throwable ~message)) - ([logger-ns level throwable message] - `(log *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) - (log* logger# ~level ~throwable ~message))))) - -(defmacro logp - "Logs a message using print style args. Can optionally take a throwable as its - second arg. See level-specific macros, e.g., debug. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([level message & more] [level throwable message & more])} - [level x & more] - (if (or (instance? String x) (nil? more)) ; optimize for common case - `(log ~level (print-str ~x ~@more)) - `(let [logger# (impl/get-logger *logger-factory* ~*ns*)] - (if (impl/enabled? logger# ~level) - (let [x# ~x] - (if (instance? Throwable x#) ; type check only when enabled - (log* logger# ~level x# (print-str ~@more)) - (log* logger# ~level nil (print-str x# ~@more)))))))) - -(defmacro logf - "Logs a message using a format string and args. Can optionally take a - throwable as its second arg. See level-specific macros, e.g., debugf. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([level fmt & fmt-args] [level throwable fmt & fmt-args])} - [level x & more] - (if (or (instance? String x) (nil? more)) ; optimize for common case - `(log ~level (format ~x ~@more)) - `(let [logger# (impl/get-logger *logger-factory* ~*ns*)] - (if (impl/enabled? logger# ~level) - (let [x# ~x] - (if (instance? Throwable x#) ; type check only when enabled - (log* logger# ~level x# (format ~@more)) - (log* logger# ~level nil (format x# ~@more)))))))) - -(defmacro enabled? - "Returns true if the specific logging level is enabled. Use of this macro - should only be necessary if one needs to execute alternate code paths beyond - whether the log should be written to." - ([level] - `(enabled? ~level ~*ns*)) - ([level logger-ns] - `(impl/enabled? (impl/get-logger *logger-factory* ~logger-ns) ~level))) - -(defmacro spy - "Evaluates expr and may write the form and its result to the log. Returns the - result of expr. Defaults to :debug log level." - ([expr] - `(spy :debug ~expr)) - ([level expr] - `(let [a# ~expr] - (log ~level - (let [s# (with-out-str - (with-pprint-dispatch code-dispatch ; need a better way - (pprint '~expr) - (print "=> ") - (pprint a#)))] - (trim-newline s#))) - a#))) - -(defmacro spyf - "Evaluates expr and may write (format fmt result) to the log. Returns the - result of expr. Defaults to :debug log level. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - ([fmt expr] - `(spyf :debug ~fmt ~expr)) - ([level fmt expr] - `(let [a# ~expr] - (log ~level (format ~fmt a#)) - a#))) - -(defn log-stream - "Creates a PrintStream that will output to the log at the specified level." - [level logger-ns] - (let [logger (impl/get-logger *logger-factory* logger-ns)] - (java.io.PrintStream. - (proxy [java.io.ByteArrayOutputStream] [] - (flush [] - ; deal with reflection in proxy-super - (let [^java.io.ByteArrayOutputStream this this] - (proxy-super flush) - (let [message (.trim (.toString this))] - (proxy-super reset) - (if (> (.length message) 0) - (log* logger level nil message)))))) - true))) - -(let [orig (atom nil) ; holds original System.out and System.err - monitor (Object.)] ; sync monitor for calling setOut/setErr - (defn log-capture! - "Captures System.out and System.err, piping all writes of those streams to - the log. If unspecified, levels default to :info and :error, respectively. - The specified logger-ns value will be used to namespace all log entries. - - Note: use with-logs to redirect output of *out* or *err*. - - Warning: if the logging implementation is configured to output to System.out - (as is the default with java.util.logging) then using this function will - result in StackOverflowException when writing to the log." - ; Implementation Notes: - ; - only set orig when nil to preserve original out/err - ; - no enabled? check before making streams since that may change later - ([logger-ns] - (log-capture! logger-ns :info :error)) - ([logger-ns out-level err-level] - (locking monitor - (compare-and-set! orig nil [System/out System/err]) - (System/setOut (log-stream out-level logger-ns)) - (System/setErr (log-stream err-level logger-ns))))) - (defn log-uncapture! - "Restores System.out and System.err to their original values." - [] - (locking monitor - (when-let [[out err :as v] @orig] - (swap! orig (constantly nil)) - (System/setOut out) - (System/setErr err))))) - -(defmacro with-logs - "Evaluates exprs in a context in which *out* and *err* write to the log. The - specified logger-ns value will be used to namespace all log entries. - - By default *out* and *err* write to :info and :error, respectively." - {:arglists '([logger-ns & body] - [[logger-ns out-level err-level] & body])} - [arg & body] - ; Implementation Notes: - ; - no enabled? check before making writers since that may change later - (let [[logger-ns out-level err-level] (if (vector? arg) - arg - [arg :info :error])] - `(binding [*out* (java.io.OutputStreamWriter. - (log-stream ~out-level ~logger-ns)) - *err* (java.io.OutputStreamWriter. - (log-stream ~err-level ~logger-ns))] - ~@body))) - -;; level-specific macros - -(defmacro trace - "Trace level logging using print-style args. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([message & more] [throwable message & more])} - [& args] - `(logp :trace ~@args)) - -(defmacro debug - "Debug level logging using print-style args. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([message & more] [throwable message & more])} - [& args] - `(logp :debug ~@args)) - -(defmacro info - "Info level logging using print-style args. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([message & more] [throwable message & more])} - [& args] - `(logp :info ~@args)) - -(defmacro warn - "Warn level logging using print-style args. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([message & more] [throwable message & more])} - [& args] - `(logp :warn ~@args)) - -(defmacro error - "Error level logging using print-style args. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([message & more] [throwable message & more])} - [& args] - `(logp :error ~@args)) - -(defmacro fatal - "Fatal level logging using print-style args. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([message & more] [throwable message & more])} - [& args] - `(logp :fatal ~@args)) - -(defmacro tracef - "Trace level logging using format. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} - [& args] - `(logf :trace ~@args)) - -(defmacro debugf - "Debug level logging using format. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} - [& args] - `(logf :debug ~@args)) - -(defmacro infof - "Info level logging using format. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} - [& args] - `(logf :info ~@args)) - -(defmacro warnf - "Warn level logging using format. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} - [& args] - `(logf :warn ~@args)) - -(defmacro errorf - "Error level logging using format. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} - [& args] - `(logf :error ~@args)) - -(defmacro fatalf - "Fatal level logging using format. - Use the 'logging.readable' namespace to avoid wrapping args in pr-str." - {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} - [& args] - `(logf :fatal ~@args)) - -(defn- call-str [str] - (let [fq-sym (symbol str) - ns-str (or (namespace fq-sym) - (throw (RuntimeException. - (format "The value of the clojure.tools.logging.factory system property is not fully-qualified: %s" - (pr-str str))))) - ns-sym (symbol ns-str) - _ (try - (require ns-sym) - (catch Exception ex - (throw (RuntimeException. - (format "Could not resolve namespace for %s. Either it does not exist or it has a (circular) dependency on clojure.tools.logging." - (pr-str str)))))) - fn-sym (symbol (name fq-sym)) - fn-var (ns-resolve ns-sym fn-sym)] - (if fn-var - (fn-var) - (throw (RuntimeException. - (format "Could not resolve var for %s." - (pr-str str))))))) - -(defn- find-factory [] - (if-let [factory-fn-str (System/getProperty "clojure.tools.logging.factory")] - (call-str factory-fn-str) - (impl/find-factory))) - -(def ^:dynamic *logger-factory* - "An instance satisfying the clojure.tools.logging.impl/LoggerFactory protocol, - which allows uniform access to an underlying logging implementation. - - The default value will be obtained by invoking a no-arg function named by the - \"clojure.tools.logging.factory\" system property, or if unset, by invoking - clojure.tools.logging.impl/find-factory. - - After loading, this var can be programmatically changed to a different - LoggerFactory implementation via binding or alter-var-root. - - See the various factory functions in clojure.tools.logger.impl." - (find-factory)) diff --git a/src/jolt/clojure/tools/logging/impl.clj b/src/jolt/clojure/tools/logging/impl.clj deleted file mode 100644 index 41efb8a..0000000 --- a/src/jolt/clojure/tools/logging/impl.clj +++ /dev/null @@ -1,50 +0,0 @@ -;; 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) diff --git a/test/spec/host-interop-spec.janet b/test/spec/host-interop-spec.janet index 19f8c04..39d228f 100644 --- a/test/spec/host-interop-spec.janet +++ b/test/spec/host-interop-spec.janet @@ -259,34 +259,25 @@ ["file-seq finds files" "true" "(do (require '[clojure.java.io :as io]) (pos? (count (filter (fn [f] (.isFile f)) (file-seq (io/file \"docs\"))))))"]) -# The REAL clojure.tools.logging (vendored verbatim) runs on jolt: a jolt -# clojure.tools.logging.impl backend (stderr LoggerFactory) plus host shims -# (agent/send-off, clojure.lang.LockingTransaction/isRunning, a clojure.pprint -# subset, clojure.string/trim-newline) and the defmacro/syntax-quote/ns fixes. -# These cases assert the public API behaves; the level rows return nil because -# the real level macros return the (nil) result of log* when enabled. -(defspec "host-interop / clojure.tools.logging" - ["info returns nil" "nil" "(do (require '[clojure.tools.logging :as log]) (log/info \"hi\" 42))"] - ["debug suppressed" "nil" "(do (require '[clojure.tools.logging :as log]) (log/debug \"x\"))"] - ["debug skips args" "0" "(do (require '[clojure.tools.logging :as log]) (let [a (atom 0)] (log/debug (reset! a 9)) @a))"] - ["enabled? info" "true" "(do (require '[clojure.tools.logging :as log]) (log/enabled? :info))"] - ["enabled? debug" "false" "(do (require '[clojure.tools.logging :as log]) (log/enabled? :debug))"] - ["spy returns value" "3" "(do (require '[clojure.tools.logging :as log]) (log/spy :info (+ 1 2)))"] - ["impl factory name" "\"jolt/stderr\"" - "(do (require '[clojure.tools.logging.impl :as impl]) (impl/name (impl/find-factory)))"] - # vars that exist only in the real library (not in a hand-rolled shim) - ["real lib *tx-agent-levels*" "#{:info :warn}" - "(do (require '[clojure.tools.logging :as log]) log/*tx-agent-levels*)"] - ["real lib log-capture! present" "true" - "(do (require '[clojure.tools.logging :as log]) (fn? log/log-capture!))"] - ["real lib spyf present" "true" - "(do (require '[clojure.tools.logging :as log]) (boolean (resolve 'log/spyf)))"]) - -# Host shims that let the real clojure.tools.logging load: no STM (a transaction -# is never running) and a minimal clojure.pprint (used by spy). +# Host shims that let libraries like clojure.tools.logging load: no STM (a +# transaction is never running) and a minimal clojure.pprint (used by spy). (defspec "host-interop / logging host shims" ["LockingTransaction/isRunning" "false" "(clojure.lang.LockingTransaction/isRunning)"] ["pprint writes value" "\"[1 2 3]\\n\"" "(do (require '[clojure.pprint :as pp]) (with-out-str (pp/pprint [1 2 3])))"] ["with-pprint-dispatch runs body" "42" "(do (require '[clojure.pprint :as pp]) (pp/with-pprint-dispatch pp/code-dispatch 42))"]) + +# Language capabilities that real-world macros like clojure.tools.logging exercise: +# multi-arity defmacro dispatch, conditional-eval macros (short-circuit like +# debug), and macros that eval+print+return (like spy). Self-contained — no +# external library needed. +(defspec "host-interop / macro dispatch & short-circuit patterns" + ["conditional-eval suppresses" "0" + "(do (def ^:dynamic *enabled* false) (defmacro when-on [& body] `(when *enabled* ~@body)) (let [a (atom 0)] (when-on (reset! a 9)) @a))"] + ["conditional-eval fires" "9" + "(do (def ^:dynamic *enabled* true) (defmacro when-on [& body] `(when *enabled* ~@body)) (let [a (atom 0)] (when-on (reset! a 9)) @a))"] + ["spy-like eval+print+return" "3" + "(do (defmacro spylog [expr] `(let [v# ~expr] (println v#) v#)) (spylog (+ 1 2)))"] + ["multi-arity 4 dispatch" "[:single :double :triple :quad]" + "(do (defmacro ml ([a] :single) ([a b] :double) ([a b c] :triple) ([a b c d] :quad)) [(ml 1) (ml 1 2) (ml 1 2 3) (ml 1 2 3 4)])"])