diff --git a/config/test.exs b/config/test.exs index 0ca068b..a8a3876 100644 --- a/config/test.exs +++ b/config/test.exs @@ -13,13 +13,17 @@ config :logger, level: :info config :ex_audit, ecto_repos: [ExAudit.Test.Repo], - version_schema: ExAudit.Test.Version, - tracked_schemas: [ - ExAudit.Test.User, - ExAudit.Test.BlogPost, - ExAudit.Test.BlogPost.Section, - ExAudit.Test.Comment - ], + ecto_repos_schemas: %{ + ExAudit.Test.Repo => %{ + version_schema: ExAudit.Test.Version, + tracked_schemas: [ + ExAudit.Test.User, + ExAudit.Test.BlogPost, + ExAudit.Test.BlogPost.Section, + ExAudit.Test.Comment + ] + } + }, primitive_structs: [ Date ] diff --git a/lib/repo/queryable.ex b/lib/repo/queryable.ex index c4e729c..c4979ff 100644 --- a/lib/repo/queryable.ex +++ b/lib/repo/queryable.ex @@ -1,12 +1,6 @@ defmodule ExAudit.Queryable do require Logger - defp version_schema() do - Application.get_env(:ex_audit, :version_schema) - end - - @compile {:inline, version_schema: 0} - def update_all(module, queryable, updates, opts) do Ecto.Repo.Queryable.update_all(module, queryable, updates, opts) end @@ -20,7 +14,7 @@ defmodule ExAudit.Queryable do query = from( - v in version_schema(), + v in version_schema(module), order_by: [desc: :recorded_at] ) @@ -64,7 +58,7 @@ defmodule ExAudit.Queryable do versions ++ [ - struct(version_schema(), %{ + struct(version_schema(module), %{ id: oldest_id }) |> Map.put(:original, empty_map_to_nil(oldest_struct)) @@ -83,7 +77,7 @@ defmodule ExAudit.Queryable do query = from( - v in version_schema(), + v in version_schema(module), where: v.entity_id == ^version.entity_id, where: v.entity_schema == ^version.entity_schema, where: v.recorded_at >= ^version.recorded_at, @@ -176,4 +170,8 @@ defmodule ExAudit.Queryable do defp reverse_action(:updated), do: :updated defp reverse_action(:created), do: :deleted defp reverse_action(:deleted), do: :created + + defp version_schema(repo_module) do + Application.get_env(:ex_audit, :ecto_repos_schemas) |> get_in([repo_module, :version_schema]) + end end diff --git a/lib/repo/repo.ex b/lib/repo/repo.ex index 02a7851..4a6f03c 100644 --- a/lib/repo/repo.ex +++ b/lib/repo/repo.ex @@ -57,8 +57,10 @@ defmodule ExAudit.Repo do delete!: 2 ) - defp tracked?(struct_or_changeset) do - tracked_schemas = Application.get_env(:ex_audit, :tracked_schemas, []) + @compile {:inline, tracked?: 2} + + defp tracked?(repo_module, struct_or_changeset) do + tracked_schemas = ExAudit.Tracking.tracked_schemas(repo_module) schema = case struct_or_changeset do @@ -72,10 +74,8 @@ defmodule ExAudit.Repo do schema in tracked_schemas end - @compile {:inline, tracked?: 1} - def insert(struct, opts) do - if tracked?(struct) do + if tracked?(__MODULE__, struct) do ExAudit.Schema.insert( __MODULE__, get_dynamic_repo(), @@ -88,7 +88,7 @@ defmodule ExAudit.Repo do end def update(struct, opts) do - if tracked?(struct) do + if tracked?(__MODULE__, struct) do ExAudit.Schema.update( __MODULE__, get_dynamic_repo(), @@ -101,7 +101,7 @@ defmodule ExAudit.Repo do end def insert_or_update(changeset, opts) do - if tracked?(changeset) do + if tracked?(__MODULE__, changeset) do ExAudit.Schema.insert_or_update( __MODULE__, get_dynamic_repo(), @@ -114,7 +114,7 @@ defmodule ExAudit.Repo do end def delete(struct, opts) do - if tracked?(struct) do + if tracked?(__MODULE__, struct) do ExAudit.Schema.delete( __MODULE__, get_dynamic_repo(), @@ -127,7 +127,7 @@ defmodule ExAudit.Repo do end def insert!(struct, opts) do - if tracked?(struct) do + if tracked?(__MODULE__, struct) do ExAudit.Schema.insert!( __MODULE__, get_dynamic_repo(), @@ -140,7 +140,7 @@ defmodule ExAudit.Repo do end def update!(struct, opts) do - if tracked?(struct) do + if tracked?(__MODULE__, struct) do ExAudit.Schema.update!( __MODULE__, get_dynamic_repo(), @@ -153,7 +153,7 @@ defmodule ExAudit.Repo do end def insert_or_update!(changeset, opts) do - if tracked?(changeset) do + if tracked?(__MODULE__, changeset) do ExAudit.Schema.insert_or_update!( __MODULE__, get_dynamic_repo(), @@ -166,7 +166,7 @@ defmodule ExAudit.Repo do end def delete!(struct, opts) do - if tracked?(struct) do + if tracked?(__MODULE__, struct) do ExAudit.Schema.delete!( __MODULE__, get_dynamic_repo(), diff --git a/lib/repo/schema.ex b/lib/repo/schema.ex index efa87a7..3fc439a 100644 --- a/lib/repo/schema.ex +++ b/lib/repo/schema.ex @@ -1,4 +1,6 @@ defmodule ExAudit.Schema do + require Logger + def insert_all(module, name, schema_or_source, entries, opts) do # TODO! opts = augment_opts(opts) @@ -13,7 +15,7 @@ defmodule ExAudit.Schema do case result do {:ok, resulting_struct} -> - ExAudit.Tracking.track_change(module, :created, struct, resulting_struct, opts) + track_change(module, :created, struct, resulting_struct, opts) _ -> :ok @@ -31,7 +33,7 @@ defmodule ExAudit.Schema do case result do {:ok, resulting_struct} -> - ExAudit.Tracking.track_change(module, :updated, struct, resulting_struct, opts) + track_change(module, :updated, struct, resulting_struct, opts) _ -> :ok @@ -50,7 +52,7 @@ defmodule ExAudit.Schema do case result do {:ok, resulting_struct} -> state = if changeset.data.__meta__.state == :loaded, do: :updated, else: :created - ExAudit.Tracking.track_change(module, state, changeset, resulting_struct, opts) + track_change(module, state, changeset, resulting_struct, opts) _ -> :ok @@ -64,12 +66,12 @@ defmodule ExAudit.Schema do opts = augment_opts(opts) augment_transaction(module, fn -> - ExAudit.Tracking.track_assoc_deletion(module, struct, opts) + track_assoc_deletion(module, struct, opts) result = Ecto.Repo.Schema.delete(module, name, struct, opts) case result do {:ok, resulting_struct} -> - ExAudit.Tracking.track_change(module, :deleted, struct, resulting_struct, opts) + track_change(module, :deleted, struct, resulting_struct, opts) _ -> :ok @@ -86,7 +88,7 @@ defmodule ExAudit.Schema do module, fn -> result = Ecto.Repo.Schema.insert!(module, name, struct, opts) - ExAudit.Tracking.track_change(module, :created, struct, result, opts) + track_change(module, :created, struct, result, opts) result end, true @@ -100,7 +102,7 @@ defmodule ExAudit.Schema do module, fn -> result = Ecto.Repo.Schema.update!(module, name, struct, opts) - ExAudit.Tracking.track_change(module, :updated, struct, result, opts) + track_change(module, :updated, struct, result, opts) result end, true @@ -115,7 +117,7 @@ defmodule ExAudit.Schema do fn -> result = Ecto.Repo.Schema.insert_or_update!(module, name, changeset, opts) state = if changeset.data.__meta__.state == :loaded, do: :updated, else: :created - ExAudit.Tracking.track_change(module, state, changeset, result, opts) + track_change(module, state, changeset, result, opts) result end, true @@ -128,9 +130,9 @@ defmodule ExAudit.Schema do augment_transaction( module, fn -> - ExAudit.Tracking.track_assoc_deletion(module, struct, opts) + track_assoc_deletion(module, struct, opts) result = Ecto.Repo.Schema.delete!(module, name, struct, opts) - ExAudit.Tracking.track_change(module, :deleted, struct, result, opts) + track_change(module, :deleted, struct, result, opts) result end, true @@ -174,4 +176,25 @@ defmodule ExAudit.Schema do end ++ custom_fields end) end + + # It wraps Tracking.track_change in a try rescue block because we don't want to crash the caller process when there is exception in track_change + # It's opinionated here that tracking is something that's not mission critical and thus an exception caused by bug should + # TODO make this try rescue behaviour configurable + defp track_change(module, action, changeset, resulting_struct, opts) do + ExAudit.Tracking.track_change(module, action, changeset, resulting_struct, opts) + rescue + e -> + :error + |> Exception.format(e, __STACKTRACE__) + |> Logger.error(crash_reason: {e, __STACKTRACE__}) + end + + def track_assoc_deletion(module, struct, opts) do + ExAudit.Tracking.track_assoc_deletion(module, struct, opts) + rescue + e -> + :error + |> Exception.format(e, __STACKTRACE__) + |> Logger.error(crash_reason: {e, __STACKTRACE__}) + end end diff --git a/lib/repo/schema_type.ex b/lib/repo/schema_type.ex index a62a07e..50cceec 100644 --- a/lib/repo/schema_type.ex +++ b/lib/repo/schema_type.ex @@ -37,7 +37,9 @@ defmodule ExAudit.Type.Schema do def type, do: :string - defp schemas do - Application.get_env(:ex_audit, :tracked_schemas, []) + defp schemas() do + Application.get_env(:ex_audit, :ecto_repos_schemas) + |> Map.values() + |> Enum.flat_map(& &1.tracked_schemas) end end diff --git a/lib/tracking/tracking.ex b/lib/tracking/tracking.ex index 9fd1772..84ffd2b 100644 --- a/lib/tracking/tracking.ex +++ b/lib/tracking/tracking.ex @@ -1,5 +1,5 @@ defmodule ExAudit.Tracking do - def find_changes(action, struct_or_changeset, resulting_struct) do + def find_changes(module, action, struct_or_changeset, resulting_struct) do old = case {action, struct_or_changeset} do {:created, _} -> %{} @@ -17,13 +17,13 @@ defmodule ExAudit.Tracking do %{} end - compare_versions(action, old, new) + compare_versions(module, action, old, new) end - def compare_versions(action, old, new) do + def compare_versions(module, action, old, new) do schema = Map.get(old, :__struct__, Map.get(new, :__struct__)) - if schema in tracked_schemas() do + if schema in tracked_schemas(module) do assocs = schema.__schema__(:associations) patch = @@ -53,7 +53,7 @@ defmodule ExAudit.Tracking do def track_change(module, action, changeset, resulting_struct, opts) do if not Keyword.get(opts, :ignore_audit, false) do - changes = find_changes(action, changeset, resulting_struct) + changes = find_changes(module, action, changeset, resulting_struct) insert_versions(module, changes, opts) end @@ -78,7 +78,7 @@ defmodule ExAudit.Tracking do _ -> opts = Keyword.drop(opts, [:on_conflict, :conflict_target]) - module.insert_all(version_schema(), changes, opts) + module.insert_all(version_schema(module), changes, opts) end end @@ -102,7 +102,7 @@ defmodule ExAudit.Tracking do root ++ Enum.map(root, &find_assoc_deletion(module, &1, repo_opts)) end) |> List.flatten() - |> Enum.flat_map(&compare_versions(:deleted, &1, %{})) + |> Enum.flat_map(&compare_versions(module, :deleted, &1, %{})) end def track_assoc_deletion(module, struct, opts) do @@ -111,11 +111,11 @@ defmodule ExAudit.Tracking do insert_versions(module, deleted_structs, opts) end - defp tracked_schemas do - Application.get_env(:ex_audit, :tracked_schemas, []) + def tracked_schemas(repo_module) do + Application.get_env(:ex_audit, :ecto_repos_schemas) |> get_in([repo_module, :tracked_schemas]) end - defp version_schema do - Application.get_env(:ex_audit, :version_schema) + defp version_schema(repo_module) do + Application.get_env(:ex_audit, :ecto_repos_schemas) |> get_in([repo_module, :version_schema]) end end diff --git a/test/ex_audit_test.exs b/test/ex_audit_test.exs index 0d447dd..a9309e5 100644 --- a/test/ex_audit_test.exs +++ b/test/ex_audit_test.exs @@ -1,5 +1,5 @@ defmodule ExAuditTest do - use ExUnit.Case + use ExUnit.Case, async: false doctest ExAudit import Ecto.Query @@ -245,4 +245,19 @@ defmodule ExAuditTest do assert 2 = Repo.aggregate(query, :count, :id) end + + test "will not crash the caller process if the tracking " do + original = Application.get_env(:ex_audit, :ecto_repos_schemas) + Application.put_env(:ex_audit, :ecto_repos, :crash) + + ExUnit.Callbacks.on_exit(fn -> + Application.put_env(:ex_audit, :ecto_repos, original) + end) + + user = Util.create_user() + + changeset = User.changeset(user, %{transient_field: 3}) + + assert {:ok, user} = Repo.update(changeset) + end end diff --git a/test/test_helper.exs b/test/test_helper.exs index 6d539a6..86e3496 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -2,7 +2,6 @@ ExAudit.Test.Repo.start_link() Ecto.Adapters.SQL.Sandbox.mode(ExAudit.Test.Repo, :auto) - migrations_path = Path.join([:code.priv_dir(:ex_audit), "repo", "migrations"]) Ecto.Migrator.run(ExAudit.Test.Repo, migrations_path, :up, all: true)