Model defenition for the Metabase Audit Log, which tracks actions taken by users across the Metabase app. This is distinct from the View Log model, which predates this namespace, and which powers specific API endpoints used for in-app functionality, such as the recently-viewed items displayed on the homepage. | (ns metabase.models.audit-log (:require [clojure.data :as data] [clojure.set :as set] [metabase.api.common :as api] [metabase.models.interface :as mi] [metabase.public-settings.premium-features :as premium-features] [metabase.util :as u] [metabase.util.malli :as mu] [metabase.util.malli.registry :as mr] [metabase.util.malli.schema :as ms] [methodical.core :as m] [steffan-westcott.clj-otel.api.trace.span :as span] [toucan2.core :as t2])) |
(set! *warn-on-reflection* true) | |
(doto :model/AuditLog (derive :metabase/model)) | |
(m/defmethod t2/table-name :model/AuditLog [_model] :audit_log) | |
(t2/deftransforms :model/AuditLog {:topic mi/transform-keyword :details mi/transform-json}) | |
Returns a map with data about an entity that should be included in the | (defmulti model-details {:arglists '([entity event-type])} mi/dispatch-on-model) |
(defmethod model-details :default [_entity _event-type] {}) | |
(def ^:private model-name->audit-logged-name {"RootCollection" "Collection"}) | |
Given an instance of a model or a keyword model identifier, returns the name to store in the database as a string, or | (defn model-name [instance-or-model] (let [model (or (t2/model instance-or-model) instance-or-model) raw-model-name (cond (= model :model/LegacyMetric) "Metric" (keyword? model) (name model) (class? model) (.getSimpleName ^java.lang.Class model))] (model-name->audit-logged-name raw-model-name raw-model-name))) |
Returns a map with previous and new versions of the objects, _keeping only fields that are present in both but have changed values_. | (defn- prepare-update-event-data [object previous-object] (let [[previous-only new-only _both] (data/diff previous-object object) shared-updated-keys (set/intersection (set (keys previous-only)) (set (keys new-only)))] {:previous (select-keys previous-object shared-updated-keys) :new (select-keys object shared-updated-keys)})) |
(mr/def ::event-params [:map {:closed true :doc "Used when inserting a value to the Audit Log."} [:object {:optional true} [:maybe :map]] [:previous-object {:optional true} [:maybe :map]] [:user-id {:optional true} [:maybe pos-int?]] [:model {:optional true} [:maybe [:or :keyword :string]]] [:model-id {:optional true} [:maybe pos-int?]] [:details {:optional true} [:maybe :map]]]) | |
(mu/defn construct-event :- [:map [:unqualified-topic simple-keyword?] [:user-id [:maybe ms/PositiveInt]] [:model-name [:maybe :string]] [:model-id [:maybe ms/PositiveInt]] [:details :map]] "Generates the data to be recorded in the Audit Log." ([topic :- :keyword params :- ::event-params current-user-id :- [:maybe pos-int?]] (let [unqualified-topic (keyword (name topic)) object (:object params) previous-object (:previous-object params) object-details (model-details object unqualified-topic) previous-details (model-details previous-object unqualified-topic)] {:unqualified-topic unqualified-topic :user-id (or (:user-id params) current-user-id) :model-name (model-name (or (:model params) object)) :model-id (or (:model-id params) (u/id object)) :details (merge {} (:details params) (if (not-empty previous-object) (prepare-update-event-data object-details previous-details) object-details))}))) | |
Records an event in the Audit Log.
Under certain conditions this function does not insert anything into the audit log. - If nothing is logged, returns nil - Otherwise, returns the audit logged row. | (mu/defn record-event! [topic :- :keyword params :- ::event-params] (when (premium-features/log-enabled?) (span/with-span! {:name "record-event!" :attributes (cond-> {} (:model-id params) (assoc :model/id (:model-id params)) (:user-id params) (assoc :user/id (:user-id params)) (:model params) (assoc :model/name (u/lower-case-en (:model params))))} (let [{:keys [user-id model-name model-id details unqualified-topic]} (construct-event topic params api/*current-user-id*)] (t2/insert! :model/AuditLog :topic unqualified-topic :details details :model model-name :model_id model-id :user_id user-id))))) |
(t2/define-before-insert :model/AuditLog [activity] (let [defaults {:timestamp :%now :details {}}] (merge defaults activity))) | |