diff --git a/README.md b/README.md index 019927bc..02f7127f 100644 --- a/README.md +++ b/README.md @@ -175,21 +175,39 @@ value, err := client.BooleanValue( ### Logging -The standard Go log package is used by default to show error logs. -This can be overridden using the structured logging, [logr](https://github.com/go-logr/logr) API, allowing integration to any package. -There are already [integration implementations](https://github.com/go-logr/logr#implementations-non-exhaustive) for many of the popular logger packages. +Note that in accordance with the OpenFeature specification, the SDK doesn't generally log messages during flag evaluation. + +#### Logging Hook + +The GO SDK includes a `LoggingHook`, which logs detailed information at key points during flag evaluation, using [slog](https://pkg.go.dev/log/slog) structured logging API. +This hook can be particularly helpful for troubleshooting and debugging; simply attach it at the global, client or invocation level and ensure your log level is set to "debug". + +##### Usage example ```go -var l logr.Logger -l = integratedlogr.New() // replace with your chosen integrator +// configure slog +var programLevel = new(slog.LevelVar) +programLevel.Set(slog.LevelDebug) +h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}) +slog.SetDefault(slog.New(h)) + +// add a hook globally, to run on all evaluations +hook, err := NewLoggingHook(false) +if err != nil { + // handle error +} -openfeature.SetLogger(l) // set the logger at global level +openfeature.AddHooks(hook) + +client.BooleanValueDetails(context.Background(), "not-exist", true, openfeature.EvaluationContext{}) -c := openfeature.NewClient("log").WithLogger(l) // set the logger at client level ``` -[logr](https://github.com/go-logr/logr) uses incremental verbosity levels (akin to named levels but in integer form). -The SDK logs `info` at level `0` and `debug` at level `1`. Errors are always logged. +###### Output +> {"time":"2024-10-23T13:33:09.8870867+03:00","level":"DEBUG","msg":"Before stage","domain":"test-client","provider_name":"InMemoryProvider","flag_key":"not-exist","default_value":true} +{"time":"2024-10-23T13:33:09.8968242+03:00","level":"ERROR","msg":"Error stage","domain":"test-client","provider_name":"InMemoryProvider","flag_key":"not-exist","default_value":true,"error_message":"error code: FLAG_NOT_FOUND: flag for key not-exist not found"} + +See [hooks](#hooks) for more information on configuring hooks. ### Domains Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider. If a domain has no associated provider, the default provider is used. diff --git a/openfeature/client.go b/openfeature/client.go index f66e1921..d44bc436 100644 --- a/openfeature/client.go +++ b/openfeature/client.go @@ -41,7 +41,6 @@ type Client struct { metadata ClientMetadata hooks []Hook evaluationContext EvaluationContext - logger logr.Logger mx sync.RWMutex } @@ -52,25 +51,24 @@ var _ IClient = (*Client)(nil) // NewClient returns a new Client. Name is a unique identifier for this client // This helper exists for historical reasons. It is recommended to interact with IEvaluation to derive IClient instances. func NewClient(name string) *Client { - return newClient(name, api, eventing, logger) + return newClient(name, api, eventing) } -func newClient(name string, apiRef evaluationImpl, eventRef clientEvent, log logr.Logger) *Client { +func newClient(name string, apiRef evaluationImpl, eventRef clientEvent) *Client { return &Client{ api: apiRef, clientEventing: eventRef, - logger: log, metadata: ClientMetadata{name: name}, hooks: []Hook{}, evaluationContext: EvaluationContext{}, } } +// Deprecated // WithLogger sets the logger of the client func (c *Client) WithLogger(l logr.Logger) *Client { c.mx.Lock() defer c.mx.Unlock() - c.logger = l return c } @@ -395,10 +393,6 @@ func (c *Client) BooleanValueDetails(ctx context.Context, flag string, defaultVa value, ok := evalDetails.Value.(bool) if !ok { err := errors.New("evaluated value is not a boolean") - c.logger.Error( - err, "invalid flag resolution type", "expectedType", "boolean", - "gotType", fmt.Sprintf("%T", evalDetails.Value), - ) boolEvalDetails := BooleanEvaluationDetails{ Value: defaultValue, EvaluationDetails: evalDetails.EvaluationDetails, @@ -443,10 +437,6 @@ func (c *Client) StringValueDetails(ctx context.Context, flag string, defaultVal value, ok := evalDetails.Value.(string) if !ok { err := errors.New("evaluated value is not a string") - c.logger.Error( - err, "invalid flag resolution type", "expectedType", "string", - "gotType", fmt.Sprintf("%T", evalDetails.Value), - ) strEvalDetails := StringEvaluationDetails{ Value: defaultValue, EvaluationDetails: evalDetails.EvaluationDetails, @@ -491,10 +481,6 @@ func (c *Client) FloatValueDetails(ctx context.Context, flag string, defaultValu value, ok := evalDetails.Value.(float64) if !ok { err := errors.New("evaluated value is not a float64") - c.logger.Error( - err, "invalid flag resolution type", "expectedType", "float64", - "gotType", fmt.Sprintf("%T", evalDetails.Value), - ) floatEvalDetails := FloatEvaluationDetails{ Value: defaultValue, EvaluationDetails: evalDetails.EvaluationDetails, @@ -539,10 +525,6 @@ func (c *Client) IntValueDetails(ctx context.Context, flag string, defaultValue value, ok := evalDetails.Value.(int64) if !ok { err := errors.New("evaluated value is not an int64") - c.logger.Error( - err, "invalid flag resolution type", "expectedType", "int64", - "gotType", fmt.Sprintf("%T", evalDetails.Value), - ) intEvalDetails := IntEvaluationDetails{ Value: defaultValue, EvaluationDetails: evalDetails.EvaluationDetails, @@ -698,10 +680,6 @@ func (c *Client) evaluate( evalCtx, err = c.beforeHooks(ctx, hookCtx, apiClientInvocationProviderHooks, evalCtx, options) hookCtx.evaluationContext = evalCtx if err != nil { - c.logger.Error( - err, "before hook", "flag", flag, "defaultValue", defaultValue, - "evaluationContext", evalCtx, "evaluationOptions", options, "type", flagType.String(), - ) err = fmt.Errorf("before hook: %w", err) c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, err, options) return evalDetails, err @@ -736,11 +714,6 @@ func (c *Client) evaluate( err = resolution.Error() if err != nil { - c.logger.Error( - err, "flag resolution", "flag", flag, "defaultValue", defaultValue, - "evaluationContext", evalCtx, "evaluationOptions", options, "type", flagType.String(), "errorCode", err, - "errMessage", resolution.ResolutionError.message, - ) err = fmt.Errorf("error code: %w", err) c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, err, options) evalDetails.ResolutionDetail = resolution.ResolutionDetail() @@ -751,10 +724,6 @@ func (c *Client) evaluate( evalDetails.ResolutionDetail = resolution.ResolutionDetail() if err := c.afterHooks(ctx, hookCtx, providerInvocationClientApiHooks, evalDetails, options); err != nil { - c.logger.Error( - err, "after hook", "flag", flag, "defaultValue", defaultValue, - "evaluationContext", evalCtx, "evaluationOptions", options, "type", flagType.String(), - ) err = fmt.Errorf("after hook: %w", err) c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, err, options) return evalDetails, err diff --git a/openfeature/event_executor.go b/openfeature/event_executor.go index 93d5db4b..388cfb42 100644 --- a/openfeature/event_executor.go +++ b/openfeature/event_executor.go @@ -6,7 +6,8 @@ import ( "sync" "time" - "github.com/go-logr/logr" + "log/slog" + "golang.org/x/exp/maps" ) @@ -37,19 +38,17 @@ type eventExecutor struct { activeSubscriptions []providerReference apiRegistry map[EventType][]EventCallback scopedRegistry map[string]scopedCallback - logger logr.Logger eventChan chan eventPayload once sync.Once mu sync.Mutex } -func newEventExecutor(logger logr.Logger) *eventExecutor { +func newEventExecutor() *eventExecutor { executor := eventExecutor{ namedProviderReference: map[string]providerReference{}, activeSubscriptions: []providerReference{}, apiRegistry: map[EventType][]EventCallback{}, scopedRegistry: map[string]scopedCallback{}, - logger: logger, eventChan: make(chan eventPayload, 5), } @@ -87,14 +86,6 @@ type providerReference struct { shutdownSemaphore chan interface{} } -// updateLogger updates the executor's logger -func (e *eventExecutor) updateLogger(l logr.Logger) { - e.mu.Lock() - defer e.mu.Unlock() - - e.logger = l -} - // AddHandler adds an API(global) level handler func (e *eventExecutor) AddHandler(t EventType, c EventCallback) { e.mu.Lock() @@ -377,7 +368,7 @@ func (e *eventExecutor) executeHandler(f func(details EventDetails), event Event go func() { defer func() { if r := recover(); r != nil { - e.logger.Info("recovered from a panic") + slog.Info("recovered from a panic") } }() diff --git a/openfeature/event_executor_test.go b/openfeature/event_executor_test.go index 2d8339da..83f85b8d 100644 --- a/openfeature/event_executor_test.go +++ b/openfeature/event_executor_test.go @@ -6,13 +6,11 @@ import ( "testing" "time" - "github.com/go-logr/logr" - "github.com/open-feature/go-sdk/openfeature/internal" "golang.org/x/exp/slices" ) func init() { - logger = logr.New(internal.Logger{}) + } // Requirement 5.1.1 The provider MAY define a mechanism for signaling the occurrence of one of a set of events, @@ -31,7 +29,7 @@ func TestEventHandler_RegisterUnregisterEventProvider(t *testing.T) { eventingImpl, } - executor := newEventExecutor(logger) + executor := newEventExecutor() err := executor.registerDefaultProvider(eventingProvider) if err != nil { t.Fatal(err) @@ -1173,7 +1171,7 @@ func TestEventHandler_1ToNMapping(t *testing.T) { &ProviderEventing{}, } - executor := newEventExecutor(logger) + executor := newEventExecutor() err := executor.registerDefaultProvider(eventingProvider) if err != nil { @@ -1215,7 +1213,7 @@ func TestEventHandler_1ToNMapping(t *testing.T) { }, } - executor := newEventExecutor(logger) + executor := newEventExecutor() err := executor.registerDefaultProvider(eventingProvider) if err != nil { @@ -1266,7 +1264,7 @@ func TestEventHandler_1ToNMapping(t *testing.T) { }, } - executor := newEventExecutor(logger) + executor := newEventExecutor() err := executor.registerNamedEventingProvider("clientA", eventingProvider) if err != nil { @@ -1317,7 +1315,7 @@ func TestEventHandler_1ToNMapping(t *testing.T) { }, } - executor := newEventExecutor(logger) + executor := newEventExecutor() err := executor.registerNamedEventingProvider("clientA", eventingProvider) if err != nil { @@ -1354,7 +1352,7 @@ func TestEventHandler_1ToNMapping(t *testing.T) { func TestEventHandler_Registration(t *testing.T) { t.Run("API handlers", func(t *testing.T) { - executor := newEventExecutor(logger) + executor := newEventExecutor() // Add multiple - ProviderReady executor.AddHandler(ProviderReady, &h1) @@ -1392,7 +1390,7 @@ func TestEventHandler_Registration(t *testing.T) { }) t.Run("Client handlers", func(t *testing.T) { - executor := newEventExecutor(logger) + executor := newEventExecutor() // Add multiple - client a executor.AddClientHandler("a", ProviderReady, &h1) @@ -1429,7 +1427,7 @@ func TestEventHandler_Registration(t *testing.T) { func TestEventHandler_APIRemoval(t *testing.T) { t.Run("API level removal", func(t *testing.T) { - executor := newEventExecutor(logger) + executor := newEventExecutor() // Add multiple - ProviderReady executor.AddHandler(ProviderReady, &h1) @@ -1482,7 +1480,7 @@ func TestEventHandler_APIRemoval(t *testing.T) { }) t.Run("Client level removal", func(t *testing.T) { - executor := newEventExecutor(logger) + executor := newEventExecutor() // Add multiple - client a executor.AddClientHandler("a", ProviderReady, &h1) @@ -1539,7 +1537,7 @@ func TestEventHandler_APIRemoval(t *testing.T) { }) t.Run("remove handlers that were not added", func(t *testing.T) { - executor := newEventExecutor(logger) + executor := newEventExecutor() // removal of non-added handlers shall not panic executor.RemoveHandler(ProviderReady, &h1) diff --git a/openfeature/hooks/logging_hook.go b/openfeature/hooks/logging_hook.go new file mode 100644 index 00000000..0d08d66a --- /dev/null +++ b/openfeature/hooks/logging_hook.go @@ -0,0 +1,96 @@ +package hooks + +import ( + "context" + "log/slog" + + of "github.com/open-feature/go-sdk/openfeature" +) + +const ( + DOMAIN_KEY = "domain" + PROVIDER_NAME_KEY = "provider_name" + FLAG_KEY_KEY = "flag_key" + DEFAULT_VALUE_KEY = "default_value" + EVALUATION_CONTEXT_KEY = "evaluation_context" + ERROR_MESSAGE_KEY = "error_message" + REASON_KEY = "reason" + VARIANT_KEY = "variant" + VALUE_KEY = "value" +) + +type LoggingHook struct { + includeEvaluationContext bool + logger *slog.Logger +} + +func NewLoggingHook(includeEvaluationContext bool) (*LoggingHook, error) { + return NewCustomLoggingHook(includeEvaluationContext, slog.Default()) +} + +func NewCustomLoggingHook(includeEvaluationContext bool, logger *slog.Logger) (*LoggingHook, error) { + return &LoggingHook{ + logger: logger, + includeEvaluationContext: includeEvaluationContext, + }, nil +} + +type MarshaledEvaluationContext struct { + TargetingKey string + Attributes map[string]interface{} +} + +func (l LoggingHook) buildArgs(hookContext of.HookContext) ([]interface{}, error) { + + args := []interface{}{ + DOMAIN_KEY, hookContext.ClientMetadata().Domain(), + PROVIDER_NAME_KEY, hookContext.ProviderMetadata().Name, + FLAG_KEY_KEY, hookContext.FlagKey(), + DEFAULT_VALUE_KEY, hookContext.DefaultValue(), + } + if l.includeEvaluationContext { + marshaledEvaluationContext := MarshaledEvaluationContext{ + TargetingKey: hookContext.EvaluationContext().TargetingKey(), + Attributes: hookContext.EvaluationContext().Attributes(), + } + args = append(args, EVALUATION_CONTEXT_KEY, marshaledEvaluationContext) + } + + return args, nil +} + +func (h *LoggingHook) Before(ctx context.Context, hookContext of.HookContext, + hint of.HookHints) (*of.EvaluationContext, error) { + var args, err = h.buildArgs(hookContext) + if err != nil { + return nil, err + } + h.logger.Debug("Before stage", args...) + return nil, nil +} + +func (h *LoggingHook) After(ctx context.Context, hookContext of.HookContext, + flagEvaluationDetails of.InterfaceEvaluationDetails, hookHints of.HookHints) error { + var args, err = h.buildArgs(hookContext) + if err != nil { + return err + } + args = append(args, REASON_KEY, flagEvaluationDetails.Reason) + args = append(args, VARIANT_KEY, flagEvaluationDetails.Variant) + args = append(args, VALUE_KEY, flagEvaluationDetails.Value) + h.logger.Debug("After stage", args...) + return nil +} + +func (h *LoggingHook) Error(ctx context.Context, hookContext of.HookContext, err error, hint of.HookHints) { + args, buildArgsErr := h.buildArgs(hookContext) + if buildArgsErr != nil { + slog.Error("Error building args", "error", buildArgsErr) + } + args = append(args, ERROR_MESSAGE_KEY, err) + h.logger.Error("Error stage", args...) +} + +func (h *LoggingHook) Finally(ctx context.Context, hCtx of.HookContext, hint of.HookHints) { + +} diff --git a/openfeature/hooks/logging_hook_test.go b/openfeature/hooks/logging_hook_test.go new file mode 100644 index 00000000..a8b20a87 --- /dev/null +++ b/openfeature/hooks/logging_hook_test.go @@ -0,0 +1,237 @@ +package hooks + +import ( + "bytes" + "context" + "encoding/json" + "os" + "testing" + + "log/slog" + + "github.com/open-feature/go-sdk/openfeature" + "github.com/open-feature/go-sdk/openfeature/memprovider" +) + +func TestCreateLoggingHookWithDefaultLoggerAndContextInclusion(t *testing.T) { + hook, err := NewLoggingHook(true) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if hook == nil { + t.Fatal("expected a valid LoggingHook, got nil") + } +} + +func TestLoggingHookInitializesCorrectly(t *testing.T) { + logger := slog.New(slog.NewJSONHandler(os.Stderr, nil)) + hook, err := NewCustomLoggingHook(true, logger) + if err != nil { + t.Error("expected no error") + } + + if hook.logger != logger { + t.Errorf("Expected logger to be %v, got %v", logger, hook.logger) + } + + if !hook.includeEvaluationContext { + t.Errorf("Expected includeEvaluationContext to be true, got %v", hook.includeEvaluationContext) + } +} + +func TestLoggingHookHandlesNilLoggerGracefully(t *testing.T) { + hook, err := NewCustomLoggingHook(false, nil) + if err != nil { + t.Error("expected no error") + } + + if hook.logger != nil { + t.Errorf("Expected logger to be nil, got %v", hook.logger) + } + + if hook.includeEvaluationContext { + t.Errorf("Expected includeEvaluationContext to be false, got %v", hook.includeEvaluationContext) + } +} + +func TestLoggingHookLogsMessagesAsExpected(t *testing.T) { + var buf *bytes.Buffer = new(bytes.Buffer) + handler := slog.NewJSONHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + logger := slog.New(handler) + + hook, err := NewCustomLoggingHook(false, logger) + if err != nil { + t.Error("expected no error") + } + + // Check if resultMap contains all key-value pairs from expected + testLoggingHookLogsMessagesAsExpected(*hook, logger, t, buf) +} + +func TestLoggingHookLogsMessagesAsExpectedIncludeEvaluationContext(t *testing.T) { + var buf *bytes.Buffer = new(bytes.Buffer) + handler := slog.NewJSONHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + logger := slog.New(handler) + + hook, err := NewCustomLoggingHook(true, logger) + if err != nil { + t.Error("expected no error") + } + + // Check if resultMap contains all key-value pairs from expected + testLoggingHookLogsMessagesAsExpected(*hook, logger, t, buf) +} + +func testLoggingHookLogsMessagesAsExpected(hook LoggingHook, logger *slog.Logger, t *testing.T, buf *bytes.Buffer) { + if hook.logger != logger { + t.Errorf("Expected logger to be %v, got %v", logger, hook.logger) + } + + memoryProvider := memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{ + "boolFlag": { + Key: "boolFlag", + State: memprovider.Enabled, + DefaultVariant: "true", + Variants: map[string]interface{}{ + "true": true, + "false": false, + }, + ContextEvaluator: nil, + }, + }) + + err := openfeature.SetProviderAndWait(memoryProvider) + if err != nil { + t.Error("error setting provider", err) + } + openfeature.AddHooks(&hook) + client := openfeature.NewClient("test-app") + + t.Run("test boolean success", func(t *testing.T) { + res, err := client.BooleanValue( + context.Background(), + "boolFlag", + false, + openfeature.NewEvaluationContext( + "target1", + map[string]interface{}{ + "color": "green", + }, + ), + ) + if err != nil { + t.Error("expected nil error") + } + if res != true { + t.Errorf("incorect evaluation, expected %t, got %t", true, res) + } + + ms := prepareOutput(buf, t) + + var expected = map[string]map[string]any{ + "Before stage": { + "provider_name": "InMemoryProvider", + "domain": "test-app", + }, + "After stage": { + "provider_name": "InMemoryProvider", + "domain": "test-app", + "flag_key": "boolFlag", + }, + } + + compare(expected, ms, t, hook) + }) + + t.Run("test boolean error", func(t *testing.T) { + res, err := client.BooleanValue( + context.Background(), + "non-existing", + false, + openfeature.NewEvaluationContext( + "target1", + map[string]interface{}{ + "color": "green", + }, + ), + ) + if err == nil { + t.Error("expected error") + } + if res != false { + t.Errorf("incorect evaluation, expected %t, got %t", false, res) + } + + ms := prepareOutput(buf, t) + + var expected = map[string]map[string]any{ + "Before stage": { + "provider_name": "InMemoryProvider", + "domain": "test-app", + }, + "Error stage": { + "provider_name": "InMemoryProvider", + "domain": "test-app", + "flag_key": "non-existing", + }, + } + + compare(expected, ms, t, hook) + }) +} + +func prepareOutput(buf *bytes.Buffer, t *testing.T) map[string]map[string]any { + var ms map[string]map[string]any = make(map[string]map[string]any) + for _, line := range bytes.Split(buf.Bytes(), []byte{'\n'}) { + if len(line) == 0 { + continue + } + var m map[string]any + if err := json.Unmarshal(line, &m); err != nil { + t.Fatal(err) + } + ms[m["msg"].(string)] = m + } + return ms +} + +func compare(expected map[string]map[string]any, ms map[string]map[string]any, t *testing.T, hook LoggingHook) { + for key, expectedInnerMap := range expected { + resultInnerMap, exists := ms[key] + if !exists { + t.Errorf("Key %s not found in resultMap", key) + continue + } + for innerKey, expectedValue := range expectedInnerMap { + resultValue, exists := resultInnerMap[innerKey] + if !exists { + t.Errorf("Inner key %s not found in resultMap[%s]", innerKey, key) + continue + } + if resultValue != expectedValue { + t.Errorf("Value for resultMap[%s][%s] does not match. Expected %v, got %v", key, innerKey, expectedValue, resultValue) + } + + if hook.includeEvaluationContext { + evaluationContext, exists := resultInnerMap[EVALUATION_CONTEXT_KEY] + if !exists { + t.Errorf("Inner key %s not found in resultMap[%s]", EVALUATION_CONTEXT_KEY, key) + } + attributes, attributesExists := evaluationContext.(map[string]any)["Attributes"] + if !attributesExists { + t.Errorf("attributes do not exist") + } + color, colorExists := attributes.(map[string]any)["color"] + if !colorExists { + t.Errorf("color not exist") + } + if color != "green" { + t.Errorf("expected green color in evaluationContext") + } + if evaluationContext.(map[string]any)["TargetingKey"] != "target1" { + t.Errorf("expected TargetingKey in evaluationContext") + } + } + } + } +} diff --git a/openfeature/internal/logger.go b/openfeature/internal/logger.go deleted file mode 100644 index 1b355f96..00000000 --- a/openfeature/internal/logger.go +++ /dev/null @@ -1,25 +0,0 @@ -package internal - -import ( - "log" - - "github.com/go-logr/logr" -) - -// Logger is the sdk's default logr.LogSink implementation. -// Logs using this logger logs only on error, all other logs are no-ops -type Logger struct{} - -func (l Logger) Init(info logr.RuntimeInfo) {} - -func (l Logger) Enabled(level int) bool { return true } - -func (l Logger) Info(level int, msg string, keysAndValues ...interface{}) {} - -func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) { - log.Println("openfeature:", err) -} - -func (l Logger) WithValues(keysAndValues ...interface{}) logr.LogSink { return l } - -func (l Logger) WithName(name string) logr.LogSink { return l } diff --git a/openfeature/openfeature.go b/openfeature/openfeature.go index 84551286..c6f297fc 100644 --- a/openfeature/openfeature.go +++ b/openfeature/openfeature.go @@ -1,14 +1,10 @@ package openfeature -import ( - "github.com/go-logr/logr" - "github.com/open-feature/go-sdk/openfeature/internal" -) +import "github.com/go-logr/logr" // api is the global evaluationImpl implementation. This is a singleton and there can only be one instance. var api evaluationImpl var eventing eventingImpl -var logger logr.Logger // init initializes the OpenFeature evaluation API func init() { @@ -16,12 +12,10 @@ func init() { } func initSingleton() { - logger = logr.New(internal.Logger{}) - - var exec = newEventExecutor(logger) + var exec = newEventExecutor() eventing = exec - api = newEvaluationAPI(exec, logger) + api = newEvaluationAPI(exec) } // GetApiInstance returns the current singleton IEvaluation instance. @@ -69,9 +63,9 @@ func SetEvaluationContext(evalCtx EvaluationContext) { api.SetEvaluationContext(evalCtx) } +// Deprecated // SetLogger sets the global Logger. func SetLogger(l logr.Logger) { - api.SetLogger(l) } // AddHooks appends to the collection of any previously added hooks diff --git a/openfeature/openfeature_api.go b/openfeature/openfeature_api.go index 4c6a5c12..0d854dfa 100644 --- a/openfeature/openfeature_api.go +++ b/openfeature/openfeature_api.go @@ -15,7 +15,10 @@ type evaluationImpl interface { GetProvider() FeatureProvider GetNamedProviders() map[string]FeatureProvider GetHooks() []Hook + + // Deprecated SetLogger(l logr.Logger) + ForEvaluation(clientName string) (FeatureProvider, []Hook, EvaluationContext) } @@ -26,18 +29,16 @@ type evaluationAPI struct { hks []Hook apiCtx EvaluationContext eventExecutor *eventExecutor - logger logr.Logger mu sync.RWMutex } // newEvaluationAPI is a helper to generate an API. Used internally -func newEvaluationAPI(eventExecutor *eventExecutor, log logr.Logger) *evaluationAPI { +func newEvaluationAPI(eventExecutor *eventExecutor) *evaluationAPI { return &evaluationAPI{ defaultProvider: NoopProvider{}, namedProviders: map[string]FeatureProvider{}, hks: []Hook{}, apiCtx: EvaluationContext{}, - logger: log, mu: sync.RWMutex{}, eventExecutor: eventExecutor, } @@ -109,12 +110,12 @@ func (api *evaluationAPI) GetNamedProviders() map[string]FeatureProvider { // GetClient returns a IClient bound to the default provider func (api *evaluationAPI) GetClient() IClient { - return newClient("", api, api.eventExecutor, api.logger) + return newClient("", api, api.eventExecutor) } // GetNamedClient returns a IClient bound to the given named provider func (api *evaluationAPI) GetNamedClient(clientName string) IClient { - return newClient(clientName, api, api.eventExecutor, api.logger) + return newClient(clientName, api, api.eventExecutor) } func (api *evaluationAPI) SetEvaluationContext(apiCtx EvaluationContext) { @@ -124,12 +125,9 @@ func (api *evaluationAPI) SetEvaluationContext(apiCtx EvaluationContext) { api.apiCtx = apiCtx } +// Deprecated func (api *evaluationAPI) SetLogger(l logr.Logger) { - api.mu.Lock() - defer api.mu.Unlock() - api.logger = l - api.eventExecutor.updateLogger(l) } func (api *evaluationAPI) AddHooks(hooks ...Hook) { diff --git a/openfeature/openfeature_test.go b/openfeature/openfeature_test.go index da81bf65..f01eacd8 100644 --- a/openfeature/openfeature_test.go +++ b/openfeature/openfeature_test.go @@ -6,9 +6,7 @@ import ( "testing" "time" - "github.com/go-logr/logr" "github.com/golang/mock/gomock" - "github.com/open-feature/go-sdk/openfeature/internal" ) // The `API`, and any state it maintains SHOULD exist as a global singleton, @@ -720,18 +718,6 @@ func TestDefaultClientUsage(t *testing.T) { } } -// Ability to override default logger -func TestLoggerOverride(t *testing.T) { - defer t.Cleanup(initSingleton) - - newOverride := internal.Logger{} - SetLogger(logr.New(newOverride)) - - if !reflect.DeepEqual(logger.GetSink(), newOverride) { - t.Error("logger overriding failed") - } -} - // Nil providers are not accepted for default and named providers func TestForNilProviders(t *testing.T) { defer t.Cleanup(initSingleton)