can create matric in there respective module.

This commit is contained in:
Manendra Pal Singh
2025-11-24 10:25:26 +05:30
parent c38e7b75ca
commit 88eef682df
12 changed files with 352 additions and 158 deletions

View File

@@ -242,17 +242,39 @@ http://your-server:port/metrics
### Metrics Collected ### 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_requests_total`, `http_server_request_duration_seconds`, `http_server_requests_in_flight`
- `http_server_request_size_bytes`, `http_server_response_size_bytes` - `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_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` #### Handler Metrics (from `handler` module)
- `onix_routing_decisions_total` - `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` - `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` - 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. 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 ## Plugin Manager Configuration

View File

@@ -69,6 +69,11 @@ The **Beckn Protocol** is an open protocol that enables location-aware, local co
- Per-step histograms with error attribution - Per-step histograms with error attribution
- Cache, routing, plugin, and business KPIs (signature/schema validations, Beckn messages) - Cache, routing, plugin, and business KPIs (signature/schema validations, Beckn messages)
- Native Prometheus exporter with Grafana dashboards & alert rules (`monitoring/`) - 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 - **Runtime Instrumentation**: Go runtime + Redis client metrics baked in
- **Health Checks**: Liveness and readiness probes for Kubernetes - **Health Checks**: Liveness and readiness probes for Kubernetes

View File

@@ -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
}

View File

