From 33d4fe97b8c1ed43e186c025b613ff25f4ff876e Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sun, 1 Mar 2026 14:58:16 +0100 Subject: [PATCH 1/6] Group optional deps in mix.exs --- mix.exs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index cc977b7..b3ddcf8 100644 --- a/mix.exs +++ b/mix.exs @@ -89,10 +89,6 @@ defmodule ErrorTracker.MixProject do {:phoenix_live_view, "~> 1.0"}, {:phoenix_ecto, "~> 4.6"}, {:plug, "~> 1.10"}, - {:jason, "~> 1.1", optional: true}, - {:postgrex, ">= 0.0.0", optional: true}, - {:myxql, ">= 0.0.0", optional: true}, - {:ecto_sqlite3, ">= 0.0.0", optional: true}, # Dev dependencies {:bun, "~> 1.3", only: :dev}, {:credo, "~> 1.7", only: [:dev, :test]}, @@ -101,6 +97,10 @@ defmodule ErrorTracker.MixProject do {:plug_cowboy, ">= 0.0.0", only: :dev}, {:tailwind, "~> 0.2", only: :dev}, # Optional dependencies + {:jason, "~> 1.1", optional: true}, + {:postgrex, ">= 0.0.0", optional: true}, + {:myxql, ">= 0.0.0", optional: true}, + {:ecto_sqlite3, ">= 0.0.0", optional: true}, {:igniter, "~> 0.5", optional: true} ] end From d9d4df55a557f6bcfa99705b8aec30b011d49066 Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sun, 1 Mar 2026 14:59:03 +0100 Subject: [PATCH 2/6] Drop Credo as a dependency --- .credo.exs | 217 ----------------------------------- .github/workflows/elixir.yml | 3 - mix.exs | 1 - mix.lock | 1 - 4 files changed, 222 deletions(-) delete mode 100644 .credo.exs diff --git a/.credo.exs b/.credo.exs deleted file mode 100644 index e130a68..0000000 --- a/.credo.exs +++ /dev/null @@ -1,217 +0,0 @@ -# This file contains the configuration for Credo and you are probably reading -# this after creating it with `mix credo.gen.config`. -# -# If you find anything wrong or unclear in this file, please report an -# issue on GitHub: https://github.com/rrrene/credo/issues -# -%{ - # - # You can have as many configs as you like in the `configs:` field. - configs: [ - %{ - # - # Run any config using `mix credo -C `. If no config name is given - # "default" is used. - # - name: "default", - # - # These are the files included in the analysis: - files: %{ - # - # You can give explicit globs or simply directories. - # In the latter case `**/*.{ex,exs}` will be used. - # - included: [ - "lib/", - "src/", - "test/", - "web/", - "apps/*/lib/", - "apps/*/src/", - "apps/*/test/", - "apps/*/web/" - ], - excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] - }, - # - # Load and configure plugins here: - # - plugins: [], - # - # If you create your own checks, you must specify the source files for - # them here, so they can be loaded by Credo before running the analysis. - # - requires: [], - # - # If you want to enforce a style guide and need a more traditional linting - # experience, you can change `strict` to `true` below: - # - strict: true, - # - # To modify the timeout for parsing files, change this value: - # - parse_timeout: 5000, - # - # If you want to use uncolored output by default, you can change `color` - # to `false` below: - # - color: true, - # - # You can customize the parameters of any check by adding a second element - # to the tuple. - # - # To disable a check put `false` as second element: - # - # {Credo.Check.Design.DuplicatedCode, false} - # - checks: %{ - enabled: [ - # - ## Consistency Checks - # - {Credo.Check.Consistency.ExceptionNames, []}, - {Credo.Check.Consistency.LineEndings, []}, - {Credo.Check.Consistency.ParameterPatternMatching, []}, - {Credo.Check.Consistency.SpaceAroundOperators, []}, - {Credo.Check.Consistency.SpaceInParentheses, []}, - {Credo.Check.Consistency.TabsOrSpaces, []}, - - # - ## Design Checks - # - # You can customize the priority of any check - # Priority values are: `low, normal, high, higher` - # - {Credo.Check.Design.AliasUsage, - [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 3]}, - {Credo.Check.Design.TagFIXME, []}, - # You can also customize the exit_status of each check. - # If you don't want TODO comments to cause `mix credo` to fail, just - # set this value to 0 (zero). - # - {Credo.Check.Design.TagTODO, [exit_status: 2]}, - - # - ## Readability Checks - # - {Credo.Check.Readability.AliasOrder, []}, - {Credo.Check.Readability.FunctionNames, []}, - {Credo.Check.Readability.LargeNumbers, []}, - {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, - {Credo.Check.Readability.ModuleAttributeNames, []}, - {Credo.Check.Readability.ModuleDoc, []}, - {Credo.Check.Readability.ModuleNames, []}, - {Credo.Check.Readability.ParenthesesInCondition, []}, - {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, - {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, - {Credo.Check.Readability.PredicateFunctionNames, []}, - {Credo.Check.Readability.PreferImplicitTry, []}, - {Credo.Check.Readability.RedundantBlankLines, []}, - {Credo.Check.Readability.Semicolons, []}, - {Credo.Check.Readability.SpaceAfterCommas, []}, - {Credo.Check.Readability.StringSigils, []}, - {Credo.Check.Readability.TrailingBlankLine, []}, - {Credo.Check.Readability.TrailingWhiteSpace, []}, - {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, - {Credo.Check.Readability.VariableNames, []}, - {Credo.Check.Readability.WithSingleClause, []}, - - # - ## Refactoring Opportunities - # - {Credo.Check.Refactor.Apply, []}, - {Credo.Check.Refactor.CondStatements, []}, - {Credo.Check.Refactor.FilterCount, []}, - {Credo.Check.Refactor.FilterFilter, []}, - {Credo.Check.Refactor.FunctionArity, []}, - {Credo.Check.Refactor.LongQuoteBlocks, []}, - {Credo.Check.Refactor.MapJoin, []}, - {Credo.Check.Refactor.MatchInCondition, []}, - {Credo.Check.Refactor.NegatedConditionsInUnless, []}, - {Credo.Check.Refactor.NegatedConditionsWithElse, []}, - {Credo.Check.Refactor.Nesting, []}, - {Credo.Check.Refactor.RedundantWithClauseResult, []}, - {Credo.Check.Refactor.RejectReject, []}, - {Credo.Check.Refactor.UnlessWithElse, []}, - {Credo.Check.Refactor.WithClauses, []}, - - # - ## Warnings - # - {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, - {Credo.Check.Warning.BoolOperationOnSameValues, []}, - {Credo.Check.Warning.Dbg, []}, - {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, - {Credo.Check.Warning.IExPry, []}, - {Credo.Check.Warning.IoInspect, []}, - {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, - {Credo.Check.Warning.OperationOnSameValues, []}, - {Credo.Check.Warning.OperationWithConstantResult, []}, - {Credo.Check.Warning.RaiseInsideRescue, []}, - {Credo.Check.Warning.SpecWithStruct, []}, - {Credo.Check.Warning.UnsafeExec, []}, - {Credo.Check.Warning.UnusedEnumOperation, []}, - {Credo.Check.Warning.UnusedFileOperation, []}, - {Credo.Check.Warning.UnusedKeywordOperation, []}, - {Credo.Check.Warning.UnusedListOperation, []}, - {Credo.Check.Warning.UnusedPathOperation, []}, - {Credo.Check.Warning.UnusedRegexOperation, []}, - {Credo.Check.Warning.UnusedStringOperation, []}, - {Credo.Check.Warning.UnusedTupleOperation, []}, - {Credo.Check.Warning.WrongTestFileExtension, []} - ], - disabled: [ - # - # Checks scheduled for next check update (opt-in for now) - {Credo.Check.Refactor.UtcNowTruncate, []}, - - # - # Controversial and experimental checks (opt-in, just move the check to `:enabled` - # and be sure to use `mix credo --strict` to see low priority checks) - # - {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, - {Credo.Check.Consistency.UnusedVariableNames, []}, - {Credo.Check.Design.DuplicatedCode, []}, - {Credo.Check.Design.SkipTestWithoutComment, []}, - {Credo.Check.Readability.AliasAs, []}, - {Credo.Check.Readability.BlockPipe, []}, - {Credo.Check.Readability.ImplTrue, []}, - {Credo.Check.Readability.MultiAlias, []}, - {Credo.Check.Readability.NestedFunctionCalls, []}, - {Credo.Check.Readability.OneArityFunctionInPipe, []}, - {Credo.Check.Readability.OnePipePerLine, []}, - {Credo.Check.Readability.SeparateAliasRequire, []}, - {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, - {Credo.Check.Readability.SinglePipe, []}, - {Credo.Check.Readability.Specs, []}, - {Credo.Check.Readability.StrictModuleLayout, []}, - {Credo.Check.Readability.WithCustomTaggedTuple, []}, - {Credo.Check.Refactor.ABCSize, []}, - {Credo.Check.Refactor.AppendSingleItem, []}, - {Credo.Check.Refactor.CyclomaticComplexity, []}, - {Credo.Check.Refactor.DoubleBooleanNegation, []}, - {Credo.Check.Refactor.FilterReject, []}, - {Credo.Check.Refactor.IoPuts, []}, - {Credo.Check.Refactor.MapMap, []}, - {Credo.Check.Refactor.ModuleDependencies, []}, - {Credo.Check.Refactor.NegatedIsNil, []}, - {Credo.Check.Refactor.PassAsyncInTestCases, []}, - {Credo.Check.Refactor.PipeChainStart, []}, - {Credo.Check.Refactor.RejectFilter, []}, - {Credo.Check.Refactor.VariableRebinding, []}, - {Credo.Check.Warning.LazyLogging, []}, - {Credo.Check.Warning.LeakyEnvironment, []}, - {Credo.Check.Warning.MapGetUnsafePass, []}, - {Credo.Check.Warning.MixEnv, []}, - {Credo.Check.Warning.UnsafeToAtom, []} - - # {Credo.Check.Refactor.MapInto, []}, - - # - # Custom checks can be created using `mix credo.gen.check`. - # - ] - } - } - ] -} diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 06cd7f5..d04e922 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -85,9 +85,6 @@ jobs: - name: Check application compile warnings run: mix compile --force --warnings-as-errors - - name: Check Credo warnings - run: mix credo - - name: Run Tests - SQLite3 run: mix test env: diff --git a/mix.exs b/mix.exs index b3ddcf8..cb324db 100644 --- a/mix.exs +++ b/mix.exs @@ -91,7 +91,6 @@ defmodule ErrorTracker.MixProject do {:plug, "~> 1.10"}, # Dev dependencies {:bun, "~> 1.3", only: :dev}, - {:credo, "~> 1.7", only: [:dev, :test]}, {:ex_doc, "~> 0.33", only: :dev}, {:phoenix_live_reload, ">= 0.0.0", only: :dev}, {:plug_cowboy, ">= 0.0.0", only: :dev}, diff --git a/mix.lock b/mix.lock index f66bb98..d06f8ba 100644 --- a/mix.lock +++ b/mix.lock @@ -6,7 +6,6 @@ "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"}, - "credo": {:hex, :credo, "1.7.14", "c7e75216cea8d978ba8c60ed9dede4cc79a1c99a266c34b3600dd2c33b96bc92", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "12a97d6bb98c277e4fb1dff45aaf5c137287416009d214fb46e68147bd9e0203"}, "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "earmark_parser": {:hex, :earmark_parser, "1.4.42", "f23d856f41919f17cd06a493923a722d87a2d684f143a1e663c04a2b93100682", [:mix], [], "hexpm", "6915b6ca369b5f7346636a2f41c6a6d78b5af419d61a611079189233358b8b8b"}, From bf38e99165253a60b9158114cf11c689a595bc39 Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sun, 1 Mar 2026 15:01:08 +0100 Subject: [PATCH 3/6] Sort alphabetically deps in groups --- mix.exs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index cb324db..9f0395c 100644 --- a/mix.exs +++ b/mix.exs @@ -86,8 +86,8 @@ defmodule ErrorTracker.MixProject do [ {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, - {:phoenix_live_view, "~> 1.0"}, {:phoenix_ecto, "~> 4.6"}, + {:phoenix_live_view, "~> 1.0"}, {:plug, "~> 1.10"}, # Dev dependencies {:bun, "~> 1.3", only: :dev}, @@ -96,11 +96,11 @@ defmodule ErrorTracker.MixProject do {:plug_cowboy, ">= 0.0.0", only: :dev}, {:tailwind, "~> 0.2", only: :dev}, # Optional dependencies + {:ecto_sqlite3, ">= 0.0.0", optional: true}, + {:igniter, "~> 0.5", optional: true}, {:jason, "~> 1.1", optional: true}, - {:postgrex, ">= 0.0.0", optional: true}, {:myxql, ">= 0.0.0", optional: true}, - {:ecto_sqlite3, ">= 0.0.0", optional: true}, - {:igniter, "~> 0.5", optional: true} + {:postgrex, ">= 0.0.0", optional: true} ] end From bff117c8eec22d577ba6aa2b9e4626580a972bcd Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sun, 1 Mar 2026 15:01:41 +0100 Subject: [PATCH 4/6] Add Styler to enforce code style --- .formatter.exs | 2 +- config/dev.example.exs | 49 +++++++++---------- config/test.example.exs | 25 +++++----- dev.exs | 10 +++- lib/error_tracker.ex | 24 ++++----- lib/error_tracker/integrations/phoenix.ex | 7 +-- lib/error_tracker/integrations/plug.ex | 12 ++--- lib/error_tracker/migration/mysql.ex | 1 + lib/error_tracker/migration/postgres.ex | 1 + lib/error_tracker/migration/postgres/v01.ex | 2 +- lib/error_tracker/migration/sql_migrator.ex | 10 ++-- lib/error_tracker/migration/sqlite.ex | 1 + lib/error_tracker/plugins/pruner.ex | 6 +-- lib/error_tracker/schemas/error.ex | 2 +- lib/error_tracker/schemas/occurrence.ex | 11 ++--- lib/error_tracker/schemas/stacktrace.ex | 10 ++-- lib/error_tracker/telemetry.ex | 8 +-- lib/error_tracker/web.ex | 5 +- .../web/components/core_components.ex | 10 ++-- lib/error_tracker/web/helpers.ex | 2 +- lib/error_tracker/web/live/dashboard.ex | 8 +-- lib/error_tracker/web/live/show.ex | 2 +- lib/error_tracker/web/router.ex | 6 ++- lib/error_tracker/web/router/routes.ex | 8 +-- lib/mix/tasks/error_tracker.install.ex | 8 +-- mix.exs | 1 + mix.lock | 1 + test/error_tracker/filter_test.exs | 4 +- test/error_tracker/ignorer_test.exs | 1 + test/error_tracker/telemetry_test.exs | 12 ++--- test/error_tracker_test.exs | 3 +- test/integrations/plug_test.exs | 6 +-- 32 files changed, 132 insertions(+), 126 deletions(-) diff --git a/.formatter.exs b/.formatter.exs index 8881922..a13f79f 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -4,7 +4,7 @@ locals_without_parens = [error_tracker_dashboard: 1, error_tracker_dashboard: 2] [ import_deps: [:ecto, :ecto_sql, :plug, :phoenix], inputs: ["{mix,.formatter,dev,dev.*}.exs", "{config,lib,test}/**/*.{heex,ex,exs}"], - plugins: [Phoenix.LiveView.HTMLFormatter], + plugins: [Phoenix.LiveView.HTMLFormatter, Styler], locals_without_parens: locals_without_parens, export: [locals_without_parens: locals_without_parens] ] diff --git a/config/dev.example.exs b/config/dev.example.exs index 8bb5a5a..6a8b201 100644 --- a/config/dev.example.exs +++ b/config/dev.example.exs @@ -1,16 +1,5 @@ import Config -config :tailwind, - version: "3.4.3", - default: [ - args: ~w( - --config=tailwind.config.js - --input=css/app.css - --output=../priv/static/app.css - ), - cd: Path.expand("../assets", __DIR__) - ] - config :bun, version: "1.1.18", default: [ @@ -19,32 +8,40 @@ config :bun, env: %{} ] -# PostgreSQL adapter -# -# To use SQLite3 on your local development machine uncomment these lines and -# comment the lines of other adapters. - -config :error_tracker, :ecto_adapter, :postgres - -config :error_tracker, ErrorTrackerDev.Repo, - url: "ecto://postgres:postgres@127.0.0.1/error_tracker_dev" - # MySQL/MariaDB adapter # # To use MySQL/MariaDB on your local development machine uncomment these lines and # comment the lines of other adapters. - +# # config :error_tracker, :ecto_adapter, :mysql - +# # config :error_tracker, ErrorTrackerDev.Repo, # url: "ecto://root:root@127.0.0.1/error_tracker_dev" - +# # SQLite3 adapter # # To use SQLite3 on your local development machine uncomment these lines and # comment the lines of other adapters. - +# # config :error_tracker, :ecto_adapter, :sqlite3 - +# # config :error_tracker, ErrorTrackerDev.Repo, # database: System.get_env("SQLITE_DB") || "dev.db" +# +# PostgreSQL adapter +# +# To use PostgreSQL on your local development machine uncomment these lines and +# comment the lines of other adapters. +config :error_tracker, ErrorTrackerDev.Repo, url: "ecto://postgres:postgres@127.0.0.1/error_tracker_dev" +config :error_tracker, :ecto_adapter, :postgres + +config :tailwind, + version: "3.4.3", + default: [ + args: ~w( + --config=tailwind.config.js + --input=css/app.css + --output=../priv/static/app.css + ), + cd: Path.expand("../assets", __DIR__) + ] diff --git a/config/test.example.exs b/config/test.example.exs index 5ebac14..c29894d 100644 --- a/config/test.example.exs +++ b/config/test.example.exs @@ -1,25 +1,28 @@ import Config -config :error_tracker, ErrorTracker.Test.Repo, - url: "ecto://postgres:postgres@127.0.0.1/error_tracker_test", - pool: Ecto.Adapters.SQL.Sandbox, - log: false +alias Ecto.Adapters.SQL.Sandbox +alias ErrorTracker.Test.Repo -config :error_tracker, ErrorTracker.Test.MySQLRepo, - url: "ecto://root:root@127.0.0.1/error_tracker_test", - pool: Ecto.Adapters.SQL.Sandbox, +config :error_tracker, ErrorTracker.Test.LiteRepo, + database: "priv/lite_repo/test.db", + pool: Sandbox, log: false, # Use the same migrations as the PostgreSQL repo priv: "priv/repo" -config :error_tracker, ErrorTracker.Test.LiteRepo, - database: "priv/lite_repo/test.db", - pool: Ecto.Adapters.SQL.Sandbox, +config :error_tracker, ErrorTracker.Test.MySQLRepo, + url: "ecto://root:root@127.0.0.1/error_tracker_test", + pool: Sandbox, log: false, # Use the same migrations as the PostgreSQL repo priv: "priv/repo" -config :error_tracker, ecto_repos: [ErrorTracker.Test.Repo] +config :error_tracker, Repo, + url: "ecto://postgres:postgres@127.0.0.1/error_tracker_test", + pool: Sandbox, + log: false + +config :error_tracker, ecto_repos: [Repo] # Repo is selected in the test_helper.exs based on the given ENV vars config :error_tracker, otp_app: :error_tracker diff --git a/dev.exs b/dev.exs index a2f0427..372425a 100644 --- a/dev.exs +++ b/dev.exs @@ -22,10 +22,12 @@ Application.put_all_env( ) defmodule ErrorTrackerDev.Repo do - require Logger use Ecto.Repo, otp_app: otp_app, adapter: Ecto.Adapters.SQLite3 + require Logger + defmodule Migration do + @moduledoc false use Ecto.Migration def up, do: ErrorTracker.Migration.up() @@ -80,6 +82,7 @@ defmodule ErrorTrackerDev.Controller do end defmodule ErrorTrackerDev.Live do + @moduledoc false use Phoenix.LiveView def mount(params, _session, socket) do @@ -226,7 +229,7 @@ defmodule ErrorTrackerDev.Endpoint do |> Plug.Conn.put_resp_header("content-security-policy", Enum.join(policies, " ")) end - defp plug_exception(conn = %Plug.Conn{path_info: path_info}, _opts) when is_list(path_info) do + defp plug_exception(%Plug.Conn{path_info: path_info} = conn, _opts) when is_list(path_info) do if "plug_exception" in path_info, do: raise("Crashed in Endpoint"), else: conn @@ -244,6 +247,7 @@ defmodule ErrorTrackerDev.ErrorView do end defmodule ErrorTrackerDev.GenServer do + @moduledoc false use GenServer # Client @@ -267,10 +271,12 @@ defmodule ErrorTrackerDev.GenServer do end defmodule ErrorTrackerDev.Exception do + @moduledoc false defexception [:message, :bread_crumbs] end defmodule ErrorTrackerDev.Telemetry do + @moduledoc false def handle_event(event, measure, metadata, _opts) do dbg([event, measure, metadata]) end diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex index b05b699..d88ced0 100644 --- a/lib/error_tracker.ex +++ b/lib/error_tracker.ex @@ -79,6 +79,13 @@ defmodule ErrorTracker do Breadcrumbs can be viewed in the dashboard on the details page of an occurrence. """ + import Ecto.Query + + alias ErrorTracker.Error + alias ErrorTracker.Occurrence + alias ErrorTracker.Repo + alias ErrorTracker.Telemetry + @typedoc """ A map containing the relevant context for a particular error. """ @@ -89,13 +96,6 @@ defmodule ErrorTracker do """ @type exception :: Exception.t() | {:error, any()} | {Exception.non_error_kind(), any()} - import Ecto.Query - - alias ErrorTracker.Error - alias ErrorTracker.Occurrence - alias ErrorTracker.Repo - alias ErrorTracker.Telemetry - @doc """ Report an exception to be stored. @@ -154,7 +154,7 @@ defmodule ErrorTracker do appear as unresolved again. """ @spec resolve(Error.t()) :: {:ok, Error.t()} | {:error, Ecto.Changeset.t()} - def resolve(error = %Error{status: :unresolved}) do + def resolve(%Error{status: :unresolved} = error) do changeset = Ecto.Changeset.change(error, status: :resolved) with {:ok, updated_error} <- Repo.update(changeset) do @@ -167,7 +167,7 @@ defmodule ErrorTracker do Marks an error as unresolved. """ @spec unresolve(Error.t()) :: {:ok, Error.t()} | {:error, Ecto.Changeset.t()} - def unresolve(error = %Error{status: :resolved}) do + def unresolve(%Error{status: :resolved} = error) do changeset = Ecto.Changeset.change(error, status: :unresolved) with {:ok, updated_error} <- Repo.update(changeset) do @@ -188,7 +188,7 @@ defmodule ErrorTracker do receive notifications about. """ @spec mute(Error.t()) :: {:ok, Error.t()} | {:error, Ecto.Changeset.t()} - def mute(error = %Error{}) do + def mute(%Error{} = error) do changeset = Ecto.Changeset.change(error, muted: true) Repo.update(changeset) @@ -201,7 +201,7 @@ defmodule ErrorTracker do for new occurrences of this error again. """ @spec unmute(Error.t()) :: {:ok, Error.t()} | {:error, Ecto.Changeset.t()} - def unmute(error = %Error{}) do + def unmute(%Error{} = error) do changeset = Ecto.Changeset.change(error, muted: false) Repo.update(changeset) @@ -341,7 +341,7 @@ defmodule ErrorTracker do {:ok, {error, occurrence}} = Repo.transaction(fn -> error = - ErrorTracker.Repo.with_adapter(fn + Repo.with_adapter(fn :mysql -> Repo.insert!(error, on_conflict: [set: [status: :unresolved, last_occurrence_at: DateTime.utc_now()]] diff --git a/lib/error_tracker/integrations/phoenix.ex b/lib/error_tracker/integrations/phoenix.ex index 8bc393b..2e0e7b0 100644 --- a/lib/error_tracker/integrations/phoenix.ex +++ b/lib/error_tracker/integrations/phoenix.ex @@ -119,12 +119,7 @@ defmodule ErrorTracker.Integrations.Phoenix do }) end - def handle_event( - [:phoenix, :live_component, :handle_event, :exception], - _, - metadata, - :no_config - ) do + def handle_event([:phoenix, :live_component, :handle_event, :exception], _, metadata, :no_config) do ErrorTracker.report({metadata.kind, metadata.reason}, metadata.stacktrace, %{ "live_view.component" => metadata.component, "live_view.event" => metadata.event, diff --git a/lib/error_tracker/integrations/plug.ex b/lib/error_tracker/integrations/plug.ex index f29f9a3..e9d6a41 100644 --- a/lib/error_tracker/integrations/plug.ex +++ b/lib/error_tracker/integrations/plug.ex @@ -97,7 +97,7 @@ defmodule ErrorTracker.Integrations.Plug do @doc false def report_error(conn, reason, stack) do - unless Process.get(:error_tracker_router_exception_reported) do + if !Process.get(:error_tracker_router_exception_reported) do try do ErrorTracker.report(reason, stack, build_context(conn)) after @@ -107,13 +107,13 @@ defmodule ErrorTracker.Integrations.Plug do end @doc false - def set_context(conn = %Plug.Conn{}) do - conn |> build_context |> ErrorTracker.set_context() + def set_context(%Plug.Conn{} = conn) do + conn |> build_context() |> ErrorTracker.set_context() end @sensitive_headers ["cookie", "authorization"] - defp build_context(conn = %Plug.Conn{}) do + defp build_context(%Plug.Conn{} = conn) do %{ "request.host" => conn.host, "request.path" => conn.request_path, @@ -122,11 +122,11 @@ defmodule ErrorTracker.Integrations.Plug do "request.ip" => remote_ip(conn), "request.headers" => conn.req_headers |> Map.new() |> Map.drop(@sensitive_headers), # Depending on the error source, the request params may have not been fetched yet - "request.params" => unless(is_struct(conn.params, Plug.Conn.Unfetched), do: conn.params) + "request.params" => if(!is_struct(conn.params, Plug.Conn.Unfetched), do: conn.params) } end - defp remote_ip(conn = %Plug.Conn{}) do + defp remote_ip(%Plug.Conn{} = conn) do remote_ip = case Plug.Conn.get_req_header(conn, "x-forwarded-for") do [x_forwarded_for | _] -> diff --git a/lib/error_tracker/migration/mysql.ex b/lib/error_tracker/migration/mysql.ex index ff9efd9..43cb7f2 100644 --- a/lib/error_tracker/migration/mysql.ex +++ b/lib/error_tracker/migration/mysql.ex @@ -4,6 +4,7 @@ defmodule ErrorTracker.Migration.MySQL do @behaviour ErrorTracker.Migration use Ecto.Migration + alias ErrorTracker.Migration.SQLMigrator @initial_version 3 diff --git a/lib/error_tracker/migration/postgres.ex b/lib/error_tracker/migration/postgres.ex index f0a6a38..eec4a26 100644 --- a/lib/error_tracker/migration/postgres.ex +++ b/lib/error_tracker/migration/postgres.ex @@ -4,6 +4,7 @@ defmodule ErrorTracker.Migration.Postgres do @behaviour ErrorTracker.Migration use Ecto.Migration + alias ErrorTracker.Migration.SQLMigrator @initial_version 1 diff --git a/lib/error_tracker/migration/postgres/v01.ex b/lib/error_tracker/migration/postgres/v01.ex index 24efb77..cbc3531 100644 --- a/lib/error_tracker/migration/postgres/v01.ex +++ b/lib/error_tracker/migration/postgres/v01.ex @@ -5,7 +5,7 @@ defmodule ErrorTracker.Migration.Postgres.V01 do import Ecto.Query - def up(opts = %{create_schema: create_schema, prefix: prefix}) do + def up(%{create_schema: create_schema, prefix: prefix} = opts) do # Prior to V02 the migration version was stored in table comments. # As of now the migration version is stored in a new table (created in V02). # diff --git a/lib/error_tracker/migration/sql_migrator.ex b/lib/error_tracker/migration/sql_migrator.ex index 8d5fa4a..35f47f5 100644 --- a/lib/error_tracker/migration/sql_migrator.ex +++ b/lib/error_tracker/migration/sql_migrator.ex @@ -5,6 +5,8 @@ defmodule ErrorTracker.Migration.SQLMigrator do import Ecto.Query + alias Ecto.Adapters.SQL + def migrate_up(migrator, opts, initial_version) do initial = current_version(opts) @@ -61,7 +63,7 @@ defmodule ErrorTracker.Migration.SQLMigrator do defp record_version(_opts, 0), do: :ok defp record_version(opts, version) do - timestamp = DateTime.utc_now() |> DateTime.to_unix() + timestamp = DateTime.to_unix(DateTime.utc_now()) ErrorTracker.Repo.with_adapter(fn :postgres -> @@ -92,8 +94,8 @@ defmodule ErrorTracker.Migration.SQLMigrator do defp meta_table_exists?(repo, opts) do ErrorTracker.Repo.with_adapter(fn :postgres -> - Ecto.Adapters.SQL.query!( - repo, + repo + |> SQL.query!( "SELECT TRUE FROM information_schema.tables WHERE table_name = 'error_tracker_meta' AND table_schema = $1", [opts.prefix], log: false @@ -102,7 +104,7 @@ defmodule ErrorTracker.Migration.SQLMigrator do |> Enum.any?() _other -> - Ecto.Adapters.SQL.table_exists?(repo, "error_tracker_meta", log: false) + SQL.table_exists?(repo, "error_tracker_meta", log: false) end) end end diff --git a/lib/error_tracker/migration/sqlite.ex b/lib/error_tracker/migration/sqlite.ex index 7d51024..44a54f4 100644 --- a/lib/error_tracker/migration/sqlite.ex +++ b/lib/error_tracker/migration/sqlite.ex @@ -4,6 +4,7 @@ defmodule ErrorTracker.Migration.SQLite do @behaviour ErrorTracker.Migration use Ecto.Migration + alias ErrorTracker.Migration.SQLMigrator @initial_version 2 diff --git a/lib/error_tracker/plugins/pruner.ex b/lib/error_tracker/plugins/pruner.ex index 3dcb6ce..f282339 100644 --- a/lib/error_tracker/plugins/pruner.ex +++ b/lib/error_tracker/plugins/pruner.ex @@ -119,8 +119,8 @@ defmodule ErrorTracker.Plugins.Pruner do def init(state \\ []) do state = %{ limit: state[:limit] || 200, - max_age: state[:max_age] || :timer.hours(24), - interval: state[:interval] || :timer.minutes(30) + max_age: state[:max_age] || to_timeout(day: 1), + interval: state[:interval] || to_timeout(minute: 30) } {:ok, schedule_prune(state)} @@ -134,7 +134,7 @@ defmodule ErrorTracker.Plugins.Pruner do {:noreply, schedule_prune(state)} end - defp schedule_prune(state = %{interval: interval}) do + defp schedule_prune(%{interval: interval} = state) do Process.send_after(self(), :prune, interval) state diff --git a/lib/error_tracker/schemas/error.ex b/lib/error_tracker/schemas/error.ex index 643332b..20f7bf2 100644 --- a/lib/error_tracker/schemas/error.ex +++ b/lib/error_tracker/schemas/error.ex @@ -39,7 +39,7 @@ defmodule ErrorTracker.Error do end @doc false - def new(kind, reason, stacktrace = %ErrorTracker.Stacktrace{}) do + def new(kind, reason, %ErrorTracker.Stacktrace{} = stacktrace) do source = ErrorTracker.Stacktrace.source(stacktrace) {source_line, source_function} = diff --git a/lib/error_tracker/schemas/occurrence.ex b/lib/error_tracker/schemas/occurrence.ex index b57b91d..c4b8466 100644 --- a/lib/error_tracker/schemas/occurrence.ex +++ b/lib/error_tracker/schemas/occurrence.ex @@ -6,10 +6,10 @@ defmodule ErrorTracker.Occurrence do in which the exception raised. """ - import Ecto.Changeset - use Ecto.Schema + import Ecto.Changeset + require Logger @type t :: %__MODULE__{} @@ -61,13 +61,10 @@ defmodule ErrorTracker.Occurrence do context rescue _e -> - Logger.warning( - "[ErrorTracker] Context has been ignored: it is not serializable to JSON." - ) + Logger.warning("[ErrorTracker] Context has been ignored: it is not serializable to JSON.") %{ - error: - "Context not stored because it contains information not serializable to JSON." + error: "Context not stored because it contains information not serializable to JSON." } end diff --git a/lib/error_tracker/schemas/stacktrace.ex b/lib/error_tracker/schemas/stacktrace.ex index eb3162d..b5e6678 100644 --- a/lib/error_tracker/schemas/stacktrace.ex +++ b/lib/error_tracker/schemas/stacktrace.ex @@ -44,7 +44,7 @@ defmodule ErrorTracker.Stacktrace do defp normalize_arity(a) when is_integer(a), do: a defp normalize_arity(a) when is_list(a), do: length(a) - defp line_changeset(line = %__MODULE__.Line{}, params) do + defp line_changeset(%__MODULE__.Line{} = line, params) do Ecto.Changeset.cast(line, params, ~w[application module function arity file line]a) end @@ -54,21 +54,21 @@ defmodule ErrorTracker.Stacktrace do The first line matching the client application. If no line belongs to the current application, just the first line. """ - def source(stack = %__MODULE__{}) do - client_app = Application.fetch_env!(:error_tracker, :otp_app) |> to_string() + def source(%__MODULE__{} = stack) do + client_app = :error_tracker |> Application.fetch_env!(:otp_app) |> to_string() Enum.find(stack.lines, &(&1.application == client_app)) || List.first(stack.lines) end end defimpl String.Chars, for: ErrorTracker.Stacktrace do - def to_string(stack = %ErrorTracker.Stacktrace{}) do + def to_string(%ErrorTracker.Stacktrace{} = stack) do Enum.join(stack.lines, "\n") end end defimpl String.Chars, for: ErrorTracker.Stacktrace.Line do - def to_string(stack_line = %ErrorTracker.Stacktrace.Line{}) do + def to_string(%ErrorTracker.Stacktrace.Line{} = stack_line) do "#{stack_line.module}.#{stack_line.function}/#{stack_line.arity} in #{stack_line.file}:#{stack_line.line}" end end diff --git a/lib/error_tracker/telemetry.ex b/lib/error_tracker/telemetry.ex index 075b3f9..6246068 100644 --- a/lib/error_tracker/telemetry.ex +++ b/lib/error_tracker/telemetry.ex @@ -46,28 +46,28 @@ defmodule ErrorTracker.Telemetry do """ @doc false - def new_error(error = %ErrorTracker.Error{}) do + def new_error(%ErrorTracker.Error{} = error) do measurements = %{system_time: System.system_time()} metadata = %{error: error} :telemetry.execute([:error_tracker, :error, :new], measurements, metadata) end @doc false - def unresolved_error(error = %ErrorTracker.Error{}) do + def unresolved_error(%ErrorTracker.Error{} = error) do measurements = %{system_time: System.system_time()} metadata = %{error: error} :telemetry.execute([:error_tracker, :error, :unresolved], measurements, metadata) end @doc false - def resolved_error(error = %ErrorTracker.Error{}) do + def resolved_error(%ErrorTracker.Error{} = error) do measurements = %{system_time: System.system_time()} metadata = %{error: error} :telemetry.execute([:error_tracker, :error, :resolved], measurements, metadata) end @doc false - def new_occurrence(occurrence = %ErrorTracker.Occurrence{}, muted) when is_boolean(muted) do + def new_occurrence(%ErrorTracker.Occurrence{} = occurrence, muted) when is_boolean(muted) do measurements = %{system_time: System.system_time()} metadata = %{error: occurrence.error, occurrence: occurrence, muted: muted} :telemetry.execute([:error_tracker, :occurrence, :new], measurements, metadata) diff --git a/lib/error_tracker/web.ex b/lib/error_tracker/web.ex index 4aeb016..720c01e 100644 --- a/lib/error_tracker/web.ex +++ b/lib/error_tracker/web.ex @@ -106,12 +106,11 @@ defmodule ErrorTracker.Web do quote do use Phoenix.Component - import Phoenix.HTML - import Phoenix.LiveView.Helpers - import ErrorTracker.Web.CoreComponents import ErrorTracker.Web.Helpers import ErrorTracker.Web.Router.Routes + import Phoenix.HTML + import Phoenix.LiveView.Helpers alias Phoenix.LiveView.JS end diff --git a/lib/error_tracker/web/components/core_components.ex b/lib/error_tracker/web/components/core_components.ex index 4c413b4..20e27ef 100644 --- a/lib/error_tracker/web/components/core_components.ex +++ b/lib/error_tracker/web/components/core_components.ex @@ -16,7 +16,7 @@ defmodule ErrorTracker.Web.CoreComponents do slot :inner_block, required: true - def button(assigns = %{type: "link"}) do + def button(%{type: "link"} = assigns) do ~H""" <.link class={[ @@ -133,7 +133,7 @@ defmodule ErrorTracker.Web.CoreComponents do attr :name, :string, values: ~w[bell bell-slash arrow-left arrow-right] - def icon(assigns = %{name: "bell"}) do + def icon(%{name: "bell"} = assigns) do ~H""" Ecto.assoc(:occurrences) |> where([o], o.error_id in ^error_ids) |> group_by([o], o.error_id) |> select([o], {o.error_id, count(o.id)}) |> Repo.all() - else - [] end assign(socket, errors: errors, occurrences: Map.new(occurrences), - total_pages: (total_errors / @per_page) |> Float.ceil() |> trunc + total_pages: (total_errors / @per_page) |> Float.ceil() |> trunc() ) end diff --git a/lib/error_tracker/web/live/show.ex b/lib/error_tracker/web/live/show.ex index 93ebaff..a832a89 100644 --- a/lib/error_tracker/web/live/show.ex +++ b/lib/error_tracker/web/live/show.ex @@ -12,7 +12,7 @@ defmodule ErrorTracker.Web.Live.Show do @occurrences_to_navigate 50 @impl Phoenix.LiveView - def mount(params = %{"id" => id}, _session, socket) do + def mount(%{"id" => id} = params, _session, socket) do error = Repo.get!(Error, id) {:ok, diff --git a/lib/error_tracker/web/router.ex b/lib/error_tracker/web/router.ex index 294342f..d495072 100644 --- a/lib/error_tracker/web/router.ex +++ b/lib/error_tracker/web/router.ex @@ -42,10 +42,12 @@ defmodule ErrorTracker.Web.Router do scope path, alias: false, as: false do import Phoenix.LiveView.Router, only: [live: 4, live_session: 3] + alias ErrorTracker.Web.Live.Show + live_session session_name, session_opts do live "/", ErrorTracker.Web.Live.Dashboard, :index, as: session_name - live "/:id", ErrorTracker.Web.Live.Show, :show, as: session_name - live "/:id/:occurrence_id", ErrorTracker.Web.Live.Show, :show, as: session_name + live "/:id", Show, :show, as: session_name + live "/:id/:occurrence_id", Show, :show, as: session_name end end end diff --git a/lib/error_tracker/web/router/routes.ex b/lib/error_tracker/web/router/routes.ex index 696ca0e..5aaf40d 100644 --- a/lib/error_tracker/web/router/routes.ex +++ b/lib/error_tracker/web/router/routes.ex @@ -8,7 +8,7 @@ defmodule ErrorTracker.Web.Router.Routes do @doc """ Returns the dashboard path """ - def dashboard_path(socket = %Socket{}, params \\ %{}) do + def dashboard_path(%Socket{} = socket, params \\ %{}) do socket |> dashboard_uri(params) |> URI.to_string() @@ -17,7 +17,7 @@ defmodule ErrorTracker.Web.Router.Routes do @doc """ Returns the path to see the details of an error """ - def error_path(socket = %Socket{}, %Error{id: id}, params \\ %{}) do + def error_path(%Socket{} = socket, %Error{id: id}, params \\ %{}) do socket |> dashboard_uri(params) |> URI.append_path("/#{id}") @@ -27,14 +27,14 @@ defmodule ErrorTracker.Web.Router.Routes do @doc """ Returns the path to see the details of an occurrence """ - def occurrence_path(socket = %Socket{}, %Occurrence{id: id, error_id: error_id}, params \\ %{}) do + def occurrence_path(%Socket{} = socket, %Occurrence{id: id, error_id: error_id}, params \\ %{}) do socket |> dashboard_uri(params) |> URI.append_path("/#{error_id}/#{id}") |> URI.to_string() end - defp dashboard_uri(socket = %Socket{}, params) do + defp dashboard_uri(%Socket{} = socket, params) do %URI{ path: socket.private[:dashboard_path], query: if(Enum.any?(params), do: URI.encode_query(params)) diff --git a/lib/mix/tasks/error_tracker.install.ex b/lib/mix/tasks/error_tracker.install.ex index 0c1361f..66acea3 100644 --- a/lib/mix/tasks/error_tracker.install.ex +++ b/lib/mix/tasks/error_tracker.install.ex @@ -30,6 +30,8 @@ if Code.ensure_loaded?(Igniter) do use Igniter.Mix.Task + alias Igniter.Project.Config + @impl Igniter.Mix.Task def info(_argv, _composing_task) do %Igniter.Mix.Task.Info{ @@ -77,9 +79,9 @@ if Code.ensure_loaded?(Igniter) do defp set_up_configuration(igniter, app_name, repo) do igniter - |> Igniter.Project.Config.configure_new("config.exs", :error_tracker, [:repo], repo) - |> Igniter.Project.Config.configure_new("config.exs", :error_tracker, [:otp_app], app_name) - |> Igniter.Project.Config.configure_new("config.exs", :error_tracker, [:enabled], true) + |> Config.configure_new("config.exs", :error_tracker, [:repo], repo) + |> Config.configure_new("config.exs", :error_tracker, [:otp_app], app_name) + |> Config.configure_new("config.exs", :error_tracker, [:enabled], true) end defp set_up_formatter(igniter) do diff --git a/mix.exs b/mix.exs index 9f0395c..b2cb0cd 100644 --- a/mix.exs +++ b/mix.exs @@ -94,6 +94,7 @@ defmodule ErrorTracker.MixProject do {:ex_doc, "~> 0.33", only: :dev}, {:phoenix_live_reload, ">= 0.0.0", only: :dev}, {:plug_cowboy, ">= 0.0.0", only: :dev}, + {:styler, "~> 1.11", only: [:dev, :test], runtime: false}, {:tailwind, "~> 0.2", only: :dev}, # Optional dependencies {:ecto_sqlite3, ">= 0.0.0", optional: true}, diff --git a/mix.lock b/mix.lock index d06f8ba..4c05f8b 100644 --- a/mix.lock +++ b/mix.lock @@ -47,6 +47,7 @@ "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "sourceror": {:hex, :sourceror, "1.8.2", "f486ddded3b884175583413b431178b691b42d3e616f1ee80bed15503c5f7fd7", [:mix], [], "hexpm", "3f3126d50c222e1029c31861165e73e9c89ebcd543d2896192e2c1d792688fef"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, + "styler": {:hex, :styler, "1.11.0", "35010d970689a23c2bcc8e97bd8bf7d20e3561d60c49be84654df5c37d051a9c", [:mix], [], "hexpm", "70f36165d0cf238a32b7a456fdef6a9c72e77e657d7ac4a0ace33aeba3f2b8c0"}, "tailwind": {:hex, :tailwind, "0.2.2", "9e27288b568ede1d88517e8c61259bc214a12d7eed271e102db4c93fcca9b2cd", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "ccfb5025179ea307f7f899d1bb3905cd0ac9f687ed77feebc8f67bdca78565c4"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, diff --git a/test/error_tracker/filter_test.exs b/test/error_tracker/filter_test.exs index 2231155..118cd8a 100644 --- a/test/error_tracker/filter_test.exs +++ b/test/error_tracker/filter_test.exs @@ -43,6 +43,7 @@ defmodule ErrorTracker.FilterTest do end defmodule ErrorTracker.FilterTest.AuthHeaderHider do + @moduledoc false @behaviour ErrorTracker.Filter def sanitize(context) do @@ -54,10 +55,9 @@ defmodule ErrorTracker.FilterTest.AuthHeaderHider do o -> o end) - |> Enum.map(fn + |> Map.new(fn {key, val} when is_map(val) -> {key, sanitize(val)} o -> o end) - |> Map.new() end end diff --git a/test/error_tracker/ignorer_test.exs b/test/error_tracker/ignorer_test.exs index 9b1bbd8..af4b25e 100644 --- a/test/error_tracker/ignorer_test.exs +++ b/test/error_tracker/ignorer_test.exs @@ -26,6 +26,7 @@ defmodule ErrorTracker.IgnorerTest do end defmodule ErrorTracker.EveryErrorIgnorer do + @moduledoc false @behaviour ErrorTracker.Ignorer @impl true diff --git a/test/error_tracker/telemetry_test.exs b/test/error_tracker/telemetry_test.exs index 6a760b3..07c0405 100644 --- a/test/error_tracker/telemetry_test.exs +++ b/test/error_tracker/telemetry_test.exs @@ -22,8 +22,7 @@ defmodule ErrorTracker.TelemetryTest do %Occurrence{error: error = %Error{}} = ErrorTracker.report(exception, stacktrace) assert_receive {:telemetry_event, [:error_tracker, :error, :new], _, %{error: %Error{}}} - assert_receive {:telemetry_event, [:error_tracker, :occurrence, :new], _, - %{occurrence: %Occurrence{}, muted: false}} + assert_receive {:telemetry_event, [:error_tracker, :occurrence, :new], _, %{occurrence: %Occurrence{}, muted: false}} # The error is already known so the new error event won't be emitted ErrorTracker.report(exception, stacktrace) @@ -31,15 +30,13 @@ defmodule ErrorTracker.TelemetryTest do refute_receive {:telemetry_event, [:error_tracker, :error, :new], _, %{error: %Error{}}}, 150 - assert_receive {:telemetry_event, [:error_tracker, :occurrence, :new], _, - %{occurrence: %Occurrence{}, muted: false}} + assert_receive {:telemetry_event, [:error_tracker, :occurrence, :new], _, %{occurrence: %Occurrence{}, muted: false}} # The error is muted so the new occurrence event will include the muted=true metadata ErrorTracker.mute(error) ErrorTracker.report(exception, stacktrace) - assert_receive {:telemetry_event, [:error_tracker, :occurrence, :new], _, - %{occurrence: %Occurrence{}, muted: true}} + assert_receive {:telemetry_event, [:error_tracker, :occurrence, :new], _, %{occurrence: %Occurrence{}, muted: true}} end test "events are emitted for resolved and unresolved errors" do @@ -52,7 +49,6 @@ defmodule ErrorTracker.TelemetryTest do # The unresolved event will be emitted {:ok, _unresolved} = ErrorTracker.unresolve(resolved) - assert_receive {:telemetry_event, [:error_tracker, :error, :unresolved], _, - %{error: %Error{}}} + assert_receive {:telemetry_event, [:error_tracker, :error, :unresolved], _, %{error: %Error{}}} end end diff --git a/test/error_tracker_test.exs b/test/error_tracker_test.exs index b5121ee..84f8b17 100644 --- a/test/error_tracker_test.exs +++ b/test/error_tracker_test.exs @@ -88,7 +88,7 @@ defmodule ErrorTrackerTest do @tag capture_log: true test "reports errors with invalid context" do # It's invalid because cannot be serialized to JSON - invalid_context = %{foo: %ErrorTracker.Error{}} + invalid_context = %{foo: %Error{}} assert %Occurrence{} = report_error(fn -> raise "test" end, invalid_context) end @@ -172,5 +172,6 @@ defmodule ErrorTrackerTest do end defmodule ErrorWithBreadcrumbs do + @moduledoc false defexception [:message, :bread_crumbs] end diff --git a/test/integrations/plug_test.exs b/test/integrations/plug_test.exs index 75e2269..2bde330 100644 --- a/test/integrations/plug_test.exs +++ b/test/integrations/plug_test.exs @@ -10,7 +10,7 @@ defmodule ErrorTracker.Integrations.PlugTest do end test "it reports errors, including the request headers", %{conn: conn} do - conn = conn |> Plug.Conn.put_req_header("accept", "application/json") + conn = Plug.Conn.put_req_header(conn, "accept", "application/json") IntegrationPlug.report_error( conn, @@ -49,8 +49,8 @@ defmodule ErrorTracker.Integrations.PlugTest do header_names = occurrence.context |> Map.get("request.headers") |> Map.keys() - assert "cookie" not in header_names - assert "authorization" not in header_names + refute "cookie" in header_names + refute "authorization" in header_names assert "safe" in header_names end From 6845aa94176aee6315201cedaa4abf96a7700696 Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sun, 1 Mar 2026 15:08:23 +0100 Subject: [PATCH 5/6] Unlock unused dependency --- mix.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/mix.lock b/mix.lock index 4c05f8b..113f549 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,5 @@ %{ "bun": {:hex, :bun, "1.3.0", "6833722da5b073777e043aec42091b0cf8bbacb84262ec6d348a914dda4c6a98", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "dde1b8116ba57269a9f398b4b28492b16fb29a78800c9533b7c9fb036793d62a"}, - "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, From d6a8b498684ece3292519a48316442746c698056 Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sun, 1 Mar 2026 15:12:13 +0100 Subject: [PATCH 6/6] Support Elixir 1.15 --- .formatter.exs | 9 ++++++++- lib/error_tracker/plugins/pruner.ex | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.formatter.exs b/.formatter.exs index a13f79f..af39ab7 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,10 +1,17 @@ # Used by "mix format" locals_without_parens = [error_tracker_dashboard: 1, error_tracker_dashboard: 2] +# Parse SemVer minor elixir version from project configuration +# eg `"~> 1.15"` version requirement will yield `"1.15"` +[elixir_minor_version | _] = Regex.run(~r/([\d\.]+)/, Mix.Project.config()[:elixir]) + [ import_deps: [:ecto, :ecto_sql, :plug, :phoenix], inputs: ["{mix,.formatter,dev,dev.*}.exs", "{config,lib,test}/**/*.{heex,ex,exs}"], plugins: [Phoenix.LiveView.HTMLFormatter, Styler], locals_without_parens: locals_without_parens, - export: [locals_without_parens: locals_without_parens] + export: [locals_without_parens: locals_without_parens], + styler: [ + minimum_supported_elixir_version: "#{elixir_minor_version}.0" + ] ] diff --git a/lib/error_tracker/plugins/pruner.ex b/lib/error_tracker/plugins/pruner.ex index f282339..ebe5521 100644 --- a/lib/error_tracker/plugins/pruner.ex +++ b/lib/error_tracker/plugins/pruner.ex @@ -119,8 +119,8 @@ defmodule ErrorTracker.Plugins.Pruner do def init(state \\ []) do state = %{ limit: state[:limit] || 200, - max_age: state[:max_age] || to_timeout(day: 1), - interval: state[:interval] || to_timeout(minute: 30) + max_age: state[:max_age] || :timer.hours(24), + interval: state[:interval] || :timer.minutes(30) } {:ok, schedule_prune(state)}