Skip to content

Commit

Permalink
Load open tracing configuration options with environment variables (T…
Browse files Browse the repository at this point in the history
…ykTechnologies#3112)

<!-- Provide a general summary of your changes in the Title above -->

## Description
<!-- Describe your changes in detail -->
This refactors loading of configuration  with environment variables to load open tracing configurations for both zipkin and jaeger.
## Related Issue
<!-- This project only accepts pull requests related to open issues -->
<!-- If suggesting a new feature or change, please discuss it in an issue first -->
<!-- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!-- Please link to the issue here -->
fixes TykTechnologies#3104

## Motivation and Context
<!-- Why is this change required? What problem does it solve? -->
Opent tracing options are passed as `map[string]interface{}` this was hard to map to environment variables. This PR add support for custom configuration option loaders that are invoked after the default envconfig loader is complete allowing safe and sane passing of opentracing options as env vars.

Supported zipkin env vars
```
 TYK_GW_TRACER_OPTIONS_REPORTER_URL
 TYK_GW_TRACER_OPTIONS_REPORTER_BATCHSIZE
 TYK_GW_TRACER_OPTIONS_REPORTER_MAXBACKLOG
 TYK_GW_TRACER_OPTIONS_SAMPLER_NAME
 TYK_GW_TRACER_OPTIONS_SAMPLER_RATE
 TYK_GW_TRACER_OPTIONS_SAMPLER_SALT
 TYK_GW_TRACER_OPTIONS_SAMPLER_MOD
```
supported jaeger env vars
```
TYK_GW_TRACER_OPTIONS_SERVICENAME
TYK_GW_TRACER_OPTIONS_DISABLED
TYK_GW_TRACER_OPTIONS_RPCMETRICS
TYK_GW_TRACER_OPTIONS_TAGS
TYK_GW_TRACER_OPTIONS_SAMPLER_TYPE
TYK_GW_TRACER_OPTIONS_SAMPLER_PARAM
TYK_GW_TRACER_OPTIONS_SAMPLER_SAMPLINGSERVERURL
TYK_GW_TRACER_OPTIONS_SAMPLER_MAXOPERATIONS
TYK_GW_TRACER_OPTIONS_SAMPLER_SAMPLINGREFRESHINTERVAL
TYK_GW_TRACER_OPTIONS_REPORTER_QUEUESIZE
TYK_GW_TRACER_OPTIONS_REPORTER_BUFFERFLUSHINTERVAL
TYK_GW_TRACER_OPTIONS_REPORTER_LOGSPANS
TYK_GW_TRACER_OPTIONS_REPORTER_LOCALAGENTHOSTPORT
TYK_GW_TRACER_OPTIONS_REPORTER_COLLECTORENDPOINT
TYK_GW_TRACER_OPTIONS_REPORTER_USER
TYK_GW_TRACER_OPTIONS_REPORTER_PASSWORD
TYK_GW_TRACER_OPTIONS_HEADERS_JAEGERDEBUGHEADER
TYK_GW_TRACER_OPTIONS_HEADERS_JAEGERBAGGAGEHEADER
TYK_GW_TRACER_OPTIONS_HEADERS_TRACECONTEXTHEADERNAME
TYK_GW_TRACER_OPTIONS_HEADERS_TRACEBAGGAGEHEADERPREFIX
TYK_GW_TRACER_OPTIONS_BAGGAGERESTRICTIONS_DENYBAGGAGEONINITIALIZATIONFAILURE
TYK_GW_TRACER_OPTIONS_BAGGAGERESTRICTIONS_HOSTPORT
TYK_GW_TRACER_OPTIONS_BAGGAGERESTRICTIONS_REFRESHINTERVAL
TYK_GW_TRACER_OPTIONS_THROTTLER_HOSTPORT
TYK_GW_TRACER_OPTIONS_THROTTLER_REFRESHINTERVAL
TYK_GW_TRACER_OPTIONS_THROTTLER_SYNCHRONOUSINITIALIZATION
```

## How This Has Been Tested
<!-- Please describe in detail how you tested your changes -->
<!-- Include details of your testing environment, and the tests you ran to see how your change affects other areas of
the code, etc. -->
Provided with unit tests
```
go test ./config
```

## Screenshots (if appropriate)

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes that apply -->
<!-- If you're unsure about any of these, don't hesitate to ask; we're here to help! -->
- [x] Make sure you are requesting to **pull a topic/feature/bugfix branch** (right side). If pulling from your own
      fork, don't request your `master`!
- [x] Make sure you are making a pull request against the **`master` branch** (left side). Also, you should start
      *your branch* off *our latest `master`*.
- [ ] My change requires a change to the documentation.
  - [ ] If you've changed APIs, describe what needs to be updated in the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] Modules and vendor dependencies have been updated; run `go mod tidy && go mod vendor`
- [ ] When updating library version must provide reason/explanation for this update.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] Check your code additions will not fail linting checks:
  - [ ] `go fmt -s`
  - [ ] `go vet`
  • Loading branch information
