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})))