Configures the logger system for Metabase. Sets up an in-memory logger in a ring buffer for showing in the UI. Other logging options are set in [[metabase.bootstrap]]: the context locator for log4j2 and ensuring log4j2 is the logger that clojure.tools.logging uses. | (ns metabase.logger (:require [amalloy.ring-buffer :refer [ring-buffer]] [clj-time.coerce :as time.coerce] [clj-time.format :as time.format] ^{:clj-kondo/ignore [:discouraged-namespace]} [clojure.tools.logging :as log] [clojure.tools.logging.impl :as log.impl] [metabase.config :as config] [metabase.plugins.classloader :as classloader]) (:import (java.lang AutoCloseable) (org.apache.commons.lang3.exception ExceptionUtils) (org.apache.logging.log4j LogManager Level) (org.apache.logging.log4j.core Appender LogEvent Logger LoggerContext) (org.apache.logging.log4j.core.appender AbstractAppender FileAppender OutputStreamAppender) (org.apache.logging.log4j.core.config AbstractConfiguration Configuration LoggerConfig))) |
(set! *warn-on-reflection* true) | |
(def ^:private ^:const max-log-entries 2500) | |
(defonce ^:private messages* (atom (ring-buffer max-log-entries))) | |
Get the list of currently buffered log entries, from most-recent to oldest. | (defn messages [] (reverse (seq @messages*))) |
(defn- event->log-data [^LogEvent event] {:timestamp (time.format/unparse (time.format/formatter :date-time) (time.coerce/from-long (.getTimeMillis event))) :level (.getLevel event) :fqns (.getLoggerName event) :msg (.getMessage event) :exception (when-let [throwable (.getThrown event)] (seq (ExceptionUtils/getStackFrames throwable))) :process_uuid config/local-process-uuid}) | |
(defn- metabase-appender ^Appender [] (let [^org.apache.logging.log4j.core.Filter filter nil ^org.apache.logging.log4j.core.Layout layout nil ^"[Lorg.apache.logging.log4j.core.config.Property;" properties nil] (proxy [org.apache.logging.log4j.core.appender.AbstractAppender] ["metabase-appender" filter layout false properties] (append [event] (swap! messages* conj (event->log-data event)) nil)))) | |
(defonce ^:private has-added-appender? (atom false)) | |
Get global logging context. | (defn context ^LoggerContext [] (LogManager/getContext (classloader/the-classloader) false)) |
Get global logging configuration | (defn configuration ^Configuration [] (.getConfiguration (context))) |
(when-not *compile-files* (when-not @has-added-appender? (reset! has-added-appender? true) (let [appender (metabase-appender) config (configuration)] (.start appender) (.addAppender config appender) (doseq [[_ ^LoggerConfig logger-config] (.getLoggers config)] (.addAppender logger-config appender (.getLevel logger-config) (.getFilter logger-config)) (.updateLoggers (context)))))) | |
Custom loggers | |
Get string name from symbol or ns | (defn logger-name ^String [a-namespace] (if (instance? clojure.lang.Namespace a-namespace) (name (ns-name a-namespace)) (name a-namespace))) |
Is logging at | (defn level-enabled? (^Boolean [level] (level-enabled? *ns* level)) (^Boolean [a-namespace level] (let [^Logger logger (log.impl/get-logger log/*logger-factory* a-namespace)] (.isEnabled logger level)))) |
Get the logger that will be used for the namespace named by | (defn effective-ns-logger ^LoggerConfig [a-namespace] (let [^Logger logger (log.impl/get-logger log/*logger-factory* a-namespace)] (.get logger))) |
Find any logger with a specified layout. | (defn- find-logger-layout [^LoggerConfig logger] (when logger (or (first (keep #(.getLayout ^AbstractAppender (val %)) (.getAppenders logger))) (recur (.getParent logger))))) |
(defprotocol MakeAppender (make-appender ^AbstractAppender [out layout])) | |
(extend-protocol MakeAppender java.io.File (make-appender [^java.io.File out layout] (.build (doto (FileAppender/newBuilder) (.setName "shared-appender-file") (.setLayout layout) (.withFileName (.getPath out))))) java.io.OutputStream (make-appender [^java.io.OutputStream out layout] (.build (doto (OutputStreamAppender/newBuilder) (.setName "shared-appender-os") (.setLayout layout) (.setTarget out))))) | |
Add a logger for a given namespace to the configuration. | (defn add-ns-logger [ns appender level additive] (let [logger-name (str ns) ns-logger (LoggerConfig. logger-name level additive)] (.addAppender ns-logger appender level nil) (.addLogger (configuration) logger-name ns-logger) ns-logger)) |
Create separate logger for a given namespace(s) to fork out some logs. | (defn for-ns ^AutoCloseable [out nses & [{:keys [additive level] :or {additive true level Level/INFO}}]] (let [nses (if (vector? nses) nses [nses]) config (configuration) parents (mapv effective-ns-logger nses) appender (make-appender out (find-logger-layout (first parents))) loggers (vec (for [ns nses] (add-ns-logger ns appender level additive)))] (.start appender) (.addAppender config appender) (.updateLoggers (context)) (reify AutoCloseable (close [_] (let [^AbstractConfiguration config (configuration)] (doseq [logger loggers] (.removeLogger config (.getName ^LoggerConfig logger))) (.stop appender) ;; this method is only present in AbstractConfiguration (.removeAppender config (.getName appender)) (.updateLoggers (context))))))) |