Skip to main content

2 posts tagged with "hooks"

View All Tags

· 2 min read
Skye Gill

Logging

Logging is the act of keeping a log. A log (in this case) records events that occur in software.

Subject to many opinions and differing principles of best practice, the best thing we could do for the go-sdk was to create an implementation as open & configurable as possible. To achieve this, we've integrated logr, this allows the use of any logger that conforms to its API.

Applications may already have a chosen logging solution at the point of introducing openfeature. An integration with logr may already exist for their chosen solution (integrations exist for many of the popular logger packages in go). If not, they could write their own integration.

Objective

Configure the popular go logger zap with the go-sdk.

Prerequisites

  • Golang 1.17+

Scaffolding

  1. Go get the following dependencies

    go get github.com/open-feature/go-sdk
    go get go.uber.org/zap
    go get github.com/go-logr/logr
    go get github.com/go-logr/zapr # an integration of zap with logr's API
  2. Import all of the above into your main.go and create func main()

    package main

    import (
    "github.com/go-logr/logr"
    "github.com/go-logr/zapr"
    "github.com/open-feature/go-sdk/pkg/openfeature"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "context"
    "log"
    )

    func main() {

    }

Integrating the logger

  1. Create the zap logger with preset development config (for the sake of this tutorial)

    func main() {
    zc := zap.NewDevelopmentConfig()
    zc.Level = zap.NewAtomicLevelAt(zapcore.Level(-1)) // the level here decides the verbosity of our logs
    z, err := zc.Build()
    if err != nil {
    log.Fatal(err)
    }
    }
  2. Create the zapr logger (zap logger that conforms to logr's interface)

    l := zapr.NewLogger(z)
  3. Set the logger to the global openfeature singleton

    openfeature.SetLogger(l)
  4. Create an openfeature client and invoke a flag evaluation

    c := openfeature.NewClient("log")

    evalCtx := openfeature.NewEvaluationContext("foo", nil)

    c.BooleanValue(context.Background(), "bar", false, evalCtx)
  5. Check the result of go run main.go

    2022-09-02T14:22:31.109+0100    INFO    openfeature/openfeature.go:76   set global logger
    2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:230 evaluating flag {"flag": "bar", "type": "bool", "defaultValue": false, "evaluationContext": {"targetingKey":"foo","attributes":null}, "evaluationOptions": {}}
    2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:336 executing before hooks
    2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:349 executed before hooks
    2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:355 executing after hooks
    2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:364 executed after hooks
    2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:318 evaluated flag {"flag": "bar", "details": {"FlagKey":"bar","FlagType":0,"Value":false,"ErrorCode":"","Reason":"","Variant":""}, "type": "bool"}
    2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:377 executing finally hooks
    2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:383 executed finally hooks
  6. (optional) Tweak the level set in step 1 to decrease the verbosity

· 3 min read
Skye Gill

Hooks

A Hook taps into one or more of the flag evaluation's lifecycle events (before/after/error/finally) to perform the same action at that point for every evaluation.

Objective

Create and integrate a spec compliant hook that validates that the result of a flag evaluation is a hex color.

Prerequisites

  • Golang 1.17+

Repository setup

Hooks written for the go-sdk are all maintained in the go-sdk-contrib repository, containing both hooks and providers. The following commands can be used to setup the go-sdk-contrib repository, this clones the repository and sets up your hook specific go module under /hooks/MY-NEW-HOOK-NAME adding a go.mod and README.md file. The module will then be referenced in the top level go.work file.

git clone https://github.com/open-feature/go-sdk-contrib.git
cd go-sdk-contrib
make HOOK=MY-NEW-HOOK-NAME new-hook
make workspace-init

Creating the hook

In order for the Hook to be compatible with the OpenFeature go-sdk, it will need to comply to the OpenFeature spec. For the go-sdk this means that it will need to conform to the following interface:

type Hook interface {
Before(ctx context.Context, hookContext HookContext, hookHints HookHints) (*EvaluationContext, error)
After(ctx context.Context, hookContext HookContext, flagEvaluationDetails InterfaceEvaluationDetails, hookHints HookHints) error
Error(ctx context.Context, hookContext HookContext, err error, hookHints HookHints)
Finally(ctx context.Context, hookContext HookContext, hookHints HookHints)
}

In order to avoid implementing all the required functions to conform to the interface, embed the of.UnimplementedHook struct in the Hook struct. Then define the After lifecycle event, overriding of.UnimplementedHook's null implementation.

// Hook validates the flag evaluation details After flag resolution
type Hook struct {
of.UnimplementedHook
Validator validator
}

func (h Hook) After(ctx context.Context, hookContext of.HookContext, flagEvaluationDetails of.InterfaceEvaluationDetails, hookHints of.HookHints) error {
err := h.Validator.IsValid(flagEvaluationDetails)
if err != nil {
return err
}

return nil
}

Notice the Validator field of type validator in the Hook struct. This is defined as such:

type validator interface {
IsValid(flagEvaluationDetails of.InterfaceEvaluationDetails) error
}

This allows us to supply any validator that implements this function signature, you can either create your own validator or use one of the existing validators. This tutorial uses the existing hex regex validator.

Integrating the hook

  1. Install dependencies

    go get github.com/open-feature/go-sdk
    go get github.com/open-feature/go-sdk-contrib/hooks/validator
  2. Import the dependencies

    package main

    import (
    "context"
    "fmt"
    "github.com/open-feature/go-sdk-contrib/hooks/validator/pkg/regex"
    "github.com/open-feature/go-sdk-contrib/hooks/validator/pkg/validator"
    "github.com/open-feature/go-sdk/pkg/openfeature"
    "log"
    )
  3. Create an instance of the validator hook struct using the regex hex validator

    func main() {
    hexValidator, err := regex.Hex()
    if err != nil {
    log.Fatal(err)
    }
    v := validator.Hook{Validator: hexValidator}
    }
  4. Register the NoopProvider, this simply returns the given default value on flag evaluation.

    This step is optional, the sdk uses the NoopProvider by default but we're explicitly setting it for completeness

    openfeature.SetProvider(openfeature.NoopProvider{})
  5. Create the client, call the flag evaluation using the validator hook at the point of invocation

    client := openfeature.NewClient("foo")

    result, err := client.
    StringValueDetails(
    context.Background(),
    "blue",
    "invalidhex",
    openfeature.EvaluationContext{},
    openfeature.WithHooks(v),
    )
    if err != nil {
    fmt.Println("err:", err)
    }
    fmt.Println("result:", result)
  6. Check that the flag evaluation returns an error as invalidhex is not a valid hex color

    go run main.go
    err: execute after hook: regex doesn't match on flag value
    result {blue 1 {invalidhex }}

    Note that despite getting an error we still get a result.