A wrapper around goog.log
inspired by pedestal.log
.
For more info see the accompanying blog post: ClojureScript logging with goog.log, and Logging in Practice with Glögi and Pedestal.
WARNING: There are two packages with different artefact IDs published on clojars. One is
com.lambdaisland/glogi
and another islambdaisland/glogi
(without thecom.
). Please migrate your dependency tocom.lambdaisland/glogi
as this is the only one which recieves new updates. We apologise for this confusion, but we had to make some changes to stay in accordance with the clojars policy.
deps.edn
com.lambdaisland/glogi {:mvn/version "1.3.169"}
project.clj
[com.lambdaisland/glogi "1.3.169"]
It is recommended to initialize Glögi at the top of your main namespace, so that no log messages are lost.
(ns my.app
(:require [lambdaisland.glogi :as log]
[lambdaisland.glogi.console :as glogi-console]))
(glogi-console/install!)
(log/set-levels
{:glogi/root :info ;; Set a root logger level, this will be inherited by all loggers
'my.app.thing :trace ;; Some namespaces you might want detailed logging
'my.app.other :error ;; or for others you only want to see errors.
})
(log/info :hello {:message "Hello, world!"})
Result in your browser console (but pretty with colors):
[my.app] {:hello {:message "Hello, world!"}}
Before you can start logging you need to install a handler that knows where to
output the log messages (browser console, in a div, ...).
(glogi-console/install!)
is recommended. It contains some smarts so that your
Clojure data is logged nicely. When cljs-devtools is active then it will pass
data structures unchanged to js/console.log
so devtools can render them. If
not then it will stringify and colorize them so you get pretty EDN in your
console, instead of seeing the implementation details of ClojureScript
persistent data structures.
Log functions take key/value pairs.
In goog.log
, which glogi is based on, loggers have hierarchical names, with
segments separated by dots. If you use Glogi's logging macros then it will
automatically get the logger based on the namespace name.
When you call a logging function it will check the log level of the logger to decide if the message should be output ot not. To find this level the logger will go up the hierarchy until it finds a logger with an explicit level set. This is usually the root logger, but it doesn't have to be.
So say you have these namespaces:
my.app.ui.subs
my.app.ui.events
my.app.api
my.app.api.routes
some.lib.config
some.lib.print
You can set the level for individual namespaces
(log/set-levels '{my.app.ui.subs :debug
my.app.ui.events :config
...})
But you can also set the level for a subtree of namespaces. So say you're debugging the API, which uses some.lib
.
(log/set-levels '{my.app.api :all
some.lib :debug
...})
This is really convenient and powerful. By sprinkling your code with logging at various levels you can easily get insight in what a particular part is doing, on demand.
Instead of adding more and more print statements as you debug, and then deleting them afterwards, you can add increasingly detailed levels of logging instead. Later when you find yourself in the same waters you can dial the verbosity up or down as you see fit.
Glogi is based on goog.log, and so it understand the log levels that goog.log provides. Glogi also aims to be at least partially API compatible with pedestal.log, and so we provide extra log levels that internally map to goog.log levels.
pedestal | goog.log | value | description |
---|---|---|---|
:off | Infinity | Special value to disable logging | |
:shout | 1200 | Critical error | |
:error | :severe | 1000 | Serious failure |
:warn | :warning | 900 | Potential problem, but program continues |
:info | :info | 800 | Informational message, e.g. to make background tasks visible |
:config | 700 | Configuration info | |
:debug | :fine | 500 | Step-by-step debug messages |
:trace | :finer | 400 | More verbose, detailed tracing messages |
:finest | 300 | Highly verbose and detailed tracing | |
:all | 0 | Special value to show all log messages |
There are also two special levels, :all
and :off
, which you can use in
set-levels
to turn logging up to the maximum, or turn it off instead.
It is recommended to use a consistent set of logging methods, for instance to use only the pedestal version, possibly augmented by the goog.log levels that don't have a pedestal equivalent.
for instance:
(log/shout ,,,)
(log/severe ,,,)
(log/error ,,,)
(log/warn ,,,)
(log/info ,,,)
(log/config ,,,)
(log/debug ,,,)
(log/trace ,,,)
(log/finest ,,,)
If you are using lambdaisland.glogi.console
, then these levels will also
influence with js/console
method is used, as well as the color used to print
the namespace name.
There is a convenience macro spy
which you can use to have a quick look at a
value. It outputs the form, the value, and returns the value so you can simply
wrap any expression you want to see the value. Spy expressions are logged at the
:debug
level.
(let [x (spy (+ 1 1))
y (spy (+ x x))]
(+ x y))
;;=> 6
[my.ns] {:spy (+ 1 1) :=> 2}
[my.ns] {:spy (+ x x) :=> 4}
As you may have noticed, unlike js/console.log
or prn
, spy
returns
the value of the expression so you can wrap expressions with it without
disrupting the flow of your program.
If you pass multiple values to spy, all will be printed:
(fn [{:keys [host port path protocol] :as config}]
(spy host port)
;;...
)
[my.ns] {:spy [host "localhost" port 8080]}
This will be logged slighly diferently on Clojure. This is because Pedestal doesn't directly support multiple values to spy, and Glogc tries to be a thin wrapper:
[my.ns] {:spy [host port] :value ["localhost" 8080]}
If you call spy on multiple values, the last value will be returned, analogous
to do
.
Two keywords have a special meaning in logging calls.
:exception
use this if you want to log an exception, this will make sure you get a proper stacktrace in the browser console:lambdaisland.glogi/logger
name of the logger to use, defaults to(str *ns*)
When using lambdaisland.glogi.console/install!
it will try to detect what the
best logging strategy is for your environment.
- if cljs-devtools is detected then it will log ClojureScript objects directly
- if it detects a browser that is not pre-chromium IE/Edge, then it will use
console.log
"%c"
CSS-based colorization - all other cases it logs plain text
This behaviour can be changed by setting lambdaisland.glogi.console.colorize
in :closure-defines
in your compiler options.
"auto"
the autodetect behavior described above (default)"raw"
always log ClojureScript objects directly"true"
format using CSS"false"
format as plain text
You can have each message get prefixed with a timestamp like this:
:closure-defines
{lambdaisland.glogi.console/timestamp "true"}
Production builds typically have goog.DEBUG
set to false
. This strips out
some development checks, it also strips out logging. If you still want to see
logs on production then add this to your ClojureScript compiler options:
:closure-defines {goog.DEBUG false
goog.debug.LOGGING_ENABLED true}
The lambdaisland.glogc
namespace provides a cross-platform (cljc) API, which
uses Glogi on ClojureScript, and io.pedestal.log
on Clojure. This way it's
easy to do logging from cljc
code, or just to have a consistent logging setup
without having to wonder what kind of file you are in.
Note that the pedestal.log dependency is "BYO" (bring your own), you need to add it explicitly to your dependencies.
(ns my.ns
(:require [lambdaisland.glogc :as log))
(log/debug :foo :bar)
goog.log
has more distinct log levels than Pedestal. We provide macros for all
of them, on Clojure they simply map to the nearest equivalent.
- finest -> trace
- finer -> trace
- fine -> debug
- config -> info
Many thanks to Nextjournal for coming up with an interesting problem, and giving me the opportunity to explore and solve it.
glogi is part of a growing collection of quality Clojure libraries created and maintained by the fine folks at Gaiwan.
Pay it forward by becoming a backer on our Open Collective, so that we may continue to enjoy a thriving Clojure ecosystem.
You can find an overview of our projects at lambdaisland/open-source.
Everyone has a right to submit patches to glogi, and thus become a contributor.
Contributors MUST
- adhere to the LambdaIsland Clojure Style Guide
- write patches that solve a problem. Start by stating the problem, then supply a minimal solution.
*
- agree to license their contributions as MPL 2.0.
- not break the contract with downstream consumers.
**
- not break the tests.
Contributors SHOULD
- update the CHANGELOG and README.
- add tests for new functionality.
If you submit a pull request that adheres to these rules, then it will almost certainly be merged immediately. However some things may require more consideration. If you add new dependencies, or significantly increase the API surface, then we need to decide if these changes are in line with the project's goals. In this case you can start by writing a pitch, and collecting feedback on it.
*
This goes for features too, a feature needs to solve a problem. State the problem it solves, then supply a minimal solution.
**
As long as this project has not seen a public release (i.e. is not on Clojars)
we may still consider making breaking changes, if there is consensus that the
changes are justified.
Copyright © 2019-2021 Arne Brasseur and Contributors
Licensed under the term of the Mozilla Public License 2.0, see LICENSE.