{"id":26194270,"url":"https://github.com/active-group/active-logger","last_synced_at":"2025-04-15T03:15:15.200Z","repository":{"id":37404311,"uuid":"276052747","full_name":"active-group/active-logger","owner":"active-group","description":"Clojure library with utilities and a DSL for logging on top of Timbre with support for Riemann.","archived":false,"fork":false,"pushed_at":"2025-02-24T14:19:16.000Z","size":446,"stargazers_count":1,"open_issues_count":4,"forks_count":2,"subscribers_count":14,"default_branch":"main","last_synced_at":"2025-04-15T03:15:09.865Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/active-group.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-30T09:16:42.000Z","updated_at":"2025-02-24T14:19:21.000Z","dependencies_parsed_at":"2023-02-14T20:15:48.600Z","dependency_job_id":"f628d95b-660f-467b-a858-6c51a758eb38","html_url":"https://github.com/active-group/active-logger","commit_stats":{"total_commits":204,"total_committers":6,"mean_commits":34.0,"dds":0.5490196078431373,"last_synced_commit":"0ed314983471bff24340c11baea5d8d747cb06a4"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/active-group%2Factive-logger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/active-group%2Factive-logger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/active-group%2Factive-logger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/active-group%2Factive-logger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/active-group","download_url":"https://codeload.github.com/active-group/active-logger/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248997080,"owners_count":21195799,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-12T01:56:00.012Z","updated_at":"2025-04-15T03:15:15.179Z","avatar_url":"https://github.com/active-group.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+TITLE: active-logger\n\nUtilities and DSL for logging on top of [[https://github.com/ptaoussanis/timbre][Timbre]] with support for [[https://github.com/riemann/riemann][Riemann]],\n[[https://www.elastic.co/de/logstash][Logstash]], and more.\n\nCurrently =active-logger= supports hijacking log messages from Log4j,\njava.util.logging (JUL), and Apache Commons Logging (JCL).\n\n[[https://img.shields.io/clojars/v/de.active-group/active-logger.svg]]\n\n* Breaking changes\n\n** Breaking changes in version 0.14.0:\n\n- Upgraded Timbre from version 5 to 6, which are incompatible due to\n  namespace changes of the 'community'/'3rdparty' appenders.\n\n** Breaking changes in version 0.10.0:\n\n- We removed ~active.clojure.logger.timbre/timbre-config-section~; the client\n  should choose its section key for the configuration itself and the library\n  should be called with a configuration object of schema\n  ~active.clojure.logger.timbre/timbre-config-schema~.\n\n** Breaking changes in version 0.8.0:\n\n- We revamped the Metrics implementation: You need to adjust all your calls to\n  ~log-metric~ in your code.  Details see below.\n** 0.7.0\n- A call to ~redirect-log4j!~ isn't necessary anymore. You have to remove the\n  ~active.clojure.logger.log4j~ namespace from your ~:require~ statements. You\n  also have to remove the calls to ~redirect-log4j!~ without substitution.\n\n* TL;DR\nIf you just want to get going, here is a minimal working example on how to\nstart logging events.\n   1. Add =active-logger= and =active-clojure= to your =deps.edn=:\n      #+begin_src clojure\n        {:deps {de.active-group/active-clojure {:mvn/version \"0.42.0\"}\n                de.active-group/active-logger  {:mvn/version \"0.14.0\"}}}\n      #+end_src\n   2. Start logging some events:\n      #+begin_src clojure\n        (ns my.app\n          (:require ...\n                    [active.clojure.logger.event :as event-logger]))\n\n        (event-logger/log-event! :info \"We are live!\")\n      #+end_src\n      The default configuration logs via =println= to =stdout=.\n\n      See [[./examples][examples/]] for example projects.\n\n** Change configuration\n\nTo adjust the configuration you can provide a [[http://ptaoussanis.github.io/timbre/taoensso.timbre.html#var-*config*][Timbre configuration map]]:\n#+begin_src clojure\n  (ns my.app\n    (:require [active.clojure.logger.event :as event-logger]))\n\n  (def timbre-config-map\n    {:min-level :debug  ;; Log everything that is at least a :debug message.\n     :appenders\n     {;; Print messages to stdout/stderr.\n      :println '(println)\n      ;; Write messages to a file.\n      :spit '(spit \"/tmp/my-app.log\")\n      ;; Submit messages to a logstash instance.\n      :logstash '(logstash \"localhost\" 4660)}})\n\n  (event-logger/set-global-log-events-config-from-map! timbre-config-map)\n#+end_src\n\nOr use [[https://github.com/active-group/active-clojure][active-clojure]]'s =active.clojure.config= facilities:\n\n#+begin_src clojure\n  (ns my.app\n    (:require [active.clojure.config :as active-config]\n              [active.clojure.logger.event :as event-logger]\n              [active.clojure.logger.timbre :as timbre]))\n\n  (def config-map\n    {:min-level :debug  ;; Log everything that is at least a :debug message.\n     :appenders\n     {;; Print messages to stdout/stderr.\n      :println '(println)\n      ;; Write messages to a file.\n      :spit '(spit \"/tmp/my-app.log\")\n      ;; Submit messages to a logstash instance.\n      :logstash '(logstash \"localhost\" 4660)}})\n\n  (def configuration (active-config/make-configuration timbre/timbre-config-schema [] config-map))\n\n  ;; Initialize the event-logger.\n  (-\u003e configuration\n      timbre/configuration-\u003etimbre-config\n      event-logger/set-global-log-events-config!)\n#+end_src\n\n* Prerequisites\nFor =active-logger= to work as expected, the only SLF4J binding on the\nclasspath should be =slf4j-timbre=, which comes with =active-logger=.\n\nExclude direct or transitive dependencies on =org.slf4j/slf4j-log4j12=\nand other =slf4j= bindings from your project. SLF4J should give you a\nwarning along these lines if you still have any of those:\n\n#+begin_src\nSLF4J: Class path contains multiple SLF4J bindings.\n#+end_src\n* Usage\n=active-logger= is intended to be used alongside [[https://github.com/active-group/active-clojure][active-clojure]] and follows\nthe same naming scheme (all =active-logger= namespaces can be found under\n=active.clojure.logger=).\n\nThere are two basic ways to do logging with =active-logger=: Effectful and\nmonadic.\nIn general, both of theses options are available for each logging function in\n=active-logger=.\n** Effectful\n*Effectful* logging refers to logging functions that immediately execute.\nUsually they are signified by a bang (=!=) at the end of the var's name\n(i. e. =active.clojure.logger.event/log-event!=).\nIn any non-monadic execution context, use these functions for logging,\nmetrics, etc.\n** Monadic\n*Monadic* logging refers to logging functions that can be used to formulate\nthe intent on logging at this location when executing a monadic program.\nThe log function will only be executed when the monadic program is run.\n\nMonadic in this context means programs defined using =active.clojure.monad=.\n** Example for /effectful/ and /monadic/ logging\n#+begin_src clojure\n  (ns my.ns\n    (:require [active.clojure.config :as active-config]\n              [active.clojure.logger.timbre :as timbre]\n              [active.clojure.logger.event :as event-logger]\n              [active.clojure.monad :as monad :refer [monadic]]))\n\n  ;; NOTE: Initialize as shown in TL;DR section.\n\n  ;; Effectful logging\n  (let [res (+ 2 2)]\n    (event-logger/log-event! :trace (str \"res =\" res))\n    res)\n  ;; 2020-07-11 15:19:02.659 host INFO [my.ns] TRACE - res = 4\n  ;; =\u003e 4\n\n\n  ;; Monadic logging\n  (def prog (monadic [res (monad/return 4)]\n                     (event-logger/log-event :trace (str \"res =\" res))\n                     (monad/return 4)))\n\n  (monad/run-free-reader-state-exception\n   event-logger/log-events-command-config\n   prog)\n  ;; 2020-07-11 15:19:02.659 host INFO [my.ns] TRACE - res = 4\n  ;; =\u003e 4\n#+end_src\n* Components\n=active-logger= includes several different entities that can be logged.\n** Event\n*Events* are reports of things that the system did that may be of interest\nfor later perusal by system administrators in case of failures or by\ndevelopers.\nEach event carries a /namespace/ saying where the event came from, and a\n/level/ that specifies how “important” the event is.\nFunctions to log events can be found in =active.clojure.logger.event=.\n** Metrics\n*Metrics* can be used to log metrics for specified parts of your system.\nFunctions to log events can be found in =active.clojure.logger.metric=.\n\nMetrics use Prometheus-style metric types, for more details see\nhttps://prometheus.io/docs/concepts/metric_types/.\n\nAs for Events described above, we have an effectful logging and an monadic\nlogin.  The command config for monadic logging is\n~active.clojure.logger.metric/monad-command-config~.  We currently implement:\n\n- Counters with ~log-counter-metric!~ and the monadic ~log-counter-metric~,\n- Gauges with ~log-gauge-metric!~ and the monadic ~log-gauge-metric~, and\n- Histograms with ~log-histogram-metric!~ and the monadic ~log-histogram-metric~\n  that supports an arbitrary number of buckets.\n\nCounters and histograms are state-based, which means that their next value\ndepends on previous values and that they accumulate over time and need to be\nstored.  The metric store is internal to the implementation and there are\nfunctions for (re-)setting the store, most useful is\n~active.clojure.logger.metric-accumulator/reset-global-metric-store!~.  This\nresets the store for both the effectful and monadic implementation as both\nimplementations can be used concurrently and share the same store.\n\nSince the existence of metrics, respectively the absence of metrics may hold\nimportant information about the health of a system, the library comes with\nfunctionality to prune stale metrics, that is to clean the metric store from\nmetrics that have not been updated in a while.  Without pruning old metrics,\nthey will still be in the store and may be cause wrong conclusions about the\nsystem.  You can use\n~active.clojure.logger.metric-accumulator/prune-stale-metrics!~ or as a\nconvenience start a thread that prunes stale metrics periodically with\n~active.clojure.logger.metric-accumulator/start-prune-stale-metrics-thread!~.\n\n(We also implement ~set-counter-metric!~ and the monadic ~set-counter-metric~ for\nmetrics that represent counters but can be used by gauges by setting the value\ninstead of incrementing the value.  Use this only if you know what you are\ndoing.)\n\n*** Emitter\n\nMetrics can be emitted\n\n- as Events and use the events configuration.  This is the default and by\n  default it logs the events with log level ~:info~.  This can be changed to\n  ~:debug~ for example:\n\n#+begin_src Clojure\n(active.clojure.logger.metric-emitter/set-global-log-metrics-config!\n  active.clojure.logger.metric-emitter/configure-metrics-loging :events :debug)\n#+end_src\n\n- to Riemann by passing the result of\n  ~(active.clojure.logger.metric-emitter/configure-metrics-logging\n  riemann-config :riemann)~ to\n  ~active.clojure.logger.metric-emitter/set-global-log-metrics-config~;\n  ~riemann-config~ is a Riemann configuration object, see below.\n\n- not at all -- this can be set with\n  ~(active.clojure.logger.metric-emitter/set-global-log-metrics-config! :no-push)~\n\nNot emitting metrics at all is the recommended configuration when using\nPrometheus, see next section.\n\n*** Integrate with Prometheus\n\nPrometheus scrapes metrics from its targets from their HTTP endpoints.  The\nnamespace ~active.clojure.logger.metric-prometheus~ provides functionality for\nproviding such an endpoint, the most important functions are:\n\n- ~(render-metrics!)~ that returns the Prometheus-parseable string of all\n  metrics in the store, and\n\n- ~(wrap-prometheus-metrics-ring-handler handler)~ which is a ring handler that\n  returns the rendered page on the endpoint ~/metrics~.  This is a convenient\n  way to hook the route into your already existing webserver.\n\n**** Example webserver\n\nHere is an example on how to add a webserver that serves the metrics to your\napplication using the [[https://github.com/http-kit/http-kit][HTTP Kit webserver]]:\n\n#+begin_src Clojure\n(ns example.webserver\n  (:require [org.httpkit.server :as http]\n            [active.clojure.config :as config]\n            [active.clojure.logger.metric-prometheus :as metric-prometheus]\n            [active.clojure.logger.metric :as metrics]\n            [active.clojure.logger.event :as events]))\n\n(def webserver-host\n  (config/setting\n   :host\n   \"Where the webserver is hosted.\"\n   (config/default-string-range \"0.0.0.0\")))\n\n(def webserver-port\n  (config/setting\n   :port\n   \"The port the webserver is listening on.\"\n   (config/integer-between-range 1024 49151 8002)))\n\n(def webserver-section\n  (config/section\n   :webserver\n   (config/schema \"Configuration for the webserver.\"\n                  webserver-host\n                  webserver-port)))\n\n(defn record-http-requests-total-handler\n  [handler]\n  (fn [req]\n    (let [res (handler req)]\n      (metrics/log-counter-metric! \"http_requests_total\"\n                                  (merge\n                                    {:uri (:uri req)}\n                                    (when-let [status (:status res)]\n                                      {:status status}))\n                                  1)\n      res)))\n\n(def app\n  (record-http-requests-total-handler\n    (metric-prometheus/wrap-prometheus-metrics-ring-handler\n     (fn [_req]\n       {:status 404 :headers {\"Content-Type\" \"text/plain\"} :body \"not found\"}))))\n\n(defn start-webserver!\n  ([webserver-config]\n   (let [host (config/access webserver-config webserver-host)\n         port (config/access webserver-config webserver-port)]\n     (start-webserver! host port)))\n  ([host port]\n   (events/log-event :info (str \"Starting webserver on \" host \":\" port))\n   (let [stop-server! (http/run-server app {:ip host :port port})]\n     (fn []\n       (stop-server! :timeout 100)\n       (events/log-event :info \"Stopped webserver.\")))))\n#+end_src\n\nThis example uses ~active.clojure.config~ to configure the host and port of the\nwebserver, you can hook the ~webserver-section~ into your already existing\n~active.clojure.config~ setup.\n\nIt also records a metric that counts all the HTTP requests that reach the\nwebserver.  An example output when pointing a browser to the endpoint might look\nlike:\n\n#+begin_src\n# HELP http_requests_total http_requests_total\n# TYPE http_requests_total counter\nhttp_requests_total{uri=\"/metrics\",status=\"200\"} 5 1662025543973\n#+end_src\n\n** Timed Metrics\n*Timed metrics* can be used to log timing characteristics specified parts of\nyour system.  Functions to log events can be found in\n=active.clojure.logger.timed-metric=.  Basically syntactic sugar around\n=active.clojure.logger.metric= -- setup and configuration of Metrics applies to\nTimed metrics as well.\n** State Change\n*State changes* are for monitoring the live operation of a system.\nThey announce for a certain system component its state, its /service/.\nFunctions to log events can be found in =active.clojure.logger.state-change=.\n* Configuration\n=active-logger= comes with two basic configuration sections: Timbre and Riemann.\nFor more information on =active.clojure.config=, refer to [[https://github.com/active-group/active-clojure][active-clojure]]'s documentation on the topic.\n\nIn general, to provide a configuration means to supply a map that contains\nonly keys and values according to some schema defined as a\n=active.clojure.config/schema= and consumed by\n=active.clojure.config/make-configuration=.\n** Timbre\nThere are lots of configuration options to define how the event logger should\nbehave.\nFor a full list of options, refer to [[./src/active/clojure/logger/config/timbre.clj][the respective sources]].\n\nHere, we will cover the most important options:\n*** =:min-level=\n*default*: =:debug=\n\nThe minimum level a message must have to be printed.\nPossible values are =#{:trace :debug :info :warn :error :fatal :report}=.\n\nThere is syntax for advanced settings for levels for namespaces:\n=[[#{\\\"taoensso.*\\\"} :error] ... [#{\\\"*\\\"} :debug]]=\n\n*** =:appenders=\n*default*: ={:println (println)}=\n\nDefines how messages are printed (=stdout=, to a file, ...).\nAn appender spec is a list starting with one of\n={spit, rotor, logstash, println}=,\nfollowed by keyword parameters corresponding to the respective appender.\n\nMultiple appenders can be configured and active at the same time.\nA full example of an appender configuration might looks like this:\n#+begin_src clojure\n  {:appenders {:spit    (spit {:fname \"/tmp/app.log\"})\n               :riemann (riemann {:host \"localhost\"\n                                  :port 5555})\n               :println (println)}}\n#+end_src\n**** Appender: spit\nSpecifies an appender that writes to a file, specified via the =:fname=\nsetting.\nExample: ={:spit (spit {:fname \"my.log\"})}=\n**** Appender: rotor\nSpecifies an appender that writes to a file and rotates the file when it\nreaches a given file size.\nIt accepts the following settings:\n| option      | description                                                                            | default                |\n|-------------+----------------------------------------------------------------------------------------+------------------------|\n| =:path=     | Path to log file. Historical versions are suffixed with a 3-digit index.               | =\"./timbre-rotor.log\"= |\n| =:max-size= | Maximum size of a log file in bytes. Log files are rotated when they exceed this size. | 1.048.576 bytes (1~MB) |\n| =:backlog=  | Number of rotated logs to keep.                                                        | =5=                    |\nExample:\n#+begin_src clojure\n  {:rotor (rotor {:path \"/tmp/project.log\"\n                  :max-size 1073741824\n                  :backlog 999})}\n#+end_src\n**** Appender: println\nSpecifies an appender that will print regular log entries to stdout, errors to stderr.\nExample: ={:println (println)}=\n**** Appender: logstash\nSpecifies an appender that writes to a Logstash instance.\nIt takes two arguments: the host name, and the port number of the Logstash instance.\nExample: ={:logstash (logstash \"localhost\" 4660)}=\n**** Appender: riemann\nSpecifies an appender that writes to a Riemann instance.\nIt accepts the following settings:\n| option  | description                       | default     |\n|---------+-----------------------------------+-------------|\n| =:host= | The host Riemann is served on.    | \"localhost\" |\n| =:port= | The port Riemann is listening on. | 5555        |\nExample\n#+begin_src clojure\n  {:riemann (riemann {:host \"localhost\"\n                      :port 5555})}\n#+end_src\n*** =:ns-filter:=\nIt is possible to filter specific namespaces in the log-output.  You can give a\nmap with keys `:allow` and `:deny` with set values that contain namespace pattern.\nExample: ={:ns-filter {:allow #{\"*\"} :deny #{\"taoensso.*\"}}}=\n*** =:timestamp-opts=\nSection containing three settings related to how timestamps are formatted in\nlogs:\n| option      | description                                                                                                                                                                                                                                                      |\n|-------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| =:pattern=  | Pattern for the timestamp (see [[http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html][SimpleDateFormat]])                                                                                                                                                                                                                 |\n| =:locale=   | This is an IETF BCP 47 language tag string specifying the locale such as =\"de-DE\"= or =\"en-US\"= or =:jvm-default=.                                                                                                                                               |\n| =:timezone= | This is an ID for the time zone relative to which log entry dates should formatted. This can be a full name such as =\"Germany/Berlin\"=, or a custom ID such as =\"GMT-8:00\"=. The value may also be =:jvm-default= for the default time zone, and =:utc= for UTC. |\n** Riemann\nHere are the configuration settings for Riemann. They happen in the\n=:riemann= section:\n| option     | description                                                                                   | default      |\n|------------+-----------------------------------------------------------------------------------------------+--------------|\n| =:host=    | String specifying the host where Riemann runs.                                                | =\"127.0.0.1= |\n| =:port=    | Port where Riemann runs.                                                                      | =5555=       |\n| =:tls?=    | Specifies whether the communication with Riemann should use TLS. It can be =true= or =false=. | =false=      |\n| =:key=     | If =:tls?= is true, use the specified TLS key-file.                                           | -            |\n| =:cert=    | If =:tls?= is true, use the specified TLS cert-file.                                          | -            |\n| =:ca-cert= | If =:tls?= is true, use the specified TLS CA cert-file.                                       | -            |\n* Troubleshooting/Bad Players\nCertain libraries and frameworks are notorious for emitting log\nmessages as soon as their namespaces are included.  This will happen\nbefore any configuration for =active-logger= can be applied.\nTherefore, you need to handle each such scenario differently.  Refer\nto the Jetty example below to get an idea on how such problems might\nbe fixed.\n** Jetty\n[[https://www.eclipse.org/jetty/][Jetty]] is one example of a bad player.  It will, as soon as any of it's\nnamespaces are included, emit an 'announcement' message that will be\nprinted to stdout as soon as your =ns=-declaration is evaluated:\n\n#+begin_src\nLogging to org.slf4j.impl.Log4jLoggerAdapter(org.eclipse.jetty.util.log) via org.eclipse.jetty.util.log.Slf4jLog\nLogging initialized @18641ms to org.eclipse.jetty.util.log.Slf4jLog\n#+end_src\n\nThe solution to disable the message is to\n\n- create a namespace, i.e. =my-project.disable-jetty-logs= that turns\n  off this message in particular\n- include that namespace in the namespace that will be called first\n  (usually the one that contains your =-main= function, likely\n  =core.clj=) *as the very first dependency*\n\nExample:\n\n#+begin_src clojure\n  ;; disable_jetty_logs.clj\n  (ns my-project.disable-jetty-logs)\n\n  (.setProperty (org.eclipse.jetty.util.log.Log/getProperties) \"org.eclipse.jetty.util.log.announce\" \"false\")\n\n  ;; core.clj\n  (ns my-project.core\n    (:require [my-project.disable-jetty-logs]\n              ...))  ; more imports\n#+end_src\n\n[[https://stackoverflow.com/a/53064639][This answer on StackOverflow]] led to this solution.\n* License\nCopyright © 2022 Active Group GmbH\n\nThis program and the accompanying materials are made available under the\nterms of the Eclipse Public License 2.0 which is available at\nhttp://www.eclipse.org/legal/epl-2.0.\n\nThis Source Code may also be made available under the following Secondary\nLicenses when the conditions for such availability set forth in the Eclipse\nPublic License, v. 2.0 are satisfied: GNU General Public License as published by\nthe Free Software Foundation, either version 2 of the License, or (at your\noption) any later version, with the GNU Classpath Exception which is available\nat https://www.gnu.org/software/classpath/license.html.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factive-group%2Factive-logger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Factive-group%2Factive-logger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factive-group%2Factive-logger/lists"}