diff --git a/src/taoensso/telemere/consoles.cljc b/src/taoensso/telemere/consoles.cljc index bf29895..481aaa1 100644 --- a/src/taoensso/telemere/consoles.cljc +++ b/src/taoensso/telemere/consoles.cljc @@ -10,8 +10,6 @@ (remove-ns 'taoensso.telemere.consoles) (:api (enc/interns-overview))) -;;;; Handlers - #?(:clj (defn ^:public handler:console "Experimental, subject to change. diff --git a/src/taoensso/telemere/files.clj b/src/taoensso/telemere/files.clj index 770c45d..3a72642 100644 --- a/src/taoensso/telemere/files.clj +++ b/src/taoensso/telemere/files.clj @@ -320,7 +320,7 @@ (let [main-path path main-file (utils/as-file main-path) - fw (utils/file-writer main-file true) + fw (utils/file-writer {:file main-file, :append? true}) >max-file-size? (when max-file-size diff --git a/src/taoensso/telemere/postal.clj b/src/taoensso/telemere/postal.clj index b5c0173..e849322 100644 --- a/src/taoensso/telemere/postal.clj +++ b/src/taoensso/telemere/postal.clj @@ -11,8 +11,6 @@ (remove-ns 'taoensso.telemere.postal) (:api (enc/interns-overview))) -;;;; Implementation - (defn signal-subject-fn "Experimental, subject to change. Returns a (fn format [signal]) that: @@ -43,7 +41,14 @@ (comment ((signal-subject-fn) (tel/with-signal (tel/event! ::ev-id1 #_{:postal/subject "My subject"})))) -;;;; Handler +(def default-dispatch-opts + {:min-level :info + :rate-limit + [[5 (enc/msecs :mins 1)] + [10 (enc/msecs :mins 15)] + [15 (enc/msecs :hours 1)] + [30 (enc/msecs :hours 6)] + ]}) (defn handler:postal "Experimental, subject to change. @@ -56,18 +61,24 @@ Useful for emailing important alerts to admins, etc. - NB can incur financial costs!! - See tips section re: protecting against unexpected costs. + Default handler dispatch options (override when calling `add-handler!`): + `:min-level` - `:info` + `:rate-limit` - + [[5 (enc/msecs :mins 1)] ; Max 5 emails in 1 min + [10 (enc/msecs :mins 15)] ; Max 10 emails in 15 mins + [15 (enc/msecs :hours 1)] ; Max 15 emails in 1 hour + [30 (enc/msecs :hours 6)] ; Max 30 emails in 6 hours + ] Options: - `:postal/conn-opts` - Map of connection opts given to `postal/send-message` + `:conn-opts` - Map of connection opts given to `postal/send-message` Examples: {:host \"mail.isp.net\", :user \"jsmith\", :pass \"a-secret\"}, {:host \"smtp.gmail.com\", :user \"jsmith@gmail.com\", :pass \"a-secret\" :port 587 :tls true}, - {:host \"email-smtp.us-east-1.amazonaws.com\", :port 587, :tls true - :user \"AKIAIDTP........\" :pass \"AikCFhx1P.......\"} + {:host \"email-smtp.us-east-1.amazonaws.com\", :port 587, :tls true, + :user \"AKIAIDTP........\", :pass \"AikCFhx1P.......\"} - `:postal/msg-opts` - Map of message options given to `postal/send-message` + `:msg-opts` - Map of message opts given to `postal/send-message` Examples: {:from \"foo@example.com\", :to \"bar@example.com\"}, {:from \"Alice \"}, @@ -81,59 +92,42 @@ see `format-signal-fn` or `pr-signal-fn` Tips: - - Sending emails can incur financial costs! - Use appropriate dispatch filtering options when calling `add-handler!` to prevent - handler from sending unnecessary emails! - - At least ALWAYS set an appropriate `:rate-limit` option, e.g.: - (add-handler! :my-postal-handler (handler:postal {}) - {:async {:mode :dropping, :buffer-size 128, :n-threads 4} ...}), etc. - - - Ref. for more info on `postal` options." + - Ref. for more info on `postal` options. + - Sending emails can be slow, and can incur financial costs! + Use appropriate handler dispatch options for async handling and rate limiting, etc." ;; ([] (handler:postal nil)) - ([{:keys - [postal/conn-opts - postal/msg-opts - subject-fn - body-fn] - + ([{:keys [conn-opts msg-opts, subject-fn body-fn] :or {subject-fn (signal-subject-fn) body-fn (utils/format-signal-fn)}}] - (when-not conn-opts (throw (ex-info "No `:postal/conn-opts` was given" {}))) - (when-not msg-opts (throw (ex-info "No `:postal/msg-opts` was given" {}))) + (when-not (map? conn-opts) (throw (ex-info "Expected `:conn-opts` map" (enc/typed-val conn-opts)))) + (when-not (map? msg-opts) (throw (ex-info "Expected `:msg-opts` map" (enc/typed-val msg-opts)))) - (let [] - (defn a-handler:postal - ([]) ; Shut down (no-op) - ([signal] - (enc/when-let [subject (subject-fn signal) - body (body-fn signal)] - (let [msg - (assoc msg-opts - :subject (str subject) - :body - (if (string? body) - [{:type "text/plain; charset=utf-8" - :content (str body)}] - body)) + (let [handler-fn + (fn a-handler:postal + ([]) ; Shut down (no-op) + ([signal] + (enc/when-let [subject (subject-fn signal) + body (body-fn signal)] + (let [msg + (assoc msg-opts + :subject (str subject) + :body + (if (string? body) + [{:type "text/plain; charset=utf-8" + :content (str body)}] + body)) - [result ex] - (try - [(postal/send-message conn-opts msg) nil] - (catch Exception ex [nil ex])) + [result ex] + (try + [(postal/send-message conn-opts msg) nil] + (catch Exception ex [nil ex])) - success? (= (get result :code) 0)] + success? (= (get result :code) 0)] - (when-not success? - (throw (ex-info "Failed to send email" result ex)))))))))) + (when-not success? + (throw (ex-info "Failed to send email" result ex)))))))] + + (with-meta handler-fn default-dispatch-opts)))) diff --git a/src/taoensso/telemere/sockets.clj b/src/taoensso/telemere/sockets.clj index e8328b9..3b47593 100644 --- a/src/taoensso/telemere/sockets.clj +++ b/src/taoensso/telemere/sockets.clj @@ -14,10 +14,6 @@ (remove-ns 'taoensso.telemere.sockets) (:api (enc/interns-overview))) -;;;; Implementation - -;;;; Handlers - (defn handler:tcp-socket "Experimental, subject to change. @@ -28,10 +24,12 @@ Can output signals as human or machine-readable (edn, JSON) strings. Options: - `host` - Destination TCP socket hostname string - `port` - Destination TCP socket port int + `:socket-opts` - {:keys [host port ssl? connect-timeout-msecs]} + `:host` - Destination TCP socket hostname string + `:port` - Destination TCP socket port int + `:ssl?` - Use SSL/TLS (default false) + `:connect-timeout-msecs` - Connection timeout (default 3000 msecs) - `:socket-opts` - {:keys [ssl? connect-timeout-msecs]} `:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn` Limitations: @@ -39,13 +37,12 @@ - Writes lock on a single underlying socket, so IO won't benefit from adding extra handler threads. Let me know if there's demand for socket pooling." - ([host port] (handler:tcp-socket host port nil)) - ([host port - {:keys [socket-opts output-fn] + ;; ([] (handler:tcp-socket nil)) + ([{:keys [socket-opts output-fn] :or {output-fn (utils/format-signal-fn)}}] - (let [sw (utils/tcp-socket-writer host port socket-opts)] - (defn a-handler:tcp-socket + (let [sw (utils/tcp-socket-writer socket-opts)] + (fn a-handler:tcp-socket ([] (sw)) ; Shut down ([signal] (when-let [output (output-fn signal)] @@ -61,11 +58,12 @@ Can output signals as human or machine-readable (edn, JSON) strings. Options: - `host` - Destination UDP socket hostname string - `port` - Destination UDP socket port int + `:socket-opts` - {:keys [host port max-packet-bytes]} + `:host` - Destination UDP socket hostname string + `:port` - Destination UDP socket port int + `:max-packet-bytes` - Max packet size (in bytes) before truncating output (default 512) `:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn` - `:max-packet-bytes` - Max packet size (in bytes) before truncating output (default 512) `:truncation-warning-fn` - Optional (fn [{:keys [max actual signal]}]) to call whenever output is truncated. Should be appropriately rate-limited! @@ -77,20 +75,26 @@ - No DTLS (Datagram Transport Layer Security) support, please let me know if there's demand." - ([host port] (handler:udp-socket host port nil)) - ([host port - {:keys [output-fn max-packet-bytes truncation-warning-fn] + ;; ([] (handler:udp-socket nil)) + ([{:keys [socket-opts output-fn truncation-warning-fn] :or - {output-fn (utils/format-signal-fn) - max-packet-bytes 512}}] + {socket-opts {:max-packet-bytes 512} + output-fn (utils/format-signal-fn)}}] + + (let [{:keys [host port max-packet-bytes] + :or {max-packet-bytes 512}} socket-opts + + max-packet-bytes (int max-packet-bytes) - (let [max-packet-bytes (int max-packet-bytes) socket (DatagramSocket.) ; No need to change socket once created lock (Object.)] + (when-not (string? host) (throw (ex-info "Expected `:host` string" (enc/typed-val host)))) + (when-not (int? port) (throw (ex-info "Expected `:port` int" (enc/typed-val port)))) + (.connect socket (InetSocketAddress. (str host) (int port))) - (defn a-handler:udp-socket + (fn a-handler:udp-socket ([] (.close socket)) ; Shut down ([signal] (when-let [output (output-fn signal)] diff --git a/src/taoensso/telemere/utils.cljc b/src/taoensso/telemere/utils.cljc index fe30473..bfd8bd5 100644 --- a/src/taoensso/telemere/utils.cljc +++ b/src/taoensso/telemere/utils.cljc @@ -205,7 +205,11 @@ - Flushes after every write. - Thread safe, locks on single file stream." - [file append?] + [{:keys [file append?] + :or {append? true}}] + + (when-not file (throw (ex-info "Expected `:file` value" (enc/typed-val file)))) + (let [file (writeable-file! file) stream_ (volatile! (file-stream file append?)) open?_ (enc/latom true) @@ -258,7 +262,7 @@ (reset!) (write-ba! ba)))))))))))) -(comment (def fw1 (file-writer "test.txt" true)) (fw1 "x") (fw1)) +(comment (def fw1 (file-writer {:file "test.txt"})) (fw1 "x") (fw1)) ;;;; Sockets @@ -306,9 +310,8 @@ - Advanced users may want a custom implementation using a connection pool and/or more sophisticated retry semantics, etc." - [host port - {:keys - [ssl? connect-timeout-msecs, + [{:keys + [host port, ssl? connect-timeout-msecs, socket-fn ssl-socket-fn] :as opts :or @@ -316,6 +319,9 @@ socket-fn default-socket-fn ssl-socket-fn default-ssl-socket-fn}}] + (when-not (string? host) (throw (ex-info "Expected `:host` string" (enc/typed-val host)))) + (when-not (int? port) (throw (ex-info "Expected `:port` int" (enc/typed-val port)))) + (let [new-conn! ; => [ ], or throws (fn [] (try diff --git a/test/taoensso/telemere_tests.cljc b/test/taoensso/telemere_tests.cljc index f17ba75..a99dd4c 100644 --- a/test/taoensso/telemere_tests.cljc +++ b/test/taoensso/telemere_tests.cljc @@ -644,7 +644,7 @@ #?(:clj (testing "File writer" (let [f (java.io.File/createTempFile "file-writer-test" ".txt") - fw (utils/file-writer f false)] + fw (utils/file-writer {:file f, :append? false})] [(is (true? (fw "1"))) (is (true? (.delete f)))