Code Monkey home page Code Monkey logo

timbre-json-appender's Introduction

timbre-json-appender

CircleCI Clojars Project

A structured log appender for Timbre using jsonista.

Makes extracting data from logs easier in for example AWS CloudWatch Logs and GCP Stackdriver Logging.

A Timbre log invocation maps to JSON messages the following way:

(timbre/error (IllegalStateException. "Not logged in") "Action failure" :user-id 1)
=>
{"timestamp": "2019-07-03T10:00:08Z", # Always included
 "level": "error",                    # ditto
 "thread": "nRepl-session-...",       # ditto
 "hostname": "localhost",             # ditto
 "msg": "Action failure",             # An (optional) first argument
 "user-id": 1,                        # Keyword style arguments
 "err": {"via":[{"type":"...,         # When exception is logged, a Throwable->map presentation of the exception
 "ns": "user",                        # Included when exception is logged
 "file": "...",                       # ditto
 "line": "..."}                       # ditto

Note that in version 0.1.1, :inline-args? became the default style, and previously arguments were placed under :args key. This output style is still available with :inline-args? false:

...
"args": {"user-id": 1},
...

Usage

user> (require '[timbre-json-appender.core :as tas])
user> (tas/install) ;; Set json-appender as sole Timbre appender
user> (require '[taoensso.timbre :as timbre])

user> (timbre/info "Hello" :user-id 1 :profile {:role :tester}) ;; keyword style args (supports varg pairs with an optional leading message)
{"timestamp":"2019-07-03T10:00:08Z","level":"info","thread":"nRepl-session-97b9389e-a563-4f0d-8b8a-f58050297092","msg":"Hello","args":{"user-id":1,"profile":{"role":"tester"}}}

user> (timbre/info "Hello" {:user-id 1 :profile {:role :tester}}) ;; map style args (supports a single map with an optional leading message)
{"timestamp":"2019-07-03T10:00:08Z","level":"info","thread":"nRepl-session-97b9389e-a563-4f0d-8b8a-f58050297092","msg":"Hello","args":{"user-id":1,"profile":{"role":"tester"}}}

user> (tas/install {:pretty true}) ;; For repl only

user> (timbre/info "Hello" :user-id 1 :profile {:role :tester})
{
  "timestamp" : "2019-07-03T10:23:38Z",
  "level" : "info",
  "hostname": "localhost",
  "thread" : "nRepl-session-97b9389e-a563-4f0d-8b8a-f58050297092",
  "msg" : "Hello",
  "args" : {
    "user-id" : 1,
    "profile" : {
      "role" : "tester"
    }
  }
}

;; Allow to deal with non-json-serializable data in ex-info, here replace with nil
user> (tas/install {:ex-data-field-fn (fn [f] (when (instance? java.io.Serializable f) f))})

user> (timbre/error (ex-info "Hello" {:user-id 1 :not-serial (java.lang.ClassLoader/getSystemClassLoader)}))
{"timestamp": "2021-10-03T17:09:00Z", "level": "error" ... "cause": "Hello", "data": {"user-id": 1, "not-serial": null}}}

Format of the log invocation

Note the expected format:

  • An (optional) leading message is used as the as msg key
  • Any subsequent keyword-pair style args (e.g. :arg-1 1 :arg-2 "value") are added to the args map
  • If no message is provided, keyword-pair style args will be taken as args
  • If a message and a map is provided, the message will be used as msg and the hash-map will be taken as args
  • If only a hash-map is provided, it will be taken as args

Exceptions are included in err field via Throwable->map and contain ns, file and line fields:

user> (tas/install)
user> (timbre/info (IllegalStateException. "Not logged in") "Hello" :user-id 1 :profile {:role :tester})
(timbre/info (IllegalStateException. "Not logged in") "Hello" :user-id 1 :profile {:role :tester})
{"args":{"user-id":1,"profile":{"role":"tester"}},"ns":"user","file":"*cider-repl home/timbre-json-appender:localhost:49943(clj)*","line":523,"err":{"via":[{"type":"java.lang.IllegalStateException","message":"Not logged in","at":["user$eval11384$fn__11385","invoke","NO_SOURCE_FILE",523]}],"trace":[["user$eval11384$fn__11385","invoke","NO_SOURCE_FILE",523],["clojure.lang.Delay","deref","Delay.java",42],["clojure.core$deref","invokeStatic","core.clj",2320],["clojure.core$deref","invoke","core.clj",2306]

Data that isn't serializable is omitted, to not prevent logging:

user> (tas/install {:pretty true}) ;; For repl only
user> (timbre/info "Hello" :o (Object.))
{
  "timestamp" : "2019-07-03T10:26:38Z",
  "level" : "info",
  "thread" : "nRepl-session-97b9389e-a563-4f0d-8b8a-f58050297092",
  "msg" : "Hello",
  "args" : {
    "o" : { }
  }
}

As a last resort, default println appender is used, if JSON serialization fails.

Arguments can also be placed inline, instead of being put behind :args key.

user> (tas/install {:inline-args? true}) ;; Note that :inline-args? defaults to true in tas/install
user> (timbre/info "Hello" :role :admin)
{"timestamp":"2020-09-18T20:26:59Z","level":"info","thread":"nREPL-session-0ac148ff-e0c2-4578-ac64-e5411de14d1f","msg":"Hello","role":"admin"}
nil

Logging context

If you use Timbre's with-context, it will be added to your output automatically (and respects inline-args settings too)

user=> (tas/install)
user=> (timbre/with-context {:important-context "goes-here" :and :here} (timbre/info "test"))
{"timestamp":"2020-11-03T11:24:45Z","level":"info","thread":"main","msg":"test","important-context":"goes-here","and":"here"}
user=> (tas/install {:inline-args? false})
user=> (timbre/with-context {:important-context "goes-here" :and :here} (timbre/info "test"))
{"timestamp":"2020-11-03T11:25:14Z","level":"info","thread":"main","msg":"test","args":{"important-context":"goes-here","and":"here"}}

Naming keys in the log output

The key names in the resulting log map can be configured with :key-names option:

user> (tas/install {:key-names {:timestamp "@timestamp"
                                :thread "@thread_name"}})
user> (timbre/info "test")
{"msg":"test","@timestamp":"2022-02-08T18:28:16Z","level":"info","@thread_name":"nREPL-session-42cb6250-ab2d-4bf5-9e16-6a6293a66357","hostname":"paju.local"}
nil

Default key names are found in timbre-json-appender.core/default-key-names.

Additionally, before version 0.2.6, leveland msg keys can be configured separately (these options are kept for backward compatibility).

If you need to emit the log-level to a key other than level, you can supply the level-key arg

user=> (tas/install)
user=> (timbre/info "test")
{"timestamp":"2020-11-07T00:28:36Z","level":"info","thread":"main","msg":"test"}
user=> (tas/install {:level-key :severity})
user=> (timbre/info "test")
{"timestamp":"2020-11-07T00:28:50Z","severity":"info","thread":"main","msg":"test"}

If you need to emit the message to a key other than msg, you can supply the msg-key arg.

user=> (tas/install)
user=> (timbre/info "test")
{"timestamp":"2020-11-07T00:28:36Z","level":"info","thread":"main","msg":"test"}
user=> (tas/install {:msg-key :message})
user=> (timbre/info "test")
{"timestamp":"2020-11-07T00:28:50Z","severity":"info","thread":"main","message":"test"}

Maps as log data

Map can be passed as an argument, to merge data onto the log output map. This avoids the need to use keyword style to get data onto the top level output map:

user=> (tas/install)
user=> (timbre/info "Hello" {:user-id 1})
{"timestamp":"2021-03-26T15:05:16Z","level":"info","thread":"main","msg":"Hello","user-id":1}

If you wish to change the default fields: :hostname :thread :ns :file :line which are logged a function: :should-log-field-fn with the signature (field-name, timbre-data) -> boolean can be provided which should return a boolean indicating whether to log the field. By default tas/default-should-log-field-fn is used. This only logs :hostname and :thread unless an error occurs, in which case :ns, :file and :line are also output.

Support output-fn

You can now also use make-json-output-fn to create a output-fn, for use with other Timbre appenders:

user> (require '[timbre-json-appender.core :as tas])
nil
user> (require '[taoensso.timbre :as timbre])
nil
user> (require '[taoensso.timbre.appenders.3rd-party.rolling :refer [rolling-appender]])
nil
user> (timbre/set-config! {:level :info
                           :output-fn (tas/make-json-output-fn)
                           :timestamp-opts {:pattern "yyyy-MM-dd'T'HH:mm:ssX"}
                           :appenders {:rolling (rolling-appender {:path "rolling.log"})}})
{:level :info,
 :output-fn #function[timbre-json-appender.core/make-json-output-fn/fn--31957],
 :timestamp-opts {:pattern "yyyy-MM-dd'T'HH:mm:ssX"},
 :appenders
 {:rolling
  {:enabled? true,
   :async? false,
   :min-level nil,
   :rate-limit nil,
   :output-fn :inherit,
   :fn #function[taoensso.timbre.appenders.3rd-party.rolling/rolling-appender/fn--32903]}}}
user> (timbre/info "test")
nil
user> (println (slurp "rolling.log"))
{"msg":"test","timestamp":"2022-02-08T18:36:36Z","level":"info","thread":"nREPL-session-42cb6250-ab2d-4bf5-9e16-6a6293a66357","hostname":"paju.local"}
nil

make-json-output-fn takes the same options as install.

Changelog

2024-03-26 (0.2.13)

  • If JSON serialization fails with an exception, allow to handle it with :json-error-fn

2024-03-08 (0.2.12)

  • Indent arrays when pretty print enabled

2023-10-27 (0.2.11)

  • Fix the defunct argument used in the single-argument (install) call.
  • Support trailing map in a format logging call

2023-09-30 (0.2.10)

  • Same as previous, but fixed pom.xml dependency metadata (had broken clj installation on the deploy machine)

2023-09-29 (0.2.9)

  • Bump jsonista due to CVE-2022-42003

2022-09-21 (0.2.8)

  • Keep original stack trace of ExceptionInfo
    • While making sure the data of an ExceptionInfo is serializable, we re-create the ExceptionInfo instance. Now the original stack trace is also set to the newly created ExceptionInfo.

2022-06-25 (0.2.7)

  • Bump jsonista due to CVE-2020-36518

2022-02-08 (0.2.6)

  • Support :output-fn, which allows use with other appenders
  • Support renaming keys in the JSON log output with :key-names

2021-11-15 (0.2.5)

  • Respect :timestamp-opts to allow customizing timestamp format

2021-10-04 (0.2.4)

  • Allow to process non-json-serializable data in ex-info data map (for example, discard :http-client in exceptions thrown by clj-http)

2021-08-28 (0.2.3)

  • Allow to override msg-key (eg to support logz.io requiring a field named "message")

2021-07-31 (0.2.2)

  • Make printing atomic

2021-04-26 (0.2.1)

  • Allow to filter default fields via should-log-field-fn. Add hostname field.

2021-03-26 (0.2.0)

  • Support maps as argument

2020-11-07 (0.1.3)

  • Support to change the level key from level to (eg severity to support GCP Logging)

2020-11-03 (0.1.2)

  • Support timbre/with-context

2020-11-02 (0.1.1)

  • Support inlining arguments

2020-08-13

  • Use taoensso.timbre/println-appender as fallback if JSON serialization fails

2019-08-11

  • Create object mapper only once (improves performance)
  • Support format string style log formatting

2019-07-03

  • Initial release

timbre-json-appender's People

Contributors

coyotesqrl avatar gilch avatar jakepearson avatar limess avatar miyamotoakira avatar noahtheduke avatar pieterbreed avatar viesti avatar xlfe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

timbre-json-appender's Issues

Interleaving logs

After running this library in production on a system with a high log volume we're seeing some weird interleaving of log lines.

This is probably as println is not safe from interleaving logs across threads out-of-the-box.

See this fix for the timbre println appender: taoensso/timbre@057b5a4

`install` specifies a timestamp pattern

#24 added a timestamp pattern that is less granular than the default :iso8601 pattern in Timbre. I'm not sure why this is. We've run into issues with log lines being out of order due to other tools having greater granularity (matching the timbre defaults) than tja's default.

Maybe a solution would be to change install to pass-through the given options to timbre itself, so folks can specify all of their timbre and timbre-json-appender config in one call?

Either way, once I discovered this, I switched us to using tja/json-appender directly as we need the greater timestamp granularity.

Thanks for the great library, it's a real godsend.

Fix the first example in the readme

Since :inline-args? was introduced, it became the default option, and the first example in the readme doesn't reflect the current JSON output (with :inline-args?, there is not :args key in the output).

Requesting option to customize the key of the `msg` field

Hi, thanks for working on this library.

I'm currently logging in an environment where logz.io is used for handling logs. This log parsing fails on msg field, because it expects that field to be called message. The behaviour that I'm seeing is that all the fields are extracted correctly except for message (because it doesn't exist when using this output). Then logz.io displays the entire JSON string for the message.

This could be fixed if I could configure the key for this field to be :message instead of :msg.

I don't mind working on a pull request for this. Is this even a feature you would consider?

Error when namespace is filtered

I haven't dug into this much, but when I'm setting the log namespace to not include the namespace which is logging, I'm getting a Jackson exception:

i.e. when min-level is set to [["other.*"]] and I call (timbre/info "message") from say, signal.test I get:

Exception: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
 at [Source: (String)""; line: 1, column: 0]
 at com.fasterxml.jackson.databind.exc.MismatchedInputException.from (MismatchedInputException.java:59)
    com.fasterxml.jackson.databind.ObjectMapper._initForReading (ObjectMapper.java:4686)
    com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose (ObjectMapper.java:4584)
    com.fasterxml.jackson.databind.ObjectMapper.readValue (ObjectMapper.java:3546)
    com.fasterxml.jackson.databind.ObjectMapper.readValue (ObjectMapper.java:3514)
    jsonista.core$eval7884$fn__7885.invoke (core.clj:188)
    jsonista.core$eval7849$fn__7850$G__7840__7857.invoke (core.clj:166)
    jsonista.core$read_value.invokeStatic (core.clj:232)
    jsonista.core$read_value.invoke (core.clj:222)

Separate formatting and appending

Probably the number 1. mistake was to tangle appending and formatting together, though JSON-only appending has utility value :) Would be neat to separate formatting (make the JSON string out of a logging call) and appending (print the string to stdout).

Log whenever fallbacks happen

Per

(catch Throwable _t
(timbre/default-output-fn data))))))))

, there's a fallback.

While that's pretty sound, in practice in production I occasionally get text logging with no hint of what went wrong.

I'd suggest that the Throwable _t was passed to a customizable function, default no-op.

(Personally I would log it 1 out of 10 times or so)

WDYT?

Thanks - V

Extra stack trace lines when using ex-info

Hey there!

When using ex-info, the first couple lines of the stack trace are from this line.

Example:

(let [ex (ex-info "asdf" {})]
  (timbre/error ex "asdf"))

{"dd.trace_id":"0","dd.span_id":"0","ns":"example.config","file":"/home/noah/personal/demo/src/example/config.clj","err":{"via":[{"type":"clojure.lang.ExceptionInfo","message":"asdf","data":{},"at":["timbre_json_appender.core$process_ex_data_map","invokeStatic","core.clj",100]}],"trace":[["timbre_json_appender.core$process_ex_data_map","invokeStatic","core.clj",100],["timbre_json_appender.core$process_ex_data_map","invoke","core.clj",97],["clojure.core$partial$fn__5908","invoke","core.clj",2641],["timbre_json_appender.core$json_appender$fn__29908","invoke","core.clj",125],["taoensso.timbre$_log_BANG_fn__24565","invoke","timbre.cljc",684],["clojure.lang.PersistentArrayMap","kvreduce","PersistentArrayMap.java",429],["clojure.core$fn__8525","invokeStatic","core.clj",6908],["clojure.core$fn__8525","invoke","core.clj",6888],["clojure.core.protocols$fn__8257$G__8252__8266","invoke","protocols.clj",175],["clojure.core$reduce_kv","invokeStatic","core.clj",6919],["clojure.core$reduce_kv","invoke","core.clj",6910],["taoensso.timbre$_log_BANG_","invokeStatic","timbre.cljc",628],["taoensso.timbre$_log_BANG_","invoke","timbre.cljc",540],["example.config$eval80065","invokeStatic","form-init17595965402220594328.clj",118],["example.config$eval80065","invoke","form-init17595965402220594328.clj",117],

That's a lot of stack to get through before reaching the place where I've thrown the exception. Compare this to using an Exception.:

{"dd.trace_id":"0","dd.span_id":"0","ns":"example.config","file":"/home/noah/personal/demo/src/example/config.clj","err":{"via":[{"type":"java.lang.Exception","message":"asdf","at":["example.config$eval80069","invokeStatic","form-init17595965402220594328.clj",117]}],"trace":[["example.config$eval80069","invokeStatic","form-init17595965402220594328.clj",117],["example.config$eval80069","invoke","form-init17595965402220594328.clj",117],

(My apologies, if I make it a code block, there are no line breaks ๐Ÿ˜ญ)

This comes from creating a new ex-info when passed an ex-info. A solution is to overwrite the stack trace with the one from the passed-in ex:

(defn- process-ex-data-map [ex-data-field-fn ex]
  (if (and ex (instance? ExceptionInfo ex))
    (let [cause (process-ex-data-map ex-data-field-fn (ex-cause ex))
          new-ex (ex-info (ex-message ex)
                          (into {} (map (fn [[k v]] {k (ex-data-field-fn v)}) (ex-data ex)))
                          cause)]
      (.setStackTrace ^ExceptionInfo new-ex (.getStackTrace ^ExceptionInfo ex))
      new-ex)
    ex))

Nothing else changes, which I think is pretty cool. I don't know a lot about how Java's nested exceptions work, but given the quality of life improvement here, I feel like making this change is worth it.

If you agree, I can open a PR with this fix.

How to combine with timbre's `with-context`?

Firstly thank you for making this library available!

Just wondering if it is possible to use timbre's with-context to add args to the log context?

Appreciate your thoughts, or whether you'd like a PR to do that?

Cheers

Terraform examples

Examples, or even a module, for use with CloudWatch/Stackdriver e.g alarm from exceptions, metric from json field.

Not respecting :timestamp-opts

When I setup code like

(defn init []
  (timbre/merge-config!
   {:timestamp-opts
    {:pattern  "yyyy-MM-dd'T'HH:mm:ss.SSSX"
     :locale   :jvm-default
     :timezone :utc         #_(java.util.TimeZone/getTimeZone "Europe/Amsterdam")}})
  (tas/install))

(or with the merge-config! after tas/install)

The appender doesn't seems to respect the changes for the timestamp

Trailing map interaction with formatting calls

Hey Kimmo!

Thanks for the library, we really love it at Crossbeam.

I've run into a small bug or maybe an unintended interaction. If you use the trailing map form with a format logging call, it'll throw: (log/infof "Hello %s" "world!" {:role "admin"}) This is due to the unconditional (apply hash-map vargs) call in handle-vargs.

I think this could be fixed by checking the one branch from count-vargs in the format side: args (if (and (= 1 (count vargs)) (map? (first vargs))) (first vargs) (apply hash-map vargs))

com.fasterxml.jackson.databind.JsonMappingException / java.lang.UnsupportedOperationException when logging certain objects

After starting to use the :json-error-fn, I found about the following happening when logging http-related stuff:

[{:type com.fasterxml.jackson.databind.JsonMappingException
 :message "(was java.lang.UnsupportedOperationException) (through reference chain: clojure.lang.PersistentHashMap[\":response\"]->clojure.lang.PersistentHashMap[\":http-client\"]->org.apache.http.impl.client.InternalHttpClient[\"params\"])"
:at [com.fasterxml.jackson.databind.JsonMappingException wrapWithPath "JsonMappingException.java" 402]}
{:type java.lang.UnsupportedOperationException
:message nil
:at [org.apache.http.impl.client.InternalHttpClient getParams "InternalHttpClient.java" 211]}]
:trace
[[org.apache.http.impl.client.InternalHttpClient getParams "InternalHttpClient.java" 211]
[jdk.internal.reflect.DirectMethodHandleAccessor invoke nil -1]
[java.lang.reflect.Method invoke nil -1]
[com.fasterxml.jackson.databind.ser.BeanPropertyWriter serializeAsField "BeanPropertyWriter.java" 688]
[com.fasterxml.jackson.databind.ser.std.BeanSerializerBase serializeFields "BeanSerializerBase.java" 770]
[com.fasterxml.jackson.databind.ser.BeanSerializer serialize "BeanSerializer.java" 183]
[com.fasterxml.jackson.databind.ser.std.MapSerializer serializeFields "MapSerializer.java" 808]
[com.fasterxml.jackson.databind.ser.std.MapSerializer serializeWithoutTypeInfo "MapSerializer.java" 764]
[com.fasterxml.jackson.databind.ser.std.MapSerializer serialize "MapSerializer.java" 720]
[com.fasterxml.jackson.databind.ser.std.MapSerializer serialize "MapSerializer.java" 35]
[com.fasterxml.jackson.databind.ser.std.MapSerializer serializeFields "MapSerializer.java" 808]
[com.fasterxml.jackson.databind.ser.std.MapSerializer serializeWithoutTypeInfo "MapSerializer.java" 764]
[com.fasterxml.jackson.databind.ser.std.MapSerializer serialize "MapSerializer.java" 720]
[com.fasterxml.jackson.databind.ser.std.MapSerializer serialize "MapSerializer.java" 35]
[com.fasterxml.jackson.databind.ser.DefaultSerializerProvider _serialize "DefaultSerializerProvider.java" 502]
[com.fasterxml.jackson.databind.ser.DefaultSerializerProvider serializeValue "DefaultSerializerProvider.java" 341]
[com.fasterxml.jackson.databind.ObjectMapper _writeValueAndClose "ObjectMapper.java" 4793]
[com.fasterxml.jackson.databind.ObjectMapper writeValueAsString "ObjectMapper.java" 4038]
[jsonista.core$write_value_as_string invokeStatic "core.clj" 249]
[timbre_json_appender.core$make_json_output_fn$fn__44143 invoke "core.clj" 179]
[taoensso.timbre$protected_fn$fn__17814 invoke "timbre.cljc" 612]
[taoensso.timbre$_log_BANG_$fn__17845$fn__17847 invoke "timbre.cljc" 745]
[clojure.lang.Delay deref "Delay.java" 42]
[clojure.lang.Delay force "Delay.java" 28]
[clojure.core$force invokeStatic "core.clj" 767]
[timbre_json_appender.core$json_appender$fn__44155 invoke "core.clj" 199]
[taoensso.timbre$_log_BANG_$fn__17845 invoke "timbre.cljc" 777]
[clojure.lang.PersistentArrayMap kvreduce "PersistentArrayMap.java" 429]
[clojure.core$fn__8525 invokeStatic "core.clj" 6909]
[clojure.core$fn__8525 invoke "core.clj" 6889]
[clojure.core.protocols$fn__8257$G__8252__8266 invoke "protocols.clj" 175]
[clojure.core$reduce_kv invokeStatic "core.clj" 6920]
[taoensso.timbre$_log_BANG_ invokeStatic "timbre.cljc" 779]

My guess is that jsonista knows how to encode PersistentHashMaps (ref), but not how to encode InternalHttpClients.

Thoughts? It would not be reasonable for timbre-json-appender/jsonista to know about every class imaginable.

I don't immediately know if jsonista has a recipe for this. Worst-case scenario one could experiment with clojure.data.json, which I hear got a lot faster in recent times.

Thanks - V

Formatted log messages are handled weirdly

If you log like this with the *f variants of logging calls:

(log/infof "%s %s" foo bar)

You'll get a result where message is empty and args is essentially {foo bar}.

Include default fields: host, namespace:file number

By default the timbre println-appender will print the host as well as the namespace and line number where the log line occurred:

2021-04-09T16:03:30.292Z ip-192-168-0-14.eu-west-1.compute.internal INFO main [signal.logging:125] - Logging initialised :min-level [["taoensso.*" :error] ["signal.*" :debug] ["*" :warn]] :json false

These aren't included in this appender:

{
  "timestamp" : "2021-04-09T16:05:09Z",
  "level" : "info",
  "thread" : "main",
  "msg" : "Logging initialised",
  "args" : {
    "min-level" : [ [ "taoensso.*", "error" ], [ "signal.*", "debug" ], [ "*", "warn" ] ],
    "json" : true
  }
}

It'd be useful to include these so as to act as a drop-in replacement for the default appender.

`:pretty` doesn't prettify stacktraces

Hi!

First of all, massive kudos for this lib! It's exactly what I was seeking.

I noticed that the :pretty option works, but stacktraces seem to be excluded from prettification, e.g.:

image

Is this intentional?

Thanks - V

A question about stack traces

Hi,

I was an hour into writing something very much like this, when I happened on yours! One of the things that's kept me from embracing json logging in the past was dealing with hard-to-read stacktraces. Especially in clojure, where they tend to be huge. I'm curious, how do you work with stack trace outputs from this library? Did you write some formatting code that you run on the output to make it easier to read when needed, or did you just learn to read the matrix? :)

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.