Back
log (clj)
(source)macro
(log level & args)
Examples
timbre
(ns taoensso.timbre.appenders.community.rotor-test
(:require
[clojure.test :refer :all]
[clojure.java.io :as io]
[clojure.set :as set]
[taoensso.timbre :as timbre]
[taoensso.timbre.appenders.community.rotor :as rotor]))
(def logfile "rotor-test.log")
(defn logname
[i]
(format "%s.%03d" logfile i))
(defn setup
[n-logs]
(timbre/merge-config!
{:appenders {:rotor (rotor/rotor-appender
{:path logfile
:max-size 200
:backlog n-logs})}}))
(defn check-logs-present
[n-logs]
(let [f (io/file logfile)]
(is (.exists f)))
(doseq [i (range 1 (inc n-logs))]
(let [f (io/file (logname i))]
(is (.exists f)))))
(defn delete
[logfile]
(let [f (io/file logfile)]
(try
(.delete f)
(catch java.io.FileNotFoundException e nil))))
(defn teardown
[n-logs]
(delete logfile)
(doseq [i (range 1 (inc n-logs))]
(delete (logname i))))
(deftest rotor-test
(let [n-logs 5]
(setup n-logs)
(doseq [i (range 100)]
(timbre/info "testing..."))
(check-logs-present n-logs)
(teardown n-logs)))
(defn check-complete
[n-logs n-lines]
(is (= (set (range n-lines))
(apply set/union
(for [n (cons logfile (map logname (range 1 (inc n-logs))))]
(try
(with-open [rdr (io/reader n)]
(set (map (fn [line]
(let [[_ x] (re-matches #".*testing: ([0-9]+)$" line)]
(Integer/parseInt x)))
(line-seq rdr))))
(catch java.io.FileNotFoundException e (set []))))))))
(deftest rotor-complete-test
(testing "no log entry gets thrown away"
(let [n-logs 100
n-lines 100]
(setup n-logs)
(check-complete n-logs n-lines)
(teardown n-logs))))
(deftest rotor-concurrency-test
(testing "no race rotating log files"
(let [n-logs 100
n-lines 100]
(setup n-logs)
(check-complete n-logs n-lines)
(teardown n-logs))))
timbre
(ns taoensso.timbre.appenders.community.rolling-test
(:require
[clojure.test :refer [deftest is use-fixtures]]
[taoensso.timbre.appenders.community.rolling :as rolling])
(:import
(java.io File)
(java.nio.file Files)
(java.nio.file.attribute FileTime)
(java.time Instant)
(java.util Date TimeZone)))
(defn spawns
"Given a log file (a java.io.File), return that log file and every spawn of
that log file."
[log-file]
(filter #(.startsWith (.getName %) (.getName log-file))
(-> log-file .getParent File. .listFiles)))
(deftest rolling-appender-concurrency
(let [log-file (doto (File/createTempFile "timbre.rolling." ".log") (.deleteOnExit))]
(try
(let [rolling-appender (rolling/rolling-appender {:path (.getPath log-file) :pattern :daily})
now (Instant/parse "2021-11-04T00:00:00.00Z")
hour-ago (.minusSeconds now 3600)
rolled-over-log-file (File. (str log-file ".20211103"))
old-messages ["AAA" "BBB" "CCC"]
log-at #((:fn rolling-appender) {:instant %1 :output_ %2})]
;; Emulate log entries from an hour ago
(run! #(log-at (Date/from hour-ago) %) old-messages)
;; Set the last modified time of the log file to an hour ago to force rollover
(Files/setLastModifiedTime (.toPath log-file) (FileTime/from hour-ago))
(let [new-messages (set (map str (range 100)))]
;; Log new messages from many threads
(run! deref (mapv #(future (log-at (Date/from now) %)) new-messages))
;; Rolled-over log file should only have the entries from an hour ago
(is (= old-messages (vec (.split (slurp rolled-over-log-file) "\n"))))
;; New log file should have every message we logged
(is (= new-messages (set (.split (slurp log-file) "\n"))))
;; There should only be two log files: one log file with timestamp
;; suffix and one without
(is (= 2 (count (spawns log-file))))))
(finally
(run! #(.delete %) (spawns log-file))))))
timbre
(ns taoensso.timbre-tests
(:require
[clojure.test :as test :refer [deftest testing is]]
[taoensso.encore :as enc]
[taoensso.timbre :as timbre])
#?(:cljs
(:require-macros
[taoensso.timbre-tests :refer [log-data]])))
(defmacro log-data
"Executes an easily-configured log call and returns ?data sent to test appender."
[ns level m-config m-appender args]
`(let [appender# (apn ~m-appender)]
(binding [timbre/*config*
(conj timbre/default-config ~m-config
{:appenders {:test-appender appender#}})]
(timbre/log! ~level :p ~args {:loc {:ns ~ns}})
(deref (:data_ appender#)))))
(comment (macroexpand '(log-data "my-ns" :info {:min-level :trace} {} ["x"])))
(deftest levels
[(testing "Levels.global/basic"
[(is (map? (log-data "ns" :trace {:min-level nil} {} [])) "call >= default (:trace)")
(is (map? (log-data "ns" :trace {:min-level :trace} {} [])) "call >= min")
(is (nil? (log-data "ns" :trace {:min-level :info} {} [])) "call < min")
(is (map? (log-data "ns" :info {:min-level :info} {} [])) "call >= min")
(is (not (:error-level? (log-data "ns" :info {:min-level :info} {} []))))
(is (boolean (:error-level? (log-data "ns" :error {:min-level :error} {} []))))])
(testing "Levels.global/by-ns"
[(is (map? (log-data "ns.2" :trace {:min-level [["ns.1" :info] ]} {} [])) "call >= default (:trace)")
(is (map? (log-data "ns.2" :info {:min-level [["ns.1" :warn] ["ns.2" :info]]} {} [])) "call >= match")
(is (map? (log-data "ns.2" :info {:min-level [["ns.1" :warn] [#{"ns.3" "ns.2"} :info]]} {} [])) "call >= match")
(is (map? (log-data "ns.2" :info {:min-level [["ns.1" :warn] ["*" :info]]} {} [])) "call >= *")
(is (map? (log-data "ns.1" :info {:min-level [["ns.1" :info] ["*" :warn]]} {} [])) "Ordered pattern search")])
(testing "Levels.appender/basic"
[(is (map? (log-data "ns" :info {:min-level :info} {:min-level :info} [])) "call >= both global and appender")
(is (nil? (log-data "ns" :info {:min-level :report} {:min-level :info} [])) "call < global")
(is (nil? (log-data "ns" :info {:min-level :info} {:min-level :report} [])) "call < appender")])
(testing "Levels.appender/by-ns"
[(is (map? (log-data "ns" :info {:min-level [["ns" :info]]} {:min-level :info} [])) "call >= both global and appender")
(is (map? (log-data "ns" :info {:min-level [["ns" :info]]} {:min-level [["ns" :info]]} [])) "call >= both global and appender")
(is (nil? (log-data "ns" :info {:min-level [["ns" :warn]]} {:min-level [["ns" :info]]} [])) "call < global")
(is (nil? (log-data "ns" :info {:min-level [["ns" :info]]} {:min-level [["ns" :warn]]} [])) "call < appender")])])
(deftest namespaces
[(testing "Namespaces/global"
[(is (map? (log-data "ns.1.a" :report {:min-level :trace :ns-filter "ns.1.*"} {} [])))
(is (nil? (log-data "ns.1.b" :report {:min-level :trace :ns-filter "ns.2.*"} {} [])))
(is (nil? (log-data "ns.1.c" :report {:min-level :trace :ns-filter {:allow "ns.1.*" :deny "ns.1.c"}} {} [])) ":deny match")])
(testing "Namespaces/appender"
[(is (map? (log-data "ns.1.a" :report {:min-level :trace :ns-filter "ns.1.*"} {:ns-filter "ns.1.*"} [])) "both global and appender allowed")
(is (nil? (log-data "ns.1.a" :report {:min-level :trace :ns-filter "ns.2.*"} {:ns-filter "ns.1.*"} [])) "global denied")
(is (nil? (log-data "ns.1.a" :report {:min-level :trace :ns-filter "ns.1.*"} {:ns-filter "ns.2.*"} [])) "appender denied")])])
(deftest middleware
[(is (= :bar (:foo (log-data "ns" :info {:middleware [(fn [m] (assoc m :foo :bar))]} {} []))))
(is (= nil (log-data "ns" :info {:middleware [(fn [_] nil)]} {} [])))])
(deftest special-args
[(testing "Special-args/errors"
[(is (nil? (:?err (log-data "ns" :report {} {} ["foo" ]))))
(is (enc/error? (:?err (log-data "ns" :report {} {} [(ex-info "ex" {}) "foo"]))) "First-arg ex -> :?err")
(is (enc/error? (:?err (log-data "ns" :report {} {} [(ex-info "ex" {}) ]))) "First-arg ex -> :?err")
(is (= ["foo"] (:vargs (log-data "ns" :report {} {} [(ex-info "ex" {}) "foo"]))) "First-arg ex dropped from vargs")
(is (= [] (:vargs (log-data "ns" :report {} {} [(ex-info "ex" {}) ]))) "First-arg ex dropped from vargs")])
(testing "Special-args/meta"
[(is (nil? (:?meta (log-data "ns" :report {} {} [ "foo"]))))
(is (nil? (:?meta (log-data "ns" :report {} {} [ {:a :A} "foo"]))))
(is (map? (:?meta (log-data "ns" :report {} {} [^:meta {:a :A} "foo"]))) "First-arg ^:meta {} -> :?meta")
(is (= ["foo"] (:vargs (log-data "ns" :report {} {} [^:meta {:a :A} "foo"]))) "First-arg ^:meta {} dropped from vargs")])])
(deftest output
[(is (= "o1" @(:output_ (log-data "ns" :report {:output-fn (fn [data] "o1")} {} ["a1"]))))
(is (= "o2" @(:output_ (log-data "ns" :report
{:output-fn (fn [data] "o1")} ; Config
{:output-fn (fn [data] "o2")} ; Appender
["a1"])))
(is (= @(:output_ (log-data "ns" :report {:output-fn :output-opts :output-opts {:k :v1}}
{} ["a1"]))
{:k :v1})
(is (= @(:output_ (log-data "ns" :report
{:output-fn :output-opts :output-opts {:k :v1}} ; Config
{ :output-opts {:k :v2}} ; Appender
["a1"]))
{:k :v2})
replikativ/datahike
(ns datahike.http.writer
"Remote writer implementation for datahike.http.server through datahike.http.client."
(:require [datahike.writer :refer [PWriter create-writer create-database delete-database]]
[datahike.http.client :refer [request-json] :as client]
[datahike.json :as json]
[datahike.tools :as dt :refer [throwable-promise]]
[taoensso.timbre :as log]
[clojure.core.async :refer [promise-chan put!]]))
(defrecord DatahikeServerWriter [remote-peer conn]
PWriter
(-dispatch! [_ arg-map]
(let [{:keys [op args]} arg-map
p (promise-chan)
config (:config @(:wrapped-atom conn))]
(log/debug "Sending operation to datahike-server:" op)
(log/trace "Arguments:" arg-map)
(put! p
(try
(request-json :post
(str op "-writer")
remote-peer
(vec (concat [config] args))
json/mapper)
(catch Exception e
e)))
p))
(-shutdown [_])
(-streaming? [_] false))
(defmethod create-writer :datahike-server
[config connection]
(log/debug "Creating datahike-server writer for " connection config)
(->DatahikeServerWriter config connection))
fulcrologic/fulcro
(ns fulcro-todomvc.server
(:require
[com.fulcrologic.fulcro.mutations :as m :refer [defmutation]]
[clojure.core.async :as async]
[com.wsscode.pathom.core :as p]
[com.wsscode.pathom.connect :as pc]
[taoensso.timbre :as log]))
(pc/defmutation todo-new-item [env {:keys [id list-id text]}]
{::pc/sym `fulcro-todomvc.api/todo-new-item
::pc/params [:list-id :id :text]
::pc/output [:item/id]}
(log/info "New item on server")
(let [new-id (random-uuid)]
(swap! item-db assoc new-id {:item/id new-id :item/label text :item/complete false})
{:tempids {id new-id}
:item/id new-id}))
fulcrologic/fulcro
(ns com.fulcrologic.fulcro.cards.error-boundary-cards
(:require
[nubank.workspaces.card-types.fulcro3 :as ct.fulcro]
[nubank.workspaces.core :as ws]
[com.fulcrologic.fulcro.application :as app]
[com.fulcrologic.fulcro.components :as comp :refer [defsc]]
[com.fulcrologic.fulcro.dom :as dom]
[com.fulcrologic.fulcro.mutations :as m]
[taoensso.timbre :as log]))
(defsc BadActor [this {:keys [id] :as props}]
{:query [:id]
:ident :id
:componentDidMount (fn [this]
(when (and
(not (comp/get-computed this :reset?))
(= 2 (:id (comp/props this))))
(throw (ex-info "mount Craptastic!" {}))))
:componentWillUnmount (fn [this] (log/spy :info (comp/props this))
(when (= 3 (:id (comp/props this)))
(throw (ex-info "unmount Craptastic!" {}))))
:initial-state {:id :param/id}}
(if (and (= id 4)
(not (comp/get-computed this :reset?)))
(throw (ex-info "Render craptastic!" {}))
(dom/div "Actor")))
(defsc Child [this {:child/keys [id name actor] :as props}]
{:query [:child/id :child/name {:child/actor (comp/get-query BadActor)}]
:ident :child/id
:componentDidCatch (fn [this err info] (log/spy :error [err info]))
:getDerivedStateFromError (fn [error]
(log/spy :info error)
{:error? true})
:initial-state {:child/id :param/id :child/name :param/name
:child/actor {:id :param/id}}}
(if (log/spy :info (comp/get-state this :error?))
(dom/div
"There was an error in this part of the UI."
(dom/button {:onClick #(comp/set-state! this {:error? false})} "Retry"))
(dom/div
(dom/label "Child: " name)
(ui-bad-actor actor {:reset? (contains? (comp/get-state this) :error?)}))))
braidchat/braid
(ns braid.core.server.routes.api.private
(:require
[braid.core.server.db :as db]
[braid.chat.db.group :as group]
[braid.chat.events :as events]
[braid.base.server.cqrs :as cqrs]
[braid.core.server.routes.helpers :as helpers :refer [error-response edn-response]]
[braid.lib.markdown :refer [markdown->hiccup]]
[clojure.java.io :as io]
[clojure.string :as string]
[compojure.core :refer [GET PUT DELETE defroutes]]
[taoensso.timbre :as timbre]))
; get current logged in user
(GET "/session" req
(if-let [user (helpers/current-user req)]
(edn-response {:user user
:csrf-token (helpers/session-token)})
{:status 401 :body "" :session nil}))
; log out
(DELETE "/session" _
{:status 200 :session nil})
(PUT "/groups/:group-id/join" [group-id :as req]
(let [group-id (java.util.UUID/fromString group-id)]
(cond
; logged in?
(not (helpers/logged-in? req))
(error-response 401 "Must be logged in.")
(GET "/changelog" []
(edn-response {:braid/ok
(-> (io/resource "CHANGELOG.md")
slurp
markdown->hiccup)})))
vlaaad/reveal
(ns e07-timbre-tap-appender
(:require [taoensso.timbre :as log]
[vlaaad.reveal :as r]))
;; The goal is to get timbre's logs into the reveal window.
;; A simple way to achieve this is to `tap>` the messages.
;; That can be achieved by adding a custom appender.
;; Timbre generates the log message from a map that contains
;; various metadata. We can keep that around and still display
;; just the message by using `rx/as`.
(log/merge-config!
{:appenders
{:println {:enabled? false}
:reveal {:enabled? true
:fn (fn [data]
(tap> (r/submit
(r/as data
(r/raw-string
(format "[%1$tH:%1$tM:%1$tS.%1$tL %2$s:%3$s]: %4$s"
(:instant data)
(:?ns-str data)
(:?line data)
@(:msg_ data))
{:fill ({:info :symbol
:report :symbol
:warn "#db8618"
:error :error
:fatal :error}
(:level data)
:util)})))))}}})
(comment
(log/info "hi")
(log/warn "bad")
(log/error "very bad" (ex-info "nope" {:x 1})))