[![Gleam](https://img.shields.io/badge/Gleam-FFAFF3?style=for-the-badge&logo=gleam&logoColor=black)](https://gleam.run/) [![BEAM](https://img.shields.io/badge/BEAM-A90533?style=for-the-badge&logo=erlang&logoColor=white)](https://www.erlang.org/) [![OTP](https://img.shields.io/badge/OTP_27+-4B275F?style=for-the-badge)](https://www.erlang.org/doc/design_principles/des_princ) [![Hex](https://img.shields.io/badge/hex.pm-viva__telemetry-A678DD?style=for-the-badge&logo=hex&logoColor=white)](https://hex.pm/packages/viva_telemetry) [![Prometheus](https://img.shields.io/badge/Prometheus-export-E6522C?style=for-the-badge&logo=prometheus&logoColor=white)](https://prometheus.io/) [![Tests](https://img.shields.io/badge/tests-41_passing-00875A?style=for-the-badge)](./test) [![Version](https://img.shields.io/badge/version-1.0.101-CD5C5C?style=for-the-badge)](./CHANGELOG.md) [![License](https://img.shields.io/badge/license-Apache_2.0-228B22?style=for-the-badge)](./LICENSE) --- *"Logs whisper. Metrics count. Benchmarks judge. The BEAM observes."*

[!IMPORTANT] 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.

Property Value
Language Pure Gleam (type-safe functional)
Runtime BEAM / OTP 27+
Logging backend Erlang :logger, console, JSON file
Metrics backend ETS-backed counters, gauges, histograms
Export format Prometheus text (# HELP / # TYPE)
Tests 41 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 | Tool | Version | Required for | | :--------- | :--------- | :--------------- | | Gleam | `>= 1.11` | Build / runtime | | Erlang/OTP | `>= 27` | BEAM 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 | Module | Description | | :-------------------------------- | :--------------------------------------------------------- | | `viva_telemetry/log` | Structured logs, named loggers, context, sampling, handlers | | `viva_telemetry/log/level` | RFC 5424 levels (trace β†’ emergency) | | `viva_telemetry/log/entry` | Internal log record type | | `viva_telemetry/log/handler` | Console / JSON file / custom handler dispatch | | `viva_telemetry/metrics` | Counter, gauge, histogram, BEAM memory, Prometheus export | | `viva_telemetry/bench` | Warmup, samples, percentiles, IPS, JSON/Markdown export | | `viva_telemetry_ffi.erl` | Erlang FFI: process dict, time, JSON glue | | `viva_telemetry_metrics_ffi.erl` | ETS metric storage + atomic counters |

🧬 Design Principles

Principle Description
Three independent surfaceslog, metrics, bench are import-only-what-you-need
BEAM-native ETS for metrics, :logger for logs, no external runtime needed
Process-local config Handler config & with_context data live per process
Prometheus-shaped metrics Output drops straight into any scraper, no adapters
No surprises Negative 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 ```gleam 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 ```gleam 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 ```text # 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

Phase Status
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

Package Purpose
viva_math Mathematical foundations
viva_emotion PAD emotional dynamics
viva_tensor FP8 LLM inference on the BEAM
viva_aion Time perception
viva_glyph Symbolic language
viva_telemetryObservability (this package)

πŸ’‘ Inspiration


**Star if BEAM observability deserves a Gleam-native voice ⭐** [![GitHub stars](https://img.shields.io/github/stars/gabrielmaialva33/viva_telemetry?style=social)](https://github.com/gabrielmaialva33/viva_telemetry) *Created by Gabriel Maia · Apache-2.0 License*