metrics

package
v1.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 2, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

Metrics Package

A pluggable metrics collection system with Prometheus implementation.

Quick Start

import "github.com/bitechdev/ResolveSpec/pkg/metrics"

// Initialize Prometheus provider with default config
provider := metrics.NewPrometheusProvider(nil)
metrics.SetProvider(provider)

// Apply middleware to your router
router.Use(provider.Middleware)

// Expose metrics endpoint
http.Handle("/metrics", provider.Handler())

Configuration

You can customize the metrics provider using a configuration struct:

import "github.com/bitechdev/ResolveSpec/pkg/metrics"

// Create custom configuration
config := &metrics.Config{
    Enabled:  true,
    Provider: "prometheus",
    Namespace: "myapp", // Prefix all metrics with "myapp_"
    HTTPRequestBuckets: []float64{0.01, 0.05, 0.1, 0.5, 1, 2, 5},
    DBQueryBuckets: []float64{0.001, 0.01, 0.05, 0.1, 0.5, 1},
}

// Initialize with custom config
provider := metrics.NewPrometheusProvider(config)
metrics.SetProvider(provider)
Configuration Options
Field Type Default Description
Enabled bool true Enable/disable metrics collection
Provider string "prometheus" Metrics provider type
Namespace string "" Prefix for all metric names
HTTPRequestBuckets []float64 See below Histogram buckets for HTTP duration (seconds)
DBQueryBuckets []float64 See below Histogram buckets for DB query duration (seconds)

Default HTTP Request Buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]

Default DB Query Buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5]

Pushgateway Configuration (Optional)

For batch jobs, cron tasks, or short-lived processes, you can push metrics to Prometheus Pushgateway:

Field Type Default Description
PushgatewayURL string "" URL of Pushgateway (e.g., "http://pushgateway:9091")
PushgatewayJobName string "resolvespec" Job name for pushed metrics
PushgatewayInterval int 0 Auto-push interval in seconds (0 = disabled)
config := &metrics.Config{
    PushgatewayURL:      "http://pushgateway:9091",
    PushgatewayJobName:  "batch-job",
    PushgatewayInterval: 30, // Push every 30 seconds
}

Provider Interface

The package uses a provider interface, allowing you to plug in different metric systems:

type Provider interface {
    RecordHTTPRequest(method, path, status string, duration time.Duration)
    IncRequestsInFlight()
    DecRequestsInFlight()
    RecordDBQuery(operation, table string, duration time.Duration, err error)
    RecordCacheHit(provider string)
    RecordCacheMiss(provider string)
    UpdateCacheSize(provider string, size int64)
    Handler() http.Handler
}

Recording Metrics

HTTP Metrics (Automatic)

When using the middleware, HTTP metrics are recorded automatically:

router.Use(provider.Middleware)

Collected:

  • Request duration (histogram)
  • Request count by method, path, and status
  • Requests in flight (gauge)
Database Metrics
start := time.Now()
rows, err := db.Query("SELECT * FROM users WHERE id = ?", userID)
duration := time.Since(start)

metrics.GetProvider().RecordDBQuery("SELECT", "users", duration, err)
Cache Metrics
// Record cache hit
metrics.GetProvider().RecordCacheHit("memory")

// Record cache miss
metrics.GetProvider().RecordCacheMiss("memory")

// Update cache size
metrics.GetProvider().UpdateCacheSize("memory", 1024)

Prometheus Metrics

When using PrometheusProvider, the following metrics are available:

Metric Name Type Labels Description
http_request_duration_seconds Histogram method, path, status HTTP request duration
http_requests_total Counter method, path, status Total HTTP requests
http_requests_in_flight Gauge - Current in-flight requests
db_query_duration_seconds Histogram operation, table Database query duration
db_queries_total Counter operation, table, status Total database queries
cache_hits_total Counter provider Total cache hits
cache_misses_total Counter provider Total cache misses
cache_size_items Gauge provider Current cache size
events_published_total Counter source, event_type Total events published
events_processed_total Counter source, event_type, status Total events processed
event_processing_duration_seconds Histogram source, event_type Event processing duration
event_queue_size Gauge - Current event queue size
panics_total Counter method Total panics recovered

