O11y
Convenience functions and other things to (hopefully) make your life easier when working with OpenTelemetry in Elixir.
Installation
Using Igniter (Recommended)
Install the Igniter archive if you haven't already (running it again won't hurt):
mix archive.install hex igniter_newThen set up o11y and all of its OpenTelemetry dependencies automatically:
mix igniter.install o11y
This will add the required dependencies, configure dev and runtime exporters, and inject setup calls into your Application module if you're using Phoenix, Ecto, or Cowboy/Bandit.
To also install open_telemetry_decorator:
mix igniter.install o11y --with-decorator
To configure exporters for Honeycomb (reads API key from HONEYCOMB_KEY env var):
mix igniter.install o11y --exporter honeycombManual
Add o11y to your list of dependencies in mix.exs.
We include the opentelemetry_api package, but you'll need to add opentelemetry, and opentelemetry_exporter yourself in order to report spans and traces:
def deps do
[
{:o11y, "~> 0.2"},
{:opentelemetry, "~> 1.5"},
{:opentelemetry_exporter, "~> 1.8"}
]
end
đź’ˇ Note: if you use open_telemetry_decorator, o11y will already be included as a transitive dependency.
Then follow the directions for the exporter of your choice to send traces to zipkin, honeycomb, etc.
Honeycomb Example
config/runtime.exs
api_key = System.fetch_env!("HONEYCOMB_KEY")
config :opentelemetry_exporter,
otlp_endpoint: "https://api.honeycomb.io:443",
otlp_headers: [{"x-honeycomb-team", api_key}]Usage
The docs are a great place to start, but below are examples of the most common use cases.
Basic Example
In this example we use Trace.with_span/2 to create a span named "worker.do_work" and then use the two set_attribute(s) functions to... set attributes on it.
defmodule MyApp.Worker do
require OpenTelemetry.Tracer, as: Tracer
def do_work(arg1, arg2) do
Tracer.with_span "Worker.do_work" do
O11y.set_attributes(%{arg1: arg1, arg2: arg2})
# ...doing work
O11y.set_attribute(:result, "something")
end
end
end
📝 Note that, where possible, the functions in O11y return the value they're given so they can be used in pipelines.
%User{email: "alice@aol.com", password: "hunter2"}
|> O11y.set_attributes(prefix: :user)
|> login_user()
|> O11y.set_attributes(prefix: :logged_in_user)Exception Handling
In this example we use O11y.record_exception/2, which does a number of things:
-
Sets the span's
status_codetoERROR -
Sets the span's
status_messageto the exception's message -
Adds
exception.messageandexception.stack_traceattributes -
Adds a separate
exceptionspan event
Note that we're only rescuing in order to record the exception, so we also reraise with the original stacktrace.
try do
# ...doing work
rescue
e ->
O11y.record_exception(e)
reraise e, __STACKTRACE__
endError Handling
You may want to indicate that an error occurred in a span in cases where an exception is not raised. For this we'll use O11y.set_error/1, which will set the status_code and status_message attributes.
case do_some_work() do
{:ok, _} -> :ok
error -> O11y.set_error(error)
endEvents
Events are essentially structured log lines that can be associated to a span. They can be used as checkpoints during the span's lifetime, or to indicate that something notable happened (or failed to happen). Attributes given in the second argument are processed in the same way as attributes given to O11y.set_attribute(s).
O11y.add_event("Something happened", %{some: "context", what: "happened"})Baggage
Baggage is a way to pass data between spans in a trace. You can add attributes to the baggage using O11y.set_global_attribute and O11y.set_global_attributes.
The O11y.BaggageProcessor module will include any attributes added to the baggage to all spans created in that context.
config(:opentelemetry, :processors, [{O11y.BaggageProcessor, %{}}])O11y.set_global_attribute(:user_id, 123)Configuration
Attribute Namespace
You can set a global attribute namespace, which will prefix all attributes added to spans with the given value. This reduces the risk of attribute name collisions and helpfully keeps all of your custom attributes together in trace front ends.
config :o11y, :attribute_namespace, "app"O11y.set_attributes(%{key: "value"})
%{"app.key" => "value"}Filtered Attributes
You can set a global list of attribute names that should be filtered out (names can be strings or atoms). Any attributes whose name contains one of the values in this list will be removed from the span.
config :o11y, :filtered_attributes, ["secret", "token", :password, "email"]Development
make check before you commit! If you'd prefer to do it manually:
mix do deps.get, deps.unlock --unused, deps.clean --unusedif you change dependenciesmix compile --warnings-as-errorsfor a stricter compilemix coveralls.htmlto check for test coveragemix credoto suggest more idiomatic style for your codemix dialyzerto find problems typing might reveal… albeit slowlymix docsto generate documentation