Timeless

LiveDashboard Plugin for Timeless Traces

Hex.pmDocsLicense


"I found it ironic that the first thing you do to time series data is squash the timestamp. That's how the name Timeless was born." --Mark Cotner

Phoenix LiveDashboard page for browsing OpenTelemetry spans stored by TimelessTraces.

Provides four tabs:

Installation

Quick Start (Igniter)

mix igniter.install timeless_traces_dashboard

This automatically:

  1. Adds config :timeless_traces, data_dir: "priv/timeless_traces" to your config
  2. Adds config :opentelemetry, traces_exporter: {TimelessTraces.Exporter, []} to your config
  3. Adds import TimelessTracesDashboard.Router to your router
  4. Adds timeless_traces_dashboard "/dashboard" to your browser scope
  5. Updates your .formatter.exs

For in-memory storage (traces lost on restart):

mix igniter.install timeless_traces_dashboard --storage memory

Manual Setup

Add timeless_traces_dashboard to your dependencies:

def deps do
  [
    {:timeless_traces_dashboard, "~> 0.3"}
  ]
end

Configure TimelessTraces and OpenTelemetry in config/config.exs:

config :timeless_traces, data_dir: "priv/timeless_traces"
config :opentelemetry, traces_exporter: {TimelessTraces.Exporter, []}

Add the router macro:

# lib/my_app_web/router.ex
import TimelessTracesDashboard.Router

scope "/" do
  pipe_through :browser
  timeless_traces_dashboard "/dashboard"
end

Or add the page directly to an existing LiveDashboard:

live_dashboard "/dashboard",
  additional_pages: [
    traces: TimelessTracesDashboard.Page
  ]

Navigate to /dashboard/traces in your browser.

Existing OpenTelemetry Exporter

OpenTelemetry's :traces_exporter config only supports a single exporter. If you already export traces to an external system (Jaeger, Tempo, Datadog, etc.), setting traces_exporter: {TimelessTraces.Exporter, []} will replace your existing exporter and traces will stop flowing to that system.

If you need traces sent to both TimelessTraces and an external collector, you'll need a thin fan-out exporter that calls both. For example:

defmodule MyApp.CompositeExporter do
  @behaviour :otel_exporter_traces

  @impl true
  def init(_config) do
    {:ok, otel_state} = :otel_exporter_otlp.init(%{})
    {:ok, timeless_state} = TimelessTraces.Exporter.init(%{})
    {:ok, %{otlp: otel_state, timeless: timeless_state}}
  end

  @impl true
  def export(tab, resource, state) do
    :otel_exporter_otlp.export(tab, resource, state.otlp)
    TimelessTraces.Exporter.export(tab, resource, state.timeless)
    {:ok, state}
  end

  @impl true
  def shutdown(state) do
    :otel_exporter_otlp.shutdown(state.otlp)
    TimelessTraces.Exporter.shutdown(state.timeless)
    :ok
  end
end

Then configure: config :opentelemetry, traces_exporter: {MyApp.CompositeExporter, []}

This does not apply to metrics or logs -- the metrics Reporter uses :telemetry (which supports multiple handlers) and TimelessLogs registers as a Logger handler (which coexists with other handlers).

Requirements

License

MIT