Note: If a custom Namespace is configured, all metric names will be prefixed with {namespace}_.

Prometheus Queries

HTTP Request Rate
rate(http_requests_total[5m])
HTTP Request Duration (95th percentile)
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
Database Query Error Rate
rate(db_queries_total{status="error"}[5m])
Cache Hit Rate
rate(cache_hits_total[5m]) / (rate(cache_hits_total[5m]) + rate(cache_misses_total[5m]))

No-Op Provider

If metrics are disabled:

// No provider set - uses no-op provider automatically
metrics.GetProvider().RecordHTTPRequest(...) // Does nothing

Custom Provider

Implement your own metrics provider:

type CustomProvider struct{}

func (c *CustomProvider) RecordHTTPRequest(method, path, status string, duration time.Duration) {
    // Send to your metrics system
}

// Implement other Provider interface methods...

func (c *CustomProvider) Handler() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Return your metrics format
    })
}

// Use it
metrics.SetProvider(&CustomProvider{})

Pushgateway Usage

Automatic Push (Batch Jobs)

For jobs that run periodically, use automatic pushing:

package main

import (
    "time"

    "github.com/bitechdev/ResolveSpec/pkg/metrics"
)

func main() {
    // Configure with automatic pushing every 30 seconds
    config := &metrics.Config{
        Enabled:             true,
        Provider:            "prometheus",
        Namespace:           "batch_job",
        PushgatewayURL:      "http://pushgateway:9091",
        PushgatewayJobName:  "data-processor",
        PushgatewayInterval: 30, // Push every 30 seconds
    }

    provider := metrics.NewPrometheusProvider(config)
    metrics.SetProvider(provider)

    // Ensure cleanup on exit
    defer provider.StopAutoPush()

    // Your batch job logic here
    processBatchData()
}
Manual Push (Short-lived Processes)

For one-time jobs or when you want manual control:

package main

import (
    "log"

    "github.com/bitechdev/ResolveSpec/pkg/metrics"
)

func main() {
    // Configure without automatic pushing
    config := &metrics.Config{
        Enabled:            true,
        Provider:           "prometheus",
        PushgatewayURL:     "http://pushgateway:9091",
        PushgatewayJobName: "migration-job",
        // PushgatewayInterval: 0 (default - no auto-push)
    }

    provider := metrics.NewPrometheusProvider(config)
    metrics.SetProvider(provider)

    // Run your job
    err := runMigration()

    // Push metrics at the end
    if pushErr := provider.Push(); pushErr != nil {
        log.Printf("Failed to push metrics: %v", pushErr)
    }

    if err != nil {
        log.Fatal(err)
    }
}
Docker Compose with Pushgateway
version: '3'
services:
  batch-job:
    build: .
    environment:
      PUSHGATEWAY_URL: "http://pushgateway:9091"

  pushgateway:
    image: prom/pushgateway
    ports:
      - "9091:9091"

  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'

prometheus.yml for Pushgateway:

global:
  scrape_interval: 15s

scrape_configs:
  # Scrape the pushgateway
  - job_name: 'pushgateway'
    honor_labels: true  # Important: preserve job labels from pushed metrics
    static_configs:
      - targets: ['pushgateway:9091']

Complete Example

Basic Usage
package main

import (
    "database/sql"
    "log"
    "net/http"
    "time"

    "github.com/bitechdev/ResolveSpec/pkg/metrics"
    "github.com/gorilla/mux"
)

func main() {
    // Initialize metrics with default config
    provider := metrics.NewPrometheusProvider(nil)
    metrics.SetProvider(provider)

    // Create router
    router := mux.NewRouter()

    // Apply metrics middleware
    router.Use(provider.Middleware)

    // Expose metrics endpoint
    router.Handle("/metrics", provider.Handler())

    // Your API routes
    router.HandleFunc("/api/users", getUsersHandler)

    log.Fatal(http.ListenAndServe(":8080", router))
}

func getUsersHandler(w http.ResponseWriter, r *http.Request) {
    // Record database query
    start := time.Now()
    users, err := fetchUsers()
    duration := time.Since(start)

    metrics.GetProvider().RecordDBQuery("SELECT", "users", duration, err)

    if err != nil {
        http.Error(w, "Internal Server Error", 500)
        return
    }

    // Return users...
}
With Custom Configuration
package main

