O11y LogoO11y

Build status badgeHex.pm version badgeHex.pm downloads badge

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_new

Then 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 honeycomb

Manual

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:

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__
end

Error 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)
end

Events

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: