Skip to content

Commit 90f0a4c

Browse files
committed
Add support for initializing via system property
1 parent 7a728e8 commit 90f0a4c

5 files changed

Lines changed: 162 additions & 78 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66
([despite its flaws](https://www.youtube.com/watch?v=oyLBGkS5ICk)).
77

88
## [Unreleased]
9+
### Added
10+
- Add support for explicitly selecting a logger factory by setting the
11+
`clojure.tools.logging.factory` system property.
912

1013
## [0.5.0] - 2019-07-22
1114
### Added

README.md

Lines changed: 72 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,114 @@
11
# Logging
22

3-
Logging macros which delegate to a specific logging implementation. At runtime a specific implementation is selected from, in order, slf4j, Apache commons-logging, log4j2, log4j, and finally java.util.logging.
3+
Logging macros which delegate to a specific logging implementation, selected
4+
at runtime when the `clojure.tools.logging` namespace is first loaded.
45

5-
Logging levels are specified by clojure keywords, in increasingly severe order:
6+
## Installation
7+
8+
Lastest stable release is [0.5.0]
9+
10+
Leiningen:
611

712
```clojure
8-
:trace, :debug, :info, :warn, :error, :fatal
13+
[org.clojure/tools.logging "0.5.0"]
14+
```
15+
16+
Maven:
17+
18+
```xml
19+
<dependency>
20+
<groupId>org.clojure</groupId>
21+
<artifactId>tools.logging</artifactId>
22+
<version>0.5.0</version>
23+
</dependency>
924
```
1025

11-
Logging occurs with the `log` macro, or the level-specific convenience macros (e.g., `debug`, `debugf`). Only when the specified logging level is enabled will the message arguments be evaluated and the underlying logging implementation be invoked. By default that invocation will occur via an agent when inside a running STM transaction.
26+
Gradle:
1227

13-
Unless otherwise specified, the current namespace (as identified by `*ns*`) will be used as the log-ns (similar to how the java class name is usually used). Note: you should configure your logging implementation to display the name that was passed to it; if it instead performs stack-inspection you'll see some ugly and unhelpful text in your logs.
28+
```clojure
29+
compile "org.clojure:tools.logging:0.5.0"
30+
```
1431

15-
You can redirect all java writes of `System.out` and `System.err` to the log system by calling `log-capture!`. To bind `*out*` and `*err*` to the log system invoke `with-logs`. In both cases a log-ns value must be specified in order to namespace the output.
1632

1733
## Usage
1834

19-
The latest API documentation can be found at http://clojure.github.com/tools.logging
35+
[Latest API Documentation](http://clojure.github.com/tools.logging)
2036

21-
The following short example should give you what you need to get started:
37+
Logging occurs with the `log` macro, or the level-specific convenience macros
38+
(e.g., `debug`, `debugf`). Only when the specified logging level is enabled will
39+
the message arguments be evaluated and the underlying logging implementation be
40+
invoked. By default that invocation will occur via an agent when inside a running
41+
STM transaction.
2242

23-
```clojure
24-
(ns example.math
25-
(:require [clojure.tools.logging :as log]))
26-
27-
(defn divide [x y]
28-
(log/info "dividing" x "by" y)
29-
(try
30-
(log/spyf "result is %s" (/ x y)) ; yields the result
31-
(catch Exception ex
32-
(log/error ex "There was an error in calculation"))))
33-
```
43+
### Namespacing of log entries
3444

35-
Example repl output using the configuration below:
45+
Unless otherwise specified, the current namespace (as identified by `*ns*`) will
46+
be used as the log-ns. This value can be emitted in the log entry, and used by most
47+
logging implementations when using namespace-specific logging levels.
3648

37-
```
38-
user=> (use 'my.example)
39-
nil
40-
user=> (divide 1 2)
41-
INFO example.math: dividing 1 by 2
42-
DEBUG example.math: result is 1/2
43-
1/2
44-
user=> (divide 2 0)
45-
INFO example.math: dividing 2 by 0
46-
ERROR example.math: There was an error in calculation
47-
java.lang.ArithmeticException: Divide by zero
48-
at clojure.lang.Numbers.divide(Numbers.java:156)
49-
...
50-
```
49+
Note: You should configure your logging implementation to display the name that
50+
was passed to it. If it instead performs stack-inspection you'll see some ugly
51+
and unhelpful text in your logs.
5152

52-
For those new to using a java logging library, the following is a very basic configuration for log4j. Place it in a file called `log4j.properties` and place that file (and the log4j JAR) on the classpath.
53+
### Redirecting output to logs
5354

54-
```
55-
log4j.rootLogger=INFO, console
56-
log4j.logger.example=DEBUG
57-
log4j.appender.console=org.apache.log4j.ConsoleAppender
58-
log4j.appender.console.layout=org.apache.log4j.PatternLayout
59-
log4j.appender.console.layout.ConversionPattern=%-5p %c: %m%n
60-
```
55+
You can redirect all java writes of `System.out` and `System.err` to the log
56+
system by calling `log-capture!`. To bind `*out*` and `*err*` to the log system
57+
invoke `with-logs`. In both cases a log-ns value must be specified in order to
58+
namespace the output.
6159

62-
The above will print messages to the console for `:debug` or higher if one is in the `example` namespace, and `:info` or higher in all other namespaces.
60+
## Configuration
6361

64-
### Installation
62+
_NOTE: Logging configuration (e.g., setting of logging levels, formatting) is
63+
specific to the underlying logging implementation, and is out of scope for this
64+
library._
6565

66-
Logging is available in Maven central. Add it to your Maven project's `pom.xml`:
66+
### Selecting a logging implementation
6767

68-
```xml
69-
<dependency>
70-
<groupId>org.clojure</groupId>
71-
<artifactId>tools.logging</artifactId>
72-
<version>0.5.0</version>
73-
</dependency>
74-
```
68+
To control which logging implementation is used, set the `clojure.tools.logging.factory`
69+
system property to the fully-qualified name of a no-arg function that returns an
70+
instance of `clojure.tools.logging.impl/LoggerFactory`. There are a number of
71+
factory functions provided in the `clojure.tools.logging.impl` namespace.
7572

76-
or your leiningen project.clj:
73+
[Leiningen example]:
7774

7875
```clojure
79-
[org.clojure/tools.logging "0.5.0"]
76+
:jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/slf4j-factory"]
8077
```
8178

82-
Please note the changelog below.
79+
If the system property is unset, an implementation will be automatically chosen
80+
based on whichever of the following implementations is successfully loaded first:
8381

84-
### Building Logging
82+
1. [SLF4J]
83+
2. [Apache Commons Logging]
84+
3. [Log4J 2]
85+
4. [Log4J]
86+
5. [java.util.logging]
87+
88+
The above approach is problematic given that applications often inadvertently pull
89+
in multiple logging implementations as transitive dependencies. As such, it is
90+
_strongly_ advised that you set the system property.
8591

86-
0. Clone the repo
87-
1. Make sure you have maven installed
88-
2. Run the maven build; run either:
89-
1. `mvn install`: This will produce a logging jar file in the `target`
90-
directory, and run all tests with the most recently-released build
91-
of Clojure.
9292

9393
## Thanks
9494

9595
* Chris Dean
9696
* Phil Hagelberg
9797
* Richard Newman
98+
* Sean Corfield
9899
* Timothy Pratley
99100

100101
## License
101102

102103
Copyright © 2009 Alex Taggart
103104

104105
Licensed under the EPL. (See the file epl.html.)
106+
107+
108+
[0.5.0]: https://github.com/clojure/tools.logging/tree/tools.logging-0.5.0
109+
[Leiningen example]: https://github.com/technomancy/leiningen/blob/master/doc/TUTORIAL.md#setting-jvm-options
110+
[SLF4J]: http://www.slf4j.org/
111+
[Apache Commons Logging]: https://commons.apache.org/logging
112+
[Log4J 2]: https://logging.apache.org/log4j/2.x/
113+
[Log4J]: http://logging.apache.org/log4j/1.2/
114+
[java.util.logging]: https://docs.oracle.com/en/java/javase/13/docs/api/java.logging/java/util/logging/package-summary.html

src/main/clojure/clojure/tools/logging.clj

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@
1111
;; agreeing to be bound by the terms of this license. You must not
1212
;; remove this notice, or any other, from this software.
1313
(ns ^{:author "Alex Taggart"
14-
:doc "Logging macros which delegate to a specific logging implementation. At
15-
runtime a specific implementation is selected by invoking
16-
clojure.tools.logging.impl/find-factory.
17-
18-
The logging implementation can be explicitly provided by using
19-
binding or alter-var-root to change the value of *logger-factory* to
20-
another implementation of clojure.tools.logging.impl/LoggerFactory
21-
(see also the *-factory functions in the impl namespace)."}
14+
:doc "Logging macros which delegate to a specific logging implementation.
15+
16+
A logging implementation is selected at runtime when this namespace is first
17+
loaded. For more details, see the documentation for *logger-factory*.
18+
19+
If you want to test that your code emits specific log messages, see the
20+
clojure.tools.logging.test namespace."}
2221
clojure.tools.logging
2322
(:use
2423
[clojure.string :only [trim-newline]]
@@ -283,9 +282,42 @@
283282
[& args]
284283
`(logf :fatal ~@args))
285284

286-
(def ^{:doc
287-
"An instance satisfying the impl/LoggerFactory protocol. Used internally to
288-
obtain an impl/Logger. Defaults to the value returned from impl/find-factory."
289-
:dynamic true}
290-
*logger-factory*
291-
(impl/find-factory))
285+
(defn- call-str [str]
286+
(let [fq-sym (symbol str)
287+
ns-str (or (namespace fq-sym)
288+
(throw (RuntimeException.
289+
(format "The value of the clojure.tools.logging.factory system property is not fully-qualified: %s"
290+
(pr-str str)))))
291+
ns-sym (symbol ns-str)
292+
_ (try
293+
(require ns-sym)
294+
(catch Exception ex
295+
(throw (RuntimeException.
296+
(format "Could not resolve namespace for %s. Either it does not exist or it has a (circular) dependency on clojure.tools.logging."
297+
(pr-str str))))))
298+
fn-sym (symbol (name fq-sym))
299+
fn-var (ns-resolve ns-sym fn-sym)]
300+
(if fn-var
301+
(fn-var)
302+
(throw (RuntimeException.
303+
(format "Could not resolve var for %s."
304+
(pr-str str)))))))
305+
306+
(defn- find-factory []
307+
(if-let [factory-fn-str (System/getProperty "clojure.tools.logging.factory")]
308+
(call-str factory-fn-str)
309+
(impl/find-factory)))
310+
311+
(def ^:dynamic *logger-factory*
312+
"An instance satisfying the clojure.tools.logging.impl/LoggerFactory protocol,
313+
which allows uniform access to an underlying logging implementation.
314+
315+
The default value will be obtained by invoking a no-arg function named by the
316+
\"clojure.tools.logging.factory\" system property, or if unset, by invoking
317+
clojure.tools.logging.impl/find-factory.
318+
319+
After loading, this var can be programmatically changed to a different
320+
LoggerFactory implementation via binding or alter-var-root.
321+
322+
See the various factory functions in clojure.tools.logger.impl."
323+
(find-factory))

src/test/clojure/clojure/tools/test_logging.clj

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
(ns clojure.tools.test-logging
2-
(:use clojure.test
3-
clojure.tools.logging)
2+
(:use clojure.test)
43
(:require
54
[clojure.set :as set]
5+
[clojure.tools.logging :as log :refer :all]
66
[clojure.tools.logging.impl :as impl]
77
[clojure.tools.logging.test :as log-test :refer [logged?
88
matches]]))
@@ -357,3 +357,34 @@
357357
warnf :warn
358358
errorf :error
359359
fatalf :fatal)))
360+
361+
362+
(deftest test-call-str
363+
(testing "throws exception if input is not fully-qualified"
364+
(is (thrown-with-msg? RuntimeException #"fully-qualified"
365+
(#'log/call-str "foobar"))))
366+
367+
(testing "throws exception if ns does not exist"
368+
(is (thrown-with-msg? RuntimeException #"resolve namespace"
369+
(#'log/call-str "missing.ns/some-fn"))))
370+
371+
(testing "throws exception if fn does not exist"
372+
(is (thrown-with-msg? RuntimeException #"resolve var"
373+
(#'log/call-str "external.ns/does-not-exist-fn"))))
374+
375+
(testing "yields the right factory when specified"
376+
(is (= "external.ns/good-fn"
377+
(impl/name (#'log/call-str "external.ns/factory"))))))
378+
379+
380+
(deftest test-find-factory
381+
(testing "when system property is unset, invokes impl/find-factory"
382+
(System/clearProperty "clojure.tools.logging.factory")
383+
(is (= (impl/name (impl/find-factory)) (impl/name (#'log/find-factory)))))
384+
385+
(testing "when system propery is set, yields the results"
386+
(System/setProperty "clojure.tools.logging.factory" "external.ns/factory")
387+
(is (= "external.ns/good-fn" (impl/name (#'log/find-factory)))))
388+
389+
(System/clearProperty "clojure.tools.logging.factory"))
390+

src/test/clojure/external/ns.clj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(ns external.ns
2+
"Used by clojure.tools.logging.test-logging."
3+
(:require [clojure.tools.logging.impl :as impl]))
4+
5+
(defn factory []
6+
(reify impl/LoggerFactory
7+
(name [_] "external.ns/good-fn")
8+
(get-logger [_ _] (impl/disabled-logger))))

0 commit comments

Comments
 (0)