import (
    "log"
    "net/http"

    "github.com/bitechdev/ResolveSpec/pkg/metrics"
    "github.com/gorilla/mux"
)

func main() {
    // Custom metrics configuration
    metricsConfig := &metrics.Config{
        Enabled:  true,
        Provider: "prometheus",
        Namespace: "myapp",
        // Custom buckets optimized for your application
        HTTPRequestBuckets: []float64{0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10},
        DBQueryBuckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1},
    }

    // Initialize with custom config
    provider := metrics.NewPrometheusProvider(metricsConfig)
    metrics.SetProvider(provider)

    router := mux.NewRouter()
    router.Use(provider.Middleware)
    router.Handle("/metrics", provider.Handler())

    log.Fatal(http.ListenAndServe(":8080", router))
}

Docker Compose Example

version: '3'
services:
  app:
    build: .
    ports:
      - "8080:8080"

  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    depends_on:
      - prometheus

prometheus.yml:

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'resolvespec'
    static_configs:
      - targets: ['app:8080']

Best Practices

  1. Label Cardinality: Keep labels low-cardinality

    • ✅ Good: method, status_code
    • ❌ Bad: user_id, timestamp
  2. Path Normalization: Normalize dynamic paths

    // Instead of /api/users/123
    // Use /api/users/:id
    
  3. Metric Naming: Follow Prometheus conventions

    • Use _total suffix for counters
    • Use _seconds suffix for durations
    • Use base units (seconds, not milliseconds)
  4. Performance: Metrics collection is lock-free and highly performant

    • Safe for high-throughput applications
    • Minimal overhead (<1% in most cases)
  5. Pull vs Push:

    • Use Pull (default): Long-running services, web servers, microservices
    • Use Push (Pushgateway): Batch jobs, cron tasks, short-lived processes, serverless functions
    • Pull is preferred for most applications as it allows Prometheus to detect if your service is down

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func SetProvider

func SetProvider(p Provider)

SetProvider sets the global metrics provider

Types

type Config added in v1.0.5

type Config struct {
	// Enabled determines whether metrics collection is enabled
	Enabled bool `mapstructure:"enabled"`

	// Provider specifies which metrics provider to use (prometheus, noop)
	Provider string `mapstructure:"provider"`

	// Namespace is an optional prefix for all metric names
	Namespace string `mapstructure:"namespace"`

	// HTTPRequestBuckets defines histogram buckets for HTTP request duration (in seconds)
	// Default: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
	HTTPRequestBuckets []float64 `mapstructure:"http_request_buckets"`

	// DBQueryBuckets defines histogram buckets for database query duration (in seconds)
	// Default: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5]
	DBQueryBuckets []float64 `mapstructure:"db_query_buckets"`

	// PushgatewayURL is the URL of the Prometheus Pushgateway (optional)
	// If set, metrics will be pushed to this gateway instead of only being scraped
	// Example: "http://pushgateway:9091"
	PushgatewayURL string `mapstructure:"pushgateway_url"`

	// PushgatewayJobName is the job name to use when pushing metrics to Pushgateway
	// Default: "resolvespec"
	PushgatewayJobName string `mapstructure:"pushgateway_job_name"`

	// PushgatewayInterval is the interval at which to push metrics to Pushgateway
	// Only used if PushgatewayURL is set. If 0, automatic pushing is disabled.
	// Default: 0 (no automatic pushing)
	PushgatewayInterval int `mapstructure:"pushgateway_interval"`
}

Config holds configuration for the metrics provider

func DefaultConfig added in v1.0.5

func DefaultConfig() *Config

DefaultConfig returns a Config with sensible defaults

Example

ExampleDefaultConfig demonstrates getting default configuration

package main

import (
	"fmt"

	"github.com/bitechdev/ResolveSpec/pkg/metrics"
)

func main() {
	config := metrics.DefaultConfig()
	fmt.Printf("Default provider: %s\n", config.Provider)
	fmt.Printf("Default enabled: %v\n", config.Enabled)
}
Output:

Default provider: prometheus
Default enabled: true

func (*Config) ApplyDefaults added in v1.0.5

func (c *Config) ApplyDefaults()

