162 lines
6.0 KiB

(ns splunksearch.core
(:require [clojure.java.io :refer :all])
(:import (com.splunk Service ServiceArgs Args ResultsReaderJson ResultsReaderCsv ResultsReaderXml Event))
(:import (com.splunk Command))
(:import (java.io InputStreamReader OutputStreamWriter)))
; Given a set of command-line arguments, creates, populates and
; returns a splunk.Command instance.
(defn build-splunk-command [args]
(let [command
(doto (Command/splunk "search")
(.addRule "count" Integer
"The Maximum Number of results to return (default: 100)")
(.addRule "earliest_time" String
"Search earliest time")
(.addRule "field_list" String
"A comma-separated list of the fields to return")
(.addRule "latest_time" String
"Search latest time")
(.addRule "offset" Integer
"The first result (inclusive) from which to begin returning data. (default: 0)")
(.addRule "output" String
"Which search results to output {events, results, preview, searchlog, summary, timeline} (default: results)")
(.addRule "output_mode" String
"Search output format {csv, raw, json, xml} (default: xml)")
(.addRule "reader"
"Use ResultsReader")
(.addRule "status_buckets" Integer
"Number of status buckets to use for search (default: 0)")
(.addRule "verbose"
"Display search progress")
(.parse (into-array String args)))]
(if (not= (count (.args command)) 1)
(Command/error "Search expression required" nil))
; Given an initialized splunk.Command instance, returns a map of
; arguments commonly used to control search behavior. The repetition
; of names above is a giveaway that something is a bit off here.
(defn build-argument-map [command]
(let [opts (.opts command)
ruleset [["count" 100]
["earliest_time" nil ]
["reader" false]
["verbose" false]
["field_list" nil ]
["latest_time" nil ]
["offset" 0]
["output" "results"]
["output_mode" "xml"]]]
doall (into {} (for [[k v] ruleset] [k (if (.containsKey opts k) (.get opts k) v)]))))
; From a map of commonly used control arguments, create, populate, and
; return a splunk.Args instance suitable for passing to a query
(defn build-splunk-queryargs [argument-map]
(let [rulelist ["earliest_time" "field_list" "latest_time" "status_buckets" "output_mode"]
qa (Args.)]
(fn [a] (not= a nil))
(for [fieldname rulelist]
(fn [fieldname]
(if (argument-map fieldname)
(.put qa fieldname (argument-map fieldname))
; Given a splunk.Command instance and a set of initialized arguments,
; generate an actual query; wait until the query is done.
(defn build-splunk-job [command argument-map]
(let [queryargs (build-splunk-queryargs argument-map)
service (Service/connect (.opts command))
job (.. service (getJobs) (create (first (.args command)) queryargs))]
(while (not (.isDone job))
(if (argument-map "verbose")
(println (format "\n%03.1f%% done -- %d scanned -- %d matched -- %d results"
(* (.getDoneProgress job) 100.0)
(.getScanCount job)
(.getEventCount job)
(.getResultCount job))))
(Thread/sleep 1000))
; From a map of commonly used control arguments, create, populate, and
; return a splunk.Args instance suitable for passing to a splunk.Job
; specifying desired output paramaters.
(defn build-splunk-output-args [argument-map]
(let [rulelist ["count" "offset" "output_mode"]
args (Args.)]
(doseq [fieldname rulelist]
(if (argument-map fieldname)
(.put args fieldname (argument-map fieldname))))
; Given a job and a map of commonly use arguments, return an
; InputStream with the content of the response
(defn get-splunk-stream [job argument-map]
(let [outputargs (build-splunk-output-args argument-map)
output (argument-map "output")]
(case output
"results" (.getResults job outputargs)
"preview" (.getPreview job outputargs)
"searchlog" (.getSearchLog job outputargs)
"summary" (.getSummary job outputargs)
"timeline" (.getTimeline job outputargs)
(defn construct-reader [stream output-mode]
(case output-mode
"xml" (ResultsReaderXml. stream)
"json" (ResultsReaderJson. stream)
"csv" (ResultsReaderCsv. stream)))
(defn get-streamtype-reader [output-mode]
(fn [stream]
(with-open [reader (construct-reader stream output-mode)]
(loop [e (.getNextEvent reader)]
(when (not= e nil)
(println "EVENT:********")
(doseq [k (seq (.keySet e))]
(printf "%s ---> %s\n" k (.get e k)))
(recur (.getNextEvent reader)))))))
; A totally unnecessary level of indirection, but it maintains an
; aesthetic symmetry.
(defn generic-reader []
(fn [stream]
(with-open [reader (InputStreamReader. stream "UTF8")
writer (OutputStreamWriter. System/out)]
(let [buffer (char-array 1024)]
(while true
(let [count (.read reader buffer)]
(if (== count -1) (throw (Exception. "EOF")))
(.write writer buffer 0 count))))
(catch Exception e nil)))))
(defn get-reader [command argument-map]
(let [use-reader (.. command opts (containsKey "reader"))]
(if use-reader
(get-streamtype-reader (argument-map "output_mode"))
(defn -main[& args]
(let [command (build-splunk-command args)]
(let [argument-map (build-argument-map command)]
(let [job (build-splunk-job command argument-map)]
(let [stream (get-splunk-stream job argument-map)]
(let [reader (get-reader command argument-map)]
(reader stream)))))))