Skip to content

Commit 785a418

Browse files
committed
Initial data model and error reporting
1 parent acc09b1 commit 785a418

12 files changed

Lines changed: 209 additions & 14 deletions

File tree

.formatter.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Used by "mix format"
22
[
3-
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
4+
import_deps: [:ecto, :ecto_sql]
45
]

config/config.exs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Config
2+
3+
config :error_tracker, ErrorTracker.Repo,
4+
name: ErrorTracker.Repo,
5+
priv: "test/support/repo",
6+
stacktrace: true,
7+
url:
8+
System.get_env("DATABASE_URL") ||
9+
"postgres://postgres:postgres@localhost:5432/error_tracker_test"
10+
11+
config :error_tracker, ecto_repos: [ErrorTracker.Repo]

lib/error_tracker.ex

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
defmodule ErrorTracker do
2-
@moduledoc """
3-
Documentation for `ErrorTracker`.
4-
"""
2+
alias ErrorTracker.Repo
53

6-
@doc """
7-
Hello world.
4+
def report(exception, stacktrace, context \\ %{}) do
5+
{:ok, stacktrace} = ErrorTracker.Stacktrace.new(stacktrace)
6+
{:ok, error} = ErrorTracker.Error.new(exception, stacktrace)
87

9-
## Examples
8+
error =
9+
Repo.insert!(error,
10+
on_conflict: [set: [status: :unresolved]],
11+
conflict_target: :fingerprint
12+
)
1013

11-
iex> ErrorTracker.hello()
12-
:world
14+
error
15+
|> Ecto.build_assoc(:occurrences, stacktrace: stacktrace, context: context)
16+
|> Repo.insert!()
17+
end
1318

14-
"""
15-
def hello do
16-
:world
19+
def raise do
20+
raise "PROBANDO PROBANDO"
21+
rescue
22+
e -> report(e, __STACKTRACE__)
1723
end
1824
end

lib/error_tracker/application.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
defmodule ErrorTracker.Application do
2+
use Application
3+
4+
def start(_type, _args) do
5+
children = [ErrorTracker.Repo]
6+
7+
Supervisor.start_link(children, strategy: :one_for_one, name: __MODULE__)
8+
end
9+
end

