View Source Tower (Tower v0.5.0)

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.

Reporters

You can either:

  1. use tower package directly and write your own custom reporter or;
  2. use one (or many) of the following reporters (separate packages) that build on top and depend on tower:

Enabling automated exception handling

Tower.attach()

Disabling automated exception handling

Tower.detach()

Manual handling

If either, for whatever reason when using automated exception handling, an exception condition is not reaching Tower handling, or you just need or want to manually handle possible errors, you can manually ask Tower to handle exceptions, throws or exits.

try do
  # possibly carshing code
rescue
  exception ->
    Tower.handle_exception(exception, __STACKTRACE__)
catch
  :throw, value ->
    Tower.handle_throw(value, __STACKTRACE__)
  :exit, reason ->
    Tower.handle_exit(reason, __STACKTRACE__)
end

or more generally

try do
  # possibly carshing code
catch
  kind, reason ->
    Tower.handle_caught(kind, reason, __STACKTRACE__)
end

which will in turn call the appropriate function based on the caught kind and reason values

Writing a custom reporter

# lib/my_app/error_reporter.ex
defmodule MyApp.ErrorReporter do
  @behaviour Tower.Reporter

  @impl true
  def report_event(%Tower.Event{} = event) do
    # do something with event

    # A `Tower.Event` is a struct with the following typespec:
    #
    # %Tower.Event{
    #   id: Uniq.UUID.t(),
    #   datetime: DateTime.t(),
    #   level: :logger.level(),
    #   kind: :error | :exit | :throw | :message,
    #   reason: Exception.t() | term(),
    #   stacktrace: Exception.stacktrace() | nil,
    #   log_event: :logger.log_event() | nil,
    #   plug_conn: struct() | nil,
    #   metadata: map()
    # }
  end
end

# in some config/*.exs
config :tower, reporters: [MyApp.ErrorReporter]

# config/application.ex
Tower.attach()

Tower.attach/0 will be responsible for registering the necessary handlers in your application so that any uncaught exception, uncaught throw or abnormal process exit is handled by Tower and passed along to the reporter.

Summary

Functions

Attaches the necessary handlers to automatically listen for application errors.

Detaches the handlers.

Compares event level severity.

Asks Tower to handle a manually caught error.

Asks Tower to handle a manually caught exception.

Asks Tower to handle a manually caught exit.

Asks Tower to handle a message of certain level.

Asks Tower to handle a manually caught throw.

Functions

@spec attach() :: :ok

Attaches the necessary handlers to automatically listen for application errors.

Adds a new logger_handler, which listens for all uncaught exceptions, uncaught throws, abnormal process exits, among other log events of interest.

Additionally adds other handlers specifically tailored for some packages that do catch errors and have their own specific error handling and emit events instead of letting errors get to the logger handler, like oban or bandit.

Note that Tower.attach/0 is not a precondition for Tower handle_* functions to work properly and inform reporters. They are independent.

@spec detach() :: :ok

Detaches the handlers.

That means it stops the automatic handling of errors. You can still manually call Tower handle_* functions and reporters will be informed about those events.

Link to this function

equal_or_greater_level?(level1, level2)

View Source
@spec equal_or_greater_level?(
  Tower.Event.t() | Tower.Event.level(),
  Tower.Event.level()
) :: boolean()

Compares event level severity.

Returns true if level1 severity is equal or greater than level2 severity.

Examples

iex> Tower.equal_or_greater_level?(:emergency, :error)
true
iex> Tower.equal_or_greater_level?(%Tower.Event{level: :info}, :info)
true
iex> Tower.equal_or_greater_level?(:warning, :critical)
false
Link to this function

handle_caught(kind, reason, stacktrace, options \\ [])

View Source
@spec handle_caught(
  Exception.kind(),
  Tower.Event.reason(),
  Exception.stacktrace(),
  Keyword.t()
) :: :ok

Asks Tower to handle a manually caught error.

Example

try do
  # possibly crashing code
catch
  # Note this will also catch and handle normal (`:normal` and `:shutdown`) exits
  kind, reason ->
    Tower.handle_caught(kind, reason, __STACKTRACE__)
end

Options

  • :plug_conn - a Plug.Conn relevant to the event, if available, that may be used by reporters to report useful context information. Be aware that the Plug.Conn may contain user and/or system sensitive information, and it's up to each reporter to be cautious about what to report or not.
  • :metadata - a Map with additional information you want to provide about the event. It's up to each reporter if and how to handle it.
Link to this function

handle_exception(exception, stacktrace, options \\ [])

View Source
@spec handle_exception(Exception.t(), Exception.stacktrace(), Keyword.t()) :: :ok

Asks Tower to handle a manually caught exception.

Example

try do
  # possibly crashing code
rescue
  exception ->
    Tower.handle_exception(exception, __STACKTRACE__)
end

Options

Link to this function

handle_exit(reason, stacktrace, options \\ [])

View Source
@spec handle_exit(term(), Exception.stacktrace(), Keyword.t()) :: :ok

Asks Tower to handle a manually caught exit.

Example

try do
  # possibly exiting code
catch
  # Note this will also catch and handle normal (`:normal` and `:shutdown`) exits
  :exit, exit_reason ->
    Tower.handle_exit(exit_reason, __STACKTRACE__)
end

Options

Link to this function

handle_message(level, message, options \\ [])

View Source
@spec handle_message(Tower.Event.level(), term(), Keyword.t()) :: :ok

Asks Tower to handle a message of certain level.

Examples

Tower.handle_message(:emergency, "System is falling apart")

Tower.handle_message(:error, "Unknown error has ocurred", metadata: %{any_key: "here"})

Tower.handle_message(:info, "Just something interesting", metadata: %{interesting: "additional data"})

Options

Link to this function

handle_throw(reason, stacktrace, options \\ [])

View Source
@spec handle_throw(term(), Exception.stacktrace(), Keyword.t()) :: :ok

Asks Tower to handle a manually caught throw.

Example

try do
  # possibly throwing code
catch
  thrown_value ->
    Tower.handle_throw(thrown_value, __STACKTRACE__)
end

Options