@@ -301,7 +301,7 @@ func (h *stdHandler) initSteps(ctx context.Context, mgr PluginManager, cfg *Conf
instrumentedStep, wrapErr := telemetry.NewInstrumentedStep(s, step, h.moduleName) instrumentedStep, wrapErr := telemetry.NewInstrumentedStep(s, step, h.moduleName)
if wrapErr != nil { if wrapErr != nil {
log.Warnf(ctx, "Failed to instrument step %s: %v", step, wrapErr) log.Warnf(ctx, "Failed to instrument step %s: %v", step, wrapErr)
h.steps = append(h.steps, s) h.steps = append(h.steps, s)
continue continue
} }
h.steps = append(h.steps, instrumentedStep) h.steps = append(h.steps, instrumentedStep)

View File

@@ -72,7 +72,7 @@ func (s *signStep) generateAuthHeader(subID, keyID string, createdAt, validTill
type validateSignStep struct { type validateSignStep struct {
validator definition.SignValidator validator definition.SignValidator
km definition.KeyManager km definition.KeyManager
metrics *telemetry.Metrics metrics *HandlerMetrics
} }
// newValidateSignStep initializes and returns a new validate sign step. // newValidateSignStep initializes and returns a new validate sign step.
@@ -83,7 +83,7 @@ func newValidateSignStep(signValidator definition.SignValidator, km definition.K
if km == nil { if km == nil {
return nil, fmt.Errorf("invalid config: KeyManager plugin not configured") return nil, fmt.Errorf("invalid config: KeyManager plugin not configured")
} }
metrics, _ := telemetry.GetMetrics(context.Background()) metrics, _ := GetHandlerMetrics(context.Background())
return &validateSignStep{ return &validateSignStep{
validator: signValidator, validator: signValidator,
km: km, km: km,
@@ -193,7 +193,7 @@ func parseHeader(header string) (*authHeader, error) {
// validateSchemaStep represents the schema validation step. // validateSchemaStep represents the schema validation step.
type validateSchemaStep struct { type validateSchemaStep struct {
validator definition.SchemaValidator validator definition.SchemaValidator
metrics *telemetry.Metrics metrics *HandlerMetrics
} }
// newValidateSchemaStep creates and returns the validateSchema step after validation. // 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") return nil, fmt.Errorf("invalid config: SchemaValidator plugin not configured")
} }
log.Debug(context.Background(), "adding schema validator") log.Debug(context.Background(), "adding schema validator")
metrics, _ := telemetry.GetMetrics(context.Background()) metrics, _ := GetHandlerMetrics(context.Background())
return &validateSchemaStep{ return &validateSchemaStep{
validator: schemaValidator, validator: schemaValidator,
metrics: metrics, metrics: metrics,
@@ -238,7 +238,7 @@ func (s *validateSchemaStep) recordMetrics(ctx *model.StepContext, err error) {
// addRouteStep represents the route determination step. // addRouteStep represents the route determination step.
type addRouteStep struct { type addRouteStep struct {
router definition.Router router definition.Router
metrics *telemetry.Metrics metrics *HandlerMetrics
} }
// newAddRouteStep creates and returns the addRoute step after validation. // newAddRouteStep creates and returns the addRoute step after validation.
@@ -246,7 +246,7 @@ func newAddRouteStep(router definition.Router) (definition.Step, error) {
if router == nil { if router == nil {
return nil, fmt.Errorf("invalid config: Router plugin not configured") return nil, fmt.Errorf("invalid config: Router plugin not configured")
} }
metrics, _ := telemetry.GetMetrics(context.Background()) metrics, _ := GetHandlerMetrics(context.Background())
return &addRouteStep{ return &addRouteStep{
router: router, router: router,
metrics: metrics, metrics: metrics,

View File

@@ -37,7 +37,7 @@ type Config struct {
// Cache wraps a Redis client to provide basic caching operations. // Cache wraps a Redis client to provide basic caching operations.
type Cache struct { type Cache struct {
Client RedisClient Client RedisClient
metrics *telemetry.Metrics metrics *CacheMetrics
} }
// Error variables to describe common failure modes. // 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") log.Infof(ctx, "Cache connection to Redis established successfully")
return &Cache{Client: client, metrics: metrics}, client.Close, nil return &Cache{Client: client, metrics: metrics}, client.Close, nil

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -15,7 +15,7 @@ import (
// Middleware instruments inbound HTTP handlers with OpenTelemetry metrics. // Middleware instruments inbound HTTP handlers with OpenTelemetry metrics.
type Middleware struct { type Middleware struct {
metrics *telemetry.Metrics metrics *HTTPMetrics
enabled bool enabled bool
} }
@@ -23,7 +23,7 @@ type Middleware struct {
func New(ctx context.Context, cfg map[string]string) (*Middleware, error) { func New(ctx context.Context, cfg map[string]string) (*Middleware, error) {
enabled := cfg["enabled"] != "false" enabled := cfg["enabled"] != "false"
metrics, err := telemetry.GetMetrics(ctx) metrics, err := GetHTTPMetrics(ctx)
if err != nil { if err != nil {
log.Warnf(ctx, "OpenTelemetry metrics unavailable: %v", err) log.Warnf(ctx, "OpenTelemetry metrics unavailable: %v", err)
} }

View File

@@ -11,27 +11,15 @@ import (
) )
// Metrics exposes strongly typed metric instruments used across the adapter. // 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 { 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 PluginExecutionDuration metric.Float64Histogram
PluginErrorsTotal metric.Int64Counter 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 ( var (
@@ -77,74 +65,6 @@ func newMetrics() (*Metrics, error) {
m := &Metrics{} m := &Metrics{}
var err error 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( if m.PluginExecutionDuration, err = meter.Float64Histogram(
"onix_plugin_execution_duration_seconds", "onix_plugin_execution_duration_seconds",
metric.WithDescription("Plugin execution time"), metric.WithDescription("Plugin execution time"),
@@ -162,61 +82,5 @@ func newMetrics() (*Metrics, error) {
return nil, fmt.Errorf("onix_plugin_errors_total: %w", err) 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 return m, nil
} }

View File

@@ -19,12 +19,12 @@ type InstrumentedStep struct {
step definition.Step step definition.Step
stepName string stepName string
moduleName string moduleName string
metrics *Metrics metrics *StepMetrics
} }
// NewInstrumentedStep returns a telemetry enabled wrapper around a definition.Step. // NewInstrumentedStep returns a telemetry enabled wrapper around a definition.Step.
func NewInstrumentedStep(step definition.Step, stepName, moduleName string) (*InstrumentedStep, error) { func NewInstrumentedStep(step definition.Step, stepName, moduleName string) (*InstrumentedStep, error) {
metrics, err := GetMetrics(context.Background()) metrics, err := GetStepMetrics(context.Background())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -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
}