Wrote this fn to parse a line from our log files. Idiomatic?
(require '[java-time :as t])
(defn parse-log-entry [line]
(let [log-format (array-map :timestamp "\\[([^\\]]+)\\]"
:file "\\[Fl:\\s*([^\\]]+)\\]"
:class "\\[Cl:\\s*([^\\]]+)\\]"
:method "\\[Fn:\\s*([^\\]]+)\\]"
:line "\\[Ln:\\s*([^\\]]+)\\]"
:level "\\[([^\\]]+)\\]"
:summary "\\[([^\\]]+)\\]"
:thread-id "([^_]+_[^\\s]+)\\s"
:server-ip "([\\d.]+)"
:client-ip "([\\d.]+)"
:msg "\\[(.+)$")
log-entry-regex (->> (concat "^" (vals log-format) "$")
(clojure.string/join "\\s*")
re-pattern)
entry (->> (re-find log-entry-regex line)
rest
(map clojure.string/trim)
(map vector (keys log-format))
(into {}))
parse-timestamp #(->> (concat % (repeat \0))
(take 24)
(apply str)
(t/local-date-time (t/formatter "dd-MM-yyyy HH:mm:ss.SSSS")))]
(-> entry
(update :line #(Integer/parseInt %))
(update :timestamp parse-timestamp))))
It seems that you might want a real parser - take a look instaparse
Yeah, instaparse seems like overkill, and bashing strings together to create regexps seems like underkill. Do you reckon instaparse is a better fit here?
Look into named group regexes in java.
They might make the code a little more obvious.
Performance might have to be taken into consideration too (if your logs are large) - apart from readability and sensitivity to subtle errors, regexes aren't terribly performant. However, instaparse is also known to be quite slow (antlr is much better, but as you say that's probably an overkill in your case)
Thanks Dominic!
Regex is fast by a lot of measures. Especially if you don't use lookahead,
Yeah @jumar this is just for a script that helps me analyze logs easily on my machine in the REPL. Will use a proper parser generator if we decide to put this somewhere at scale.
hmm, what type of logs are you trying to parse?
Logs of an application (Apache + PHP) at work.
array-map
is fragile - when you want order, you usually don't want maps. I'd use a vector of tuples instead.
I would also do some initialization outside of the function body - here you're compiling a big regex for each line of log!
Likewise, it seems that zipmap
is what you want for computing entry
.
TIL zipmap
.
I initially defined log-entry-regex
as a var, but moved it in here later. Thank you for your insight.