Gleam BEAM OTP Hex Prometheus Tests Version License


“Logs whisper. Metrics count. Benchmarks judge. The BEAM observes.”


viva_telemetry IS NOT A FRAMEWORK. It is three independent observability surfaces — structured logging, Prometheus-shaped metrics, and statistical benchmarks — that you can pull into any Gleam/BEAM app without dragging in a runtime.

Plays well with Erlang :logger, Prometheus scrapers, and Grafana.


🎯 Overview

Observability for Gleam applications on the BEAM. Structured logging, in-memory metrics, Prometheus export, BEAM memory visibility, and small statistical benchmarks — without forcing a large framework into your application.

The three surfaces (log, metrics, bench) are deliberately independent: import only what you need, configure each per process.

PropertyValue
LanguagePure Gleam (type-safe functional)
RuntimeBEAM / OTP 27+
Logging backendErlang :logger, console, JSON file
Metrics backendETS-backed counters, gauges, histograms
Export formatPrometheus text (# HELP / # TYPE)
Tests41 passing
Public APIviva_telemetry/{log,metrics,bench}

⚡ Quick Start

gleam add viva_telemetry@1

Log, count, time — in one file

import viva_telemetry/bench
import viva_telemetry/log
import viva_telemetry/metrics

pub fn main() {
  log.configure_erlang(log.info_level)
  log.info("Server started", [#("port", "8080")])

  let requests = metrics.counter("http_requests_total")
  metrics.inc(requests)

  bench.run("my_function", fn() { heavy_work() })
  |> bench.print()
}
📋 Prerequisites
ToolVersionRequired for
Gleam>= 1.11Build / runtime
Erlang/OTP>= 27BEAM runtime

Zero NIFs. Zero C dependencies. Pure BEAM.


🏗️ Architecture

   ┌──────────────────────────────────────────────────────────┐
   │                Gleam application code                    │
   │      viva_telemetry/log · /metrics · /bench              │
   └────────┬─────────────────┬──────────────────┬────────────┘
            │                 │                  │
   ┌────────▼─────┐  ┌────────▼────────┐  ┌──────▼─────────┐
   │   Logging    │  │     Metrics     │  │   Benchmarks   │
   │              │  │                 │  │                │
   │ levels       │  │ counter         │  │ warmup         │
   │ handlers     │  │ gauge           │  │ samples        │
   │ context      │  │ histogram       │  │ percentiles    │
   │ sampling     │  │ Prometheus      │  │ ips / speedup  │
   │ named logger │  │ BEAM memory     │  │ JSON / MD      │
   └──────┬───────┘  └────────┬────────┘  └────────────────┘
          │                   │
   ┌──────▼─────┐     ┌───────▼──────┐
   │ Erlang     │     │ ETS-backed   │
   │ :logger    │     │ atomic ops   │
   └────────────┘     └──────────────┘
📋 Core Modules
ModuleDescription
viva_telemetry/logStructured logs, named loggers, context, sampling, handlers
viva_telemetry/log/levelRFC 5424 levels (trace → emergency)
viva_telemetry/log/entryInternal log record type
viva_telemetry/log/handlerConsole / JSON file / custom handler dispatch
viva_telemetry/metricsCounter, gauge, histogram, BEAM memory, Prometheus export
viva_telemetry/benchWarmup, samples, percentiles, IPS, JSON/Markdown export
viva_telemetry_ffi.erlErlang FFI: process dict, time, JSON glue
viva_telemetry_metrics_ffi.erlETS metric storage + atomic counters

🧬 Design Principles

PrincipleDescription
Three independent surfaceslog, metrics, bench are import-only-what-you-need
BEAM-nativeETS for metrics, :logger for logs, no external runtime needed
Process-local configHandler config & with_context data live per process
Prometheus-shaped metricsOutput drops straight into any scraper, no adapters
No surprisesNegative counter increments ignored; gauge updates serialized

📊 Modules Walkthrough

Logging

import viva_telemetry/log

// Recommended on the BEAM
log.configure_erlang(log.info_level)

log.info("User logged in", [
  #("user_id", "42"),
  #("ip", "192.168.1.1"),
])
Handler options
log.configure_erlang(log.info_level)
log.configure_erlang_with_name(log.info_level, "my_app")
log.configure_console(log.debug_level)
log.configure_json("app.jsonl", log.info_level)
log.configure_full(log.debug_level, "app.jsonl", log.info_level)
Named loggers, context, sampling, lazy logs
import gleam/int
import gleam/option.{Some}

let logger =
  log.logger("app.http")
  |> log.with_field("request_id", "abc123")
  |> log.with_int("attempt", 1)
  |> log.with_option("user_id", Some(42), int.to_string)

logger
|> log.logger_info_with("Request completed", [#("status", "200")])

log.with_context([#("request_id", "abc123")], fn() {
  log.debug("Processing request", [])
})

log.debug_lazy(fn() { "expensive: " <> expensive_to_string(data) }, [])

log.sampled(log.trace_level, 0.01, "Hot path", [])

Metrics

import viva_telemetry/metrics

let requests = metrics.counter("http_requests_total")
metrics.inc(requests)
metrics.inc_by(requests, 5)

let connections = metrics.gauge("active_connections")
metrics.set(connections, 42.0)

let latency =
  metrics.histogram_with_labels_and_description(
    "request_duration_seconds",
    [0.1, 0.5, 1.0],
    [#("route", "/users")],
    "Request duration in seconds.",
  )

let result = metrics.time_ms(latency, fn() { do_work() })
io.println(metrics.to_prometheus())
Prometheus output
# HELP request_duration_seconds Request duration in seconds.
# TYPE request_duration_seconds histogram
request_duration_seconds_bucket{le="0.5",route="/users"} 1
request_duration_seconds_bucket{le="+Inf",route="/users"} 1
request_duration_seconds_sum{route="/users"} 0.25
request_duration_seconds_count{route="/users"} 1
# TYPE beam_memory_total_bytes gauge
beam_memory_total_bytes 12345678

Benchmarks

import viva_telemetry/bench

bench.run("fib_recursive", fn() { fib(30) })
|> bench.print()

let slow = bench.run("v1", fn() { algo_v1() })
let fast = bench.run("v2", fn() { algo_v2() })

bench.compare(slow, fast)
|> bench.print_comparison()

bench.to_json(result)
bench.to_markdown_table([slow, fast])

Warmup, percentiles (p50/p95/p99), 95% confidence intervals, IPS, and optional JSON/Markdown export — all in memory, no external profiler.


🗺️ Roadmap

PhaseStatus
Structured logging (console / JSON / Erlang)
Named loggers + context propagation
Lazy logs + sampling
ETS-backed counters / gauges / histograms
Prometheus text export with HELP / TYPE
BEAM memory metrics
Statistical benchmarks (warmup + percentiles)
Bench comparison + JSON / Markdown export
OpenTelemetry OTLP exporter
Distributed tracing primitives
Exemplars on histograms
Built-in /metrics HTTP handler

🤝 Contributing

git checkout -b feature/your-feature
gleam test                  # 41 tests
gleam format --check src test
gleam docs build

See CONTRIBUTING.md for guidelines.


📚 Documentation

Local development

make test      # run tests
make bench     # run benchmark example
make log       # run logging example
make metrics   # run metrics example
make docs      # generate HexDocs locally

🌌 VIVA Ecosystem

PackagePurpose
viva_mathMathematical foundations
viva_emotionPAD emotional dynamics
viva_tensorFP8 LLM inference on the BEAM
viva_aionTime perception
viva_glyphSymbolic language
viva_telemetryObservability (this package)

💡 Inspiration


Star if BEAM observability deserves a Gleam-native voice ⭐

GitHub stars

Created by Gabriel Maia · Apache-2.0 License

Search Document