update the as per the comment

This commit is contained in:
Manendra Pal Singh
2025-12-18 12:30:28 +05:30
parent 17d9ca865d
commit 176b8f3043
14 changed files with 60 additions and 40 deletions

View File

@@ -14,9 +14,9 @@ import (
// Note: Most metrics have been moved to their respective modules. Only plugin-level
// metrics remain here. See:
// - OTel setup: pkg/plugin/implementation/otelsetup
// - Step metrics: pkg/telemetry/step_metrics.go
// - Cache metrics: pkg/plugin/implementation/cache/metrics.go
// - Handler metrics: core/module/handler/metrics.go
// - Step metrics: core/module/handler/step_metrics.go
// - Cache metrics: pkg/plugin/implementation/cache/cache_metrics.go
// - Handler metrics: core/module/handler/handlerMetrics.go
type Metrics struct {
PluginExecutionDuration metric.Float64Histogram
PluginErrorsTotal metric.Int64Counter

View File

@@ -1,82 +0,0 @@
package telemetry
import (
"context"
"errors"
"fmt"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"github.com/beckn-one/beckn-onix/pkg/log"
"github.com/beckn-one/beckn-onix/pkg/model"
)
// StepRunner represents the minimal contract required for step instrumentation.
type StepRunner interface {
Run(*model.StepContext) error
}
// InstrumentedStep wraps a processing step with telemetry instrumentation.
type InstrumentedStep struct {
step StepRunner
stepName string
moduleName string
metrics *StepMetrics
}
// NewInstrumentedStep returns a telemetry enabled wrapper around a definition.Step.
func NewInstrumentedStep(step StepRunner, stepName, moduleName string) (*InstrumentedStep, error) {
metrics, err := GetStepMetrics(context.Background())
if err != nil {
return nil, err
}
return &InstrumentedStep{
step: step,
stepName: stepName,
moduleName: moduleName,
metrics: metrics,
}, nil
}
type becknError interface {
BecknError() *model.Error
}
// Run executes the underlying step and records RED style metrics.
func (is *InstrumentedStep) Run(ctx *model.StepContext) error {
if is.metrics == nil {
return is.step.Run(ctx)
}
start := time.Now()
err := is.step.Run(ctx)
duration := time.Since(start).Seconds()
attrs := []attribute.KeyValue{
AttrModule.String(is.moduleName),
AttrStep.String(is.stepName),
AttrRole.String(string(ctx.Role)),
}
is.metrics.StepExecutionTotal.Add(ctx.Context, 1, metric.WithAttributes(attrs...))
is.metrics.StepExecutionDuration.Record(ctx.Context, duration, metric.WithAttributes(attrs...))
if err != nil {
errorType := fmt.Sprintf("%T", err)
var becknErr becknError
if errors.As(err, &becknErr) {
if be := becknErr.BecknError(); be != nil && be.Code != "" {
errorType = be.Code
}
}
errorAttrs := append(attrs, AttrErrorType.String(errorType))
is.metrics.StepErrorsTotal.Add(ctx.Context, 1, metric.WithAttributes(errorAttrs...))
log.Errorf(ctx.Context, err, "Step %s failed", is.stepName)
}
return err
}

View File

@@ -1,50 +0,0 @@
package telemetry
import (
"context"
"errors"
"testing"
"github.com/beckn-one/beckn-onix/pkg/model"
"github.com/stretchr/testify/require"
)
type stubStep struct {
err error
}
func (s stubStep) Run(ctx *model.StepContext) error {
return s.err
}
func TestInstrumentedStepSuccess(t *testing.T) {
ctx := context.Background()
provider, err := NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
step, err := NewInstrumentedStep(stubStep{}, "test-step", "test-module")
require.NoError(t, err)
stepCtx := &model.StepContext{
Context: context.Background(),
Role: model.RoleBAP,
}
require.NoError(t, step.Run(stepCtx))
}
func TestInstrumentedStepError(t *testing.T) {
ctx := context.Background()
provider, err := NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
step, err := NewInstrumentedStep(stubStep{err: errors.New("boom")}, "test-step", "test-module")
require.NoError(t, err)
stepCtx := &model.StepContext{
Context: context.Background(),
Role: model.RoleBAP,
}
require.Error(t, step.Run(stepCtx))
}

View File

@@ -1,69 +0,0 @@
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
}

View File

@@ -1,129 +0,0 @@
package telemetry
import (
"context"
"net/http/httptest"
"testing"
"go.opentelemetry.io/otel/metric"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetStepMetrics_Success(t *testing.T) {
ctx := context.Background()
// Initialize telemetry provider first
provider, err := NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
// Test getting step metrics
metrics, err := GetStepMetrics(ctx)
require.NoError(t, err, "GetStepMetrics() should not return error")
require.NotNil(t, metrics, "GetStepMetrics() should return non-nil metrics")
// Verify all metric instruments are initialized
assert.NotNil(t, metrics.StepExecutionDuration, "StepExecutionDuration should be initialized")
assert.NotNil(t, metrics.StepExecutionTotal, "StepExecutionTotal should be initialized")
assert.NotNil(t, metrics.StepErrorsTotal, "StepErrorsTotal should be initialized")
}
func TestGetStepMetrics_ConcurrentAccess(t *testing.T) {
ctx := context.Background()
// Initialize telemetry provider first
provider, err := NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
// Test that GetStepMetrics is safe for concurrent access
// and returns the same instance (singleton pattern)
metrics1, err1 := GetStepMetrics(ctx)
require.NoError(t, err1)
require.NotNil(t, metrics1)
metrics2, err2 := GetStepMetrics(ctx)
require.NoError(t, err2)
require.NotNil(t, metrics2)
// Should return the same instance
assert.Equal(t, metrics1, metrics2, "GetStepMetrics should return the same instance")
}
func TestGetStepMetrics_WithoutProvider(t *testing.T) {
ctx := context.Background()
// Test getting step metrics without initializing provider
// This should still work but may not have a valid meter provider
metrics, err := GetStepMetrics(ctx)
// Note: This might succeed or fail depending on OTel's default behavior
// We're just checking it doesn't panic
if err != nil {
t.Logf("GetStepMetrics returned error (expected if no provider): %v", err)
} else {
assert.NotNil(t, metrics, "Metrics should be returned even without explicit provider")
}
}
func TestStepMetrics_Instruments(t *testing.T) {
ctx := context.Background()
// Initialize telemetry provider
provider, err := NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
// Get step metrics
metrics, err := GetStepMetrics(ctx)
require.NoError(t, err)
require.NotNil(t, metrics)
// Test that we can record metrics (this tests the instruments are functional)
// Note: We can't easily verify the metrics were recorded without querying the exporter,
// but we can verify the instruments are not nil and can be called without panicking
// Test StepExecutionDuration
require.NotPanics(t, func() {
metrics.StepExecutionDuration.Record(ctx, 0.5,
metric.WithAttributes(AttrStep.String("test-step"), AttrModule.String("test-module")))
}, "StepExecutionDuration.Record should not panic")
// Test StepExecutionTotal
require.NotPanics(t, func() {
metrics.StepExecutionTotal.Add(ctx, 1,
metric.WithAttributes(AttrStep.String("test-step"), AttrModule.String("test-module")))
}, "StepExecutionTotal.Add should not panic")
// Test StepErrorsTotal
require.NotPanics(t, func() {
metrics.StepErrorsTotal.Add(ctx, 1,
metric.WithAttributes(AttrStep.String("test-step"), AttrModule.String("test-module")))
}, "StepErrorsTotal.Add should not panic")
// Verify metrics are exposed via HTTP handler
rec := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/metrics", nil)
provider.MetricsHandler.ServeHTTP(rec, req)
assert.Equal(t, 200, rec.Code, "Metrics endpoint should return 200")
}
func TestStepMetrics_MultipleCalls(t *testing.T) {
ctx := context.Background()
// Initialize telemetry provider
provider, err := NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
// Call GetStepMetrics multiple times
for i := 0; i < 10; i++ {
metrics, err := GetStepMetrics(ctx)
require.NoError(t, err, "GetStepMetrics should succeed on call %d", i)
require.NotNil(t, metrics, "GetStepMetrics should return non-nil on call %d", i)
assert.NotNil(t, metrics.StepExecutionDuration, "StepExecutionDuration should be initialized")
assert.NotNil(t, metrics.StepExecutionTotal, "StepExecutionTotal should be initialized")
assert.NotNil(t, metrics.StepErrorsTotal, "StepErrorsTotal should be initialized")
}
}