Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FSSDK-9862] Add OpenTelemetry Tracing support #385

Merged
merged 21 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions examples/benchmark/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
package main

import (
"io/ioutil"
"log"
"os"
"path"

"github.com/pkg/profile"

"github.com/optimizely/go-sdk/pkg/client"
"github.com/optimizely/go-sdk/pkg/decision"
"github.com/optimizely/go-sdk/pkg/entities"

"github.com/pkg/profile"
)

func stressTest() {
Expand All @@ -23,7 +22,7 @@ func stressTest() {

var datafileDir = path.Join(os.Getenv("DATAFILES_DIR"), "100_entities.json")

datafile, err := ioutil.ReadFile(datafileDir)
datafile, err := os.ReadFile(datafileDir)
if err != nil {
log.Print(err)
}
Expand Down
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,24 @@ require (
github.com/json-iterator/go v1.1.12
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.7.0
github.com/stretchr/testify v1.8.2
github.com/stretchr/testify v1.8.4
github.com/twmb/murmur3 v1.1.6
go.opentelemetry.io/otel v1.21.0
go.opentelemetry.io/otel/trace v1.21.0
golang.org/x/sync v0.1.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
17 changes: 15 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
Expand Down Expand Up @@ -37,10 +44,16 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
83 changes: 80 additions & 3 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019-2023, Optimizely, Inc. and contributors *
* Copyright 2019-2024, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -18,13 +18,16 @@
package client

import (
"context"
"encoding/json"
"errors"
"fmt"
"reflect"
"runtime/debug"
"strconv"

"github.com/hashicorp/go-multierror"

"github.com/optimizely/go-sdk/pkg/config"
"github.com/optimizely/go-sdk/pkg/decide"
"github.com/optimizely/go-sdk/pkg/decision"
Expand All @@ -37,13 +40,18 @@ import (
pkgOdpSegment "github.com/optimizely/go-sdk/pkg/odp/segment"
pkgOdpUtils "github.com/optimizely/go-sdk/pkg/odp/utils"
"github.com/optimizely/go-sdk/pkg/optimizelyjson"
"github.com/optimizely/go-sdk/pkg/tracing"
"github.com/optimizely/go-sdk/pkg/utils"
)

"github.com/hashicorp/go-multierror"
const (
// DefaultTracerName is the name of the tracer used by the Optimizely SDK
DefaultTracerName string = "OptimizelySDK"
)

// OptimizelyClient is the entry point to the Optimizely SDK
type OptimizelyClient struct {
ctx context.Context
ConfigManager config.ProjectConfigManager
DecisionService decision.Service
EventProcessor event.Processor
Expand All @@ -52,6 +60,7 @@ type OptimizelyClient struct {
execGroup *utils.ExecGroup
logger logging.OptimizelyLogProducer
defaultDecideOptions *decide.Options
tracer tracing.Tracer
}

// CreateUserContext creates a context of the user for which decision APIs will be called.
Expand All @@ -65,6 +74,12 @@ func (o *OptimizelyClient) CreateUserContext(userID string, attributes map[strin
return newOptimizelyUserContext(o, userID, attributes, nil, nil)
}

// WithTraceContext sets the context for the OptimizelyClient which can be used to propagate trace information
func (o *OptimizelyClient) WithTraceContext(ctx context.Context) *OptimizelyClient {
o.ctx = ctx
return o
}

func (o *OptimizelyClient) decide(userContext OptimizelyUserContext, key string, options *decide.Options) OptimizelyDecision {
var err error
defer func() {
Expand All @@ -83,6 +98,9 @@ func (o *OptimizelyClient) decide(userContext OptimizelyUserContext, key string,
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "decide")
pulak-opti marked this conversation as resolved.
Show resolved Hide resolved
defer span.End()

decisionContext := decision.FeatureDecisionContext{
ForcedDecisionService: userContext.forcedDecisionService,
}
Expand Down Expand Up @@ -188,6 +206,9 @@ func (o *OptimizelyClient) decideForKeys(userContext OptimizelyUserContext, keys
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "decideForKeys")
defer span.End()

decisionMap := map[string]OptimizelyDecision{}
if _, err = o.getProjectConfig(); err != nil {
o.logger.Error("Optimizely instance is not valid, failing decideForKeys call.", err)
Expand Down Expand Up @@ -228,6 +249,9 @@ func (o *OptimizelyClient) decideAll(userContext OptimizelyUserContext, options
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "decideForKeys")
defer span.End()

projectConfig, err := o.getProjectConfig()
if err != nil {
o.logger.Error("Optimizely instance is not valid, failing decideAll call.", err)
Expand Down Expand Up @@ -261,6 +285,9 @@ func (o *OptimizelyClient) fetchQualifiedSegments(userContext *OptimizelyUserCon
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "fetchQualifiedSegments")
defer span.End()

// on failure, qualifiedSegments should be reset if a previous value exists.
userContext.SetQualifiedSegments(nil)

Expand Down Expand Up @@ -305,6 +332,9 @@ func (o *OptimizelyClient) SendOdpEvent(eventType, action string, identifiers ma
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "SendOdpEvent")
pulak-opti marked this conversation as resolved.
Show resolved Hide resolved
defer span.End()

if _, err = o.getProjectConfig(); err != nil {
o.logger.Error("SendOdpEvent failed with error:", decide.GetDecideError(decide.SDKNotReady))
return err
Expand Down Expand Up @@ -344,6 +374,9 @@ func (o *OptimizelyClient) Activate(experimentKey string, userContext entities.U
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "Activate")
pulak-opti marked this conversation as resolved.
Show resolved Hide resolved
defer span.End()

decisionContext, experimentDecision, err := o.getExperimentDecision(experimentKey, userContext)
if err != nil {
o.logger.Error("received an error while computing experiment decision", err)
Expand Down Expand Up @@ -382,6 +415,9 @@ func (o *OptimizelyClient) IsFeatureEnabled(featureKey string, userContext entit
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "IsFeatureEnabled")
defer span.End()

decisionContext, featureDecision, err := o.getFeatureDecision(featureKey, "", userContext)
if err != nil {
o.logger.Error("received an error while computing feature decision", err)
Expand Down Expand Up @@ -436,6 +472,9 @@ func (o *OptimizelyClient) GetEnabledFeatures(userContext entities.UserContext)
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetEnabledFeatures")
defer span.End()

projectConfig, err := o.getProjectConfig()
if err != nil {
o.logger.Error("Error retrieving ProjectConfig", err)
Expand All @@ -453,6 +492,8 @@ func (o *OptimizelyClient) GetEnabledFeatures(userContext entities.UserContext)

// GetFeatureVariableBoolean returns the feature variable value of type bool associated with the given feature and variable keys.
func (o *OptimizelyClient) GetFeatureVariableBoolean(featureKey, variableKey string, userContext entities.UserContext) (convertedValue bool, err error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetFeatureVariableBoolean")
defer span.End()

stringValue, variableType, featureDecision, err := o.getFeatureVariable(featureKey, variableKey, userContext)
defer func() {
Expand Down Expand Up @@ -486,6 +527,8 @@ func (o *OptimizelyClient) GetFeatureVariableBoolean(featureKey, variableKey str

// GetFeatureVariableDouble returns the feature variable value of type double associated with the given feature and variable keys.
func (o *OptimizelyClient) GetFeatureVariableDouble(featureKey, variableKey string, userContext entities.UserContext) (convertedValue float64, err error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetFeatureVariableDouble")
defer span.End()

stringValue, variableType, featureDecision, err := o.getFeatureVariable(featureKey, variableKey, userContext)
defer func() {
Expand Down Expand Up @@ -519,6 +562,8 @@ func (o *OptimizelyClient) GetFeatureVariableDouble(featureKey, variableKey stri

// GetFeatureVariableInteger returns the feature variable value of type int associated with the given feature and variable keys.
func (o *OptimizelyClient) GetFeatureVariableInteger(featureKey, variableKey string, userContext entities.UserContext) (convertedValue int, err error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetFeatureVariableInteger")
defer span.End()

stringValue, variableType, featureDecision, err := o.getFeatureVariable(featureKey, variableKey, userContext)
defer func() {
Expand Down Expand Up @@ -552,6 +597,8 @@ func (o *OptimizelyClient) GetFeatureVariableInteger(featureKey, variableKey str

// GetFeatureVariableString returns the feature variable value of type string associated with the given feature and variable keys.
func (o *OptimizelyClient) GetFeatureVariableString(featureKey, variableKey string, userContext entities.UserContext) (stringValue string, err error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetFeatureVariableString")
defer span.End()

stringValue, variableType, featureDecision, err := o.getFeatureVariable(featureKey, variableKey, userContext)

Expand Down Expand Up @@ -583,6 +630,8 @@ func (o *OptimizelyClient) GetFeatureVariableString(featureKey, variableKey stri

// GetFeatureVariableJSON returns the feature variable value of type json associated with the given feature and variable keys.
func (o *OptimizelyClient) GetFeatureVariableJSON(featureKey, variableKey string, userContext entities.UserContext) (optlyJSON *optimizelyjson.OptimizelyJSON, err error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetFeatureVariableJSON")
defer span.End()

stringVal, variableType, featureDecision, err := o.getFeatureVariable(featureKey, variableKey, userContext)
defer func() {
Expand Down Expand Up @@ -620,6 +669,8 @@ func (o *OptimizelyClient) GetFeatureVariableJSON(featureKey, variableKey string

// getFeatureVariable is a helper function, returns feature variable as a string along with it's associated type and feature decision
func (o *OptimizelyClient) getFeatureVariable(featureKey, variableKey string, userContext entities.UserContext) (string, entities.VariableType, *decision.FeatureDecision, error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "getFeatureVariable")
defer span.End()

featureDecisionContext, featureDecision, err := o.getFeatureDecision(featureKey, variableKey, userContext)
if err != nil {
Expand All @@ -639,6 +690,8 @@ func (o *OptimizelyClient) getFeatureVariable(featureKey, variableKey string, us

// GetFeatureVariable returns feature variable as a string along with it's associated type.
func (o *OptimizelyClient) GetFeatureVariable(featureKey, variableKey string, userContext entities.UserContext) (string, entities.VariableType, error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetFeatureVariable")
defer span.End()

stringValue, variableType, featureDecision, err := o.getFeatureVariable(featureKey, variableKey, userContext)

Expand Down Expand Up @@ -680,6 +733,8 @@ func (o *OptimizelyClient) GetFeatureVariable(featureKey, variableKey string, us

// GetAllFeatureVariablesWithDecision returns all the variables for a given feature along with the enabled state.
func (o *OptimizelyClient) GetAllFeatureVariablesWithDecision(featureKey string, userContext entities.UserContext) (enabled bool, variableMap map[string]interface{}, err error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetAllFeatureVariablesWithDecision")
defer span.End()

variableMap = make(map[string]interface{})
decisionContext, featureDecision, err := o.getFeatureDecision(featureKey, "", userContext)
Expand Down Expand Up @@ -730,6 +785,8 @@ func (o *OptimizelyClient) GetAllFeatureVariablesWithDecision(featureKey string,
// for a given feature along with the experiment key, variation key and the enabled state.
// Usage of this method is unsafe and not recommended since it can be removed in any of the next releases.
func (o *OptimizelyClient) GetDetailedFeatureDecisionUnsafe(featureKey string, userContext entities.UserContext, disableTracking bool) (decisionInfo decision.UnsafeFeatureDecisionInfo, err error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetDetailedFeatureDecisionUnsafe")
defer span.End()

decisionInfo = decision.UnsafeFeatureDecisionInfo{}
decisionInfo.VariableMap = make(map[string]interface{})
Expand Down Expand Up @@ -797,6 +854,9 @@ func (o *OptimizelyClient) GetDetailedFeatureDecisionUnsafe(featureKey string, u

// GetAllFeatureVariables returns all the variables as OptimizelyJSON object for a given feature.
func (o *OptimizelyClient) GetAllFeatureVariables(featureKey string, userContext entities.UserContext) (optlyJSON *optimizelyjson.OptimizelyJSON, err error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetAllFeatureVariables")
defer span.End()

_, variableMap, err := o.GetAllFeatureVariablesWithDecision(featureKey, userContext)
if err != nil {
return optlyJSON, err
Expand Down Expand Up @@ -824,6 +884,9 @@ func (o *OptimizelyClient) GetVariation(experimentKey string, userContext entiti
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetVariation")
defer span.End()

_, experimentDecision, err := o.getExperimentDecision(experimentKey, userContext)
if err != nil {
o.logger.Error("received an error while computing experiment decision", err)
Expand Down Expand Up @@ -856,6 +919,9 @@ func (o *OptimizelyClient) Track(eventKey string, userContext entities.UserConte
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "Track")
defer span.End()

projectConfig, e := o.getProjectConfig()
if e != nil {
o.logger.Error("Optimizely SDK tracking error", e)
Expand Down Expand Up @@ -899,6 +965,9 @@ func (o *OptimizelyClient) getFeatureDecision(featureKey, variableKey string, us
}
}()

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "getFeatureDecision")
defer span.End()

userID := userContext.ID
o.logger.Debug(fmt.Sprintf(`Evaluating feature %q for user %q.`, featureKey, userID))

Expand Down Expand Up @@ -937,6 +1006,8 @@ func (o *OptimizelyClient) getFeatureDecision(featureKey, variableKey string, us
}

func (o *OptimizelyClient) getExperimentDecision(experimentKey string, userContext entities.UserContext) (decisionContext decision.ExperimentDecisionContext, experimentDecision decision.ExperimentDecision, err error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "getExperimentDecision")
defer span.End()

userID := userContext.ID
o.logger.Debug(fmt.Sprintf(`Evaluating experiment %q for user %q.`, experimentKey, userID))
Expand Down Expand Up @@ -1033,6 +1104,8 @@ func (o *OptimizelyClient) getTypedValue(value string, variableType entities.Var
}

func (o *OptimizelyClient) getProjectConfig() (projectConfig config.ProjectConfig, err error) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "getProjectConfig")
defer span.End()

if isNil(o.ConfigManager) {
return nil, errors.New("project config manager is not initialized")
Expand All @@ -1057,7 +1130,8 @@ func (o *OptimizelyClient) getAllOptions(options *decide.Options) decide.Options

// GetOptimizelyConfig returns OptimizelyConfig object
func (o *OptimizelyClient) GetOptimizelyConfig() (optimizelyConfig *config.OptimizelyConfig) {

_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "GetOptimizelyConfig")
defer span.End()
return o.ConfigManager.GetOptimizelyConfig()
}

Expand All @@ -1072,6 +1146,9 @@ func (o *OptimizelyClient) Close() {
}

func (o *OptimizelyClient) getDecisionVariableMap(feature entities.Feature, variation *entities.Variation, featureEnabled bool) (map[string]interface{}, decide.DecisionReasons) {
_, span := o.tracer.StartSpan(o.ctx, DefaultTracerName, "getDecisionVariableMap")
defer span.End()

reasons := decide.NewDecisionReasons(nil)
valuesMap := map[string]interface{}{}

Expand Down
Loading
Loading