Skip to content

Commit c013feb

Browse files
fix(go): Improve logs slightly (#188)
1 parent bcf3ab3 commit c013feb

File tree

7 files changed

+221
-100
lines changed

7 files changed

+221
-100
lines changed

openfeature-provider/go/confidence/flag_logs_e2e_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ const (
2424
func TestFlagLogs_ShouldSuccessfullySendToRealBackend(t *testing.T) {
2525
ctx := context.Background()
2626

27-
// Create a custom logger that captures log messages
27+
// Create a custom logger that captures log messages (Debug level to capture all logs)
2828
var logBuffer logCaptureBuffer
29-
captureHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{Level: slog.LevelInfo})
29+
captureHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug})
3030
logger := slog.New(captureHandler)
3131

3232
// Create a real provider with real gRPC connection

openfeature-provider/go/confidence/grpc_wasm_flag_logger.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ func (g *GrpcFlagLogger) Write(ctx context.Context, request *resolverv1.WriteFla
5858
sdkID = request.TelemetryData.Sdk.GetId().String()
5959
sdkVersion = request.TelemetryData.Sdk.Version
6060
}
61-
g.logger.Info("Telemetry Data",
61+
g.logger.Debug("Telemetry Data",
6262
"sdk_id", sdkID,
6363
"sdk_version", sdkVersion)
6464
}
6565

