Skip to main content

README.md

<div align="center">

<img src="https://capsule-render.vercel.app/api?type=waving&color=0:E6522C,100:1A1A1A&height=200&section=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%"/>

[![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."*

</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 ⭐**

[![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*

<img src="https://capsule-render.vercel.app/api?type=waving&color=0:1A1A1A,100:E6522C&height=100&section=footer" width="100%"/>

</div>