gernest authored Jun 2, 2020
1 parent de1e8b7 commit 1aba6e1
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 26 deletions.
16 changes: 16 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,9 @@ func Load(paths []string, conf *Config) error {
if err := envconfig.Process(envPrefix, conf); err != nil {
return fmt.Errorf("failed to process config env vars: %v", err)
}
if err := processCustom(envPrefix, conf, loadZipkin, loadJaeger); err != nil {
return fmt.Errorf("failed to process config custom loader: %v", err)
}
return nil
}

Expand All @@ -675,3 +678,16 @@ func (c *Config) StoreAnalytics(ip string) bool {

return !c.AnalyticsConfig.ignoredIPsCompiled[ip]
}

// processCustom these are custom functions for loadign config values. They will
// be called in the order they are passed. Any function that returns an error
// then that error will be returned and no further processing will be
// happenning.
func processCustom(prefix string, c *Config, custom ...func(prefix string, c *Config) error) error {
for _, fn := range custom {
if err := fn(prefix, c); err != nil {
return err
}
}
return nil
}
140 changes: 140 additions & 0 deletions config/opentracing_custom_env_loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package config

import (
"encoding/json"

"github.com/kelseyhightower/envconfig"
jaeger "github.com/uber/jaeger-client-go/config"
)

// ZipkinConfig configuration options used to initialize openzipkin opentracing
// client.
type ZipkinConfig struct {
Reporter Reporter `json:"reporter"`
Sampler Sampler `json:"sampler"`
}

type Reporter struct {
// URL connection url to the zipkin server
URL string `json:"url"`
BatchSize int `json:"batch_size"`
MaxBacklog int `json:"max_backlog"`
}

type Sampler struct {
//Name is the name of the sampler to use. Options are
//
// "boundary"
// is appropriate for high-traffic instrumentation who
// provision random trace ids, and make the sampling decision only once.
// It defends against nodes in the cluster selecting exactly the same ids.
//
// "count"
// is appropriate for low-traffic instrumentation or
// those who do not provision random trace ids. It is not appropriate for
// collectors as the sampling decision isn't idempotent (consistent based
// on trace id).
//
// "mod"
// provides a generic type Sampler
Name string `json:"name"`
//Rate is used by both "boundary" and "count" samplers
Rate float64 `json:"rate"`
//Salt is used by "boundary" sampler
Salt int64 `json:"salt"`
// Mod is only used when sampler is mod
Mod uint64 `json:"mod"`
}

// DecodeJSON marshals src to json and tries to unmarshal the result into
// dest.
func DecodeJSON(dest, src interface{}) error {
b, err := json.Marshal(src)
if err != nil {
return err
}
return json.Unmarshal(b, dest)
}

// loadZipkin tries to lad zipkin configuration from environment variables.
//
// list of zipkin configuration env variables
//
// TYK_GW_TRACER_OPTIONS_REPORTER_URL
// TYK_GW_TRACER_OPTIONS_REPORTER_BATCHSIZE
// TYK_GW_TRACER_OPTIONS_REPORTER_MAXBACKLOG
// TYK_GW_TRACER_OPTIONS_SAMPLER_NAME
// TYK_GW_TRACER_OPTIONS_SAMPLER_RATE
// TYK_GW_TRACER_OPTIONS_SAMPLER_SALT
// TYK_GW_TRACER_OPTIONS_SAMPLER_MOD
func loadZipkin(prefix string, c *Config) error {
if c.Tracer.Name != "zipkin" || c.Tracer.Options == nil {
return nil
}
var zip ZipkinConfig
if err := DecodeJSON(&zip, c.Tracer.Options); err != nil {
return err
}
qualifyPrefix := prefix + "_TRACER_OPTIONS"
err := envconfig.Process(qualifyPrefix, &zip)
if err != nil {
return err
}
o := make(map[string]interface{})
if err := DecodeJSON(&o, zip); err != nil {
return err
}
c.Tracer.Options = o
return nil
}

