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

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

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)
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)

View File

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

View File

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

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.
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)
}

View File

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

View File

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

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
}