ApplyDefaults fills in any missing values with defaults

Example

ExampleConfig_ApplyDefaults demonstrates applying defaults to partial config

package main

import (
	"fmt"

	"github.com/bitechdev/ResolveSpec/pkg/metrics"
)

func main() {
	// Create partial configuration
	config := &metrics.Config{
		Namespace: "myapp",
		// Other fields will be filled with defaults
	}

	// Apply defaults
	config.ApplyDefaults()

	fmt.Printf("Provider: %s\n", config.Provider)
	fmt.Printf("Namespace: %s\n", config.Namespace)
}
Output:

Provider: prometheus
Namespace: myapp

type NoOpProvider

type NoOpProvider struct{}

NoOpProvider is a no-op implementation of Provider

func (*NoOpProvider) DecRequestsInFlight

func (n *NoOpProvider) DecRequestsInFlight()

func (*NoOpProvider) Handler

func (n *NoOpProvider) Handler() http.Handler

func (*NoOpProvider) IncRequestsInFlight

func (n *NoOpProvider) IncRequestsInFlight()

func (*NoOpProvider) RecordCacheHit

func (n *NoOpProvider) RecordCacheHit(provider string)

func (*NoOpProvider) RecordCacheMiss

func (n *NoOpProvider) RecordCacheMiss(provider string)

func (*NoOpProvider) RecordDBQuery

func (n *NoOpProvider) RecordDBQuery(operation, table string, duration time.Duration, err error)

func (*NoOpProvider) RecordEventProcessed added in v0.0.107

func (n *NoOpProvider) RecordEventProcessed(source, eventType, status string, duration time.Duration)

func (*NoOpProvider) RecordEventPublished added in v0.0.107

func (n *NoOpProvider) RecordEventPublished(source, eventType string)

func (*NoOpProvider) RecordHTTPRequest

func (n *NoOpProvider) RecordHTTPRequest(method, path, status string, duration time.Duration)

func (*NoOpProvider) RecordPanic added in v0.0.122

func (n *NoOpProvider) RecordPanic(methodName string)

func (*NoOpProvider) UpdateCacheSize

func (n *NoOpProvider) UpdateCacheSize(provider string, size int64)

func (*NoOpProvider) UpdateEventQueueSize added in v0.0.107

func (n *NoOpProvider) UpdateEventQueueSize(size int64)

type PrometheusProvider

type PrometheusProvider struct {
	// contains filtered or unexported fields
}

PrometheusProvider implements the Provider interface using Prometheus

func NewPrometheusProvider

func NewPrometheusProvider(cfg *Config) *PrometheusProvider

NewPrometheusProvider creates a new Prometheus metrics provider If cfg is nil, default configuration will be used

Example (Custom)

ExampleNewPrometheusProvider_custom demonstrates using custom configuration

package main

import (
	"fmt"

	"github.com/bitechdev/ResolveSpec/pkg/metrics"
)

func main() {
	// Create custom configuration
	config := &metrics.Config{
		Enabled:            true,
		Provider:           "prometheus",
		Namespace:          "myapp",
		HTTPRequestBuckets: []float64{0.01, 0.05, 0.1, 0.5, 1, 2, 5},
		DBQueryBuckets:     []float64{0.001, 0.01, 0.05, 0.1, 0.5, 1},
	}

	// Initialize with custom configuration
	provider := metrics.NewPrometheusProvider(config)
	metrics.SetProvider(provider)

	fmt.Println("Provider initialized with custom config")
}
Output:

Provider initialized with custom config
Example (Default)

ExampleNewPrometheusProvider_default demonstrates using default configuration

package main

import (
	"fmt"

	"github.com/bitechdev/ResolveSpec/pkg/metrics"
)

func main() {
	// Initialize with default configuration
	provider := metrics.NewPrometheusProvider(nil)
	metrics.SetProvider(provider)

	fmt.Println("Provider initialized with defaults")
}
Output:

Provider initialized with defaults

func (*PrometheusProvider) DecRequestsInFlight

func (p *PrometheusProvider) DecRequestsInFlight()

DecRequestsInFlight implements Provider interface

func (*PrometheusProvider) Handler

func (p *PrometheusProvider) Handler() http.Handler

