     (timbre/log console-appender level {::ns ns ::msg msg}))
 
   SystemLogger
-  (add-session [_ out]
-    (swap! sessions conj out))
+  (add-session [_ log-writer]
+    (swap! sessions conj log-writer))
 
-  (remove-session [_ out]
-    (swap! sessions disj out))
+  (remove-session [_ log-writer]
+    (swap! sessions disj log-writer))
 
-  (get-command-logger [this out]
-    (let [all-appender (mk-appender sessions nil out)
-          command-appender (mk-appender nil nil out)]
+  (get-command-logger [this log-writer]
+    (let [all-appender (mk-appender sessions nil log-writer)
+          command-appender (mk-appender nil nil log-writer)]
       (reify CommandLogger
         (log-command [_ level msg]
           (timbre/log command-appender level msg))
         (log-all [_ level msg]
-          (timbre/log all-appender level msg))))))
+          (timbre/log all-appender level msg)))))
+
+  (set-level [this app-name level]
+    (swap! levels assoc app-name level)))
 
 
 (def logger
   (reify Startable
     (start [this systems]
       (let [sessions-atom (atom #{System/out})]
-        (Logger. sessions-atom (mk-appender sessions-atom nil nil))))))
+        (Logger. sessions-atom (atom {}) (mk-appender sessions-atom nil nil))))))

;; file, You can obtain one at http://mozilla.org/MPL/2.0/.

(ns containium.systems.logging
  (:require [containium.systems :refer (Startable Stoppable)]
            [containium.systems.config :refer (Config get-config)]
            [taoensso.timbre :as timbre])
  (:import [java.io PrintStream]))


;;; Public API for apps

(defprotocol Logging
  (get-logger [this app-name]))


(defprotocol AppLogger
  (log-console [this level msg] [this level ns msg]))


(defmacro log*
  [app-logger level & vals]
  (let [ns (str *ns*)]
    `(log-console ~app-logger ~level ~ns (apply str (interpose " " [~@vals])))))

           `(log* ~app-logger# ~~level ~@vals#))))


(defn refer-logging
  "Shorthand for:
  (require
    '[containium.systems.logging :as logging
      :refer (trace debug info warn error fatal report)])"
  []
  (require
   '[containium.systems.logging :as logging
     :refer (trace debug info warn error fatal report)]))


;;; Systems API for systems

(defprotocol SystemLogger
  (add-session [this log-writer]
    "Add a LogWriter to the console sessions. Make sure the underlying
    stream IO is buffered, so it does not hinder the logging. The
    following classes are already extending LogWriter: PrintStream.")

  (remove-session [this log-writer]
    "Removes a LogWriter from the console sessions.")

  (get-command-logger [this log-writer]
    "Creates a CommandLogger instance, which can be used for logging
    messages to only the command invoker (i.e., to the given
    LogWriter), or both the console and command invoker.")

  (set-level [this app-name level]
    "Overrides the currently set log-level of timbre for a logger
    retrieved using the Logging protocol. If the level argument is
    nil, the override is removed."))


(defprotocol CommandLogger
  (log-command [this level msg]
    "Log a message to the caller of the command.")

  (log-all [this level msg]
    "Log a message to both the caller of the command and the console
    sessions."))


(defprotocol LogWriter
  (write-line [this line]))


;;; Default implementation

(extend-type java.io.PrintStream
  LogWriter
  (write-line [^PrintStream this line]
    (.println this line)))


(defn mk-appender
  [sessions-atom prefix log-writer]
  {:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ"
   :appenders
   {:containium
    {:enabled? true
     :async? false
     :fn (fn [{:keys [args timestamp level]}]
           (let [message (first args)
                 prefixed (if-let [ns (and (map? message) (::ns message))]
                            (str "[" (when prefix (str prefix "@")) ns "] " (::msg message))
                            (str (when prefix (str "[" prefix "] ")) message))]
             (when log-writer
               (write-line log-writer (str timestamp " " (upper-case (name level)) " " prefixed)))
             (when sessions-atom
               (doseq [log-writer @sessions-atom]
                 (write-line log-writer
                             (str timestamp " " (upper-case (name level)) " " prefixed))))))}}})


;; sessions = (atom #{LogWriter})
;; levels = (atom {"app-name", :level})
(defrecord Logger [sessions levels console-appender]
  Logging
  (get-logger [this app-name]
    (let [appender (mk-appender sessions app-name nil)]
      (reify AppLogger
        (log-console [_ level msg]
          (if-let [log-level (get @levels app-name)]
            (timbre/with-log-level log-level (timbre/log appender level msg))
            (timbre/log appender level msg)))
        (log-console [_ level ns msg]
          (if-let [log-level (get @levels app-name)]
            (timbre/with-log-level log-level (timbre/log appender level {::ns ns ::msg msg}))
            (timbre/log appender level {::ns ns ::msg msg}))))))

  AppLogger
  (log-console [_ level msg]
