Skip to content

Commit 20bcb78

Browse files
authored
chore: adapt telemetry setup error handling (#1315)
Closes #1194 This PR adapts the service setup to not fail when encountering an error in the telemetry setup. --------- Signed-off-by: Florian Bacher <[email protected]>
1 parent 9b8fa74 commit 20bcb78

File tree

8 files changed

+114
-24
lines changed

8 files changed

+114
-24
lines changed

core/pkg/telemetry/builder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func RegisterErrorHandling(log *logger.Logger) {
4343
// BuildMetricsRecorder is a helper to build telemetry.MetricsRecorder based on configurations
4444
func BuildMetricsRecorder(
4545
ctx context.Context, svcName string, svcVersion string, config Config,
46-
) (*MetricsRecorder, error) {
46+
) (IMetricsRecorder, error) {
4747
// Build metric reader based on configurations
4848
mReader, err := buildMetricReader(ctx, config)
4949
if err != nil {

core/pkg/telemetry/metrics.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,40 @@ const (
2626
reasonMetric = "feature_flag." + ProviderName + ".evaluation.reason"
2727
)
2828

29+
type IMetricsRecorder interface {
30+
HTTPAttributes(svcName, url, method, code string) []attribute.KeyValue
31+
HTTPRequestDuration(ctx context.Context, duration time.Duration, attrs []attribute.KeyValue)
32+
HTTPResponseSize(ctx context.Context, sizeBytes int64, attrs []attribute.KeyValue)
33+
InFlightRequestStart(ctx context.Context, attrs []attribute.KeyValue)
34+
InFlightRequestEnd(ctx context.Context, attrs []attribute.KeyValue)
35+
RecordEvaluation(ctx context.Context, err error, reason, variant, key string)
36+
Impressions(ctx context.Context, reason, variant, key string)
37+
}
38+
39+
type NoopMetricsRecorder struct{}
40+
41+
func (NoopMetricsRecorder) HTTPAttributes(_, _, _, _ string) []attribute.KeyValue {
42+
return []attribute.KeyValue{}
43+
}
44+
45+
func (NoopMetricsRecorder) HTTPRequestDuration(_ context.Context, _ time.Duration, _ []attribute.KeyValue) {
46+
}
47+
48+
func (NoopMetricsRecorder) HTTPResponseSize(_ context.Context, _ int64, _ []attribute.KeyValue) {
49+
}
50+
51+
func (NoopMetricsRecorder) InFlightRequestStart(_ context.Context, _ []attribute.KeyValue) {
52+
}
53+
54+
func (NoopMetricsRecorder) InFlightRequestEnd(_ context.Context, _ []attribute.KeyValue) {
55+
}
56+
57+
func (NoopMetricsRecorder) RecordEvaluation(_ context.Context, _ error, _, _, _ string) {
58+
}
59+
60+
func (NoopMetricsRecorder) Impressions(_ context.Context, _, _, _ string) {
61+
}
62+
2963
type MetricsRecorder struct {
3064
httpRequestDurHistogram metric.Float64Histogram
3165
httpResponseSizeHistogram metric.Float64Histogram

core/pkg/telemetry/metrics_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,35 @@ func TestMetrics(t *testing.T) {
204204
})
205205
}
206206
}
207+
208+
// some really simple tests just to make sure all methods are actually implemented and nothing panics
209+
func TestNoopMetricsRecorder_HTTPAttributes(t *testing.T) {
210+
no := NoopMetricsRecorder{}
211+
got := no.HTTPAttributes("", "", "", "")
212+
require.Empty(t, got)
213+
}
214+
215+
func TestNoopMetricsRecorder_HTTPRequestDuration(_ *testing.T) {
216+
no := NoopMetricsRecorder{}
217+
no.HTTPRequestDuration(context.TODO(), 0, nil)
218+
}
219+
220+
func TestNoopMetricsRecorder_InFlightRequestStart(_ *testing.T) {
221+
no := NoopMetricsRecorder{}
222+
no.InFlightRequestStart(context.TODO(), nil)
223+
}
224+
225+
func TestNoopMetricsRecorder_InFlightRequestEnd(_ *testing.T) {
226+
no := NoopMetricsRecorder{}
227+
no.InFlightRequestEnd(context.TODO(), nil)
228+
}
229+
230+
func TestNoopMetricsRecorder_RecordEvaluation(_ *testing.T) {
231+
no := NoopMetricsRecorder{}
232+
no.RecordEvaluation(context.TODO(), nil, "", "", "")
233+
}
234+
235+
func TestNoopMetricsRecorder_Impressions(_ *testing.T) {
236+
no := NoopMetricsRecorder{}
237+
no.Impressions(context.TODO(), "", "", "")
238+
}

flagd/pkg/runtime/from_config.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ func FromConfig(logger *logger.Logger, version string, config Config) (*Runtime,
5151
// register trace provider for the runtime
5252
err := telemetry.BuildTraceProvider(context.Background(), logger, svcName, version, telCfg)
5353
if err != nil {
54-
return nil, fmt.Errorf("error building trace provider: %w", err)
54+
// log the error but continue
55+
logger.Error(fmt.Sprintf("error building trace provider: %v", err))
5556
}
5657

5758
// build metrics recorder with startup configurations
5859
recorder, err := telemetry.BuildMetricsRecorder(context.Background(), svcName, version, telCfg)
5960
if err != nil {
60-
return nil, fmt.Errorf("error building metrics recorder: %w", err)
61+
// log the error but continue
62+
logger.Error(fmt.Sprintf("error building metrics recorder: %v", err))
6163
}
6264

6365
// build flag store, collect flag sources & fill sources details
@@ -113,7 +115,8 @@ func FromConfig(logger *logger.Logger, version string, config Config) (*Runtime,
113115

114116
options, err := telemetry.BuildConnectOptions(telCfg)
115117
if err != nil {
116-
return nil, fmt.Errorf("failed to build connect options, %w", err)
118+
// log the error but continue
119+
logger.Error(fmt.Sprintf("failed to build connect options, %v", err))
117120
}
118121

119122
return &Runtime{

flagd/pkg/service/flag-evaluation/connect_service.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func (b bufSwitchHandler) ServeHTTP(writer http.ResponseWriter, request *http.Re
5757
type ConnectService struct {
5858
logger *logger.Logger
5959
eval evaluator.IEvaluator
60-
metrics *telemetry.MetricsRecorder
60+
metrics telemetry.IMetricsRecorder
6161
eventingConfiguration IEvents
6262

6363
server *http.Server
@@ -71,17 +71,21 @@ type ConnectService struct {
7171

7272
// NewConnectService creates a ConnectService with provided parameters
7373
func NewConnectService(
74-
logger *logger.Logger, evaluator evaluator.IEvaluator, mRecorder *telemetry.MetricsRecorder,
74+
logger *logger.Logger, evaluator evaluator.IEvaluator, mRecorder telemetry.IMetricsRecorder,
7575
) *ConnectService {
76-
return &ConnectService{
76+
cs := &ConnectService{
7777
logger: logger,
7878
eval: evaluator,
79-
metrics: mRecorder,
79+
metrics: &telemetry.NoopMetricsRecorder{},
8080
eventingConfiguration: &eventingConfiguration{
8181
subs: make(map[interface{}]chan service.Notification),
8282
mu: &sync.RWMutex{},
8383
},
8484
}
85+
if mRecorder != nil {
86+
cs.metrics = mRecorder
87+
}
88+
return cs
8589
}
8690

8791
// Serve serves services with provided configuration options
@@ -242,8 +246,8 @@ func (s *ConnectService) startServer(svcConf service.Configuration) error {
242246
func (s *ConnectService) startMetricsServer(svcConf service.Configuration) error {
243247
s.logger.Info(fmt.Sprintf("metrics and probes listening at %d", svcConf.ManagementPort))
244248

245-
grpc := grpc.NewServer()
246-
grpc_health_v1.RegisterHealthServer(grpc, health.NewServer())
249+
srv := grpc.NewServer()
250+
grpc_health_v1.RegisterHealthServer(srv, health.NewServer())
247251

248252
mux := http.NewServeMux()
249253
mux.Handle("/healthz", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -261,7 +265,7 @@ func (s *ConnectService) startMetricsServer(svcConf service.Configuration) error
261265
handler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
262266
// if this is 'application/grpc' and HTTP2, handle with gRPC, otherwise HTTP.
263267
if request.ProtoMajor == 2 && strings.HasPrefix(request.Header.Get("Content-Type"), "application/grpc") {
264-
grpc.ServeHTTP(writer, request)
268+
srv.ServeHTTP(writer, request)
265269
} else {
266270
mux.ServeHTTP(writer, request)
267271
return

flagd/pkg/service/flag-evaluation/flag_evaluator.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,31 @@ type resolverSignature[T constraints] func(context context.Context, reqID, flagK
2929
type OldFlagEvaluationService struct {
3030
logger *logger.Logger
3131
eval evaluator.IEvaluator
32-
metrics *telemetry.MetricsRecorder
32+
metrics telemetry.IMetricsRecorder
3333
eventingConfiguration IEvents
3434
flagEvalTracer trace.Tracer
3535
}
3636

3737
// NewOldFlagEvaluationService creates a OldFlagEvaluationService with provided parameters
3838
func NewOldFlagEvaluationService(log *logger.Logger,
39-
eval evaluator.IEvaluator, eventingCfg IEvents, metricsRecorder *telemetry.MetricsRecorder,
39+
eval evaluator.IEvaluator, eventingCfg IEvents, metricsRecorder telemetry.IMetricsRecorder,
4040
) *OldFlagEvaluationService {
41-
return &OldFlagEvaluationService{
41+
svc := &OldFlagEvaluationService{
4242
logger: log,
4343
eval: eval,
44-
metrics: metricsRecorder,
44+
metrics: &telemetry.NoopMetricsRecorder{},
4545
eventingConfiguration: eventingCfg,
4646
flagEvalTracer: otel.Tracer("flagEvaluationService"),
4747
}
48+
49+
if metricsRecorder != nil {
50+
svc.metrics = metricsRecorder
51+
}
52+
53+
return svc
4854
}
4955

50-
// nolint:dupl
56+
// nolint:dupl,funlen
5157
func (s *OldFlagEvaluationService) ResolveAll(
5258
ctx context.Context,
5359
req *connect.Request[schemaV1.ResolveAllRequest],
@@ -68,6 +74,7 @@ func (s *OldFlagEvaluationService) ResolveAll(
6874
for _, value := range values {
6975
// register the impression and reason for each flag evaluated
7076
s.metrics.RecordEvaluation(sCtx, value.Error, value.Reason, value.Variant, value.FlagKey)
77+
7178
switch v := value.Value.(type) {
7279
case bool:
7380
res.Flags[value.FlagKey] = &schemaV1.AnyFlag{
@@ -111,6 +118,7 @@ func (s *OldFlagEvaluationService) ResolveAll(
111118
return connect.NewResponse(res), nil
112119
}
113120

121+
// nolint:dupl
114122
func (s *OldFlagEvaluationService) EventStream(
115123
ctx context.Context,
116124
req *connect.Request[schemaV1.EventStreamRequest],
@@ -276,7 +284,7 @@ func (s *OldFlagEvaluationService) ResolveObject(
276284

277285
// resolve is a generic flag resolver
278286
func resolve[T constraints](ctx context.Context, logger *logger.Logger, resolver resolverSignature[T], flagKey string,
279-
evaluationContext *structpb.Struct, resp response[T], metrics *telemetry.MetricsRecorder,
287+
evaluationContext *structpb.Struct, resp response[T], metrics telemetry.IMetricsRecorder,
280288
) error {
281289
reqID := xid.New().String()
282290
defer logger.ClearFields(reqID)
@@ -295,7 +303,9 @@ func resolve[T constraints](ctx context.Context, logger *logger.Logger, resolver
295303
evalErrFormatted = errFormat(evalErr)
296304
}
297305

298-
metrics.RecordEvaluation(ctx, evalErr, reason, variant, flagKey)
306+
if metrics != nil {
307+
metrics.RecordEvaluation(ctx, evalErr, reason, variant, flagKey)
308+
}
299309

300310
spanFromContext := trace.SpanFromContext(ctx)
301311
spanFromContext.SetAttributes(telemetry.SemConvFeatureFlagAttributes(flagKey, variant)...)

flagd/pkg/service/flag-evaluation/flag_evaluator_v2.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
type FlagEvaluationService struct {
2323
logger *logger.Logger
2424
eval evaluator.IEvaluator
25-
metrics *telemetry.MetricsRecorder
25+
metrics telemetry.IMetricsRecorder
2626
eventingConfiguration IEvents
2727
flagEvalTracer trace.Tracer
2828
}
@@ -31,15 +31,21 @@ type FlagEvaluationService struct {
3131
func NewFlagEvaluationService(log *logger.Logger,
3232
eval evaluator.IEvaluator,
3333
eventingCfg IEvents,
34-
metricsRecorder *telemetry.MetricsRecorder,
34+
metricsRecorder telemetry.IMetricsRecorder,
3535
) *FlagEvaluationService {
36-
return &FlagEvaluationService{
36+
svc := &FlagEvaluationService{
3737
logger: log,
3838
eval: eval,
39-
metrics: metricsRecorder,
39+
metrics: &telemetry.NoopMetricsRecorder{},
4040
eventingConfiguration: eventingCfg,
4141
flagEvalTracer: otel.Tracer("flagd.evaluation.v1"),
4242
}
43+
44+
if metricsRecorder != nil {
45+
svc.metrics = metricsRecorder
46+
}
47+
48+
return svc
4349
}
4450

4551
// nolint:dupl,funlen
@@ -110,6 +116,7 @@ func (s *FlagEvaluationService) ResolveAll(
110116
return connect.NewResponse(res), nil
111117
}
112118

119+
// nolint: dupl
113120
func (s *FlagEvaluationService) EventStream(
114121
ctx context.Context,
115122
req *connect.Request[evalV1.EventStreamRequest],

flagd/pkg/service/middleware/metrics/http_metrics.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
)
1717

1818
type Config struct {
19-
MetricRecorder *telemetry.MetricsRecorder
19+
MetricRecorder telemetry.IMetricsRecorder
2020
Logger *logger.Logger
2121
Service string
2222
GroupedStatus bool
@@ -41,7 +41,7 @@ func (cfg *Config) defaults() {
4141
log.Fatal("missing logger")
4242
}
4343
if cfg.MetricRecorder == nil {
44-
cfg.Logger.Fatal("missing OpenTelemetry metric recorder")
44+
cfg.MetricRecorder = &telemetry.NoopMetricsRecorder{}
4545
}
4646
}
4747

0 commit comments

Comments
 (0)