README.md

# OpentelemetryExUnitFormatter

<!-- MDOC !-->

[![Build](https://img.shields.io/github/actions/workflow/status/Recruitee/opentelemetry_ex_unit_formatter/ci.yml?style=for-the-badge)](https://github.com/Recruitee/opentelemetry_ex_unit_formatter/actions/workflows/ci.yml)
[![Hex Version](https://img.shields.io/hexpm/v/opentelemetry_ex_unit_formatter?style=for-the-badge)](https://hex.pm/packages/opentelemetry_ex_unit_formatter)
[![Hex Docs](https://img.shields.io/badge/hex-docs-informational?style=for-the-badge)](https://hexdocs.pm/opentelemetry_ex_unit_formatter)

Opentelemetry instrumentation for `ExUnit.Formatter`.

Telemetry handler that creates Opentelemetry spans from `ExUnit.Formatter` events.

## Installation

Add `:opentelemetry_ex_unit_formatter` to your application dependencies list:

```elixir
# mix.exs
defp deps do
  [
    {:opentelemetry_ex_unit_formatter, "~> 0.1.0"}
  ]
end
```

## Usage

Add `OpentelemetryExUnitFormatter` to the `ExUnit` formatters list:

```elixir
# test/test_helper.exs
ExUnit.configure(formatters: [ExUnit.CLIFormatter, OpentelemetryExUnitFormatter])
ExUnit.start()
```

By default OpentelemetryExUnitFormatter has Opentelemetry tracer set to `:none` and telemetry spans
are not exported.
If you want to export telemetry spans, please add `:opentelemetry_exporter` to your application
dependencies list:

```elixir
# mix.exs
defp deps do
  [
    {
      :opentelemetry_ex_unit_formatter,
      "~> 0.1.0",
      github: "Recruitee/opentelemetry_ex_unit_formatter", only: :test, runtime: false
    },
    {:opentelemetry, "~> 1.2"},
    {:opentelemetry_exporter, "~> 1.4", only: :test, runtime: false}
  ]
end
```

Additionally you need to configure Opentelemetry tracer as described in the
[:tracer_provider_config](#tracer_provider_config) section below.

## Configuration

OpentelemetryExUnitFormatter configuration usually should be provided in the `config/test.exs` file.
Available configuration options are described below.

### `:before_send`

User provided single arity anonymous function executed before starting and sending a new telemetry
span.
Function should accept span attributes map as an argument and should return user modified span
attributes map.

**Default:** `nil`.

Example:

```elixir
# config/test.exs
config :opentelemetry_ex_unit_formatter,
  before_send: &MyApp.Test.Support.OpentelemetryExUnitFormatterHelper.before_send/1,

# test/support/opentelemetry_ex_unit_formatter_helper.ex
defmodule MyApp.Test.Support.OpentelemetryExUnitFormatterHelper do
  @attr_prefix "code.ci"
  @undefined "undefined"

  def before_send(attributes) do
    attributes
    |> Map.put(:"#{@attr_prefix}.ref_name", System.get_env("GITHUB_REF_NAME", @undefined))
    |> Map.put(:"#{@attr_prefix}.repository", System.get_env("GITHUB_REPOSITORY", @undefined))
  end
end
```

### `:register_after_suite?`

Opentelemetry processors might process telemetry spans asynchronously.
After entire testing suite is completed by `ExUnit`, some spans might still be buffered by
Opentelemetry processor and never exported.
If `:register_after_suite?` is set to `true` OpentelemetryExUnitFormatter will register a
callback function using `ExUnit.after_suite/1`. Registered callback function will use configuration
provided with `:tracer_provider_config` key to flush processor using its `:name` and then sleep for
the amount of time provided by `:bsp_scheduled_delay_ms` key (default 5000ms), waiting for a
processor to flush all pending spans.

In many cases built-in `after_suite` callback might not be sufficient or optimal and you should
consider registering your own callback depending on your Opentelemetry processor configuration.

**Default:** `false`.

### `:root_attribute`

Name of the root span attribute. By default it is set to the
[Source Code Attribute](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/span-general/#source-code-attributes).

**Default:** `"code"`.

### `:span_name`

Name of the emitted spans (see: `:otel_tracer_default.with_span/5`).

**Default:** `"ex_unit"`.

### `:tracer_provider_config`

To emit telemetry spans OpentelemetryExUnitFormatter will run its own tracer provider, independent
of your application tracer defined in this case for the `:test` environment.

Example tracer provider configuration with `:otel_simple_processor` processor `:ex_unit_processor`
and `:opentelemetry_exporter` exporter:

```elixir
# config/test.exs
config :opentelemetry_ex_unit_formatter,
  tracer_provider_config: %{
    deny_list: [],
    id_generator: :otel_id_generator,
    sampler: {:otel_sampler_always_on, []},
    processors: [
      {
        :otel_simple_processor,
        %{
          bsp_scheduled_delay_ms: 1000,
          exporter: {:opentelemetry_exporter, %{}},
          name: :ex_unit_processor
        }
      }
    ]
  }
```

For large projects you might consider using `:otel_batch_processor`. When using batch processor, be
aware that spans are batched before being processed and it requires special attention as discussed
in the [`:register_after_suite?`](#register_after_suite) section.

For debugging purposes `:opentelemetry_exporter` can be set to `:otel_exporter_stdout`.

**Default:** `:none`.

<!-- MDOC !-->