// loads jaeger configuration from environment variables.
//
// List of jaeger configuration env vars
//
// TYK_GW_TRACER_OPTIONS_SERVICENAME
// TYK_GW_TRACER_OPTIONS_DISABLED
// TYK_GW_TRACER_OPTIONS_RPCMETRICS
// TYK_GW_TRACER_OPTIONS_TAGS
// TYK_GW_TRACER_OPTIONS_SAMPLER_TYPE
// TYK_GW_TRACER_OPTIONS_SAMPLER_PARAM
// TYK_GW_TRACER_OPTIONS_SAMPLER_SAMPLINGSERVERURL
// TYK_GW_TRACER_OPTIONS_SAMPLER_MAXOPERATIONS
// TYK_GW_TRACER_OPTIONS_SAMPLER_SAMPLINGREFRESHINTERVAL
// TYK_GW_TRACER_OPTIONS_REPORTER_QUEUESIZE
// TYK_GW_TRACER_OPTIONS_REPORTER_BUFFERFLUSHINTERVAL
// TYK_GW_TRACER_OPTIONS_REPORTER_LOGSPANS
// TYK_GW_TRACER_OPTIONS_REPORTER_LOCALAGENTHOSTPORT
// TYK_GW_TRACER_OPTIONS_REPORTER_COLLECTORENDPOINT
// TYK_GW_TRACER_OPTIONS_REPORTER_USER
// TYK_GW_TRACER_OPTIONS_REPORTER_PASSWORD
// TYK_GW_TRACER_OPTIONS_HEADERS_JAEGERDEBUGHEADER
// TYK_GW_TRACER_OPTIONS_HEADERS_JAEGERBAGGAGEHEADER
// TYK_GW_TRACER_OPTIONS_HEADERS_TRACECONTEXTHEADERNAME
// TYK_GW_TRACER_OPTIONS_HEADERS_TRACEBAGGAGEHEADERPREFIX
// TYK_GW_TRACER_OPTIONS_BAGGAGERESTRICTIONS_DENYBAGGAGEONINITIALIZATIONFAILURE
// TYK_GW_TRACER_OPTIONS_BAGGAGERESTRICTIONS_HOSTPORT
// TYK_GW_TRACER_OPTIONS_BAGGAGERESTRICTIONS_REFRESHINTERVAL
// TYK_GW_TRACER_OPTIONS_THROTTLER_HOSTPORT
// TYK_GW_TRACER_OPTIONS_THROTTLER_REFRESHINTERVAL
// TYK_GW_TRACER_OPTIONS_THROTTLER_SYNCHRONOUSINITIALIZATION
func loadJaeger(prefix string, c *Config) error {
if c.Tracer.Name != "jaeger" || c.Tracer.Options == nil {
return nil
}
var j jaeger.Configuration
if err := DecodeJSON(&j, c.Tracer.Options); err != nil {
return err
}
qualifyPrefix := prefix + "_TRACER_OPTIONS"
err := envconfig.Process(qualifyPrefix, &j)
if err != nil {
return err
}
o := make(map[string]interface{})
if err := DecodeJSON(&o, j); err != nil {
return err
}
c.Tracer.Options = o
return nil
}
150 changes: 150 additions & 0 deletions config/opentracing_custom_env_loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package config

import (
"fmt"
"os"
"reflect"
"testing"

jaeger "github.com/uber/jaeger-client-go/config"
)

