Current section
Files
Jump to
Current section
Files
lib/tower_opentelemetry.ex
defmodule TowerOpentelemetry do
@moduledoc """
A [Tower](https://github.com/mimiquate/tower) reporter that records captured
exceptions as OpenTelemetry span events, following the
[OTEL semantic conventions for exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/).
## Usage
Add `:tower` and `:tower_opentelemetry` to your dependencies, then configure
the reporter:
# config/config.exs
config :tower, reporters: [TowerOpentelemetry]
When Tower captures an unhandled exception, exit, throw or `Logger` error,
this reporter records it as an `exception` event on the currently active
span and marks the span status as `:error`. If no span is active (for
example, a crash in a background process), a short-lived span named
`"tower.event"` is started so the event still reaches the OTEL pipeline.
## Recorded attributes
Per the semantic conventions, the following attributes are attached to the
span event:
* `exception.type` — the fully-qualified exception module name, or
`Exit`/`Throw`/`Message` for non-exception kinds.
* `exception.message` — `Exception.message/1` for exceptions, otherwise
`inspect/1` of the reason.
* `exception.stacktrace` — `Exception.format_stacktrace/1`, when a
stacktrace is available. Omitted otherwise, per the spec.
* `exception.escaped` — always `true`, since Tower only reports
exceptions that escaped their original scope.
In addition, `event.id`, `event.kind` and `event.level` are attached for
correlation with Tower's normalized event stream.
## Coexisting with other reporters
This reporter is fully compatible with other Tower reporters. For example,
to ship exceptions to both Sentry and an OTEL backend at the same time:
config :tower, reporters: [Tower.Sentry, TowerOpentelemetry]
"""
@behaviour Tower.Reporter
alias OpenTelemetry.Span
alias OpenTelemetry.Tracer
alias TowerOpentelemetry.Attributes
require OpenTelemetry.Tracer
@impl true
def report_event(%Tower.Event{} = event) do
case Tracer.current_span_ctx() do
:undefined -> record_in_new_span(event)
span_ctx -> record(span_ctx, event)
end
:ok
end
defp record_in_new_span(event) do
OpenTelemetry.Tracer.with_span "tower.event" do
record(Tracer.current_span_ctx(), event)
end
end
defp record(span_ctx, %Tower.Event{} = event) do
attributes = Attributes.exception_attributes(event) ++ Attributes.tower_attributes(event)
Span.add_event(span_ctx, :exception, attributes)
Span.set_status(span_ctx, OpenTelemetry.status(:error, Attributes.status_message(event)))
end
end