Telemetry

repository·main·Indexed 21 days ago

https://github.com/beam-telemetry/telemetry

A lightweight, agnostic library for Erlang and Elixir designed for the dynamic dispatching of events. It provides a unified interface for libraries to emit metrics and instrumentation via execute/3 and span/3, allowing applications to hook into these events using custom handlers attached via attach/4.

Tokens
1.4K
Snippets
6
Records
6
Agent score
25%

What's inside Telemetry

  1. Use spans to capture discrete events

    main

    To capture the start and end of a discrete operation, use telemetry:span/3. This function automatically manages the lifecycle of an event by emitting three distinct events:

    1. EventPrefix ++ [:start]: Emitted when the function begins. Measurements include system_time (from erlang:system_time/0).
    2. EventPrefix ++ [:stop]: Emitted if the function completes successfully. Measurements include duration (monotonic time difference) and monotonic_time.
    3. EventPrefix ++ [:exception]: Emitted if the function raises an error. The error is re-raised after the event is emitted. Measurements include duration and monotonic_time.

    To listen to all lifecycle events of a span, use attach_many/4 with the start, stop, and exception suffixes.

    # Elixir: Creating a span
    :telemetry.span(
      [:worker, :processing],
      %{message: message},
      fn -> 
        # ... logic ...
        {result, %{metadata: "info"}}
      end
    )
    
    # Elixir: Attaching to all span events
    :telemetry.attach_many(
      "handler-id",
      [[:worker, :processing, :start], [:worker, :processing, :stop], [:worker, :processing, :exception]],
      &LogResponseHandler.handle_event/4,
      nil
    )
  2. How Telemetry events and handlers work together

    main

    Telemetry uses a dynamic dispatching model where libraries emit events and consumers attach handlers to those events.

    1. Emit an event: Use :telemetry.execute/3 (Elixir) or telemetry:execute/3 (Erlang) to broadcast an event. An event consists of a name (a list of atoms), measurements (numeric values), and metadata (arbitrary data).
    2. Define a handler: Create a module with a handle_event/4 function. This function is called synchronously whenever the event is executed.
    3. Attach the handler: Use :telemetry.attach/4 (Elixir) or telemetry:attach/4 (Erlang) to link your handler to specific event names.

    Critical Performance Note: The handle_event/4 callback is executed synchronously on the caller's process. To avoid blocking the application, avoid heavy or blocking operations inside the handler; instead, offload work to a separate process (e.g., by sending a message).

    # 1. Execute
    :telemetry.execute([:my, :event], %{val: 1}, %{info: "meta"})
    
    # 2. Handler
    defmodule MyHandler do
      def handle_event([:my, :event], measurements, metadata, _config) do
        # Do something
      end
    end
    
    # 3. Attach
    :telemetry.attach("my-handler", [:my, :event], &MyHandler.handle_event/4, nil)
  3. Install Telemetry

    main

    Telemetry can be installed via Hex for both Elixir and Erlang projects.

    For Elixir, add it to your mix.exs dependencies:

    For Erlang, add it to your rebar.config:

    defp deps() do
      [
        {:telemetry, "~> 1.0"}
      ]
    end
    {deps, [{telemetry, "~> 1.0"}]}.
  4. Execute a telemetry event

    main

    Use execute/3 to emit an event with measurements and metadata.

    In Elixir, the event name is a list of atoms. Measurements are a map of numeric values. Metadata is a map of arbitrary data.

    In Erlang, the event name is a list of atoms. Measurements are a map (using => syntax). Metadata is a map.

    # Elixir
    :telemetry.execute(
      [:web, :request, :done],
      %{latency: latency},
      %{request_path: path, status_code: status}
    )
    % Erlang
    telemetry:execute(
      [web, request, done],
      #{latency => Latency},
      #{request_path => Path, status_code => Status}
    ).
  5. Attach a handler to an event

    main

    Use attach/4 to register a module and function to be invoked for specific events.

    Parameters:

    • Handler ID: A unique identifier (String in Elixir, Binary in Erlang).
    • Events: A list of event names (lists of atoms) to listen to.
    • Handler: A function with arity 4 (e.g., &MyHandler.handle_event/4).
    • Config: An optional configuration value passed to the handler.
    # Elixir
    :ok = :telemetry.attach(
      "log-response-handler",
      [:web, :request, :done],
      &LogResponseHandler.handle_event/4,
      nil
    )
    % Erlang
    ok = telemetry:attach(
      <<"log-response-handler">>,
      [web, request, done],
      fun log_response_handler:handle_event/4,
      []
    ).
  6. Convert span duration to milliseconds

    main

    The duration measurement provided by telemetry:span/3 is in native units (monotonic time). To convert this to milliseconds in Elixir, use System.convert_time_unit/3.

    milliseconds = System.convert_time_unit(duration, :native, :millisecond)