diff --git a/src/stream/commander/api.clj b/src/stream/commander/api.clj index 932afb4..47ad6f7 100644 --- a/src/stream/commander/api.clj +++ b/src/stream/commander/api.clj @@ -1,5 +1,6 @@ (ns stream.commander.api (:require [stream.commander.systemd :as sys] + [stream.commander.journal :as journal] [clojure.string :as string] [taoensso.timbre :as timbre :refer [log trace debug info warn error fatal report @@ -24,6 +25,7 @@ (def ^:private processes (ref {})) (defn- generate-process-id + "Generates a guaranteed unique ID for the process." [] (loop [] (let [id (str (java.util.UUID/randomUUID))] diff --git a/src/stream/commander/journal.clj b/src/stream/commander/journal.clj new file mode 100644 index 0000000..c5521ed --- /dev/null +++ b/src/stream/commander/journal.clj @@ -0,0 +1,69 @@ +(ns stream.commander.journal + (:require [taoensso.timbre :as timbre + :refer [log trace debug info warn error fatal report + logf tracef debugf infof warnf errorf fatalf reportf + spy get-env]] + [clojure.java.shell :refer [sh]] + [clojure.data.json :as json] + [slingshot.slingshot :refer [throw+]] + [clojure.core.async + :as a + :refer [>! !! flags + "Converts a hashmap {:a 1 :b 2} to command line flags." + [map] + (reduce (fn [options [flag value]] + (conj options + (let [name (name flag)] + (if (> (count name) 1) + (str "--" name "=" value) + (str "-" name value))))) + [] map)) + +(defn- run-journalctl-command! + "Boilerplate to run a journalctl command over the shell. + Returns the parsed json output as a lazy seq of maps. + This need not be performant." + [& {:as options}] + (let [result + (apply sh "journalctl" "--user" "-ojson" (map->flags options))] + (if (= (:exit result) 0) + (map #(json/read-str % :key-fn (comp keyword str/lower-case)) + (str/split-lines (:out result))) + (throw+ {:type ::journalctl-error + :detail-type ::command-error + :message (:err result)})))) + +(defn read-logs! + "Reads the logs from a systemd user unit specified by its name: `unit`. + + :number + How many of the latest log entries to read." + [unit & {:keys [number] :or {number 10}}] + (let [logs (run-journalctl-command! :u unit :n number)] + (map + (fn [entry] + {:level (get levels (:priority entry)) + :message (:message entry)}) + logs)))