mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
Re-sync the entire SigNoz-derived Go tree to a clean fork of latest SigNoz main (upstream 3e6339019), eliminating the version-skew that broke `go build ./...` (mixed-era packages, ~14 imported-but-absent packages, partial signoz->o11y / clickhouse->datastore rebrands, self-import cycle, duplicate package decls). - pkg/ + cmd/ wholesale replaced with latest SigNoz; imports rewritten github.com/SigNoz/signoz/ -> github.com/hanzoai/o11y/. - Adopt SigNoz's CURRENT structure (resourcefilter->telemetryresourcefilter, integrationtypes->cloudintegrationtypes, roletypes/grammar/times folded in upstream); old paths dropped. - Fork deps repathed to hanzoai + bumped: signoz-otel-collector v0.144.6, clickhouse-go-mock v0.14.1, govaluate v0.1.0; expr-lang/expr => hanzoai/expr v1.17.8 (replace). go.mod reconciled from latest SigNoz; go.sum re-tidied. - ee/ + cmd/enterprise stripped — o11y stays MIT-only; community edition imports no ee packages. - Preserved o11y additions: module path github.com/hanzoai/o11y, mount.go (cloud.Register/zip mount adapter, HIP-0106), NOTICE + LICENSE (SigNoz §4c attribution retained; upstream pkg files are headerless per SigNoz convention). - Reverted o11y cosmetic renames (o11y*/datastore*) and experimental originals (authz/iamauthz, billing, zapreceiver, zapmetricreceiver) to SigNoz canonical to kill the skew; snapshots retained in git history for re-layering. go build ./... = exit 0. go vet clean except benign "unreachable code" in generated ANTLR parser files (inherited from upstream).
362 lines
14 KiB
Go
362 lines
14 KiB
Go
package flagger
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
|
|
"github.com/open-feature/go-sdk/openfeature"
|
|
|
|
"github.com/hanzoai/o11y/pkg/errors"
|
|
"github.com/hanzoai/o11y/pkg/factory"
|
|
"github.com/hanzoai/o11y/pkg/types/featuretypes"
|
|
)
|
|
|
|
// Any feature flag provider has to implement this interface.
|
|
type FlaggerProvider interface {
|
|
openfeature.FeatureProvider
|
|
|
|
// List returns all the feature flags
|
|
List(ctx context.Context) ([]*featuretypes.GettableFeature, error)
|
|
}
|
|
|
|
// This is the consumer facing interface for the Flagger service.
|
|
type Flagger interface {
|
|
// Returns value for the flag of kind boolean otherwise returns error
|
|
Boolean(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) (bool, error)
|
|
|
|
// Returns value for the flag of kind string otherwise returns error
|
|
String(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) (string, error)
|
|
|
|
// Returns value for the flag of kind float otherwise returns error
|
|
Float(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) (float64, error)
|
|
|
|
// Returns value for the flag of kind int otherwise returns error
|
|
Int(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) (int64, error)
|
|
|
|
// Returns value for the flag of kind object otherwise returns error
|
|
Object(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) (any, error)
|
|
|
|
// Returns value for the flag of kind boolean otherwise returns empty (default) value
|
|
BooleanOrEmpty(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) bool
|
|
|
|
// Returns value for the flag of kind string otherwise returns empty (default) value
|
|
StringOrEmpty(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) string
|
|
|
|
// Returns value for the flag of kind float otherwise returns empty (default) value
|
|
FloatOrEmpty(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) float64
|
|
|
|
// Returns value for the flag of kind int otherwise returns empty (default) value
|
|
IntOrEmpty(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) int64
|
|
|
|
// Returns value for the flag of kind object otherwise returns empty (default) value
|
|
ObjectOrEmpty(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) any
|
|
|
|
// Returns all the features in the registry
|
|
List(ctx context.Context, evalCtx featuretypes.FlaggerEvaluationContext) ([]*featuretypes.GettableFeature, error)
|
|
}
|
|
|
|
// This is the concrete implementation of the Flagger interface.
|
|
type flagger struct {
|
|
registry featuretypes.Registry
|
|
settings factory.ScopedProviderSettings
|
|
providers map[string]FlaggerProvider
|
|
clients map[string]*openfeature.Client
|
|
}
|
|
|
|
func New(ctx context.Context, ps factory.ProviderSettings, config Config, registry featuretypes.Registry, factories ...factory.ProviderFactory[FlaggerProvider, Config]) (Flagger, error) {
|
|
settings := factory.NewScopedProviderSettings(ps, "github.com/hanzoai/o11y/pkg/flagger")
|
|
|
|
providers := make(map[string]FlaggerProvider)
|
|
clients := make(map[string]*openfeature.Client)
|
|
|
|
for _, factory := range factories {
|
|
provider, err := factory.New(ctx, ps, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
providers[provider.Metadata().Name] = provider
|
|
|
|
openfeatureClient := openfeature.NewClient(provider.Metadata().Name)
|
|
|
|
if err := openfeature.SetNamedProviderAndWait(provider.Metadata().Name, provider); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
clients[provider.Metadata().Name] = openfeatureClient
|
|
}
|
|
|
|
return &flagger{
|
|
registry: registry,
|
|
settings: settings,
|
|
providers: providers,
|
|
clients: clients,
|
|
}, nil
|
|
}
|
|
|
|
func (f *flagger) Boolean(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) (bool, error) {
|
|
// check if the feature is present in the default registry
|
|
feature, _, err := f.registry.Get(flag)
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get feature from default registry", errors.Attr(err), slog.Any("flag", flag))
|
|
return false, err
|
|
}
|
|
|
|
// get the default value from the feature from default registry
|
|
defaultValue, _, err := featuretypes.VariantValue[bool](feature, feature.DefaultVariant)
|
|
if err != nil {
|
|
// something which should never happen
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get default value from feature", errors.Attr(err), slog.Any("flag", flag))
|
|
return false, err
|
|
}
|
|
|
|
// * this logic can be optimised based on priority of the clients and short circuiting
|
|
// now ask all the available clients for the value
|
|
for _, client := range f.clients {
|
|
value, err := client.BooleanValue(ctx, flag.String(), defaultValue, evalCtx.Ctx())
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get value from client", errors.Attr(err), slog.Any("flag", flag), slog.String("client", client.Metadata().Domain()))
|
|
continue
|
|
}
|
|
|
|
if value != defaultValue {
|
|
return value, nil
|
|
}
|
|
}
|
|
|
|
return defaultValue, nil
|
|
}
|
|
|
|
func (f *flagger) String(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) (string, error) {
|
|
// check if the feature is present in the default registry
|
|
feature, _, err := f.registry.Get(flag)
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get feature from default registry", errors.Attr(err), slog.Any("flag", flag))
|
|
return "", err
|
|
}
|
|
|
|
// get the default value from the feature from default registry
|
|
defaultValue, _, err := featuretypes.VariantValue[string](feature, feature.DefaultVariant)
|
|
if err != nil {
|
|
// something which should never happen
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get default value from feature", errors.Attr(err), slog.Any("flag", flag))
|
|
return "", err
|
|
}
|
|
|
|
// * this logic can be optimised based on priority of the clients and short circuiting
|
|
// now ask all the available clients for the value
|
|
for _, client := range f.clients {
|
|
value, err := client.StringValue(ctx, flag.String(), defaultValue, evalCtx.Ctx())
|
|
if err != nil {
|
|
f.settings.Logger().WarnContext(ctx, "failed to get value from client", errors.Attr(err), slog.Any("flag", flag), slog.String("client", client.Metadata().Domain()))
|
|
continue
|
|
}
|
|
|
|
if value != defaultValue {
|
|
return value, nil
|
|
}
|
|
}
|
|
|
|
return defaultValue, nil
|
|
}
|
|
|
|
func (f *flagger) Float(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) (float64, error) {
|
|
// check if the feature is present in the default registry
|
|
feature, _, err := f.registry.Get(flag)
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get feature from default registry", errors.Attr(err), slog.Any("flag", flag))
|
|
return 0, err
|
|
}
|
|
|
|
// get the default value from the feature from default registry
|
|
defaultValue, _, err := featuretypes.VariantValue[float64](feature, feature.DefaultVariant)
|
|
if err != nil {
|
|
// something which should never happen
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get default value from feature", errors.Attr(err), slog.Any("flag", flag))
|
|
return 0, err
|
|
}
|
|
|
|
// * this logic can be optimised based on priority of the clients and short circuiting
|
|
// now ask all the available clients for the value
|
|
for _, client := range f.clients {
|
|
value, err := client.FloatValue(ctx, flag.String(), defaultValue, evalCtx.Ctx())
|
|
if err != nil {
|
|
f.settings.Logger().WarnContext(ctx, "failed to get value from client", errors.Attr(err), slog.Any("flag", flag), slog.String("client", client.Metadata().Domain()))
|
|
continue
|
|
}
|
|
|
|
if value != defaultValue {
|
|
return value, nil
|
|
}
|
|
}
|
|
|
|
return defaultValue, nil
|
|
}
|
|
|
|
func (f *flagger) Int(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) (int64, error) {
|
|
// check if the feature is present in the default registry
|
|
feature, _, err := f.registry.Get(flag)
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get feature from default registry", errors.Attr(err), slog.Any("flag", flag))
|
|
return 0, err
|
|
}
|
|
|
|
// get the default value from the feature from default registry
|
|
defaultValue, _, err := featuretypes.VariantValue[int64](feature, feature.DefaultVariant)
|
|
if err != nil {
|
|
// something which should never happen
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get default value from feature", errors.Attr(err), slog.Any("flag", flag))
|
|
return 0, err
|
|
}
|
|
|
|
// * this logic can be optimised based on priority of the clients and short circuiting
|
|
// now ask all the available clients for the value
|
|
for _, client := range f.clients {
|
|
value, err := client.IntValue(ctx, flag.String(), defaultValue, evalCtx.Ctx())
|
|
if err != nil {
|
|
f.settings.Logger().WarnContext(ctx, "failed to get value from client", errors.Attr(err), slog.Any("flag", flag), slog.String("client", client.Metadata().Domain()))
|
|
continue
|
|
}
|
|
|
|
if value != defaultValue {
|
|
return value, nil
|
|
}
|
|
}
|
|
|
|
return defaultValue, nil
|
|
}
|
|
|
|
func (f *flagger) Object(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) (any, error) {
|
|
// check if the feature is present in the default registry
|
|
feature, _, err := f.registry.Get(flag)
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get feature from default registry", errors.Attr(err), slog.Any("flag", flag))
|
|
return nil, err
|
|
}
|
|
|
|
// get the default value from the feature from default registry
|
|
defaultValue, _, err := featuretypes.VariantValue[any](feature, feature.DefaultVariant)
|
|
if err != nil {
|
|
// something which should never happen
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get default value from feature", errors.Attr(err), slog.Any("flag", flag))
|
|
return nil, err
|
|
}
|
|
|
|
// * this logic can be optimised based on priority of the clients and short circuiting
|
|
// now ask all the available clients for the value
|
|
for _, client := range f.clients {
|
|
value, err := client.ObjectValue(ctx, flag.String(), defaultValue, evalCtx.Ctx())
|
|
if err != nil {
|
|
f.settings.Logger().WarnContext(ctx, "failed to get value from client", errors.Attr(err), slog.Any("flag", flag), slog.String("client", client.Metadata().Domain()))
|
|
continue
|
|
}
|
|
|
|
// ! for object we do not compare with the default value for now, we will figure this out better in future coming releases
|
|
// if value != defaultValue {
|
|
// return value, nil
|
|
// }
|
|
return value, nil
|
|
}
|
|
|
|
return defaultValue, nil
|
|
}
|
|
|
|
func (f *flagger) BooleanOrEmpty(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) bool {
|
|
defaultValue := false
|
|
value, err := f.Boolean(ctx, flag, evalCtx)
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get value from flagger service", errors.Attr(err), slog.Any("flag", flag))
|
|
return defaultValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (f *flagger) StringOrEmpty(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) string {
|
|
defaultValue := ""
|
|
value, err := f.String(ctx, flag, evalCtx)
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get value from flagger service", errors.Attr(err), slog.Any("flag", flag))
|
|
return defaultValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (f *flagger) FloatOrEmpty(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) float64 {
|
|
defaultValue := 0.0
|
|
value, err := f.Float(ctx, flag, evalCtx)
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get value from flagger service", errors.Attr(err), slog.Any("flag", flag))
|
|
return defaultValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (f *flagger) IntOrEmpty(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) int64 {
|
|
defaultValue := int64(0)
|
|
value, err := f.Int(ctx, flag, evalCtx)
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get value from flagger service", errors.Attr(err), slog.Any("flag", flag))
|
|
return defaultValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (f *flagger) ObjectOrEmpty(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.FlaggerEvaluationContext) any {
|
|
defaultValue := struct{}{}
|
|
value, err := f.Object(ctx, flag, evalCtx)
|
|
if err != nil {
|
|
f.settings.Logger().ErrorContext(ctx, "failed to get value from flagger service", errors.Attr(err), slog.Any("flag", flag))
|
|
return defaultValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (f *flagger) List(ctx context.Context, evalCtx featuretypes.FlaggerEvaluationContext) ([]*featuretypes.GettableFeature, error) {
|
|
// get all the feature from the default registry
|
|
allFeatures := f.registry.List()
|
|
|
|
// make a map of name of feature -> the dict we want to create from all features
|
|
featureMap := make(map[string]*featuretypes.GettableFeature, len(allFeatures))
|
|
|
|
for _, feature := range allFeatures {
|
|
variants := make(map[string]any, len(feature.Variants))
|
|
for name, value := range feature.Variants {
|
|
variants[name.String()] = value.Value
|
|
}
|
|
|
|
featureMap[feature.Name.String()] = &featuretypes.GettableFeature{
|
|
Name: feature.Name.String(),
|
|
Kind: feature.Kind.StringValue(),
|
|
Stage: feature.Stage.StringValue(),
|
|
Description: feature.Description,
|
|
DefaultVariant: feature.DefaultVariant.String(),
|
|
Variants: variants,
|
|
ResolvedValue: feature.Variants[feature.DefaultVariant].Value,
|
|
}
|
|
}
|
|
|
|
// now call each provider and fix the value in feature map
|
|
for _, provider := range f.providers {
|
|
pFeatures, err := provider.List(ctx)
|
|
if err != nil {
|
|
f.settings.Logger().WarnContext(ctx, "failed to get features from provider", errors.Attr(err), slog.String("provider", provider.Metadata().Name))
|
|
continue
|
|
}
|
|
|
|
// merge
|
|
for _, pFeature := range pFeatures {
|
|
if existing, ok := featureMap[pFeature.Name]; ok {
|
|
existing.ResolvedValue = pFeature.ResolvedValue
|
|
}
|
|
}
|
|
}
|
|
|
|
result := make([]*featuretypes.GettableFeature, 0, len(allFeatures))
|
|
|
|
for _, f := range featureMap {
|
|
result = append(result, f)
|
|
}
|
|
|
|
return result, nil
|
|
}
|