66-
g.logger.Debug("Writing flag logs",
66+
g.logger.Debug("Sending flag logs",
6767
"flag_assigned", flagAssignedCount,
6868
"client_resolve_info", clientResolveCount,
6969
"flag_resolve_info", flagResolveCount)
@@ -134,7 +134,7 @@ func (g *GrpcFlagLogger) sendAsync(ctx context.Context, request *resolverv1.Writ
134134
if _, err := g.stub.ClientWriteFlagLogs(rpcCtx, request); err != nil {
135135
g.logger.Error("Failed to write flag logs", "error", err)
136136
} else {
137-
g.logger.Info("Successfully sent flag log", "entries", len(request.FlagAssigned))
137+
g.logger.Debug("Successfully sent flag log", "entries", len(request.FlagAssigned))
138138
}
139139
}()
140140
return nil

openfeature-provider/go/confidence/local_resolver_provider.go

Lines changed: 101 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -79,31 +79,33 @@ func (p *LocalResolverProvider) BooleanEvaluation(
7979
) openfeature.BoolResolutionDetail {
8080
result := p.ObjectEvaluation(ctx, flag, defaultValue, evalCtx)
8181

82+
var detail openfeature.BoolResolutionDetail
83+
8284
if result.Value == nil {
83-
return openfeature.BoolResolutionDetail{
85+
detail = openfeature.BoolResolutionDetail{
8486
Value: defaultValue,
8587
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
8688
Reason: result.Reason,
8789
ResolutionError: result.ResolutionError,
8890
},
8991
}
90-
}
91-
92-
boolVal, ok := result.Value.(bool)
93-
if !ok {
94-
return openfeature.BoolResolutionDetail{
92+
} else if boolVal, ok := result.Value.(bool); !ok {
93+
detail = openfeature.BoolResolutionDetail{
9594
Value: defaultValue,
9695
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
9796
Reason: openfeature.ErrorReason,
9897
ResolutionError: openfeature.NewTypeMismatchResolutionError("value is not a boolean"),
9998
},
10099
}
100+
} else {
101+
detail = openfeature.BoolResolutionDetail{
102+
Value: boolVal,
103+
ProviderResolutionDetail: result.ProviderResolutionDetail,
104+
}
101105
}
102106

103-
return openfeature.BoolResolutionDetail{
104-
Value: boolVal,
105-
ProviderResolutionDetail: result.ProviderResolutionDetail,
106-
}
107+
p.logResolutionErrorIfPresent(flag, detail.ProviderResolutionDetail)
108+
return detail
107109
}
108110

109111
// StringEvaluation evaluates a string flag
@@ -115,31 +117,33 @@ func (p *LocalResolverProvider) StringEvaluation(
115117
) openfeature.StringResolutionDetail {
116118
result := p.ObjectEvaluation(ctx, flag, defaultValue, evalCtx)
117119

120+
var detail openfeature.StringResolutionDetail
121+
118122
if result.Value == nil {
119-
return openfeature.StringResolutionDetail{
123+
detail = openfeature.StringResolutionDetail{
120124
Value: defaultValue,
121125
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
122126
Reason: result.Reason,
123127
ResolutionError: result.ResolutionError,
124128
},
125129
}
126-
}
127-
128-
strVal, ok := result.Value.(string)
129-
if !ok {
130-
return openfeature.StringResolutionDetail{
130+
} else if strVal, ok := result.Value.(string); !ok {
131+
detail = openfeature.StringResolutionDetail{
131132
Value: defaultValue,
132133
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
133134
Reason: openfeature.ErrorReason,
134135
ResolutionError: openfeature.NewTypeMismatchResolutionError("value is not a string"),
135136
},
136137
}
138+
} else {
139+
detail = openfeature.StringResolutionDetail{
140+
Value: strVal,
141+
ProviderResolutionDetail: result.ProviderResolutionDetail,
142+
}
137143
}
138144

139-
return openfeature.StringResolutionDetail{
140-
Value: strVal,
141-
ProviderResolutionDetail: result.ProviderResolutionDetail,
142-
}
145+
p.logResolutionErrorIfPresent(flag, detail.ProviderResolutionDetail)
146+
return detail
143147
}
144148

145149
// FloatEvaluation evaluates a float flag
@@ -151,31 +155,33 @@ func (p *LocalResolverProvider) FloatEvaluation(
151155
) openfeature.FloatResolutionDetail {
152156
result := p.ObjectEvaluation(ctx, flag, defaultValue, evalCtx)
153157

158+
var detail openfeature.FloatResolutionDetail
159+
154160
if result.Value == nil {
155-
return openfeature.FloatResolutionDetail{
161+
detail = openfeature.FloatResolutionDetail{
156162
Value: defaultValue,
157163
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
158164
Reason: result.Reason,
159165
ResolutionError: result.ResolutionError,
160166
},
161167
}
162-
}
163-
164-
floatVal, ok := result.Value.(float64)
165-
if !ok {
166-
return openfeature.FloatResolutionDetail{
168+
} else if floatVal, ok := result.Value.(float64); !ok {
169+
detail = openfeature.FloatResolutionDetail{
167170
Value: defaultValue,
168171
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
169172
Reason: openfeature.ErrorReason,
170173
ResolutionError: openfeature.NewTypeMismatchResolutionError("value is not a float"),
171174
},
172175
}
176+
} else {
177+
detail = openfeature.FloatResolutionDetail{
178+
Value: floatVal,
179+
ProviderResolutionDetail: result.ProviderResolutionDetail,
180+
}
173181
}
174182

175-
return openfeature.FloatResolutionDetail{
176-
Value: floatVal,
177-
ProviderResolutionDetail: result.ProviderResolutionDetail,
178-
}
183+
p.logResolutionErrorIfPresent(flag, detail.ProviderResolutionDetail)
184+
return detail
179185
}
180186

181187
// IntEvaluation evaluates an int flag
@@ -187,37 +193,42 @@ func (p *LocalResolverProvider) IntEvaluation(
187193
) openfeature.IntResolutionDetail {
188194
result := p.ObjectEvaluation(ctx, flag, defaultValue, evalCtx)
189195

196+
var detail openfeature.IntResolutionDetail
197+
190198
if result.Value == nil {
191-
return openfeature.IntResolutionDetail{
199+
detail = openfeature.IntResolutionDetail{
192200
Value: defaultValue,
193201
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
194202
Reason: result.Reason,
195203
ResolutionError: result.ResolutionError,
196204
},
197205
}
198-
}
199-
200-
// Handle both int64 and float64 (JSON numbers are float64)
201-
switch v := result.Value.(type) {
202-
case int64:
203-
return openfeature.IntResolutionDetail{
204-
Value: v,
205-
ProviderResolutionDetail: result.ProviderResolutionDetail,
206-
}
207-
case float64:
208-
return openfeature.IntResolutionDetail{
209-
Value: int64(v),
210-
ProviderResolutionDetail: result.ProviderResolutionDetail,
211-
}
212-
default:
213-
return openfeature.IntResolutionDetail{
214-
Value: defaultValue,
215-
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
216-
Reason: openfeature.ErrorReason,
217-
ResolutionError: openfeature.NewTypeMismatchResolutionError("value is not an integer"),
218-
},
206+
} else {
207+
// Handle both int64 and float64 (JSON numbers are float64)
208+
switch v := result.Value.(type) {
209+
case int64:
210+
detail = openfeature.IntResolutionDetail{
211+
Value: v,
212+
ProviderResolutionDetail: result.ProviderResolutionDetail,
213+
}
214+
case float64:
215+
detail = openfeature.IntResolutionDetail{
216+
Value: int64(v),
217+
ProviderResolutionDetail: result.ProviderResolutionDetail,
218+
}
219+
default:
220+
detail = openfeature.IntResolutionDetail{
221+
Value: defaultValue,
222+
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
223+
Reason: openfeature.ErrorReason,
224+
ResolutionError: openfeature.NewTypeMismatchResolutionError("value is not an integer"),
225+
},
226+
}
219227
}
220228
}
229+
230+
p.logResolutionErrorIfPresent(flag, detail.ProviderResolutionDetail)
231+
return detail
221232
}
222233