lib/error_tracker/models/error.ex

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
defmodule ErrorTracker.Error do
2+
use Ecto.Schema
3+
4+
schema "error_tracker_errors" do
5+
field :kind, :string
6+
field :reason, :string
7+
field :source, :string
8+
field :status, Ecto.Enum, values: [:resolved, :unresolved], default: :unresolved
9+
field :fingerprint, :binary
10+
11+
has_many :occurrences, ErrorTracker.Occurrence
12+
13+
timestamps()
14+
end
15+
16+
def new(exception, stacktrace = %ErrorTracker.Stacktrace{}) do
17+
source = ErrorTracker.Stacktrace.source(stacktrace)
18+
19+
params = [
20+
kind: "error",
21+
reason: Exception.message(exception),
22+
source: to_string(source)
23+
]
24+
25+
fingerprint = :crypto.hash(:sha256, params |> Keyword.values() |> Enum.join())
26+
27+
%__MODULE__{}
28+
|> Ecto.Changeset.change(params)
29+
|> Ecto.Changeset.put_change(:fingerprint, Base.encode16(fingerprint))
30+
|> Ecto.Changeset.apply_action(:new)
31+
end
32+
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule ErrorTracker.Occurrence do
2+
use Ecto.Schema
3+
4+
schema "error_tracker_occurrences" do
5+
field :context, :map
6+
7+
embeds_one :stacktrace, ErrorTracker.Stacktrace
8+
belongs_to :error, ErrorTracker.Error
9+
10+
timestamps(updated_at: false)
11+
end
12+
end
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
defmodule ErrorTracker.Stacktrace do
2+
use Ecto.Schema
3+
4+
@primary_key false
5+
embedded_schema do
6+
embeds_many :lines, Line, primary_key: false do
7+
field :application, :string
8+
field :module, :string
9+
field :function, :string
10+
field :arity, :integer
11+
field :file, :string
12+
field :line, :integer
13+
end
14+
end
15+
16+
def new(stack) do
17+
lines_params =
18+
for {module, function, arity, opts} <- stack do
19+
application = Application.get_application(module)
20+
21+
%{
22+
application: to_string(application),
23+
module: to_string(module),
24+
function: to_string(function),
25+
arity: arity,
26+
file: to_string(opts[:file]),
27+
line: opts[:line]
28+
}
29+
end
30+
31+
%__MODULE__{}
32+
|> Ecto.Changeset.cast(%{lines: lines_params}, [])
33+
|> Ecto.Changeset.cast_embed(:lines, with: &line_changeset/2)
34+
|> Ecto.Changeset.apply_action(:new)
35+
end
36+
37+
defp line_changeset(line = %__MODULE__.Line{}, params) do
38+
Ecto.Changeset.cast(line, params, ~w[application module function arity file line]a)
39+
end
40+
41+
def source(stack = %__MODULE__{}) do
42+
List.first(stack.lines)
43+
end
44+
end
45+
46+
defimpl String.Chars, for: ErrorTracker.Stacktrace do
47+
def to_string(stack = %ErrorTracker.Stacktrace{}) do
48+
Enum.join(stack.lines, "\n")
49+
end
50+
end
51+
52+
defimpl String.Chars, for: ErrorTracker.Stacktrace.Line do
53+
def to_string(stack_line = %ErrorTracker.Stacktrace.Line{}) do
54+
"#{stack_line.module}.#{stack_line.function}/#{stack_line.arity} in #{stack_line.file}:#{stack_line.line}"
55+
end
56+
end

lib/error_tracker/plug.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule ErrorTracker.Plug do
2+
defmacro __using__(_opts) do
3+
quote do
4+
use Plug.ErrorHandler
5+
6+
@impl Plug.ErrorHandler
7+
def handle_errors(conn, %{kind: kind, reason: reason, stack: stack}) do
8+
9+
10+
dbg kind
11+
dbg reason
12+
dbg stack
13+
14+
:ok
15+
end
16+
end
17+
end
18+
end

lib/error_tracker/repo.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defmodule ErrorTracker.Repo do
2+
use Ecto.Repo,
3+
otp_app: :error_tracker,
4+
adapter: Ecto.Adapters.Postgres
5+
end

mix.exs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule ErrorTracker.MixProject do
66
app: :error_tracker,
77
version: "0.0.1",
88
elixir: "~> 1.15",
9+
elixirc_paths: elixirc_paths(Mix.env()),
910
start_permanent: Mix.env() == :prod,
1011
deps: deps(),
1112
package: package(),
@@ -16,9 +17,15 @@ defmodule ErrorTracker.MixProject do
1617

1718
# Run "mix help compile.app" to learn about applications.
1819
def application do
19-
[]
20+
[
21+
mod: {ErrorTracker.Application, []},
22+
extra_applications: [:logger]
23+
]
2024
end
2125

26+
defp elixirc_paths(:test), do: ["lib", "test/support"]
27+
defp elixirc_paths(_env), do: ["lib"]
28+
2229
def package do
2330
[
2431
licenses: ["Apache-2.0"],
@@ -35,7 +42,11 @@ defmodule ErrorTracker.MixProject do
3542
# Run "mix help deps" to learn about dependencies.
3643
defp deps do
3744
[
38-
{:ex_doc, "~> 0.33", only: :dev, runtime: false}
45+
{:ecto_sql, "~> 3.0"},
46+
{:ecto, "~> 3.11"},
47+
{:ex_doc, "~> 0.33", only: :dev, runtime: false},
48+
{:jason, "~> 1.1"},
49+
{:postgrex, ">= 0.0.0"}
3950
]
4051
end
4152
end

0 commit comments

Comments
 (0)