1 package ldclient
2
3 import (
4 "crypto/hmac"
5 "crypto/sha256"
6 "encoding/hex"
7 "errors"
8 "fmt"
9 "reflect"
10 "time"
11
12 "github.com/launchdarkly/go-sdk-common/v3/ldcontext"
13 "github.com/launchdarkly/go-sdk-common/v3/ldlog"
14 "github.com/launchdarkly/go-sdk-common/v3/ldreason"
15 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
16 ldevents "github.com/launchdarkly/go-sdk-events/v2"
17 ldeval "github.com/launchdarkly/go-server-sdk-evaluation/v2"
18 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
19 "github.com/launchdarkly/go-server-sdk/v6/interfaces"
20 "github.com/launchdarkly/go-server-sdk/v6/interfaces/flagstate"
21 "github.com/launchdarkly/go-server-sdk/v6/internal"
22 "github.com/launchdarkly/go-server-sdk/v6/internal/bigsegments"
23 "github.com/launchdarkly/go-server-sdk/v6/internal/datakinds"
24 "github.com/launchdarkly/go-server-sdk/v6/internal/datasource"
25 "github.com/launchdarkly/go-server-sdk/v6/internal/datastore"
26 "github.com/launchdarkly/go-server-sdk/v6/ldcomponents"
27 "github.com/launchdarkly/go-server-sdk/v6/subsystems"
28 "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoreimpl"
29 )
30
31
32 const Version = internal.SDKVersion
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 type LDClient struct {
50 sdkKey string
51 loggers ldlog.Loggers
52 eventProcessor ldevents.EventProcessor
53 dataSource subsystems.DataSource
54 store subsystems.DataStore
55 evaluator ldeval.Evaluator
56 dataSourceStatusBroadcaster *internal.Broadcaster[interfaces.DataSourceStatus]
57 dataSourceStatusProvider interfaces.DataSourceStatusProvider
58 dataStoreStatusBroadcaster *internal.Broadcaster[interfaces.DataStoreStatus]
59 dataStoreStatusProvider interfaces.DataStoreStatusProvider
60 flagChangeEventBroadcaster *internal.Broadcaster[interfaces.FlagChangeEvent]
61 flagTracker interfaces.FlagTracker
62 bigSegmentStoreStatusBroadcaster *internal.Broadcaster[interfaces.BigSegmentStoreStatus]
63 bigSegmentStoreStatusProvider interfaces.BigSegmentStoreStatusProvider
64 bigSegmentStoreWrapper *ldstoreimpl.BigSegmentStoreWrapper
65 eventsDefault eventsScope
66 eventsWithReasons eventsScope
67 withEventsDisabled interfaces.LDClientInterface
68 logEvaluationErrors bool
69 offline bool
70 }
71
72
73 var (
74
75
76
77 ErrInitializationTimeout = errors.New("timeout encountered waiting for LaunchDarkly client initialization")
78
79
80
81
82 ErrInitializationFailed = errors.New("LaunchDarkly client initialization failed")
83
84
85
86
87 ErrClientNotInitialized = errors.New("feature flag evaluation called before LaunchDarkly client initialization completed")
88 )
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 func MakeClient(sdkKey string, waitFor time.Duration) (*LDClient, error) {
119
120
121 return MakeCustomClient(sdkKey, Config{}, waitFor)
122 }
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 func MakeCustomClient(sdkKey string, config Config, waitFor time.Duration) (*LDClient, error) {
155
156 client := &LDClient{sdkKey: sdkKey}
157 clientValid := false
158 defer func() {
159 if !clientValid {
160 _ = client.Close()
161 }
162 }()
163
164 closeWhenReady := make(chan struct{})
165
166 eventProcessorFactory := getEventProcessorFactory(config)
167
168 clientContext, err := newClientContextFromConfig(sdkKey, config)
169 if err != nil {
170 return nil, err
171 }
172
173
174 if !config.DiagnosticOptOut {
175 if reflect.TypeOf(eventProcessorFactory) == reflect.TypeOf(ldcomponents.SendEvents()) {
176 clientContext.DiagnosticsManager = createDiagnosticsManager(clientContext, sdkKey, config, waitFor)
177 }
178 }
179
180 loggers := clientContext.GetLogging().Loggers
181 loggers.Infof("Starting LaunchDarkly client %s", Version)
182
183 client.loggers = loggers
184 client.logEvaluationErrors = clientContext.GetLogging().LogEvaluationErrors
185
186 client.offline = config.Offline
187
188 client.dataStoreStatusBroadcaster = internal.NewBroadcaster[interfaces.DataStoreStatus]()
189 dataStoreUpdateSink := datastore.NewDataStoreUpdateSinkImpl(client.dataStoreStatusBroadcaster)
190 storeFactory := config.DataStore
191 if storeFactory == nil {
192 storeFactory = ldcomponents.InMemoryDataStore()
193 }
194 clientContextWithDataStoreUpdateSink := clientContext
195 clientContextWithDataStoreUpdateSink.DataStoreUpdateSink = dataStoreUpdateSink
196 store, err := storeFactory.Build(clientContextWithDataStoreUpdateSink)
197 if err != nil {
198 return nil, err
199 }
200 client.store = store
201
202 bigSegments := config.BigSegments
203 if bigSegments == nil {
204 bigSegments = ldcomponents.BigSegments(nil)
205 }
206 bsConfig, err := bigSegments.Build(clientContext)
207 if err != nil {
208 return nil, err
209 }
210 bsStore := bsConfig.GetStore()
211 client.bigSegmentStoreStatusBroadcaster = internal.NewBroadcaster[interfaces.BigSegmentStoreStatus]()
212 if bsStore != nil {
213 client.bigSegmentStoreWrapper = ldstoreimpl.NewBigSegmentStoreWrapperWithConfig(
214 ldstoreimpl.BigSegmentsConfigurationProperties{
215 Store: bsStore,
216 StartPolling: true,
217 StatusPollInterval: bsConfig.GetStatusPollInterval(),
218 StaleAfter: bsConfig.GetStaleAfter(),
219 ContextCacheSize: bsConfig.GetContextCacheSize(),
220 ContextCacheTime: bsConfig.GetContextCacheTime(),
221 },
222 client.bigSegmentStoreStatusBroadcaster.Broadcast,
223 loggers,
224 )
225 client.bigSegmentStoreStatusProvider = bigsegments.NewBigSegmentStoreStatusProviderImpl(
226 client.bigSegmentStoreWrapper.GetStatus,
227 client.bigSegmentStoreStatusBroadcaster,
228 )
229 } else {
230 client.bigSegmentStoreStatusProvider = bigsegments.NewBigSegmentStoreStatusProviderImpl(
231 nil, client.bigSegmentStoreStatusBroadcaster,
232 )
233 }
234
235 dataProvider := ldstoreimpl.NewDataStoreEvaluatorDataProvider(store, loggers)
236 evalOptions := []ldeval.EvaluatorOption{
237 ldeval.EvaluatorOptionErrorLogger(client.loggers.ForLevel(ldlog.Error)),
238 }
239 if client.bigSegmentStoreWrapper != nil {
240 evalOptions = append(evalOptions, ldeval.EvaluatorOptionBigSegmentProvider(client.bigSegmentStoreWrapper))
241 }
242 client.evaluator = ldeval.NewEvaluatorWithOptions(dataProvider, evalOptions...)
243
244 client.dataStoreStatusProvider = datastore.NewDataStoreStatusProviderImpl(store, dataStoreUpdateSink)
245
246 client.dataSourceStatusBroadcaster = internal.NewBroadcaster[interfaces.DataSourceStatus]()
247 client.flagChangeEventBroadcaster = internal.NewBroadcaster[interfaces.FlagChangeEvent]()
248 dataSourceUpdateSink := datasource.NewDataSourceUpdateSinkImpl(
249 store,
250 client.dataStoreStatusProvider,
251 client.dataSourceStatusBroadcaster,
252 client.flagChangeEventBroadcaster,
253 clientContext.GetLogging().LogDataSourceOutageAsErrorAfter,
254 loggers,
255 )
256
257 client.eventProcessor, err = eventProcessorFactory.Build(clientContext)
258 if err != nil {
259 return nil, err
260 }
261 if isNullEventProcessorFactory(eventProcessorFactory) {
262 client.eventsDefault = newDisabledEventsScope()
263 client.eventsWithReasons = newDisabledEventsScope()
264 } else {
265 client.eventsDefault = newEventsScope(client, false)
266 client.eventsWithReasons = newEventsScope(client, true)
267 }
268
269
270 client.withEventsDisabled = newClientEventsDisabledDecorator(client)
271
272 dataSource, err := createDataSource(config, clientContext, dataSourceUpdateSink)
273 client.dataSource = dataSource
274 if err != nil {
275 return nil, err
276 }
277 client.dataSourceStatusProvider = datasource.NewDataSourceStatusProviderImpl(
278 client.dataSourceStatusBroadcaster,
279 dataSourceUpdateSink,
280 )
281
282 client.flagTracker = internal.NewFlagTrackerImpl(
283 client.flagChangeEventBroadcaster,
284 func(flagKey string, context ldcontext.Context, defaultValue ldvalue.Value) ldvalue.Value {
285 value, _ := client.JSONVariation(flagKey, context, defaultValue)
286 return value
287 },
288 )
289
290 clientValid = true
291 client.dataSource.Start(closeWhenReady)
292 if waitFor > 0 && client.dataSource != datasource.NewNullDataSource() {
293 loggers.Infof("Waiting up to %d milliseconds for LaunchDarkly client to start...",
294 waitFor/time.Millisecond)
295 timeout := time.After(waitFor)
296 for {
297 select {
298 case <-closeWhenReady:
299 if !client.dataSource.IsInitialized() {
300 loggers.Warn("LaunchDarkly client initialization failed")
301 return client, ErrInitializationFailed
302 }
303
304 loggers.Info("Initialized LaunchDarkly client")
305 return client, nil
306 case <-timeout:
307 loggers.Warn("Timeout encountered waiting for LaunchDarkly client initialization")
308 go func() { <-closeWhenReady }()
309 return client, ErrInitializationTimeout
310 }
311 }
312 }
313 go func() { <-closeWhenReady }()
314 return client, nil
315 }
316
317 func createDataSource(
318 config Config,
319 context *internal.ClientContextImpl,
320 dataSourceUpdateSink subsystems.DataSourceUpdateSink,
321 ) (subsystems.DataSource, error) {
322 if config.Offline {
323 context.GetLogging().Loggers.Info("Starting LaunchDarkly client in offline mode")
324 dataSourceUpdateSink.UpdateStatus(interfaces.DataSourceStateValid, interfaces.DataSourceErrorInfo{})
325 return datasource.NewNullDataSource(), nil
326 }
327 factory := config.DataSource
328 if factory == nil {
329
330 factory = ldcomponents.StreamingDataSource()
331 }
332 contextCopy := *context
333 contextCopy.BasicClientContext.DataSourceUpdateSink = dataSourceUpdateSink
334 return factory.Build(&contextCopy)
335 }
336
337
338
339
340 func (client *LDClient) Identify(context ldcontext.Context) error {
341 if client.eventsDefault.disabled {
342 return nil
343 }
344 if err := context.Err(); err != nil {
345 client.loggers.Warnf("Identify called with invalid context: %s", err)
346 return nil
347 }
348 evt := client.eventsDefault.factory.NewIdentifyEventData(ldevents.Context(context))
349 client.eventProcessor.RecordIdentifyEvent(evt)
350 return nil
351 }
352
353
354
355
356
357
358
359
360
361 func (client *LDClient) TrackEvent(eventName string, context ldcontext.Context) error {
362 return client.TrackData(eventName, context, ldvalue.Null())
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376 func (client *LDClient) TrackData(eventName string, context ldcontext.Context, data ldvalue.Value) error {
377 if client.eventsDefault.disabled {
378 return nil
379 }
380 if err := context.Err(); err != nil {
381 client.loggers.Warnf("Track called with invalid context: %s", err)
382 return nil
383 }
384 client.eventProcessor.RecordCustomEvent(
385 client.eventsDefault.factory.NewCustomEventData(
386 eventName,
387 ldevents.Context(context),
388 data,
389 false,
390 0,
391 ))
392 return nil
393 }
394
395
396
397
398
399
400
401
402
403
404
405
406
407 func (client *LDClient) TrackMetric(
408 eventName string,
409 context ldcontext.Context,
410 metricValue float64,
411 data ldvalue.Value,
412 ) error {
413 if client.eventsDefault.disabled {
414 return nil
415 }
416 if err := context.Err(); err != nil {
417 client.loggers.Warnf("TrackMetric called with invalid context: %s", err)
418 return nil
419 }
420 client.eventProcessor.RecordCustomEvent(
421 client.eventsDefault.factory.NewCustomEventData(
422 eventName,
423 ldevents.Context(context),
424 data,
425 true,
426 metricValue,
427 ))
428 return nil
429 }
430
431
432
433
434
435
436
437
438
439 func (client *LDClient) IsOffline() bool {
440 return client.offline
441 }
442
443
444
445
446 func (client *LDClient) SecureModeHash(context ldcontext.Context) string {
447 key := []byte(client.sdkKey)
448 h := hmac.New(sha256.New, key)
449 _, _ = h.Write([]byte(context.FullyQualifiedKey()))
450 return hex.EncodeToString(h.Sum(nil))
451 }
452
453
454
455
456
457
458
459
460
461
462
463
464
465 func (client *LDClient) Initialized() bool {
466 return client.dataSource.IsInitialized()
467 }
468
469
470
471
472 func (client *LDClient) Close() error {
473 client.loggers.Info("Closing LaunchDarkly client")
474
475
476
477
478 if client.eventProcessor != nil {
479 _ = client.eventProcessor.Close()
480 }
481 if client.dataSource != nil {
482 _ = client.dataSource.Close()
483 }
484 if client.store != nil {
485 _ = client.store.Close()
486 }
487 if client.dataSourceStatusBroadcaster != nil {
488 client.dataSourceStatusBroadcaster.Close()
489 }
490 if client.dataStoreStatusBroadcaster != nil {
491 client.dataStoreStatusBroadcaster.Close()
492 }
493 if client.flagChangeEventBroadcaster != nil {
494 client.flagChangeEventBroadcaster.Close()
495 }
496 if client.bigSegmentStoreStatusBroadcaster != nil {
497 client.bigSegmentStoreStatusBroadcaster.Close()
498 }
499 if client.bigSegmentStoreWrapper != nil {
500 client.bigSegmentStoreWrapper.Close()
501 }
502 return nil
503 }
504
505
506
507
508
509
510
511 func (client *LDClient) Flush() {
512 client.eventProcessor.Flush()
513 }
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534 func (client *LDClient) FlushAndWait(timeout time.Duration) bool {
535 return client.eventProcessor.FlushBlocking(timeout)
536 }
537
538
539
540
541
542
543
544
545
546
547
548 func (client *LDClient) AllFlagsState(context ldcontext.Context, options ...flagstate.Option) flagstate.AllFlags {
549 valid := true
550 if client.IsOffline() {
551 client.loggers.Warn("Called AllFlagsState in offline mode. Returning empty state")
552 valid = false
553 } else if !client.Initialized() {
554 if client.store.IsInitialized() {
555 client.loggers.Warn("Called AllFlagsState before client initialization; using last known values from data store")
556 } else {
557 client.loggers.Warn("Called AllFlagsState before client initialization. Data store not available; returning empty state")
558 valid = false
559 }
560 }
561
562 if !valid {
563 return flagstate.AllFlags{}
564 }
565
566 items, err := client.store.GetAll(datakinds.Features)
567 if err != nil {
568 client.loggers.Warn("Unable to fetch flags from data store. Returning empty state. Error: " + err.Error())
569 return flagstate.AllFlags{}
570 }
571
572 clientSideOnly := false
573 for _, o := range options {
574 if o == flagstate.OptionClientSideOnly() {
575 clientSideOnly = true
576 break
577 }
578 }
579
580 state := flagstate.NewAllFlagsBuilder(options...)
581 for _, item := range items {
582 if item.Item.Item != nil {
583 if flag, ok := item.Item.Item.(*ldmodel.FeatureFlag); ok {
584 if clientSideOnly && !flag.ClientSideAvailability.UsingEnvironmentID {
585 continue
586 }
587
588 result := client.evaluator.Evaluate(flag, context, nil)
589
590 state.AddFlag(
591 item.Key,
592 flagstate.FlagState{
593 Value: result.Detail.Value,
594 Variation: result.Detail.VariationIndex,
595 Reason: result.Detail.Reason,
596 Version: flag.Version,
597 TrackEvents: flag.TrackEvents || result.IsExperiment,
598 TrackReason: result.IsExperiment,
599 DebugEventsUntilDate: flag.DebugEventsUntilDate,
600 },
601 )
602 }
603 }
604 }
605
606 return state.Build()
607 }
608
609
610
611
612
613
614
615 func (client *LDClient) BoolVariation(key string, context ldcontext.Context, defaultVal bool) (bool, error) {
616 detail, err := client.variation(key, context, ldvalue.Bool(defaultVal), true, client.eventsDefault)
617 return detail.Value.BoolValue(), err
618 }
619
620
621
622
623
624 func (client *LDClient) BoolVariationDetail(
625 key string,
626 context ldcontext.Context,
627 defaultVal bool,
628 ) (bool, ldreason.EvaluationDetail, error) {
629 detail, err := client.variation(key, context, ldvalue.Bool(defaultVal), true, client.eventsWithReasons)
630 return detail.Value.BoolValue(), detail, err
631 }
632
633
634
635
636
637
638
639
640
641
642 func (client *LDClient) IntVariation(key string, context ldcontext.Context, defaultVal int) (int, error) {
643 detail, err := client.variation(key, context, ldvalue.Int(defaultVal), true, client.eventsDefault)
644 return detail.Value.IntValue(), err
645 }
646
647
648
649
650
651 func (client *LDClient) IntVariationDetail(
652 key string,
653 context ldcontext.Context,
654 defaultVal int,
655 ) (int, ldreason.EvaluationDetail, error) {
656 detail, err := client.variation(key, context, ldvalue.Int(defaultVal), true, client.eventsWithReasons)
657 return detail.Value.IntValue(), detail, err
658 }
659
660
661
662
663
664
665
666
667 func (client *LDClient) Float64Variation(key string, context ldcontext.Context, defaultVal float64) (float64, error) {
668 detail, err := client.variation(key, context, ldvalue.Float64(defaultVal), true, client.eventsDefault)
669 return detail.Value.Float64Value(), err
670 }
671
672
673
674
675
676 func (client *LDClient) Float64VariationDetail(
677 key string,
678 context ldcontext.Context,
679 defaultVal float64,
680 ) (float64, ldreason.EvaluationDetail, error) {
681 detail, err := client.variation(key, context, ldvalue.Float64(defaultVal), true, client.eventsWithReasons)
682 return detail.Value.Float64Value(), detail, err
683 }
684
685
686
687
688
689
690
691
692 func (client *LDClient) StringVariation(key string, context ldcontext.Context, defaultVal string) (string, error) {
693 detail, err := client.variation(key, context, ldvalue.String(defaultVal), true, client.eventsDefault)
694 return detail.Value.StringValue(), err
695 }
696
697
698
699
700
701 func (client *LDClient) StringVariationDetail(
702 key string,
703 context ldcontext.Context,
704 defaultVal string,
705 ) (string, ldreason.EvaluationDetail, error) {
706 detail, err := client.variation(key, context, ldvalue.String(defaultVal), true, client.eventsWithReasons)
707 return detail.Value.StringValue(), detail, err
708 }
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733 func (client *LDClient) JSONVariation(
734 key string,
735 context ldcontext.Context,
736 defaultVal ldvalue.Value,
737 ) (ldvalue.Value, error) {
738 detail, err := client.variation(key, context, defaultVal, false, client.eventsDefault)
739 return detail.Value, err
740 }
741
742
743
744
745
746 func (client *LDClient) JSONVariationDetail(
747 key string,
748 context ldcontext.Context,
749 defaultVal ldvalue.Value,
750 ) (ldvalue.Value, ldreason.EvaluationDetail, error) {
751 detail, err := client.variation(key, context, defaultVal, false, client.eventsWithReasons)
752 return detail.Value, detail, err
753 }
754
755
756
757
758
759
760
761
762
763 func (client *LDClient) GetDataSourceStatusProvider() interfaces.DataSourceStatusProvider {
764 return client.dataSourceStatusProvider
765 }
766
767
768
769
770
771
772
773
774
775 func (client *LDClient) GetDataStoreStatusProvider() interfaces.DataStoreStatusProvider {
776 return client.dataStoreStatusProvider
777 }
778
779
780
781
782 func (client *LDClient) GetFlagTracker() interfaces.FlagTracker {
783 return client.flagTracker
784 }
785
786
787
788
789
790
791
792
793 func (client *LDClient) GetBigSegmentStoreStatusProvider() interfaces.BigSegmentStoreStatusProvider {
794 return client.bigSegmentStoreStatusProvider
795 }
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812 func (client *LDClient) WithEventsDisabled(disabled bool) interfaces.LDClientInterface {
813 if !disabled || client.eventsDefault.disabled {
814 return client
815 }
816 return client.withEventsDisabled
817 }
818
819
820 func (client *LDClient) variation(
821 key string,
822 context ldcontext.Context,
823 defaultVal ldvalue.Value,
824 checkType bool,
825 eventsScope eventsScope,
826 ) (ldreason.EvaluationDetail, error) {
827 if err := context.Err(); err != nil {
828 client.loggers.Warnf("Tried to evaluate a flag with an invalid context: %s", err)
829 return newEvaluationError(defaultVal, ldreason.EvalErrorUserNotSpecified), err
830 }
831 if client.IsOffline() {
832 return newEvaluationError(defaultVal, ldreason.EvalErrorClientNotReady), nil
833 }
834 result, flag, err := client.evaluateInternal(key, context, defaultVal, eventsScope)
835 if err != nil {
836 result.Detail.Value = defaultVal
837 result.Detail.VariationIndex = ldvalue.OptionalInt{}
838 } else if checkType && defaultVal.Type() != ldvalue.NullType && result.Detail.Value.Type() != defaultVal.Type() {
839 result.Detail = newEvaluationError(defaultVal, ldreason.EvalErrorWrongType)
840 }
841
842 if !eventsScope.disabled {
843 var eval ldevents.EvaluationData
844 if flag == nil {
845 eval = eventsScope.factory.NewUnknownFlagEvaluationData(
846 key,
847 ldevents.Context(context),
848 defaultVal,
849 result.Detail.Reason,
850 )
851 } else {
852 eval = eventsScope.factory.NewEvaluationData(
853 ldevents.FlagEventProperties{
854 Key: flag.Key,
855 Version: flag.Version,
856 RequireFullEvent: flag.TrackEvents,
857 DebugEventsUntilDate: flag.DebugEventsUntilDate,
858 },
859 ldevents.Context(context),
860 result.Detail,
861 result.IsExperiment,
862 defaultVal,
863 "",
864 )
865 }
866 client.eventProcessor.RecordEvaluation(eval)
867 }
868
869 return result.Detail, err
870 }
871
872
873
874 func (client *LDClient) evaluateInternal(
875 key string,
876 context ldcontext.Context,
877 defaultVal ldvalue.Value,
878 eventsScope eventsScope,
879 ) (ldeval.Result, *ldmodel.FeatureFlag, error) {
880
881
882
883 var feature *ldmodel.FeatureFlag
884 var storeErr error
885 var ok bool
886
887 evalErrorResult := func(
888 errKind ldreason.EvalErrorKind,
889 flag *ldmodel.FeatureFlag,
890 err error,
891 ) (ldeval.Result, *ldmodel.FeatureFlag, error) {
892 detail := newEvaluationError(defaultVal, errKind)
893 if client.logEvaluationErrors {
894 client.loggers.Warn(err)
895 }
896 return ldeval.Result{Detail: detail}, flag, err
897 }
898
899 if !client.Initialized() {
900 if client.store.IsInitialized() {
901 client.loggers.Warn("Feature flag evaluation called before LaunchDarkly client initialization completed; using last known values from data store")
902 } else {
903 return evalErrorResult(ldreason.EvalErrorClientNotReady, nil, ErrClientNotInitialized)
904 }
905 }
906
907 itemDesc, storeErr := client.store.Get(datakinds.Features, key)
908
909 if storeErr != nil {
910 client.loggers.Errorf("Encountered error fetching feature from store: %+v", storeErr)
911 detail := newEvaluationError(defaultVal, ldreason.EvalErrorException)
912 return ldeval.Result{Detail: detail}, nil, storeErr
913 }
914
915 if itemDesc.Item != nil {
916 feature, ok = itemDesc.Item.(*ldmodel.FeatureFlag)
917 if !ok {
918 return evalErrorResult(ldreason.EvalErrorException, nil,
919 fmt.Errorf(
920 "unexpected data type (%T) found in store for feature key: %s. Returning default value",
921 itemDesc.Item,
922 key,
923 ))
924 }
925 } else {
926 return evalErrorResult(ldreason.EvalErrorFlagNotFound, nil,
927 fmt.Errorf("unknown feature key: %s. Verify that this feature key exists. Returning default value", key))
928 }
929
930 result := client.evaluator.Evaluate(feature, context, eventsScope.prerequisiteEventRecorder)
931 if result.Detail.Reason.GetKind() == ldreason.EvalReasonError && client.logEvaluationErrors {
932 client.loggers.Warnf("Flag evaluation for %s failed with error %s, default value was returned",
933 key, result.Detail.Reason.GetErrorKind())
934 }
935 if result.Detail.IsDefaultValue() {
936 result.Detail.Value = defaultVal
937 }
938 return result, feature, nil
939 }
940
941 func newEvaluationError(jsonValue ldvalue.Value, errorKind ldreason.EvalErrorKind) ldreason.EvaluationDetail {
942 return ldreason.EvaluationDetail{
943 Value: jsonValue,
944 Reason: ldreason.NewEvalReasonError(errorKind),
945 }
946 }
947
View as plain text