223234
// ObjectEvaluation evaluates an object flag (core implementation)
@@ -309,7 +320,6 @@ func (p *LocalResolverProvider) ObjectEvaluation(
309320

310321
// Check if flag was found
311322
if len(response.ResolvedFlags) == 0 {
312-
p.logger.Info("No active flag was found", "flag", flagPath)
313323
return openfeature.InterfaceResolutionDetail{
314324
Value: defaultValue,
315325
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
@@ -349,10 +359,21 @@ func (p *LocalResolverProvider) ObjectEvaluation(
349359

350360
// If a path was specified, extract the nested value
351361
if path != "" {
352-
value = getValueForPath(path, value)
362+
var found bool
363+
value, found = getValueForPath(path, value)
364+
// If path was specified but not found, return FLAG_NOT_FOUND error
365+
if !found {
366+
return openfeature.InterfaceResolutionDetail{
367+
Value: defaultValue,
368+
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
369+
Reason: openfeature.ErrorReason,
370+
ResolutionError: openfeature.NewFlagNotFoundResolutionError(fmt.Sprintf("path '%s' not found in flag '%s'", path, flagPath)),
371+
},
372+
}
373+
}
353374
}
354375

355-
// If value is nil, use default
376+
// If value is nil (flag has no value), use default
356377
if value == nil {
357378
value = defaultValue
358379
}
@@ -425,7 +446,7 @@ func (p *LocalResolverProvider) Shutdown() {
425446
p.cancelFunc()
426447
p.cancelFunc = nil
427448
if p.logger != nil {
428-
p.logger.Info("Cancelled scheduled tasks")
449+
p.logger.Debug("Cancelled scheduled tasks")
429450
}
430451
}
431452

@@ -438,20 +459,20 @@ func (p *LocalResolverProvider) Shutdown() {
438459
if p.resolverAPI != nil {
439460
p.resolverAPI.Close(ctx)
440461
if p.logger != nil {
441-
p.logger.Info("Closed resolver API")
462+
p.logger.Debug("Closed resolver API")
442463
}
443464
}
444465

445466
// Shutdown flag logger (which waits for log sends to complete)
446467
if p.flagLogger != nil {
447468
p.flagLogger.Shutdown()
448469
if p.logger != nil {
449-
p.logger.Info("Shut down flag logger")
470+
p.logger.Debug("Shut down flag logger")
450471
}
451472
}
452473

453474
if p.logger != nil {
454-
p.logger.Info("Provider shut down")
475+
p.logger.Info("Provider has been shut down")
455476
}
456477
}
457478

@@ -480,7 +501,7 @@ func (p *LocalResolverProvider) startScheduledTasks(parentCtx context.Context) {
480501
}
481502

482503
if accountId == "" {
483-
p.logger.Warn("AccountID is empty, skipping state update")
504+
p.logger.Error("AccountID inside fetched state is empty, skipping this state update attempt")
484505
continue
485506
}
486507

@@ -632,9 +653,10 @@ func protoValueToGo(value *structpb.Value) interface{} {
632653

633654
// getValueForPath extracts a nested value from a map using dot notation
634655
// e.g., "nested.value" from map{"nested": map{"value": 42}} returns 42
635-
func getValueForPath(path string, value interface{}) interface{} {
656+
// Returns (value, found) where found indicates if the path was fully traversed
657+
func getValueForPath(path string, value interface{}) (interface{}, bool) {
636658
if path == "" {
637-
return value
659+
return value, true
638660
}
639661

640662
parts := strings.Split(path, ".")
@@ -643,13 +665,27 @@ func getValueForPath(path string, value interface{}) interface{} {
643665
for _, part := range parts {
644666
switch v := current.(type) {
645667
case map[string]interface{}:
646-
current = v[part]
668+
var exists bool
669+
current, exists = v[part]
670+
if !exists {
671+
return nil, false
672+
}
647673
default:
648-
return nil
674+
// Can't traverse further - path not found
675+
return nil, false
649676
}
650677
}
651678

652-
return current
679+
return current, true
680+
}
681+
682+
// logResolutionErrorIfPresent logs a warning if the resolution detail contains an error
683+
func (p *LocalResolverProvider) logResolutionErrorIfPresent(flag string, detail openfeature.ProviderResolutionDetail) {
684+
errStr := detail.ResolutionError.Error()
685+
// Empty ResolutionError returns ": ", so check for meaningful error
686+
if errStr != "" && errStr != ": " {
687+
p.logger.Warn("Flag evaluation error", "flag", flag, "error_code", errStr)
688+
}
653689
}
654690

655691
// mapResolveReasonToOpenFeature converts Confidence ResolveReason to OpenFeature Reason

0 commit comments

Comments
 (0)