Skip to content

Commit

Permalink
docs: motivation and more
Browse files Browse the repository at this point in the history
  • Loading branch information
grzuy committed Aug 27, 2024
1 parent 3f1ffc1 commit ea59a5b
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 18 deletions.
101 changes: 94 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,107 @@
[![Hex.pm](https://img.shields.io/hexpm/v/tower.svg)](https://hex.pm/packages/tower)
[![Documentation](https://img.shields.io/badge/Documentation-purple.svg)](https://hexdocs.pm/tower)

> Solid and simple **error handling** and **reporting** in Elixir.
> Decoupled error capturing and error reporting in Elixir.
## Motivation

Say you need to add error tracking to your elixir app:

- You decide what service you will use to send your errors to
- You look for a good elixir library for that service
- You configure it, deploy and start receiving errors there

Normally these libraries have to take care of a few responsibilities:

1. Capturing of errors (specific to language and runtime, i.e. Elixir and BEAM)
- Automatic capturing via (at least one of):
- Logger backend
- Logger handler
- Error logger handler
- Telemetry event handler
- Plugs
- Manual captruing by providing a few public API functions the programmer to call if needed
1. Transform these errors into some format for the remote service (specific to remote service), e.g.
- JSON for an HTTP API request
- Subject and body for an e-mail message
1. Make a remote call (e.g. an HTTP request with the payload) to the remote service (specific to remote service)

```mermaid
flowchart LR;
A(Elixir App) --> B(Auto Capturing);
A --> C(Manual Capturing);
subgraph "ErrorTrackingService<br>Library"
B --> D("Transform/Format");
C --> D;
D --> E("Report/Notify");
end
E --> F("ErrorTrackingService");
```

`Tower`, instead, takes care of capturing errors (number 1), giving them a well defined shape (`Tower.Event` struct)
and pass along this event to pre-configured but seprate reporters which take care of the error reporting steps
(number 2 and 3) depending on which service or remote system they report to.

```mermaid
flowchart LR;
A(Elixir App) --> B(Auto Capturing);
A --> C(Manual Capturing);
subgraph Tower
B --> Z("Build Tower.Event");
C --> Z;
end
subgraph A Tower.Reporter
Z --> D("Transform/Format");
D --> E("Report/Notify");
end
E --> F("ErrorTrackingService");
```

Corolaries of this approach:
- You can capture once and report to as many places as you want.
- You can switch from Error Tracking service provider without making any changes to your application error
capturing configuration or expect any change or regression with respect with capturing behvaior.
- Necessary future changes caused by deprecations and/or changes in error handling behavior in the BEAM or Elixir can be just
made in `Tower` without need to change any of the service specific reporters.

```mermaid
flowchart LR;
A(Elixir App) --> B(Auto Capturing);
A --> C(Manual Capturing);
subgraph Tower
B --> Z("Build Tower.Event");
C --> Z;
end
subgraph Tower.Reporter 1
Z --> D("Transform/Format");
D --> E("Report/Notify");
end
subgraph Tower.Reporter 2
Z --> F("Transform/Format");
F --> G("Report/Notify");
end
E --> H("ErrorTrackingService 1");
G --> I("ErrorTrackingService 2");
```

## Reporters

Tower is an automated exception handler for elixir applications.
As expalained in the Motivation section, any captured errors by `Tower` will be passed along to the list of
configured reporters, which can be set in

It tries to do one job well, **handle** uncaught **error events** in an elixir application
**and inform** pre-configured list of **reporters** (one or many) about these events.
config :tower, :reporters, [...] # Defaults to [Tower.EphemeralReporter]

You can either:
So, in summary, you can either
- Depend on `tower` package directly
- play with the default built-in toy reporter `Tower.EphemeralReporter`, useful for dev and test
- at some point for production [write your own custom reporter](https://hexdocs.pm/tower/Tower.html#module-writing-a-custom-reporter)

1. use `tower` package directly and [write your own custom reporter](https://hexdocs.pm/tower/Tower.html#module-writing-a-custom-reporter) or;
1. use one (or many) of the following reporters (separate packages) that build on top and depend on `tower`:
or
- depend on one (or many) of the following reporters (separate packages) that build on top and depend on `tower`:
- [`TowerEmail`](https://hexdocs.pm/tower_email) ([`tower_email`](https://hex.pm/packages/tower_email))
- [`TowerRollbar`](https://hexdocs.pm/tower_rollbar) ([`tower_rollbar`](https://hex.pm/packages/tower_rollbar))
- [`TowerSlack`](https://hexdocs.pm/tower_slack) ([`tower_slack`](https://hex.pm/packages/tower_slack))
- and properly set the `config :tower, :reporters, [...]` configuration key

## Installation

Expand Down
110 changes: 100 additions & 10 deletions lib/tower.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,108 @@
defmodule Tower do
@moduledoc """
An automated exception handler for elixir applications.
It tries to do one job well, **handle** uncaught **error events** in an elixir application
**and inform** pre-configured list of **reporters** (one or many) about these events.
Decoupled error capturing and error reporting in Elixir.
## Motivation
Say you need to add error tracking to your elixir app:
- You decide what service you will use to send your errors to
- You look for a good elixir library for that service
- You configure it, deploy and start receiving errors there
Normally these libraries have to take care of a few responsibilities:
1. Capturing of errors (specific to language and runtime, i.e. Elixir and BEAM)
- Automatic capturing via (at least one of):
- Logger backend
- Logger handler
- Error logger handler
- Telemetry event handler
- Plugs
- Manual captruing by providing a few public API functions the programmer to call if needed
1. Transform these errors into some format for the remote service (specific to remote service), e.g.
- JSON for an HTTP API request
- Subject and body for an e-mail message
1. Make a remote call (e.g. an HTTP request with the payload) to the remote service (specific to remote service)
```mermaid
flowchart LR;
A(Elixir App) --> B(Auto Capturing);
A --> C(Manual Capturing);
subgraph "ErrorTrackingService<br>Library"
B --> D("Transform/Format");
C --> D;
D --> E("Report/Notify");
end
E --> F("ErrorTrackingService");
```
`Tower`, instead, takes care of capturing errors (number 1), giving them a well defined shape (`Tower.Event` struct)
and pass along this event to pre-configured but seprate reporters which take care of the error reporting steps
(number 2 and 3) depending on which service or remote system they report to.
```mermaid
flowchart LR;
A(Elixir App) --> B(Auto Capturing);
A --> C(Manual Capturing);
subgraph Tower
B --> Z("Build Tower.Event");
C --> Z;
end
subgraph A Tower.Reporter
Z --> D("Transform/Format");
D --> E("Report/Notify");
end
E --> F("ErrorTrackingService");
```
Corolaries of this approach:
- You can capture once and report to as many places as you want.
- You can switch from Error Tracking service provider without making any changes to your application error
capturing configuration or expect any change or regression with respect with capturing behvaior.
- Necessary future changes caused by deprecations and/or changes in error handling behavior in the BEAM or Elixir can be just
made in `Tower` without need to change any of the service specific reporters.
```mermaid
flowchart LR;
A(Elixir App) --> B(Auto Capturing);
A --> C(Manual Capturing);
subgraph Tower
B --> Z("Build Tower.Event");
C --> Z;
end
subgraph Tower.Reporter 1
Z --> D("Transform/Format");
D --> E("Report/Notify");
end
subgraph Tower.Reporter 2
Z --> F("Transform/Format");
F --> G("Report/Notify");
end
E --> H("ErrorTrackingService 1");
G --> I("ErrorTrackingService 2");
```
## Reporters
You can either:
1. use `tower` package directly and [write your own custom reporter](#module-writing-a-custom-reporter) or;
1. use one (or many) of the following reporters (separate packages) that build on top and depend on `tower`:
* [`TowerEmail`](https://hexdocs.pm/tower_email) ([`tower_email`](https://hex.pm/packages/tower_email))
* [`TowerRollbar`](https://hexdocs.pm/tower_rollbar) ([`tower_rollbar`](https://hex.pm/packages/tower_rollbar))
* [`TowerSlack`](https://hexdocs.pm/tower_slack) ([`tower_slack`](https://hex.pm/packages/tower_slack))
As expalained in the Motivation section, any captured errors by `Tower` will be passed along to the list of
configured reporters, which can be set in
```elixir
config :tower, :reporters, [...] # Defaults to [Tower.EphemeralReporter]
```
So, in summary, you can either
- Depend on `tower` package directly
- play with the default built-in toy reporter `Tower.EphemeralReporter`, useful for dev and test
- at some point for production [write your own custom reporter](#module-writing-a-custom-reporter)
or
- depend on one (or many) of the following reporters (separate packages) that build on top and depend on `tower`:
- [`TowerEmail`](https://hexdocs.pm/tower_email) ([`tower_email`](https://hex.pm/packages/tower_email))
- [`TowerRollbar`](https://hexdocs.pm/tower_rollbar) ([`tower_rollbar`](https://hex.pm/packages/tower_rollbar))
- [`TowerSlack`](https://hexdocs.pm/tower_slack) ([`tower_slack`](https://hex.pm/packages/tower_slack))
- and properly set the `config :tower, :reporters, [...]` configuration key
## Enabling automated exception handling
Expand Down
32 changes: 31 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,37 @@ defmodule Tower.MixProject do
main: "Tower",
extras: [
"CHANGELOG.md": [title: "Changelog"]
]
],
before_closing_body_tag: &before_closing_body_tag/1
]
end

defp before_closing_body_tag(:html) do
"""
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
mermaid.initialize({
startOnLoad: false,
theme: document.body.className.includes("dark") ? "dark" : "default"
});
let id = 0;
for (const codeEl of document.querySelectorAll("pre code.mermaid")) {
const preEl = codeEl.parentElement;
const graphDefinition = codeEl.textContent;
const graphEl = document.createElement("div");
const graphId = "mermaid-graph-" + id++;
mermaid.render(graphId, graphDefinition).then(({svg, bindFunctions}) => {
graphEl.innerHTML = svg;
bindFunctions?.(graphEl);
preEl.insertAdjacentElement("afterend", graphEl);
preEl.remove();
});
}
});
</script>
"""
end

defp before_closing_body_tag(_), do: ""
end

0 comments on commit ea59a5b

Please sign in to comment.