---
title: Observability
description: Metric instruments for monitoring Vault operations.
---

The `metrics` package provides a set of metric interfaces and a pre-built `Collector` with 13 instruments covering all Vault operations. The interfaces are designed to be compatible with popular metric libraries (Prometheus, OpenTelemetry, StatsD) and can be bridged to any backend via the `MetricFactory`.

## Architecture

```
metrics.MetricFactory (creates named Counter/Histogram instances)
  └── metrics.Collector (holds all 13 Vault instruments)

metrics.NoopFactory (no-op implementation for testing/disabled metrics)
  └── metrics.NewNoopCollector()
```

The `Collector` is a flat struct with one field per instrument. You pass a `MetricFactory` to `NewCollector` and it creates all instruments with standardized names. Code throughout Vault increments or observes these instruments at the appropriate points.

## Installation

```go
import "github.com/xraph/vault/metrics"
```

## Interfaces

### Counter

A monotonically increasing counter for tracking occurrences.

```go
type Counter interface {
    Inc()
    Add(float64)
}
```

| Method | Description |
|--------|-------------|
| `Inc()` | Increments by 1 |
| `Add(float64)` | Adds an arbitrary non-negative value |

### Histogram

Observes a distribution of values (typically latencies or sizes).

```go
type Histogram interface {
    Observe(float64)
}
```

| Method | Description |
|--------|-------------|
| `Observe(float64)` | Records a single observation |

### MetricFactory

Creates named metric instances. Implement this interface to bridge Vault metrics to your preferred monitoring backend.

```go
type MetricFactory interface {
    Counter(name string) Counter
    Histogram(name string) Histogram
}
```

## Collector

The `Collector` holds all 13 Vault metric instruments. Each field is initialized by the factory when `NewCollector` is called.

```go
type Collector struct {
    SecretAccessed Counter
    SecretSet      Counter
    SecretDeleted  Counter
    SecretRotated  Counter
    FlagEvaluated  Counter
    FlagEvalTime   Histogram
    ConfigRead     Counter
    ConfigWritten  Counter
    OverrideRead   Counter
    AuditRecorded  Counter
    Encrypted      Counter
    Decrypted      Counter
    SourceLatency  Histogram
}
```

### Creating a Collector

```go
func NewCollector(f MetricFactory) *Collector
```

```go
collector := metrics.NewCollector(myPrometheusFactory)
```

### Metric naming table

| Field | Metric name | Type | Description |
|-------|-------------|------|-------------|
| `SecretAccessed` | `vault_secret_accessed_total` | Counter | Secret read operations |
| `SecretSet` | `vault_secret_set_total` | Counter | Secret write operations |
| `SecretDeleted` | `vault_secret_deleted_total` | Counter | Secret delete operations |
| `SecretRotated` | `vault_secret_rotated_total` | Counter | Secret rotation completions |
| `FlagEvaluated` | `vault_flag_evaluated_total` | Counter | Flag evaluation calls |
| `FlagEvalTime` | `vault_flag_eval_duration_seconds` | Histogram | Flag evaluation latency |
| `ConfigRead` | `vault_config_read_total` | Counter | Config read operations |
| `ConfigWritten` | `vault_config_written_total` | Counter | Config write operations |
| `OverrideRead` | `vault_override_read_total` | Counter | Override resolution reads |
| `AuditRecorded` | `vault_audit_recorded_total` | Counter | Audit entries recorded |
| `Encrypted` | `vault_encrypted_total` | Counter | Encryption operations |
| `Decrypted` | `vault_decrypted_total` | Counter | Decryption operations |
| `SourceLatency` | `vault_source_latency_seconds` | Histogram | Configuration source read latency |

### Using the Collector

```go
// After a secret is read.
collector.SecretAccessed.Inc()

// After a secret is written.
collector.SecretSet.Inc()

// After a flag evaluation.
start := time.Now()
value := flagService.Bool(ctx, "my-flag", false)
collector.FlagEvaluated.Inc()
collector.FlagEvalTime.Observe(time.Since(start).Seconds())

// After an encryption operation.
collector.Encrypted.Inc()

// Observe source read latency.
start = time.Now()
val, err := source.Get(ctx, "key")
collector.SourceLatency.Observe(time.Since(start).Seconds())
```

## NoopFactory

The `NoopFactory` creates no-op metric instances that silently discard all observations. Use it when metrics collection is disabled or in tests.