Handler implements Provider interface

func (*PrometheusProvider) IncRequestsInFlight

func (p *PrometheusProvider) IncRequestsInFlight()

IncRequestsInFlight implements Provider interface

func (*PrometheusProvider) Middleware

func (p *PrometheusProvider) Middleware(next http.Handler) http.Handler

Middleware returns an HTTP middleware that collects metrics

func (*PrometheusProvider) Push added in v1.0.6

func (p *PrometheusProvider) Push() error

Push manually pushes metrics to the configured Pushgateway Returns an error if pushing fails or if Pushgateway is not configured

func (*PrometheusProvider) RecordCacheHit

func (p *PrometheusProvider) RecordCacheHit(provider string)

RecordCacheHit implements Provider interface

func (*PrometheusProvider) RecordCacheMiss

func (p *PrometheusProvider) RecordCacheMiss(provider string)

RecordCacheMiss implements Provider interface

func (*PrometheusProvider) RecordDBQuery

func (p *PrometheusProvider) RecordDBQuery(operation, table string, duration time.Duration, err error)

RecordDBQuery implements Provider interface

func (*PrometheusProvider) RecordEventProcessed added in v1.0.5

func (p *PrometheusProvider) RecordEventProcessed(source, eventType, status string, duration time.Duration)

RecordEventProcessed implements Provider interface

func (*PrometheusProvider) RecordEventPublished added in v1.0.5

func (p *PrometheusProvider) RecordEventPublished(source, eventType string)

RecordEventPublished implements Provider interface

func (*PrometheusProvider) RecordHTTPRequest

func (p *PrometheusProvider) RecordHTTPRequest(method, path, status string, duration time.Duration)

RecordHTTPRequest implements Provider interface

func (*PrometheusProvider) RecordPanic added in v0.0.122

func (p *PrometheusProvider) RecordPanic(methodName string)

RecordPanic implements the Provider interface

func (*PrometheusProvider) StopAutoPush added in v1.0.6

func (p *PrometheusProvider) StopAutoPush()

StopAutoPush stops the automatic push goroutine This should be called when shutting down the application

func (*PrometheusProvider) UpdateCacheSize

func (p *PrometheusProvider) UpdateCacheSize(provider string, size int64)

UpdateCacheSize implements Provider interface

func (*PrometheusProvider) UpdateEventQueueSize added in v1.0.5

func (p *PrometheusProvider) UpdateEventQueueSize(size int64)

UpdateEventQueueSize implements Provider interface

type Provider

type Provider interface {
	// RecordHTTPRequest records metrics for an HTTP request
	RecordHTTPRequest(method, path, status string, duration time.Duration)

	// IncRequestsInFlight increments the in-flight requests counter
	IncRequestsInFlight()

	// DecRequestsInFlight decrements the in-flight requests counter
	DecRequestsInFlight()

	// RecordDBQuery records metrics for a database query
	RecordDBQuery(operation, table string, duration time.Duration, err error)

	// RecordCacheHit records a cache hit
	RecordCacheHit(provider string)

	// RecordCacheMiss records a cache miss
	RecordCacheMiss(provider string)

	// UpdateCacheSize updates the cache size metric
	UpdateCacheSize(provider string, size int64)

	// RecordEventPublished records an event publication
	RecordEventPublished(source, eventType string)

	// RecordEventProcessed records an event processing with its status
	RecordEventProcessed(source, eventType, status string, duration time.Duration)

	// UpdateEventQueueSize updates the event queue size metric
	UpdateEventQueueSize(size int64)

	// RecordPanic records a panic event
	RecordPanic(methodName string)

	// Handler returns an HTTP handler for exposing metrics (e.g., /metrics endpoint)
	Handler() http.Handler
}

Provider defines the interface for metric collection

func GetProvider

func GetProvider() Provider

GetProvider returns the current metrics provider

type ResponseWriter

type ResponseWriter struct {
	http.ResponseWriter
	// contains filtered or unexported fields
}

ResponseWriter wraps http.ResponseWriter to capture status code

func NewResponseWriter

func NewResponseWriter(w http.ResponseWriter) *ResponseWriter

func (*ResponseWriter) WriteHeader

func (rw *ResponseWriter) WriteHeader(code int)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL