<div align="center">
<img src="https://capsule-render.vercel.app/api?type=waving&color=0:E6522C,100:1A1A1A&height=200§ion=header&text=viva_telemetry&fontSize=64&fontColor=fff&animation=twinkling&fontAlignY=35&desc=Observability%20suite%20for%20Gleam%20on%20the%20BEAM&descSize=18&descAlignY=55" width="100%"/>
[](https://gleam.run/)
[](https://www.erlang.org/)
[](https://www.erlang.org/doc/design_principles/des_princ)
[](https://hex.pm/packages/viva_telemetry)
[](https://prometheus.io/)
[](./test)
[](./CHANGELOG.md)
[](./LICENSE)
---
*"Logs whisper. Metrics count. Benchmarks judge. The BEAM observes."*
</div>
---
> [!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 API** | `viva_telemetry/{log,metrics,bench}` |
---
## β‘ Quick Start
```bash
gleam add viva_telemetry@1
```
### Log, count, time β in one file
```gleam
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()
}
```
<details>
<summary><strong>π Prerequisites</strong></summary>
| Tool | Version | Required for |
| :--------- | :--------- | :--------------- |
| Gleam | `>= 1.11` | Build / runtime |
| Erlang/OTP | `>= 27` | BEAM runtime |
Zero NIFs. Zero C dependencies. Pure BEAM.
</details>
---
## ποΈ 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 β
ββββββββββββββ ββββββββββββββββ
```
<details>
<summary><strong>π Core Modules</strong></summary>
| 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 |
</details>
---
## 𧬠Design Principles
| Principle | Description |
| :------------------------------ | :---------------------------------------------------------------- |
| **Three independent surfaces** | `log`, `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
```gleam
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"),
])
```
<details>
<summary><strong>Handler options</strong></summary>
```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)
```
</details>
<details>
<summary><strong>Named loggers, context, sampling, lazy logs</strong></summary>
```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", [])
```
</details>
### Metrics
```gleam
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())
```
<details>
<summary><strong>Prometheus output</strong></summary>
```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
```
</details>
### Benchmarks
```gleam
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
```bash
git checkout -b feature/your-feature
gleam test # 41 tests
gleam format --check src test
gleam docs build
```
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
---
## π Documentation
- **[Hex.pm package](https://hex.pm/packages/viva_telemetry)**
- **[HexDocs reference](https://hexdocs.pm/viva_telemetry/)**
- **[CHANGELOG](./CHANGELOG.md)** β release history.
- **[CONTRIBUTING](./CONTRIBUTING.md)** β guidelines.
- **[SECURITY](./SECURITY.md)** β vulnerability reporting.
### Local development
```bash
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_telemetry`** | **Observability (this package)** |
---
## π‘ Inspiration
- **Logging**: Erlang `:logger`, glimt, glog, structlog, zap, tracing
- **Metrics**: Prometheus + BEAM telemetry conventions
- **Benchmarking**: criterion, benchee, hyperfine
---
<div align="center">
**Star if BEAM observability deserves a Gleam-native voice β**
[](https://github.com/gabrielmaialva33/viva_telemetry)
*Created by Gabriel Maia Β· Apache-2.0 License*
<img src="https://capsule-render.vercel.app/api?type=waving&color=0:1A1A1A,100:E6522C&height=100§ion=footer" width="100%"/>
</div>