```go
type NoopFactory struct{}

func (NoopFactory) Counter(string) Counter   { return noopCounter{} }
func (NoopFactory) Histogram(string) Histogram { return noopHistogram{} }
```

### NewNoopCollector

Convenience function that creates a `Collector` with all no-op instruments.

```go
func NewNoopCollector() *Collector
```

```go
collector := metrics.NewNoopCollector()
// All operations are silently discarded.
collector.SecretAccessed.Inc() // no-op
```

## Prometheus bridge example

Bridge Vault metrics to Prometheus by implementing `MetricFactory`:

```go
package prommetrics

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/xraph/vault/metrics"
)

// PrometheusFactory creates Prometheus-backed metric instances.
type PrometheusFactory struct {
    registry *prometheus.Registry
}

// NewPrometheusFactory creates a factory with the given registry.
func NewPrometheusFactory(reg *prometheus.Registry) *PrometheusFactory {
    return &PrometheusFactory{registry: reg}
}

// Counter creates a Prometheus counter.
func (f *PrometheusFactory) Counter(name string) metrics.Counter {
    c := prometheus.NewCounter(prometheus.CounterOpts{
        Name: name,
        Help: name,
    })
    f.registry.MustRegister(c)
    return &promCounter{counter: c}
}

// Histogram creates a Prometheus histogram.
func (f *PrometheusFactory) Histogram(name string) metrics.Histogram {
    h := prometheus.NewHistogram(prometheus.HistogramOpts{
        Name:    name,
        Help:    name,
        Buckets: prometheus.DefBuckets,
    })
    f.registry.MustRegister(h)
    return &promHistogram{histogram: h}
}

// promCounter wraps prometheus.Counter to implement metrics.Counter.
type promCounter struct {
    counter prometheus.Counter
}

func (c *promCounter) Inc()            { c.counter.Inc() }
func (c *promCounter) Add(v float64)   { c.counter.Add(v) }

// promHistogram wraps prometheus.Histogram to implement metrics.Histogram.
type promHistogram struct {
    histogram prometheus.Histogram
}

func (h *promHistogram) Observe(v float64) { h.histogram.Observe(v) }
```

Using the Prometheus bridge:

```go
package main

import (
    "net/http"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "github.com/xraph/vault/metrics"
)

func main() {
    reg := prometheus.NewRegistry()
    factory := prommetrics.NewPrometheusFactory(reg)

    // Create the Vault collector with Prometheus backend.
    collector := metrics.NewCollector(factory)

    // Use the collector throughout your application.
    collector.SecretAccessed.Inc()
    collector.FlagEvalTime.Observe(0.003)

    // Expose metrics via HTTP.
    http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
    http.ListenAndServe(":9090", nil)
}
```

## Custom backend example

Implement `MetricFactory` for any backend (StatsD, Datadog, OpenTelemetry, etc.):

```go
type StatsDFactory struct {
    client *statsd.Client
}

func (f *StatsDFactory) Counter(name string) metrics.Counter {
    return &statsdCounter{client: f.client, name: name}
}

func (f *StatsDFactory) Histogram(name string) metrics.Histogram {
    return &statsdHistogram{client: f.client, name: name}
}

type statsdCounter struct {
    client *statsd.Client
    name   string
}

func (c *statsdCounter) Inc()          { c.client.Incr(c.name, nil, 1) }
func (c *statsdCounter) Add(v float64) { c.client.Count(c.name, int64(v), nil, 1) }

type statsdHistogram struct {
    client *statsd.Client
    name   string
}

func (h *statsdHistogram) Observe(v float64) {
    h.client.Histogram(h.name, v, nil, 1)
}
```

## Full example

```go
package main

import (
    "fmt"
    "time"

    "github.com/xraph/vault/metrics"
)

func main() {
    // For production: use a real factory (Prometheus, StatsD, etc.).
    // For development/testing: use the no-op collector.
    collector := metrics.NewNoopCollector()

    // Simulate operations.
    collector.SecretAccessed.Inc()
    collector.SecretSet.Inc()
    collector.SecretSet.Inc()
    collector.FlagEvaluated.Add(100)

    // Track latency.
    start := time.Now()
    time.Sleep(5 * time.Millisecond) // simulate work
    collector.FlagEvalTime.Observe(time.Since(start).Seconds())

    fmt.Println("Metrics recorded (no-op in this example)")
}
```
