Files
onix/pkg/metrics/requests.go
2025-11-13 13:16:16 +05:30

201 lines
5.3 KiB
Go

package metrics
import (
"context"
"net/http"
"strconv"
"time"
"go.opentelemetry.io/otel/attribute"
otelmetric "go.opentelemetry.io/otel/metric"
)
var (
// Inbound request metrics
inboundRequestsTotal otelmetric.Int64Counter
inboundSignValidationTotal otelmetric.Int64Counter
inboundSchemaValidationTotal otelmetric.Int64Counter
// Outbound request metrics
outboundRequestsTotal otelmetric.Int64Counter
outboundRequests2XX otelmetric.Int64Counter
outboundRequests4XX otelmetric.Int64Counter
outboundRequests5XX otelmetric.Int64Counter
outboundRequestDuration otelmetric.Float64Histogram
)
// InitRequestMetrics initializes request-related metrics instruments.
func InitRequestMetrics() error {
if !IsEnabled() {
return nil
}
meter := GetMeter()
var err error
// Inbound request metrics
inboundRequestsTotal, err = meter.Int64Counter(
"beckn.inbound.requests.total",
otelmetric.WithDescription("Total number of inbound requests per host"),
)
if err != nil {
return err
}
inboundSignValidationTotal, err = meter.Int64Counter(
"beckn.inbound.sign_validation.total",
otelmetric.WithDescription("Total number of inbound requests with sign validation per host"),
)
if err != nil {
return err
}
inboundSchemaValidationTotal, err = meter.Int64Counter(
"beckn.inbound.schema_validation.total",
otelmetric.WithDescription("Total number of inbound requests with schema validation per host"),
)
if err != nil {
return err
}
// Outbound request metrics
outboundRequestsTotal, err = meter.Int64Counter(
"beckn.outbound.requests.total",
otelmetric.WithDescription("Total number of outbound requests per host"),
)
if err != nil {
return err
}
outboundRequests2XX, err = meter.Int64Counter(
"beckn.outbound.requests.2xx",
otelmetric.WithDescription("Total number of outbound requests with 2XX status code per host"),
)
if err != nil {
return err
}
outboundRequests4XX, err = meter.Int64Counter(
"beckn.outbound.requests.4xx",
otelmetric.WithDescription("Total number of outbound requests with 4XX status code per host"),
)
if err != nil {
return err
}
outboundRequests5XX, err = meter.Int64Counter(
"beckn.outbound.requests.5xx",
otelmetric.WithDescription("Total number of outbound requests with 5XX status code per host"),
)
if err != nil {
return err
}
// Outbound request duration histogram (for p99, p95, p75)
outboundRequestDuration, err = meter.Float64Histogram(
"beckn.outbound.request.duration",
otelmetric.WithDescription("Duration of outbound requests in milliseconds"),
otelmetric.WithUnit("ms"),
)
if err != nil {
return err
}
return nil
}
// RecordInboundRequest records an inbound request.
func RecordInboundRequest(ctx context.Context, host string) {
if inboundRequestsTotal == nil {
return
}
inboundRequestsTotal.Add(ctx, 1, otelmetric.WithAttributes(
attribute.String("host", host),
))
}
// RecordInboundSignValidation records an inbound request with sign validation.
func RecordInboundSignValidation(ctx context.Context, host string) {
if inboundSignValidationTotal == nil {
return
}
inboundSignValidationTotal.Add(ctx, 1, otelmetric.WithAttributes(
attribute.String("host", host),
))
}
// RecordInboundSchemaValidation records an inbound request with schema validation.
func RecordInboundSchemaValidation(ctx context.Context, host string) {
if inboundSchemaValidationTotal == nil {
return
}
inboundSchemaValidationTotal.Add(ctx, 1, otelmetric.WithAttributes(
attribute.String("host", host),
))
}
// RecordOutboundRequest records an outbound request with status code and duration.
func RecordOutboundRequest(ctx context.Context, host string, statusCode int, duration time.Duration) {
if outboundRequestsTotal == nil {
return
}
attrs := []attribute.KeyValue{
attribute.String("host", host),
attribute.String("status_code", strconv.Itoa(statusCode)),
}
// Record total
outboundRequestsTotal.Add(ctx, 1, otelmetric.WithAttributes(attrs...))
// Record by status code category
statusClass := statusCode / 100
switch statusClass {
case 2:
outboundRequests2XX.Add(ctx, 1, otelmetric.WithAttributes(attrs...))
case 4:
outboundRequests4XX.Add(ctx, 1, otelmetric.WithAttributes(attrs...))
case 5:
outboundRequests5XX.Add(ctx, 1, otelmetric.WithAttributes(attrs...))
}
// Record duration for percentile calculations (p99, p95, p75)
if outboundRequestDuration != nil {
outboundRequestDuration.Record(ctx, float64(duration.Milliseconds()), otelmetric.WithAttributes(attrs...))
}
}
// HTTPTransport wraps an http.RoundTripper to track outbound request metrics.
type HTTPTransport struct {
Transport http.RoundTripper
}
// RoundTrip implements http.RoundTripper interface and tracks metrics.
func (t *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
start := time.Now()
host := req.URL.Host
resp, err := t.Transport.RoundTrip(req)
duration := time.Since(start)
statusCode := 0
if resp != nil {
statusCode = resp.StatusCode
} else if err != nil {
// Network error - treat as 5XX
statusCode = 500
}
RecordOutboundRequest(req.Context(), host, statusCode, duration)
return resp, err
}
// WrapHTTPTransport wraps an http.RoundTripper with metrics tracking.
func WrapHTTPTransport(transport http.RoundTripper) http.RoundTripper {
if !IsEnabled() {
return transport
}
return &HTTPTransport{Transport: transport}
}