Skip to content

Commit

Permalink
[#166] Add 3rd-party logstash appender (@dfrese)
Browse files Browse the repository at this point in the history
  • Loading branch information
David Frese authored and ptaoussanis committed Apr 28, 2016
1 parent b86eb0d commit 2adbabe
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 1 deletion.
3 changes: 2 additions & 1 deletion project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
[org.graylog2/gelfclient "1.3.1"]
[org.julienxx/clj-slack "0.5.3"]
[org.clojure/java.jdbc "0.5.8"]
[com.mchange/c3p0 "0.9.5.2"]]}
[com.mchange/c3p0 "0.9.5.2"]
[cheshire "5.5.0"]]}
:dev
[:1.7 :test
{:dependencies [[org.clojure/clojurescript "1.7.28"]]
Expand Down
75 changes: 75 additions & 0 deletions src/taoensso/timbre/appenders/3rd_party/logstash.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
(ns taoensso.timbre.appenders.3rd-party.logstash
"Timbre appender that send output to logstash.
Requires cheshire (https://github.com/dakrone/cheshire)."
{:author "Mike Sperber (@mikesperber), David Frese (@dfrese)"}
(:require [taoensso.timbre :as timbre]
[cheshire.core :as cheshire])
(:import [java.net Socket InetAddress]
[java.io PrintWriter]))

;; Adapted from taoensso.timbre.appenders.3rd-party.server-socket

(defn connect
[host port]
(let [addr (InetAddress/getByName host)
sock (Socket. addr (int port))]
[sock
(PrintWriter. (.getOutputStream sock))]))

(defn connection-ok?
[[^Socket sock ^PrintWriter out]]
(and (not (.isClosed sock))
(.isConnected sock)
(not (.checkError out))))

(def iso-format "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")

(defn data->json-stream
[data writer opts]
;; Note: this it meant to target the logstash-filter-json; especially "message" and "@timestamp" get a special meaning there.
(let [stacktrace-str (if-let [pr (:pr-stacktrace opts)]
#(with-out-str (pr %))
timbre/stacktrace)]
(cheshire/generate-stream
(merge (:context data)
{:level (:level data)
:namespace (:?ns-str data)
:file (:?file data)
:line (:?line data)
:stacktrace (some-> (force (:?err_ data)) (stacktrace-str))
:hostname (force (:hostname_ data))
:message (force (:msg_ data))
"@timestamp" (:instant data)})
writer
(merge {:date-format iso-format
:pretty false}
opts))))

(defn logstash-appender
"Returns a Logstash appender, which will send each event in JSON
format to the logstash server at `host:port`. Additionally `opts`
may be a map with `:pr-stracktrace` mapped to a function taking an
exception, which should write the stacktrace of that exception to
`*out`."
[host port & [opts]]
(let [conn (atom nil)
nl "\n"]
{:enabled? true
:async? false
:min-level nil
:rate-limit nil
:output-fn :inherit
:fn
(fn [data]
(try
(let [[sock out] (swap! conn
(fn [conn]
(or (and conn (connection-ok? conn) conn)
(connect host port))))]
(locking sock
(data->json-stream data out (select-keys opts [:pr-stacktrace]))
;; logstash tcp input plugin: "each event is assumed to be one line of text".
(.write ^java.io.Writer out nl)))
(catch java.io.IOException _
nil)))}))

0 comments on commit 2adbabe

Please sign in to comment.