From c9c550fee23e00d1260f1a2e8de3bbd639f01661 Mon Sep 17 00:00:00 2001 From: mse99 Date: Sun, 8 Sep 2024 23:37:46 +0200 Subject: [PATCH 01/11] Add type specs to `error_tracker.ex` --- lib/error_tracker.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex index c737cde..cf7c0f9 100644 --- a/lib/error_tracker.ex +++ b/lib/error_tracker.ex @@ -107,6 +107,11 @@ defmodule ErrorTracker do * A `{kind, exception}` tuple in which case the information is converted to an Elixir exception (if possible) and stored. """ + + @type exception :: struct() | {atom(), struct()} + @type stack_trace :: {atom(), fun(), integer(), Keyword.t()} + + @spec report(exception(), stack_trace()) :: %Occurrence{} | :noop def report(exception, stacktrace, given_context \\ %{}) do {kind, reason} = normalize_exception(exception, stacktrace) {:ok, stacktrace} = ErrorTracker.Stacktrace.new(stacktrace) @@ -127,6 +132,7 @@ defmodule ErrorTracker do If an error is marked as resolved and it happens again, it will automatically appear as unresolved again. """ + @spec resolve(%Error{}) :: {:ok, %Error{}} def resolve(error = %Error{status: :unresolved}) do changeset = Ecto.Changeset.change(error, status: :resolved) @@ -139,6 +145,7 @@ defmodule ErrorTracker do @doc """ Marks an error as unresolved. """ + @spec resolve(%Error{}) :: {:ok, %Error{}} def unresolve(error = %Error{status: :resolved}) do changeset = Ecto.Changeset.change(error, status: :unresolved) From aad7383a016f72ffa60e13c7deaec44fb573fe6c Mon Sep 17 00:00:00 2001 From: mse99 Date: Sun, 8 Sep 2024 23:45:56 +0200 Subject: [PATCH 02/11] Add type specs to `error_tracker/migration.ex` --- lib/error_tracker/migration.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/error_tracker/migration.ex b/lib/error_tracker/migration.ex index e6fdc19..cc098c8 100644 --- a/lib/error_tracker/migration.ex +++ b/lib/error_tracker/migration.ex @@ -96,14 +96,17 @@ defmodule ErrorTracker.Migration do @callback down(Keyword.t()) :: :ok @callback current_version(Keyword.t()) :: non_neg_integer() + @spec up(Keyword.t()) :: :ok def up(opts \\ []) when is_list(opts) do migrator().up(opts) end + @spec down(Keyword.t()) :: :ok def down(opts \\ []) when is_list(opts) do migrator().down(opts) end + @spec migrated_version(Keyword.t()) :: non_neg_integer() def migrated_version(opts \\ []) when is_list(opts) do migrator().migrated_version(opts) end From ebc5f472b16970735cecb6b8aa9a9d40ac63a7ba Mon Sep 17 00:00:00 2001 From: mse99 Date: Mon, 9 Sep 2024 00:04:25 +0200 Subject: [PATCH 03/11] Fix stack trace type in `error_tracker.ex` --- lib/error_tracker.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex index cf7c0f9..6b70bea 100644 --- a/lib/error_tracker.ex +++ b/lib/error_tracker.ex @@ -109,7 +109,7 @@ defmodule ErrorTracker do """ @type exception :: struct() | {atom(), struct()} - @type stack_trace :: {atom(), fun(), integer(), Keyword.t()} + @type stack_trace :: [{atom(), fun(), integer(), Keyword.t()}] @spec report(exception(), stack_trace()) :: %Occurrence{} | :noop def report(exception, stacktrace, given_context \\ %{}) do From eb07cfef63f12e8fff089a8e48869ba8f66bbc74 Mon Sep 17 00:00:00 2001 From: mse99 Date: Mon, 9 Sep 2024 15:07:58 +0200 Subject: [PATCH 04/11] Use `Occurrence.t` instead of `%Occurrence{}` in `error_tracker.ex` --- lib/error_tracker.ex | 2 +- lib/error_tracker/schemas/occurrence.ex | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex index 6b70bea..461d12a 100644 --- a/lib/error_tracker.ex +++ b/lib/error_tracker.ex @@ -111,7 +111,7 @@ defmodule ErrorTracker do @type exception :: struct() | {atom(), struct()} @type stack_trace :: [{atom(), fun(), integer(), Keyword.t()}] - @spec report(exception(), stack_trace()) :: %Occurrence{} | :noop + @spec report(exception(), stack_trace()) :: Occurrence.t() | :noop def report(exception, stacktrace, given_context \\ %{}) do {kind, reason} = normalize_exception(exception, stacktrace) {:ok, stacktrace} = ErrorTracker.Stacktrace.new(stacktrace) diff --git a/lib/error_tracker/schemas/occurrence.ex b/lib/error_tracker/schemas/occurrence.ex index 4729e2e..a623859 100644 --- a/lib/error_tracker/schemas/occurrence.ex +++ b/lib/error_tracker/schemas/occurrence.ex @@ -6,10 +6,21 @@ defmodule ErrorTracker.Occurrence do in which the exception raised. """ + import Ecto.Changeset + use Ecto.Schema require Logger - import Ecto.Changeset + + @type t :: %__MODULE__{ + context: map(), + reason: binary(), + stacktrace: map(), + error_id: non_neg_integer(), + inserted_at: DateTime.t(), + id: non_neg_integer(), + error: %ErrorTracker.Error{} + } schema "error_tracker_occurrences" do field :context, :map From 61e79f5bee4f7d03a19a8122369f8641384ef0b7 Mon Sep 17 00:00:00 2001 From: mse99 Date: Mon, 9 Sep 2024 15:13:44 +0200 Subject: [PATCH 05/11] Write type for `ErrorTracker.Error` --- lib/error_tracker/schemas/error.ex | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/error_tracker/schemas/error.ex b/lib/error_tracker/schemas/error.ex index 2c4ab45..aa6491b 100644 --- a/lib/error_tracker/schemas/error.ex +++ b/lib/error_tracker/schemas/error.ex @@ -12,6 +12,18 @@ defmodule ErrorTracker.Error do use Ecto.Schema + @type t :: %__MODULE__{ + kind: String.t(), + reason: String.t(), + source_line: String.t(), + source_function: String.t(), + status: :resolved | :unresolved, + fingerprint: binary(), + last_occurrence_at: DateTime.t(), + inserted_at: DateTime.t(), + updated_at: DateTime.t() + } + schema "error_tracker_errors" do field :kind, :string field :reason, :string From 9a6a93210de03e202b7f69e64bb8a50df5f8d5c0 Mon Sep 17 00:00:00 2001 From: mse99 Date: Mon, 9 Sep 2024 15:15:02 +0200 Subject: [PATCH 06/11] Update occurrence type def to use `ErrorTracker.Error.t()` --- lib/error_tracker/schemas/occurrence.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/error_tracker/schemas/occurrence.ex b/lib/error_tracker/schemas/occurrence.ex index a623859..90fa4d2 100644 --- a/lib/error_tracker/schemas/occurrence.ex +++ b/lib/error_tracker/schemas/occurrence.ex @@ -14,12 +14,12 @@ defmodule ErrorTracker.Occurrence do @type t :: %__MODULE__{ context: map(), - reason: binary(), + reason: String.t(), stacktrace: map(), error_id: non_neg_integer(), inserted_at: DateTime.t(), id: non_neg_integer(), - error: %ErrorTracker.Error{} + error: ErrorTracker.Error.t() } schema "error_tracker_occurrences" do From 2b29aa8c91ef016b8a79e48ee3fb46805a1d2ce4 Mon Sep 17 00:00:00 2001 From: mse99 Date: Mon, 9 Sep 2024 15:50:06 +0200 Subject: [PATCH 07/11] Fix `resolve/1` & `unresolve/1` type specs --- lib/error_tracker.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex index 461d12a..9772609 100644 --- a/lib/error_tracker.ex +++ b/lib/error_tracker.ex @@ -132,7 +132,7 @@ defmodule ErrorTracker do If an error is marked as resolved and it happens again, it will automatically appear as unresolved again. """ - @spec resolve(%Error{}) :: {:ok, %Error{}} + @spec resolve(%Error{status: :unresolved}) :: {:ok, %Error{status: :resolved}} def resolve(error = %Error{status: :unresolved}) do changeset = Ecto.Changeset.change(error, status: :resolved) @@ -145,7 +145,7 @@ defmodule ErrorTracker do @doc """ Marks an error as unresolved. """ - @spec resolve(%Error{}) :: {:ok, %Error{}} + @spec unresolve(%Error{status: :resolved}) :: {:ok, %Error{status: :unresolved}} def unresolve(error = %Error{status: :resolved}) do changeset = Ecto.Changeset.change(error, status: :unresolved) From 83b6db85f7f9a6f2fdaccd25de57b84f2ae35a29 Mon Sep 17 00:00:00 2001 From: mse99 Date: Mon, 9 Sep 2024 15:54:24 +0200 Subject: [PATCH 08/11] Add id field to `ErrorTracker.Error` type spec --- lib/error_tracker/schemas/error.ex | 1 + lib/error_tracker/schemas/occurrence.ex | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/error_tracker/schemas/error.ex b/lib/error_tracker/schemas/error.ex index aa6491b..e4e4d08 100644 --- a/lib/error_tracker/schemas/error.ex +++ b/lib/error_tracker/schemas/error.ex @@ -13,6 +13,7 @@ defmodule ErrorTracker.Error do use Ecto.Schema @type t :: %__MODULE__{ + id: non_neg_integer(), kind: String.t(), reason: String.t(), source_line: String.t(), diff --git a/lib/error_tracker/schemas/occurrence.ex b/lib/error_tracker/schemas/occurrence.ex index 90fa4d2..9942813 100644 --- a/lib/error_tracker/schemas/occurrence.ex +++ b/lib/error_tracker/schemas/occurrence.ex @@ -13,13 +13,13 @@ defmodule ErrorTracker.Occurrence do require Logger @type t :: %__MODULE__{ + id: non_neg_integer(), context: map(), reason: String.t(), stacktrace: map(), error_id: non_neg_integer(), - inserted_at: DateTime.t(), - id: non_neg_integer(), - error: ErrorTracker.Error.t() + error: ErrorTracker.Error.t(), + inserted_at: DateTime.t() } schema "error_tracker_occurrences" do From d4621fac241e889e6245d5b6ebe446f678abe351 Mon Sep 17 00:00:00 2001 From: mse99 Date: Mon, 9 Sep 2024 18:31:05 +0200 Subject: [PATCH 09/11] Add missing error return types for `resolve/1` & `unresolve/1` --- lib/error_tracker.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex index 9772609..61c427d 100644 --- a/lib/error_tracker.ex +++ b/lib/error_tracker.ex @@ -132,7 +132,8 @@ defmodule ErrorTracker do If an error is marked as resolved and it happens again, it will automatically appear as unresolved again. """ - @spec resolve(%Error{status: :unresolved}) :: {:ok, %Error{status: :resolved}} + @spec resolve(%Error{status: :unresolved}) :: + {:ok, %Error{status: :resolved}} | {:error, Ecto.Changeset.t()} def resolve(error = %Error{status: :unresolved}) do changeset = Ecto.Changeset.change(error, status: :resolved) @@ -145,7 +146,8 @@ defmodule ErrorTracker do @doc """ Marks an error as unresolved. """ - @spec unresolve(%Error{status: :resolved}) :: {:ok, %Error{status: :unresolved}} + @spec unresolve(%Error{status: :resolved}) :: + {:ok, %Error{status: :unresolved}} | {:error, Ecto.Changeset.t()} def unresolve(error = %Error{status: :resolved}) do changeset = Ecto.Changeset.change(error, status: :unresolved) From 1d3a41cc62a65cd804dd1cdad67dfe3929170f04 Mon Sep 17 00:00:00 2001 From: mse99 Date: Tue, 10 Sep 2024 17:03:22 +0200 Subject: [PATCH 10/11] Implement requested fixes --- lib/error_tracker.ex | 8 +++----- lib/error_tracker/schemas/error.ex | 13 +------------ lib/error_tracker/schemas/occurrence.ex | 10 +--------- lib/error_tracker/schemas/stacktrace.ex | 2 ++ 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex index 61c427d..0e6ab63 100644 --- a/lib/error_tracker.ex +++ b/lib/error_tracker.ex @@ -108,7 +108,7 @@ defmodule ErrorTracker do an Elixir exception (if possible) and stored. """ - @type exception :: struct() | {atom(), struct()} + @type exception :: Exception.t() | {:error, any()} | {Exception.non_error_kind(), any()} @type stack_trace :: [{atom(), fun(), integer(), Keyword.t()}] @spec report(exception(), stack_trace()) :: Occurrence.t() | :noop @@ -132,8 +132,7 @@ defmodule ErrorTracker do If an error is marked as resolved and it happens again, it will automatically appear as unresolved again. """ - @spec resolve(%Error{status: :unresolved}) :: - {:ok, %Error{status: :resolved}} | {:error, Ecto.Changeset.t()} + @spec resolve(Error.t()) :: {:ok, Error.t()} | {:error, Ecto.Changeset.t()} def resolve(error = %Error{status: :unresolved}) do changeset = Ecto.Changeset.change(error, status: :resolved) @@ -146,8 +145,7 @@ defmodule ErrorTracker do @doc """ Marks an error as unresolved. """ - @spec unresolve(%Error{status: :resolved}) :: - {:ok, %Error{status: :unresolved}} | {:error, Ecto.Changeset.t()} + @spec unresolve(Error.t()) :: {:ok, Error.t()} | {:error, Ecto.Changeset.t()} def unresolve(error = %Error{status: :resolved}) do changeset = Ecto.Changeset.change(error, status: :unresolved) diff --git a/lib/error_tracker/schemas/error.ex b/lib/error_tracker/schemas/error.ex index e4e4d08..a31bfee 100644 --- a/lib/error_tracker/schemas/error.ex +++ b/lib/error_tracker/schemas/error.ex @@ -12,18 +12,7 @@ defmodule ErrorTracker.Error do use Ecto.Schema - @type t :: %__MODULE__{ - id: non_neg_integer(), - kind: String.t(), - reason: String.t(), - source_line: String.t(), - source_function: String.t(), - status: :resolved | :unresolved, - fingerprint: binary(), - last_occurrence_at: DateTime.t(), - inserted_at: DateTime.t(), - updated_at: DateTime.t() - } + @type t :: %__MODULE__{} schema "error_tracker_errors" do field :kind, :string diff --git a/lib/error_tracker/schemas/occurrence.ex b/lib/error_tracker/schemas/occurrence.ex index 9942813..8cf0b5c 100644 --- a/lib/error_tracker/schemas/occurrence.ex +++ b/lib/error_tracker/schemas/occurrence.ex @@ -12,15 +12,7 @@ defmodule ErrorTracker.Occurrence do require Logger - @type t :: %__MODULE__{ - id: non_neg_integer(), - context: map(), - reason: String.t(), - stacktrace: map(), - error_id: non_neg_integer(), - error: ErrorTracker.Error.t(), - inserted_at: DateTime.t() - } + @type t :: %__MODULE__{} schema "error_tracker_occurrences" do field :context, :map diff --git a/lib/error_tracker/schemas/stacktrace.ex b/lib/error_tracker/schemas/stacktrace.ex index cf22f31..24e8bb0 100644 --- a/lib/error_tracker/schemas/stacktrace.ex +++ b/lib/error_tracker/schemas/stacktrace.ex @@ -6,6 +6,8 @@ defmodule ErrorTracker.Stacktrace do use Ecto.Schema + @type t :: %__MODULE__{} + @primary_key false embedded_schema do embeds_many :lines, Line, primary_key: false do From 7e1fead404a33e520b90112529147c767f6af8bf Mon Sep 17 00:00:00 2001 From: mse99 Date: Wed, 11 Sep 2024 16:20:01 +0200 Subject: [PATCH 11/11] Use `Exception.stacktrace` type and move exception type alongside context --- lib/error_tracker.ex | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex index 0e6ab63..ea9b643 100644 --- a/lib/error_tracker.ex +++ b/lib/error_tracker.ex @@ -67,6 +67,11 @@ defmodule ErrorTracker do """ @type context :: %{String.t() => any()} + @typedoc """ + An `Exception` or a `{kind, payload}` tuple compatible with `Exception.normalize/3`. + """ + @type exception :: Exception.t() | {:error, any()} | {Exception.non_error_kind(), any()} + import Ecto.Query alias ErrorTracker.Error @@ -108,10 +113,7 @@ defmodule ErrorTracker do an Elixir exception (if possible) and stored. """ - @type exception :: Exception.t() | {:error, any()} | {Exception.non_error_kind(), any()} - @type stack_trace :: [{atom(), fun(), integer(), Keyword.t()}] - - @spec report(exception(), stack_trace()) :: Occurrence.t() | :noop + @spec report(exception(), Exception.stacktrace(), context()) :: Occurrence.t() | :noop def report(exception, stacktrace, given_context \\ %{}) do {kind, reason} = normalize_exception(exception, stacktrace) {:ok, stacktrace} = ErrorTracker.Stacktrace.new(stacktrace)