mirror of https://github.com/status-im/timbre.git
Improve appender rate limiting: now specified as ncalls / window-msecs
This commit is contained in:
parent
39e568f4fc
commit
5ac604d66d
|
@ -82,7 +82,8 @@
|
||||||
(def example-config
|
(def example-config
|
||||||
"APPENDERS
|
"APPENDERS
|
||||||
An appender is a map with keys:
|
An appender is a map with keys:
|
||||||
:doc, :min-level, :enabled?, :async?, :limit-per-msecs, :fn
|
:doc, :min-level, :enabled?, :async?, :fn,
|
||||||
|
:rate-limit ([ncalls-limit window-ms] form).
|
||||||
|
|
||||||
An appender's fn takes a single map with keys:
|
An appender's fn takes a single map with keys:
|
||||||
:level, :throwable
|
:level, :throwable
|
||||||
|
@ -132,14 +133,14 @@
|
||||||
:appenders
|
:appenders
|
||||||
{:standard-out
|
{:standard-out
|
||||||
{:doc "Prints to *out*/*err*. Enabled by default."
|
{:doc "Prints to *out*/*err*. Enabled by default."
|
||||||
:min-level nil :enabled? true :async? false :limit-per-msecs nil
|
:min-level nil :enabled? true :async? false :rate-limit nil
|
||||||
:fn (fn [{:keys [error? default-output]}]
|
:fn (fn [{:keys [error? default-output]}]
|
||||||
(binding [*out* (if error? *err* *out*)]
|
(binding [*out* (if error? *err* *out*)]
|
||||||
(str-println default-output)))}
|
(str-println default-output)))}
|
||||||
|
|
||||||
:spit
|
:spit
|
||||||
{:doc "Spits to `(:spit-filename :shared-appender-config)` file."
|
{:doc "Spits to `(:spit-filename :shared-appender-config)` file."
|
||||||
:min-level nil :enabled? false :async? false :limit-per-msecs nil
|
:min-level nil :enabled? false :async? false :rate-limit nil
|
||||||
:fn (fn [{:keys [ap-config default-output]}]
|
:fn (fn [{:keys [ap-config default-output]}]
|
||||||
(when-let [filename (:spit-filename ap-config)]
|
(when-let [filename (:spit-filename ap-config)]
|
||||||
(try (spit filename default-output :append true)
|
(try (spit filename default-output :append true)
|
||||||
|
@ -154,38 +155,39 @@
|
||||||
(defn- wrap-appender-fn
|
(defn- wrap-appender-fn
|
||||||
"Wraps compile-time appender fn with additional runtime capabilities
|
"Wraps compile-time appender fn with additional runtime capabilities
|
||||||
controlled by compile-time config."
|
controlled by compile-time config."
|
||||||
[{apfn :fn :keys [async? limit-per-msecs] :as appender}]
|
[{apfn :fn :keys [async? rate-limit] :as appender}]
|
||||||
(let [limit-per-msecs (or (:max-message-per-msecs appender)
|
(let [rate-limit (or rate-limit ; Backwards comp:
|
||||||
limit-per-msecs)] ; Backwards comp
|
(if-let [x (:max-message-per-msecs appender)] [1 x]
|
||||||
|
(when-let [x (:limit-per-msecs appender)] [1 x])))]
|
||||||
|
|
||||||
|
(assert (or (nil? rate-limit) (vector? rate-limit)))
|
||||||
|
|
||||||
(->> ; Wrapping applies per appender, bottom-to-top
|
(->> ; Wrapping applies per appender, bottom-to-top
|
||||||
apfn
|
apfn
|
||||||
|
|
||||||
;; Rate limit support
|
;; Rate limit support
|
||||||
((fn [apfn]
|
((fn [apfn]
|
||||||
(if-not limit-per-msecs apfn
|
;; Compile-time:
|
||||||
(let [timers (atom {})] ; {:hash last-appended-time-msecs ...}
|
(if-not rate-limit apfn
|
||||||
(fn [{ns :ns [x1 & _] :args :as apfn-args}]
|
(let [[ncalls-limit window-ms] rate-limit
|
||||||
(let [now (System/currentTimeMillis)
|
limiter-any (utils/rate-limiter ncalls-limit window-ms)
|
||||||
hash (str ns "/" x1) ; TODO Alternatives?
|
;; This is a little hand-wavy but it's a decent general
|
||||||
limit? (fn [last-msecs]
|
;; strategy and helps us from making this overly complex to
|
||||||
(and last-msecs (<= (- now last-msecs)
|
;; configure:
|
||||||
limit-per-msecs)))]
|
limiter-specific (utils/rate-limiter (quot ncalls-limit 4)
|
||||||
|
window-ms)]
|
||||||
(when-not (limit? (@timers hash))
|
(fn [{:keys [ns args] :as apfn-args}]
|
||||||
(apfn apfn-args)
|
;; Runtime: (test smaller limit 1st):
|
||||||
(swap! timers assoc hash now))
|
(when-not (or (limiter-specific (str ns args)) (limiter-any))
|
||||||
|
(apfn apfn-args)))))))
|
||||||
(when (< (rand) 0.001) ; Occasionally garbage collect
|
|
||||||
(when-let [expired-timers (->> (keys @timers)
|
|
||||||
(remove #(limit? (@timers %)))
|
|
||||||
(seq))]
|
|
||||||
(apply swap! timers dissoc expired-timers)))))))))
|
|
||||||
|
|
||||||
;; Async (agent) support
|
;; Async (agent) support
|
||||||
((fn [apfn]
|
((fn [apfn]
|
||||||
|
;; Compile-time:
|
||||||
(if-not async? apfn
|
(if-not async? apfn
|
||||||
(let [agent (agent nil :error-mode :continue)]
|
(let [agent (agent nil :error-mode :continue)]
|
||||||
(fn [apfn-args] (send-off agent (fn [_] (apfn apfn-args)))))))))))
|
(fn [apfn-args] ; Runtime:
|
||||||
|
(send-off agent (fn [_] (apfn apfn-args)))))))))))
|
||||||
|
|
||||||
(defn- make-timestamp-fn
|
(defn- make-timestamp-fn
|
||||||
"Returns a unary fn that formats instants using given pattern string and an
|
"Returns a unary fn that formats instants using given pattern string and an
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
"timestamps, etc.")
|
"timestamps, etc.")
|
||||||
:min-level :debug
|
:min-level :debug
|
||||||
:enabled? true
|
:enabled? true
|
||||||
:async? false
|
|
||||||
:limit-per-msecs nil
|
|
||||||
:prefix-fn :ns
|
:prefix-fn :ns
|
||||||
:fn (fn [{:keys [level prefix throwable message]}]
|
:fn (fn [{:keys [level prefix throwable message]}]
|
||||||
(if throwable
|
(if throwable
|
||||||
|
|
|
@ -41,6 +41,6 @@
|
||||||
"Needs :irc config map in :shared-appender-config, e.g.:
|
"Needs :irc config map in :shared-appender-config, e.g.:
|
||||||
{:host \"irc.example.org\" :port 6667 :nick \"logger\"
|
{:host \"irc.example.org\" :port 6667 :nick \"logger\"
|
||||||
:name \"My Logger\" :chan \"#logs\"")
|
:name \"My Logger\" :chan \"#logs\"")
|
||||||
:min-level :info :enabled? true :async? false :limit-per-msecs nil
|
:min-level :info :enabled? true
|
||||||
:prefix-fn (fn [{:keys [level]}] (-> level name str/upper-case))
|
:prefix-fn (fn [{:keys [level]}] (-> level name str/upper-case))
|
||||||
:fn appender-fn})
|
:fn appender-fn})
|
|
@ -42,5 +42,5 @@
|
||||||
:server {:host \"127.0.0.1\"
|
:server {:host \"127.0.0.1\"
|
||||||
:port 27017}}")
|
:port 27017}}")
|
||||||
:min-level :warn :enabled? true :async? true
|
:min-level :warn :enabled? true :async? true
|
||||||
:max-message-per-msecs 1000 ; 1 entry / sec
|
:rate-limit [1 1000] ; 1 entry / sec
|
||||||
:fn appender-fn})
|
:fn appender-fn})
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
^{:host \"mail.isp.net\" :user \"jsmith\" :pass \"sekrat!!1\"}
|
^{:host \"mail.isp.net\" :user \"jsmith\" :pass \"sekrat!!1\"}
|
||||||
{:from \"Bob's logger <me@draines.com>\" :to \"foo@example.com\"}")
|
{:from \"Bob's logger <me@draines.com>\" :to \"foo@example.com\"}")
|
||||||
:min-level :error :enabled? true :async? true
|
:min-level :error :enabled? true :async? true
|
||||||
:limit-per-msecs (* 1000 60 10) ; 1 subject / 10 mins
|
:rate-limit [5 (* 1000 60 2)] ; 5 calls / 2 mins
|
||||||
:fn (fn [{:keys [ap-config prefix throwable args]}]
|
:fn (fn [{:keys [ap-config prefix throwable args]}]
|
||||||
(when-let [postal-config (:postal ap-config)]
|
(when-let [postal-config (:postal ap-config)]
|
||||||
(let [[subject & body] args]
|
(let [[subject & body] args]
|
||||||
|
|
|
@ -74,6 +74,4 @@
|
||||||
:backlog 5}")
|
:backlog 5}")
|
||||||
:min-level nil
|
:min-level nil
|
||||||
:enabled? true
|
:enabled? true
|
||||||
:async? false
|
|
||||||
:limit-per-msecs nil
|
|
||||||
:fn appender-fn})
|
:fn appender-fn})
|
||||||
|
|
|
@ -44,6 +44,5 @@
|
||||||
"Needs :socket config map in :shared-appender-config, e.g.:
|
"Needs :socket config map in :shared-appender-config, e.g.:
|
||||||
{:listen-addr :all
|
{:listen-addr :all
|
||||||
:port 9000}")
|
:port 9000}")
|
||||||
:min-level :trace :enabled? true :async? false
|
:min-level :trace :enabled? true
|
||||||
:max-message-per-msecs nil ; no rate limit by default
|
|
||||||
:fn appender-fn})
|
:fn appender-fn})
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
(swap! cache assoc args cv)
|
(swap! cache assoc args cv)
|
||||||
@dv)))))))))
|
@dv)))))))))
|
||||||
|
|
||||||
(defn rate-limit
|
(defn rate-limiter
|
||||||
"Returns a `(fn [& [id]])` that returns either `nil` (limit okay) or number of
|
"Returns a `(fn [& [id]])` that returns either `nil` (limit okay) or number of
|
||||||
msecs until next rate limit window (rate limited)."
|
msecs until next rate limit window (rate limited)."
|
||||||
[ncalls-limit window-ms]
|
[ncalls-limit window-ms]
|
||||||
|
|
Loading…
Reference in New Issue