func TestLoadZipkin(t *testing.T) {
base := ZipkinConfig{
Reporter: Reporter{
URL: "repoturl",
BatchSize: 10,
MaxBacklog: 20,
},
Sampler: Sampler{
Name: "boundary",
Rate: 10.1,
Salt: 10,
Mod: 12,
},
}
sample := []struct {
env string
value string
}{
{"TYK_GW_TRACER_OPTIONS_REPORTER_URL", base.Reporter.URL},
{"TYK_GW_TRACER_OPTIONS_REPORTER_BATCHSIZE", fmt.Sprint(base.Reporter.BatchSize)},
{"TYK_GW_TRACER_OPTIONS_REPORTER_MAXBACKLOG", fmt.Sprint(base.Reporter.MaxBacklog)},
{"TYK_GW_TRACER_OPTIONS_SAMPLER_NAME", base.Sampler.Name},
{"TYK_GW_TRACER_OPTIONS_SAMPLER_SALT", fmt.Sprint(base.Sampler.Salt)},
{"TYK_GW_TRACER_OPTIONS_SAMPLER_MOD", fmt.Sprint(base.Sampler.Mod)},
}
for _, v := range sample {
err := os.Setenv(v.env, v.value)
if err != nil {
t.Fatal(err)
}
}
defer func() {
for _, v := range sample {
os.Unsetenv(v.env)
}
}()
t.Run("Returns nil when it is not zipkin config", func(t *testing.T) {
conf := &Config{}
err := loadZipkin(envPrefix, conf)
if err != nil {
t.Fatal(err)
}
if conf.Tracer.Options != nil {
t.Error("expected options to be nil")
}
})
t.Run("handles nil options", func(t *testing.T) {
conf := &Config{Tracer: Tracer{Name: "zipkin"}}
err := loadZipkin(envPrefix, conf)
if err != nil {
t.Fatal(err)
}
if conf.Tracer.Options != nil {
t.Error("expected options to be nil")
}
})

t.Run("loads env vars", func(t *testing.T) {
o := make(map[string]interface{})
err := DecodeJSON(&o, base)
if err != nil {
t.Fatal(err)
}
conf := &Config{Tracer: Tracer{Name: "zipkin", Options: o}}
err = loadZipkin(envPrefix, conf)
if err != nil {
t.Fatal(err)
}
var got ZipkinConfig
err = DecodeJSON(&got, conf.Tracer.Options)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(base, got) {
t.Errorf("expected %#v got %#v", base, got)
}
})
}
func TestLoadJaeger(t *testing.T) {
base := &jaeger.Configuration{ServiceName: "jaeger-test-service"}
sample := []struct {
env string
value string
}{
{"TYK_GW_TRACER_OPTIONS_SERVICENAME", base.ServiceName},
}
for _, v := range sample {
err := os.Setenv(v.env, v.value)
if err != nil {
t.Fatal(err)
}
}
defer func() {
for _, v := range sample {
os.Unsetenv(v.env)
}
}()
t.Run("Returns nil when it is not jaeger config", func(t *testing.T) {
conf := &Config{}
err := loadJaeger(envPrefix, conf)
if err != nil {
t.Fatal(err)
}
if conf.Tracer.Options != nil {
t.Error("expected options to be nil")
}
})
t.Run("Handles nil options", func(t *testing.T) {
conf := &Config{Tracer: Tracer{Name: "jaeger"}}
err := loadJaeger(envPrefix, conf)
if err != nil {
t.Fatal(err)
}
if conf.Tracer.Options != nil {
t.Error("expected options to be nil")
}
})

t.Run("Loads env vars", func(t *testing.T) {
o := make(map[string]interface{})
err := DecodeJSON(&o, base)
if err != nil {
t.Fatal(err)
}
conf := &Config{Tracer: Tracer{Name: "jaeger", Options: o}}
err = loadJaeger(envPrefix, conf)
if err != nil {
t.Fatal(err)
}
var got jaeger.Configuration
err = DecodeJSON(&got, conf.Tracer.Options)
if err != nil {
t.Fatal(err)
}
if base.ServiceName != got.ServiceName {
t.Errorf("expected %#v got %#v", base.ServiceName, got.ServiceName)
}
})
}
33 changes: 8 additions & 25 deletions trace/openzipkin/config.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,14 @@
package openzipkin

import "encoding/json"

type Config struct {
Reporter Reporter `json:"reporter"`
Sampler Sampler `json:"sampler"`
}

type Reporter struct {
URL string `json:"url"`
BatchSize int `json:"batch_size"`
MaxBacklog int `json:"max_backlog"`
}

type Sampler struct {
Name string `json:"name"`
Rate float64 `json:"rate"`
Salt int64 `json:"salt"`
Mod uint64 `json:"mod"`
}

func Load(opts map[string]interface{}) (*Config, error) {
b, err := json.Marshal(opts)
if err != nil {
import (
"github.com/TykTechnologies/tyk/config"
)

// Load retusn a zipkin configuration from the opts.
func Load(opts map[string]interface{}) (*config.ZipkinConfig, error) {
var c config.ZipkinConfig
if err := config.DecodeJSON(&c, opts); err != nil {
return nil, err
}
var c Config
err = json.Unmarshal(b, &c)
return &c, nil
}
3 changes: 2 additions & 1 deletion trace/openzipkin/zipkin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"time"

"github.com/TykTechnologies/tyk/config"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/log"
zipkin "github.com/openzipkin/zipkin-go"
Expand Down Expand Up @@ -277,7 +278,7 @@ func Init(service string, opts map[string]interface{}) (*Tracer, error) {
return &Tracer{Tracer: NewTracer(tr), Reporter: r}, nil
}

func getSampler(s Sampler) (zipkin.Sampler, error) {
func getSampler(s config.Sampler) (zipkin.Sampler, error) {
if s.Name == "" {
return zipkin.AlwaysSample, nil
}
Expand Down

0 comments on commit 1aba6e1

Please sign in to comment.