diff --git a/CONFIG.md b/CONFIG.md index 1d97332..72d4af4 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -242,17 +242,39 @@ http://your-server:port/metrics ### Metrics Collected +Metrics are organized by module for better maintainability and encapsulation: + +#### HTTP Metrics (from `otelmetrics` plugin) - `http_server_requests_total`, `http_server_request_duration_seconds`, `http_server_requests_in_flight` - `http_server_request_size_bytes`, `http_server_response_size_bytes` +- `beckn_messages_total` - Total Beckn protocol messages processed + +#### Step Execution Metrics (from `telemetry` package) - `onix_step_executions_total`, `onix_step_execution_duration_seconds`, `onix_step_errors_total` -- `onix_plugin_execution_duration_seconds`, `onix_plugin_errors_total` -- `beckn_messages_total`, `beckn_signature_validations_total`, `beckn_schema_validations_total` -- `onix_routing_decisions_total` + +#### Handler Metrics (from `handler` module) +- `beckn_signature_validations_total` - Signature validation attempts +- `beckn_schema_validations_total` - Schema validation attempts +- `onix_routing_decisions_total` - Routing decisions taken by handler + +#### Cache Metrics (from `cache` plugin) - `onix_cache_operations_total`, `onix_cache_hits_total`, `onix_cache_misses_total` + +#### Plugin Metrics (from `telemetry` package) +- `onix_plugin_execution_duration_seconds`, `onix_plugin_errors_total` + +#### Runtime Metrics - Go runtime metrics (`go_*`) and Redis instrumentation via `redisotel` Each metric includes consistent labels such as `module`, `role`, `action`, `status`, `step`, `plugin_id`, and `schema_version` to enable low-cardinality dashboards. +**Note**: Metric definitions are now located in their respective modules: +- HTTP metrics: `pkg/plugin/implementation/otelmetrics/metrics.go` +- Step metrics: `pkg/telemetry/step_metrics.go` +- Handler metrics: `core/module/handler/metrics.go` +- Cache metrics: `pkg/plugin/implementation/cache/metrics.go` +- Plugin metrics: `pkg/telemetry/metrics.go` + --- ## Plugin Manager Configuration diff --git a/README.md b/README.md index c0f5065..05c3959 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,11 @@ The **Beckn Protocol** is an open protocol that enables location-aware, local co - Per-step histograms with error attribution - Cache, routing, plugin, and business KPIs (signature/schema validations, Beckn messages) - Native Prometheus exporter with Grafana dashboards & alert rules (`monitoring/`) + - **Modular Metrics Architecture**: Metrics are organized by module for better maintainability: + - HTTP metrics in `otelmetrics` plugin + - Step execution metrics in `telemetry` package + - Handler metrics (signature, schema, routing) in `handler` module + - Cache metrics in `cache` plugin - **Runtime Instrumentation**: Go runtime + Redis client metrics baked in - **Health Checks**: Liveness and readiness probes for Kubernetes diff --git a/core/module/handler/metrics.go b/core/module/handler/metrics.go new file mode 100644 index 0000000..ccc5932 --- /dev/null +++ b/core/module/handler/metrics.go @@ -0,0 +1,68 @@ +package handler + +import ( + "context" + "fmt" + "sync" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" +) + +// HandlerMetrics exposes handler-related metric instruments. +type HandlerMetrics struct { + SignatureValidationsTotal metric.Int64Counter + SchemaValidationsTotal metric.Int64Counter + RoutingDecisionsTotal metric.Int64Counter +} + +var ( + handlerMetricsInstance *HandlerMetrics + handlerMetricsOnce sync.Once + handlerMetricsErr error +) + +// GetHandlerMetrics lazily initializes handler metric instruments and returns a cached reference. +func GetHandlerMetrics(ctx context.Context) (*HandlerMetrics, error) { + handlerMetricsOnce.Do(func() { + handlerMetricsInstance, handlerMetricsErr = newHandlerMetrics() + }) + return handlerMetricsInstance, handlerMetricsErr +} + +func newHandlerMetrics() (*HandlerMetrics, error) { + meter := otel.GetMeterProvider().Meter( + "github.com/beckn-one/beckn-onix/handler", + metric.WithInstrumentationVersion("1.0.0"), + ) + + m := &HandlerMetrics{} + var err error + + if m.SignatureValidationsTotal, err = meter.Int64Counter( + "beckn_signature_validations_total", + metric.WithDescription("Signature validation attempts"), + metric.WithUnit("{validation}"), + ); err != nil { + return nil, fmt.Errorf("beckn_signature_validations_total: %w", err) + } + + if m.SchemaValidationsTotal, err = meter.Int64Counter( + "beckn_schema_validations_total", + metric.WithDescription("Schema validation attempts"), + metric.WithUnit("{validation}"), + ); err != nil { + return nil, fmt.Errorf("beckn_schema_validations_total: %w", err) + } + + if m.RoutingDecisionsTotal, err = meter.Int64Counter( + "onix_routing_decisions_total", + metric.WithDescription("Routing decisions taken by handler"), + metric.WithUnit("{decision}"), + ); err != nil { + return nil, fmt.Errorf("onix_routing_decisions_total: %w", err) + } + + return m, nil +} + diff --git a/core/module/handler/stdHandler.go b/core/module/handler/stdHandler.go index 7daff1a..0ffdc67 100644 --- a/core/module/handler/stdHandler.go +++ b/core/module/handler/stdHandler.go @@ -301,7 +301,7 @@ func (h *stdHandler) initSteps(ctx context.Context, mgr PluginManager, cfg *Conf instrumentedStep, wrapErr := telemetry.NewInstrumentedStep(s, step, h.moduleName) if wrapErr != nil { log.Warnf(ctx, "Failed to instrument step %s: %v", step, wrapErr) - h.steps = append(h.steps, s) + h.steps = append(h.steps, s) continue } h.steps = append(h.steps, instrumentedStep) diff --git a/core/module/handler/step.go b/core/module/handler/step.go index 35c09c3..a066954 100644 --- a/core/module/handler/step.go +++ b/core/module/handler/step.go @@ -72,7 +72,7 @@ func (s *signStep) generateAuthHeader(subID, keyID string, createdAt, validTill type validateSignStep struct { validator definition.SignValidator km definition.KeyManager - metrics *telemetry.Metrics + metrics *HandlerMetrics } // newValidateSignStep initializes and returns a new validate sign step. @@ -83,7 +83,7 @@ func newValidateSignStep(signValidator definition.SignValidator, km definition.K if km == nil { return nil, fmt.Errorf("invalid config: KeyManager plugin not configured") } - metrics, _ := telemetry.GetMetrics(context.Background()) + metrics, _ := GetHandlerMetrics(context.Background()) return &validateSignStep{ validator: signValidator, km: km, @@ -193,7 +193,7 @@ func parseHeader(header string) (*authHeader, error) { // validateSchemaStep represents the schema validation step. type validateSchemaStep struct { validator definition.SchemaValidator - metrics *telemetry.Metrics + metrics *HandlerMetrics } // newValidateSchemaStep creates and returns the validateSchema step after validation. @@ -202,7 +202,7 @@ func newValidateSchemaStep(schemaValidator definition.SchemaValidator) (definiti return nil, fmt.Errorf("invalid config: SchemaValidator plugin not configured") } log.Debug(context.Background(), "adding schema validator") - metrics, _ := telemetry.GetMetrics(context.Background()) + metrics, _ := GetHandlerMetrics(context.Background()) return &validateSchemaStep{ validator: schemaValidator, metrics: metrics, @@ -238,7 +238,7 @@ func (s *validateSchemaStep) recordMetrics(ctx *model.StepContext, err error) { // addRouteStep represents the route determination step. type addRouteStep struct { router definition.Router - metrics *telemetry.Metrics + metrics *HandlerMetrics } // newAddRouteStep creates and returns the addRoute step after validation. @@ -246,7 +246,7 @@ func newAddRouteStep(router definition.Router) (definition.Step, error) { if router == nil { return nil, fmt.Errorf("invalid config: Router plugin not configured") } - metrics, _ := telemetry.GetMetrics(context.Background()) + metrics, _ := GetHandlerMetrics(context.Background()) return &addRouteStep{ router: router, metrics: metrics, diff --git a/pkg/plugin/implementation/cache/cache.go b/pkg/plugin/implementation/cache/cache.go index 2f6f6cc..1e38f96 100644 --- a/pkg/plugin/implementation/cache/cache.go +++ b/pkg/plugin/implementation/cache/cache.go @@ -37,7 +37,7 @@ type Config struct { // Cache wraps a Redis client to provide basic caching operations. type Cache struct { Client RedisClient - metrics *telemetry.Metrics + metrics *CacheMetrics } // Error variables to describe common failure modes. @@ -97,7 +97,7 @@ func New(ctx context.Context, cfg *Config) (*Cache, func() error, error) { } } - metrics, _ := telemetry.GetMetrics(ctx) + metrics, _ := GetCacheMetrics(ctx) log.Infof(ctx, "Cache connection to Redis established successfully") return &Cache{Client: client, metrics: metrics}, client.Close, nil diff --git a/pkg/plugin/implementation/cache/metrics.go b/pkg/plugin/implementation/cache/metrics.go new file mode 100644 index 0000000..b15f298 --- /dev/null +++ b/pkg/plugin/implementation/cache/metrics.go @@ -0,0 +1,69 @@ +package cache + +import ( + "context" + "fmt" + "sync" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" +) + +// CacheMetrics exposes cache-related metric instruments. +type CacheMetrics struct { + CacheOperationsTotal metric.Int64Counter + CacheHitsTotal metric.Int64Counter + CacheMissesTotal metric.Int64Counter +} + +var ( + cacheMetricsInstance *CacheMetrics + cacheMetricsOnce sync.Once + cacheMetricsErr error +) + +// GetCacheMetrics lazily initializes cache metric instruments and returns a cached reference. +func GetCacheMetrics(ctx context.Context) (*CacheMetrics, error) { + cacheMetricsOnce.Do(func() { + cacheMetricsInstance, cacheMetricsErr = newCacheMetrics() + }) + return cacheMetricsInstance, cacheMetricsErr +} + +func newCacheMetrics() (*CacheMetrics, error) { + meter := otel.GetMeterProvider().Meter( + "github.com/beckn-one/beckn-onix/cache", + metric.WithInstrumentationVersion("1.0.0"), + ) + + m := &CacheMetrics{} + var err error + + if m.CacheOperationsTotal, err = meter.Int64Counter( + "onix_cache_operations_total", + metric.WithDescription("Redis cache operations"), + metric.WithUnit("{operation}"), + ); err != nil { + return nil, fmt.Errorf("onix_cache_operations_total: %w", err) + } + + if m.CacheHitsTotal, err = meter.Int64Counter( + "onix_cache_hits_total", + metric.WithDescription("Redis cache hits"), + metric.WithUnit("{hit}"), + ); err != nil { + return nil, fmt.Errorf("onix_cache_hits_total: %w", err) + } + + if m.CacheMissesTotal, err = meter.Int64Counter( + "onix_cache_misses_total", + metric.WithDescription("Redis cache misses"), + metric.WithUnit("{miss}"), + ); err != nil { + return nil, fmt.Errorf("onix_cache_misses_total: %w", err) + } + + return m, nil +} + + diff --git a/pkg/plugin/implementation/otelmetrics/metrics.go b/pkg/plugin/implementation/otelmetrics/metrics.go new file mode 100644 index 0000000..d7da87d --- /dev/null +++ b/pkg/plugin/implementation/otelmetrics/metrics.go @@ -0,0 +1,97 @@ +package otelmetrics + +import ( + "context" + "fmt" + "sync" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" +) + +// HTTPMetrics exposes HTTP-related metric instruments. +type HTTPMetrics struct { + HTTPRequestsTotal metric.Int64Counter + HTTPRequestDuration metric.Float64Histogram + HTTPRequestsInFlight metric.Int64UpDownCounter + HTTPRequestSize metric.Int64Histogram + HTTPResponseSize metric.Int64Histogram + BecknMessagesTotal metric.Int64Counter +} + +var ( + httpMetricsInstance *HTTPMetrics + httpMetricsOnce sync.Once + httpMetricsErr error +) + +// GetHTTPMetrics lazily initializes HTTP metric instruments and returns a cached reference. +func GetHTTPMetrics(ctx context.Context) (*HTTPMetrics, error) { + httpMetricsOnce.Do(func() { + httpMetricsInstance, httpMetricsErr = newHTTPMetrics() + }) + return httpMetricsInstance, httpMetricsErr +} + +func newHTTPMetrics() (*HTTPMetrics, error) { + meter := otel.GetMeterProvider().Meter( + "github.com/beckn-one/beckn-onix/otelmetrics", + metric.WithInstrumentationVersion("1.0.0"), + ) + + m := &HTTPMetrics{} + var err error + + if m.HTTPRequestsTotal, err = meter.Int64Counter( + "http_server_requests_total", + metric.WithDescription("Total number of HTTP requests processed"), + metric.WithUnit("{request}"), + ); err != nil { + return nil, fmt.Errorf("http_server_requests_total: %w", err) + } + + if m.HTTPRequestDuration, err = meter.Float64Histogram( + "http_server_request_duration_seconds", + metric.WithDescription("HTTP request duration in seconds"), + metric.WithUnit("s"), + metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10), + ); err != nil { + return nil, fmt.Errorf("http_server_request_duration_seconds: %w", err) + } + + if m.HTTPRequestsInFlight, err = meter.Int64UpDownCounter( + "http_server_requests_in_flight", + metric.WithDescription("Number of HTTP requests currently being processed"), + metric.WithUnit("{request}"), + ); err != nil { + return nil, fmt.Errorf("http_server_requests_in_flight: %w", err) + } + + if m.HTTPRequestSize, err = meter.Int64Histogram( + "http_server_request_size_bytes", + metric.WithDescription("Size of HTTP request payloads"), + metric.WithUnit("By"), + metric.WithExplicitBucketBoundaries(100, 1000, 10000, 100000, 1000000), + ); err != nil { + return nil, fmt.Errorf("http_server_request_size_bytes: %w", err) + } + + if m.HTTPResponseSize, err = meter.Int64Histogram( + "http_server_response_size_bytes", + metric.WithDescription("Size of HTTP responses"), + metric.WithUnit("By"), + metric.WithExplicitBucketBoundaries(100, 1000, 10000, 100000, 1000000), + ); err != nil { + return nil, fmt.Errorf("http_server_response_size_bytes: %w", err) + } + + if m.BecknMessagesTotal, err = meter.Int64Counter( + "beckn_messages_total", + metric.WithDescription("Total Beckn protocol messages processed"), + metric.WithUnit("{message}"), + ); err != nil { + return nil, fmt.Errorf("beckn_messages_total: %w", err) + } + + return m, nil +} diff --git a/pkg/plugin/implementation/otelmetrics/otelmetrics.go b/pkg/plugin/implementation/otelmetrics/otelmetrics.go index 6c74292..673e321 100644 --- a/pkg/plugin/implementation/otelmetrics/otelmetrics.go +++ b/pkg/plugin/implementation/otelmetrics/otelmetrics.go @@ -15,7 +15,7 @@ import ( // Middleware instruments inbound HTTP handlers with OpenTelemetry metrics. type Middleware struct { - metrics *telemetry.Metrics + metrics *HTTPMetrics enabled bool } @@ -23,7 +23,7 @@ type Middleware struct { func New(ctx context.Context, cfg map[string]string) (*Middleware, error) { enabled := cfg["enabled"] != "false" - metrics, err := telemetry.GetMetrics(ctx) + metrics, err := GetHTTPMetrics(ctx) if err != nil { log.Warnf(ctx, "OpenTelemetry metrics unavailable: %v", err) } diff --git a/pkg/telemetry/metrics.go b/pkg/telemetry/metrics.go index 27556c5..89d32ad 100644 --- a/pkg/telemetry/metrics.go +++ b/pkg/telemetry/metrics.go @@ -11,27 +11,15 @@ import ( ) // Metrics exposes strongly typed metric instruments used across the adapter. +// Note: Most metrics have been moved to their respective modules. Only plugin-level +// metrics remain here. See: +// - HTTP metrics: pkg/plugin/implementation/otelmetrics/metrics.go +// - Step metrics: pkg/telemetry/step_metrics.go +// - Cache metrics: pkg/plugin/implementation/cache/metrics.go +// - Handler metrics: core/module/handler/metrics.go type Metrics struct { - HTTPRequestsTotal metric.Int64Counter - HTTPRequestDuration metric.Float64Histogram - HTTPRequestsInFlight metric.Int64UpDownCounter - HTTPRequestSize metric.Int64Histogram - HTTPResponseSize metric.Int64Histogram - - StepExecutionDuration metric.Float64Histogram - StepExecutionTotal metric.Int64Counter - StepErrorsTotal metric.Int64Counter - PluginExecutionDuration metric.Float64Histogram PluginErrorsTotal metric.Int64Counter - - BecknMessagesTotal metric.Int64Counter - SignatureValidationsTotal metric.Int64Counter - SchemaValidationsTotal metric.Int64Counter - CacheOperationsTotal metric.Int64Counter - CacheHitsTotal metric.Int64Counter - CacheMissesTotal metric.Int64Counter - RoutingDecisionsTotal metric.Int64Counter } var ( @@ -77,74 +65,6 @@ func newMetrics() (*Metrics, error) { m := &Metrics{} var err error - if m.HTTPRequestsTotal, err = meter.Int64Counter( - "http_server_requests_total", - metric.WithDescription("Total number of HTTP requests processed"), - metric.WithUnit("{request}"), - ); err != nil { - return nil, fmt.Errorf("http_server_requests_total: %w", err) - } - - if m.HTTPRequestDuration, err = meter.Float64Histogram( - "http_server_request_duration_seconds", - metric.WithDescription("HTTP request duration in seconds"), - metric.WithUnit("s"), - metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10), - ); err != nil { - return nil, fmt.Errorf("http_server_request_duration_seconds: %w", err) - } - - if m.HTTPRequestsInFlight, err = meter.Int64UpDownCounter( - "http_server_requests_in_flight", - metric.WithDescription("Number of HTTP requests currently being processed"), - metric.WithUnit("{request}"), - ); err != nil { - return nil, fmt.Errorf("http_server_requests_in_flight: %w", err) - } - - if m.HTTPRequestSize, err = meter.Int64Histogram( - "http_server_request_size_bytes", - metric.WithDescription("Size of HTTP request payloads"), - metric.WithUnit("By"), - metric.WithExplicitBucketBoundaries(100, 1000, 10000, 100000, 1000000), - ); err != nil { - return nil, fmt.Errorf("http_server_request_size_bytes: %w", err) - } - - if m.HTTPResponseSize, err = meter.Int64Histogram( - "http_server_response_size_bytes", - metric.WithDescription("Size of HTTP responses"), - metric.WithUnit("By"), - metric.WithExplicitBucketBoundaries(100, 1000, 10000, 100000, 1000000), - ); err != nil { - return nil, fmt.Errorf("http_server_response_size_bytes: %w", err) - } - - if m.StepExecutionDuration, err = meter.Float64Histogram( - "onix_step_execution_duration_seconds", - metric.WithDescription("Duration of individual processing steps"), - metric.WithUnit("s"), - metric.WithExplicitBucketBoundaries(0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5), - ); err != nil { - return nil, fmt.Errorf("onix_step_execution_duration_seconds: %w", err) - } - - if m.StepExecutionTotal, err = meter.Int64Counter( - "onix_step_executions_total", - metric.WithDescription("Total processing step executions"), - metric.WithUnit("{execution}"), - ); err != nil { - return nil, fmt.Errorf("onix_step_executions_total: %w", err) - } - - if m.StepErrorsTotal, err = meter.Int64Counter( - "onix_step_errors_total", - metric.WithDescription("Processing step errors"), - metric.WithUnit("{error}"), - ); err != nil { - return nil, fmt.Errorf("onix_step_errors_total: %w", err) - } - if m.PluginExecutionDuration, err = meter.Float64Histogram( "onix_plugin_execution_duration_seconds", metric.WithDescription("Plugin execution time"), @@ -162,61 +82,5 @@ func newMetrics() (*Metrics, error) { return nil, fmt.Errorf("onix_plugin_errors_total: %w", err) } - if m.BecknMessagesTotal, err = meter.Int64Counter( - "beckn_messages_total", - metric.WithDescription("Total Beckn protocol messages processed"), - metric.WithUnit("{message}"), - ); err != nil { - return nil, fmt.Errorf("beckn_messages_total: %w", err) - } - - if m.SignatureValidationsTotal, err = meter.Int64Counter( - "beckn_signature_validations_total", - metric.WithDescription("Signature validation attempts"), - metric.WithUnit("{validation}"), - ); err != nil { - return nil, fmt.Errorf("beckn_signature_validations_total: %w", err) - } - - if m.SchemaValidationsTotal, err = meter.Int64Counter( - "beckn_schema_validations_total", - metric.WithDescription("Schema validation attempts"), - metric.WithUnit("{validation}"), - ); err != nil { - return nil, fmt.Errorf("beckn_schema_validations_total: %w", err) - } - - if m.CacheOperationsTotal, err = meter.Int64Counter( - "onix_cache_operations_total", - metric.WithDescription("Redis cache operations"), - metric.WithUnit("{operation}"), - ); err != nil { - return nil, fmt.Errorf("onix_cache_operations_total: %w", err) - } - - if m.CacheHitsTotal, err = meter.Int64Counter( - "onix_cache_hits_total", - metric.WithDescription("Redis cache hits"), - metric.WithUnit("{hit}"), - ); err != nil { - return nil, fmt.Errorf("onix_cache_hits_total: %w", err) - } - - if m.CacheMissesTotal, err = meter.Int64Counter( - "onix_cache_misses_total", - metric.WithDescription("Redis cache misses"), - metric.WithUnit("{miss}"), - ); err != nil { - return nil, fmt.Errorf("onix_cache_misses_total: %w", err) - } - - if m.RoutingDecisionsTotal, err = meter.Int64Counter( - "onix_routing_decisions_total", - metric.WithDescription("Routing decisions taken by handler"), - metric.WithUnit("{decision}"), - ); err != nil { - return nil, fmt.Errorf("onix_routing_decisions_total: %w", err) - } - return m, nil } diff --git a/pkg/telemetry/step_instrumentor.go b/pkg/telemetry/step_instrumentor.go index f4f19d3..e6a7ac7 100644 --- a/pkg/telemetry/step_instrumentor.go +++ b/pkg/telemetry/step_instrumentor.go @@ -19,12 +19,12 @@ type InstrumentedStep struct { step definition.Step stepName string moduleName string - metrics *Metrics + metrics *StepMetrics } // NewInstrumentedStep returns a telemetry enabled wrapper around a definition.Step. func NewInstrumentedStep(step definition.Step, stepName, moduleName string) (*InstrumentedStep, error) { - metrics, err := GetMetrics(context.Background()) + metrics, err := GetStepMetrics(context.Background()) if err != nil { return nil, err } diff --git a/pkg/telemetry/step_metrics.go b/pkg/telemetry/step_metrics.go new file mode 100644 index 0000000..8566d4f --- /dev/null +++ b/pkg/telemetry/step_metrics.go @@ -0,0 +1,69 @@ +package telemetry + +import ( + "context" + "fmt" + "sync" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" +) + +// StepMetrics exposes step execution metric instruments. +type StepMetrics struct { + StepExecutionDuration metric.Float64Histogram + StepExecutionTotal metric.Int64Counter + StepErrorsTotal metric.Int64Counter +} + +var ( + stepMetricsInstance *StepMetrics + stepMetricsOnce sync.Once + stepMetricsErr error +) + +// GetStepMetrics lazily initializes step metric instruments and returns a cached reference. +func GetStepMetrics(ctx context.Context) (*StepMetrics, error) { + stepMetricsOnce.Do(func() { + stepMetricsInstance, stepMetricsErr = newStepMetrics() + }) + return stepMetricsInstance, stepMetricsErr +} + +func newStepMetrics() (*StepMetrics, error) { + meter := otel.GetMeterProvider().Meter( + "github.com/beckn-one/beckn-onix/telemetry", + metric.WithInstrumentationVersion("1.0.0"), + ) + + m := &StepMetrics{} + var err error + + if m.StepExecutionDuration, err = meter.Float64Histogram( + "onix_step_execution_duration_seconds", + metric.WithDescription("Duration of individual processing steps"), + metric.WithUnit("s"), + metric.WithExplicitBucketBoundaries(0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5), + ); err != nil { + return nil, fmt.Errorf("onix_step_execution_duration_seconds: %w", err) + } + + if m.StepExecutionTotal, err = meter.Int64Counter( + "onix_step_executions_total", + metric.WithDescription("Total processing step executions"), + metric.WithUnit("{execution}"), + ); err != nil { + return nil, fmt.Errorf("onix_step_executions_total: %w", err) + } + + if m.StepErrorsTotal, err = meter.Int64Counter( + "onix_step_errors_total", + metric.WithDescription("Processing step errors"), + metric.WithUnit("{error}"), + ); err != nil { + return nil, fmt.Errorf("onix_step_errors_total: %w", err) + } + + return m, nil +} +