ExAudit

repository·master·Indexed 18 days ago

https://github.com/zenneriot/ex_audit

An Ecto auditing library that transparently tracks database changes and provides mechanisms to revert entities to previous states. It wraps Ecto.Repo functions to create diffs of changesets, offering history retrieval via `history/2` and state restoration via `revert/2`. It supports custom version schemas, field-level tracking via the `ExAudit.Tracker` protocol, and process-based context tracking using `ExAudit.track/1`.

Tokens
4.9K
Snippets
21
Records
24
Agent score
62%

What's inside ex_audit

  1. Install and Setup ExAudit

    master

    To use ExAudit, add it to your project dependencies and hook ExAudit.Repo into your existing Ecto repository module. This allows ExAudit to transparently wrap standard Ecto mutation functions.

    # 1. Add to deps
    def deps do
      [ {:ex_audit, "~> 0.9"} ]
    end
    
    # 2. Hook into your Repo
    defmodule MyApp.Repo do
      use Ecto.Repo,
        otp_app: :my_app,
        adapter: Ecto.Adapters.Postgres
    
      use ExAudit.Repo
    end
  2. Implement the Version Schema and Migration

    master

    ExAudit requires you to implement your own version schema and migration. This allows you to customize the primary key type (e.g., UUID) and add custom fields (e.g., user_id) to track who performed the action.

    Key fields in the versions table include:

    • patch: The change in Erlang External Term Format.
    • entity_id: The ID of the tracked entity.
    • entity_schema: The name of the table/schema.
    • action: The type of action (created, updated, deleted).
    • recorded_at: Timestamp of the change.
    • rollback: Boolean indicating if the change was a rollback.
    # Example Schema
    defmodule MyApp.Version do
      use Ecto.Schema
      import Ecto.Changeset
    
      schema "versions" do
        field :patch, ExAudit.Type.Patch
        field :entity_id, :integer
        field :entity_schema, ExAudit.Type.Schema
        field :action, ExAudit.Type.Action
        field :recorded_at, :utc_datetime
        field :rollback, :boolean, default: false
    
        belongs_to :user, MyApp.Accounts.User
      end
    
      def changeset(struct, params \ %{}) do
        struct
        |> cast(params, [:patch, :entity_id, :entity_schema, :action, :recorded_at, :rollback])
        |> cast(params, [:user_id])
      end
    end
    
    # Example Migration
    defmodule MyApp.Migrations.AddVersions do
      use Ecto.Migration
    
      def change do
        create table(:versions) do
          add :patch, :binary
          add :entity_id, :integer
          add :entity_schema, :string
          add :action, :string
          add :recorded_at, :utc_datetime
          add :rollback, :boolean, default: false
          add :user_id, references(:users, on_update: :update_all, on_delete: :nilify_all)
        end
    
        create index(:versions, [:entity_schema, :entity_id])
      end
    end
  3. Track custom data like User IDs

    master

    To record additional context (like the current user's ID) with an audit log, you have two primary methods:

    1. Manual passing: Use the ex_audit_custom option in any Repo function call.
    2. Plug-based tracking: Use ExAudit.track/1 in a Phoenix Plug. This attaches the data to the current process (PID), which ExAudit will then automatically pick up when Repo functions are called within that same process tree.
    # Method 1: Manual option
    MyApp.Repo.insert(changeset, ex_audit_custom: [user_id: conn.assigns.current_user.id])
    
    # Method 2: Using a Plug
    defmodule MyApp.ExAuditPlug do
      def init(_), do: nil
    
      def call(conn, _) do
        ExAudit.track(user_id: conn.assigns.current_user.id)
        conn
      end
    end
  4. Configure ExAudit schemas and versioning

    master

    You must configure which Ecto repositories to use, which schemas should be tracked, and which module defines your version schema. You can also define :primitive_structs to prevent ExAudit from recursing into specific types (like Date), recording them as a single change instead.

    config :ex_audit,
      ecto_repos: [MyApp.Repo],
      version_schema: MyApp.Version,
      tracked_schemas: [
        MyApp.Accounts.User,
        MyApp.BlogPost,
        MyApp.Comment
      ],
      primitive_structs: [
        Date
      ]
  5. Implement custom field tracking with ExAudit.Tracker

    master

    The ExAudit.Tracker protocol allows you to control which fields of a struct are captured during an audit. By implementing this protocol, you can filter out sensitive data or reduce audit noise by specifying exactly which fields should be tracked.

    For most use cases, you do not need to implement the protocol manually. Instead, you can use the @derive attribute with only or except options to declaratively define your tracking logic.

    # Track only specific fields
    @derive {ExAudit.Tracker, only: [:name, :email]}
    defmodule User do
      use ExAudit.Schema
    end
    
    # Ignore specific fields
    @derive {ExAudit.Tracker, except: [:password_hash, :internal_notes]}
    defmodule User do
      use ExAudit.Schema
    end
  6. Integrate ExAudit into your Ecto.Repo

    master

    To enable automatic version tracking for your database operations, add use ExAudit.Repo to your existing Ecto.Repo module. This extends standard Ecto functions to detect if the struct or changeset being operated on is included in the :tracked_schemas configuration list.

    defmodule MyApp.Repo do
      use Ecto.Repo, 
        otp_app: :my_app, 
        adapter: Ecto.Adapters.Postgres
    
      use ExAudit.Repo
    end
  7. Use ExAudit Repo functions for history and rollback

    master

    Once configured, ExAudit automatically tracks changes made via insert/2, insert!/2, update/2, update!/2, delete/2, and delete!/2. It also provides two new functions for managing history:

    • history(struct): Returns a list of all versions of the given struct, ordered from oldest to newest.
    • revert(struct, version): Rolls the entity back to the state it was in before the specified version was changed.
  8. Configure ExAudit tracking options

    master

    ExAudit supports additional options passed to its extended Ecto functions to control auditing behavior:

    • :ex_audit_custom - A keyword list of custom data to be included in the new version entries. These entries will overwrite data with the same keys from the ExAudit.track call.
    • :ignore_audit - If set to true, ExAudit will skip tracking changes made to the entity.
  9. Configure the audit version schema

    master

    ExAudit uses a central schema to store audit logs. You can configure which schema is used by setting the :version_schema key in your application configuration.

    Config Key:

    • :ex_audit -> :version_schema: The module name of the schema used for storing versions.
    # In config/config.exs
    config :ex_audit, version_schema: MyAuditSchema
  10. Configure field tracking options via @derive

    master

    When using @derive {ExAudit.Tracker, options}, you can pass the following options to control field extraction:

    • only: [:field1, :field2]: Tracks only the specified fields. All other fields are ignored.
    • except: [:field1, :field2]: Tracks all fields except the ones specified. Note that __meta__ and __struct__ are always ignored by default.
    • No options: If no options are provided, the tracker defaults to dropping only the internal __meta__ and __struct__ fields.
    # Example: Using 'only' to whitelist fields
    @derive {ExAudit.Tracker, only: [:id, :status]}
    
    # Example: Using 'except' to blacklist fields
    @derive {ExAudit.Tracker, except: [:secret_token]}
  11. Configure ExAudit via application environment

    master

    ExAudit uses application environment configuration to determine which schemas to track and which schema to use for storing audit versions.

    Configuration Keys:

    • :tracked_schemas: A list of module names (schemas) that ExAudit.Tracking is allowed to generate diffs for.
    • :version_schema: The module name of the schema used to store the audit/version records.
    ## Example configuration in config/config.exs
    
    config :ex_audit, 
      tracked_schemas: [MyApp.User, MyApp.Post],
      version_schema: MyApp.AuditLog