Setup

mix.exs

def project do
  [
    compilers: [:telemetria],
    deps: deps()
  ]
end

defp deps do
  [{:telemetria, "~> 0.24"}]
end

config.exs

import Config

config :telemetria,
  backend: :telemetry,
  purge_level: :debug,
  level: :info,
  events: [[:my_app, :my_module, :my_function]],
  throttle: %{my_group: {1_000, :last}}

Module Attribute @telemetria

Basic usage

defmodule MyApp.Worker do
  use Telemetria

  @telemetria true
  def process(data), do: do_work(data)
end

Wraps the function with telemetry, measuring execution time.

With logging level

@telemetria level: :warning
def important(data), do: handle(data)

Only emits the event when the configured purge_level allows :warning or above.

With compile-time condition

@telemetria if: Mix.env() == :prod
def prod_only(data), do: data

Telemetry code is included/excluded at compile time.

With runtime condition

@telemetria if: &match?({:ok, _}, &1)
def maybe_report(input) do
  {:ok, process(input)}
end

The event is emitted only when the if function returns true for the result.

Multi-clause functions

@telemetria level: :info, locals: [:i]
def classify(i \\ nil)
def classify(nil), do: :none
def classify(i) when i > 0, do: :positive
def classify(_i), do: :negative

For multi-clause functions, place @telemetria on the bodyless head clause (the one with defaults). All clauses will be wrapped. Do not mix head-level and per-clause annotations.

Per-clause annotations

@telemetria level: :warning
def handle(:error), do: alert()

@telemetria level: :debug
def handle(:ok), do: :ok

Alternatively, annotate individual clauses separately (each gets its own event with its own options). Cannot be combined with head-level annotation on the same function.

Attribute Parameters

Export local variables

@telemetria locals: [:celsius, :source]
def temperature(city) do
  source = :weather_api
  celsius = fetch_temp(city)
  celsius
end

Captures the values of local variables and includes them in the telemetry event metadata.

Throttled events

# config.exs
config :telemetria,
  throttle: %{api_calls: {5_000, :last}}

# module
@telemetria group: :api_calls
def call_api(params), do: HTTPClient.get(params)

Events in the :api_calls group are throttled: only the last event per 5-second window is emitted.

Transform args and result

@telemetria transform: [
  args: &Enum.map(&1, fn {k, _} -> k end),
  result: &inspect/1
]
def sensitive(credentials) do
  authenticate(credentials)
end

Applies transformation functions to arguments and/or results before they reach the telemetry handler. Useful for redacting sensitive data.

Reshape the event

@telemetria reshape: &Map.take(&1, [:result, :args])
def compute(x), do: x * x

Reshapes the entire event payload before sending. Receives the full metadata map.

Send to messenger

# config.exs
config :telemetria,
  messenger_channels: %{
    slack: {:slack, url: "https://hooks.slack.com/..."}
  }

# module
@telemetria messenger: :slack, level: :error
def critical_operation(input) do
  risky_work(input)
end

Routes the event to the configured messenger channel (e.g. Slack) in addition to the telemetry backend.

All parameters at once

@telemetria level: :info,
            if: &match?({:ok, _}, &1),
            group: :reports,
            locals: [:duration],
            transform: [result: &elem(&1, 1)],
            reshape: &Map.drop(&1, [:context]),
            messenger: :slack
def generate_report(params) do
  duration = measure(fn -> build(params) end)
  {:ok, format(duration)}
end

Macros

deft/2 -- public function

import Telemetria

deft add(a, b) do
  a + b
end

Equivalent to def but with telemetry attached.

defpt/2 -- private function

import Telemetria

defpt multiply(a, b) do
  a * b
end

Equivalent to defp but with telemetry attached.

t/1 -- wrap expression

import Telemetria

def work do
  result = t(expensive_computation())
  result
end

Wraps a single expression with telemetry.

t/1 -- wrap block

import Telemetria

def work do
  t do
    step_1()
    step_2()
  end
end

Wraps a do-block with telemetry.

t/2 -- with suffix

t(&(&1 * 2), suffix: :doubler)

Appends :doubler to the event name for disambiguation.

t/1 -- anonymous function clauses

divider =
  t(fn
    x when is_integer(x) -> x / 2
    _ -> nil
  end)

divider.(42) #=> 21.0

Each clause of the anonymous function gets its own telemetry event.

Application Config

Backends

ValueDescription
:telemetryStandard :telemetry (requires {:telemetry, "~> 1.0"})
:open_telemetryOpenTelemetry spans (requires {:opentelemetry_api, "~> 1.4"})
:loggerFallback: logs events via Logger

Throttle modes

ModeDescription
:noneNo throttling, events fire immediately
{ms, :last}Keep only the last event per ms window
{ms, :all}Collect all events, flush every ms

Log levels

  • :emergency
  • :alert
  • :critical
  • :error
  • :warning
  • :notice
  • :info
  • :debug

Key config options

OptionDefaultDescription
:backendTelemetria.Backend.TelemetryTelemetry backend module
:level:debugMin level to emit events
:purge_level:debugEvents below this are removed at compile time
:throttle:noneThrottling strategy
:enabledtrueMaster switch
:strictfalseRequire explicit if: on every @telemetria
:process_infofalseInclude process info in events