package metrics import ( "context" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "github.com/kanootoko/codevault/internal" ) type Metrics struct { HTTP HTTPMetrics Value ValueMetrics } func NewMetrics(meter metric.Meter) Metrics { metrics := Metrics{ HTTP: newHTTPMetrics(meter), Value: newValueMetrics(meter), } addObservableMetrics(meter) return metrics } type HTTPMetrics struct { // Total HTTP requests with [route, status] RequestsTotal metric.Int64Counter // Request duration histogram in seconds with [route] RequestDuration metric.Float64Histogram } func newHTTPMetrics(meter metric.Meter) HTTPMetrics { requestsTotal, _ := meter.Int64Counter( "http_requests_total", metric.WithDescription("Total number of HTTP requests"), ) requestDuration, _ := meter.Float64Histogram( "http_request_duration_seconds", metric.WithDescription("HTTP request duration histogram in seconds"), metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0), metric.WithUnit("s"), ) return HTTPMetrics{ RequestsTotal: requestsTotal, RequestDuration: requestDuration, } } type ValueMetrics struct { // Successful getValue lookups, with [code]. Only successful lookups carry a // code label since the set of stored codes is bounded, unlike guessed/failed ones. GetSuccessTotal metric.Int64Counter // Failed getValue lookups, with [reason] (not_found|rate_limited). GetFailedTotal metric.Int64Counter // Successful setValue writes, with [user]. SetSuccessTotal metric.Int64Counter // Failed setValue writes, with [reason] (bad_request|unauthorized). SetFailedTotal metric.Int64Counter } func newValueMetrics(meter metric.Meter) ValueMetrics { getSuccessTotal, _ := meter.Int64Counter( "codevault_get_value_success_total", metric.WithDescription("Total number of successful getValue lookups"), ) getFailedTotal, _ := meter.Int64Counter( "codevault_get_value_failed_total", metric.WithDescription("Total number of failed getValue lookups"), ) setSuccessTotal, _ := meter.Int64Counter( "codevault_set_value_success_total", metric.WithDescription("Total number of successful setValue writes"), ) setFailedTotal, _ := meter.Int64Counter( "codevault_set_value_failed_total", metric.WithDescription("Total number of failed setValue writes"), ) return ValueMetrics{ GetSuccessTotal: getSuccessTotal, GetFailedTotal: getFailedTotal, SetSuccessTotal: setSuccessTotal, SetFailedTotal: setFailedTotal, } } func addObservableMetrics(meter metric.Meter) { timeStarted := time.Now() _, _ = meter.Int64ObservableGauge( "startup_time", metric.WithDescription("Startup timestamp"), metric.WithInt64Callback(func(ctx context.Context, io metric.Int64Observer) error { io.Observe(timeStarted.Unix(), metric.WithAttributes(attribute.String("version", internal.Version))) return nil }), ) _, _ = meter.Int64ObservableGauge( "last_update_time", metric.WithDescription("Current timestamp"), metric.WithInt64Callback(func(ctx context.Context, io metric.Int64Observer) error { io.Observe(time.Now().Unix(), metric.WithAttributes(attribute.String("version", internal.Version))) return nil }), ) }