Skip to content
This repository has been archived by the owner on May 23, 2024. It is now read-only.

Commit

Permalink
Added support for client configuration via env vars
Browse files Browse the repository at this point in the history
Adds support for configuring the tracer based on environment variables,
similar to what is currently supported by the Java client.

Closes #206

Signed-off-by: Juraci Paixão Kröhling <juraci@kroehling.de>
  • Loading branch information
jpkrohling committed Mar 28, 2018
1 parent 3516374 commit c844e00
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 8 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ make install
See tracer initialization examples in [godoc](https://godoc.org/github.com/uber/jaeger-client-go/config#pkg-examples)
and [config/example_test.go](./config/example_test.go).

### Environment variables

The tracer can be initialized with values coming from environment variables. None of the env vars are required
and all of them can be overriden via direct setting of the property on the configuration object.

Property| Description
--- | ---
JAEGER_SERVICE_NAME | The service name
JAEGER_AGENT_HOST | The hostname for communicating with agent via UDP
JAEGER_AGENT_PORT | The port for communicating with agent via UDP
JAEGER_REPORTER_LOG_SPANS | Whether the reporter should also log the spans
JAEGER_REPORTER_MAX_QUEUE_SIZE | The reporter's maximum queue size
JAEGER_REPORTER_FLUSH_INTERVAL | The reporter's flush interval (ms)
JAEGER_SAMPLER_TYPE | The sampler type
JAEGER_SAMPLER_PARAM | The sampler parameter (number)
JAEGER_SAMPLER_MANAGER_HOST_PORT | The host name and port when using the remote controlled sampler
JAEGER_SAMPLER_MAX_OPERATIONS | The maximum number of operations that the sampler will keep track of
JAEGER_SAMPLER_REFRESH_INTERVAL | How often the remotely controlled sampler will poll jaeger-agent for the appropriate sampling strategy
JAEGER_TAGS | A comma separated list of `name = value` tracer level tags, which get added to all reported spans. The value can also refer to an environment variable using the format `${envVarName:default}`, where the `:default` is optional, and identifies a value to be used if the environment variable cannot be found
JAEGER_DISABLED | Whether the tracer is disabled or not
JAEGER_RPC_METRICS | Whether to store RPC metrics

### Closing the tracer via `io.Closer`

The constructor function for Jaeger Tracer returns the tracer itself and an `io.Closer` instance.
Expand Down
54 changes: 46 additions & 8 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,29 @@ const defaultSamplingProbability = 0.001

// Configuration configures and creates Jaeger Tracer
type Configuration struct {
Disabled bool `yaml:"disabled"`
// ServiceName specifies the service name to use on the tracer.
// Can be provided via environment variable named JAEGER_SERVICE_NAME
ServiceName string `yaml:"serviceName"`

// Disabled can be provided via environment variable named JAEGER_DISABLED
Disabled bool `yaml:"disabled"`

// RPCMetrics can be provided via environment variable named JAEGER_RPC_METRICS
RPCMetrics bool `yaml:"rpc_metrics"`

// Tags can be provided via environment variable named JAEGER_TAGS
Tags []opentracing.Tag `yaml:"tags"`

Sampler *SamplerConfig `yaml:"sampler"`
Reporter *ReporterConfig `yaml:"reporter"`
Headers *jaeger.HeadersConfig `yaml:"headers"`
RPCMetrics bool `yaml:"rpc_metrics"`
BaggageRestrictions *BaggageRestrictionsConfig `yaml:"baggage_restrictions"`
}

// SamplerConfig allows initializing a non-default sampler. All fields are optional.
type SamplerConfig struct {
// Type specifies the type of the sampler: const, probabilistic, rateLimiting, or remote
// Can be set by exporting an environment variable named JAEGER_SAMPLER_TYPE
Type string `yaml:"type"`

// Param is a value passed to the sampler.
Expand All @@ -52,19 +64,23 @@ type SamplerConfig struct {
// - for "rateLimiting" sampler, the number of spans per second
// - for "remote" sampler, param is the same as for "probabilistic"
// and indicates the initial sampling rate before the actual one
// is received from the mothership
// is received from the mothership.
// Can be set by exporting an environment variable named JAEGER_SAMPLER_PARAM
Param float64 `yaml:"param"`

// SamplingServerURL is the address of jaeger-agent's HTTP sampling server
// Can be set by exporting an environment variable named JAEGER_SAMPLER_MANAGER_HOST_PORT
SamplingServerURL string `yaml:"samplingServerURL"`

// MaxOperations is the maximum number of operations that the sampler
// will keep track of. If an operation is not tracked, a default probabilistic
// sampler will be used rather than the per operation specific sampler.
// Can be set by exporting an environment variable named JAEGER_SAMPLER_MAX_OPERATIONS
MaxOperations int `yaml:"maxOperations"`

// SamplingRefreshInterval controls how often the remotely controlled sampler will poll
// jaeger-agent for the appropriate sampling strategy.
// Can be set by exporting an environment variable named JAEGER_SAMPLER_REFRESH_INTERVAL
SamplingRefreshInterval time.Duration `yaml:"samplingRefreshInterval"`
}

Expand All @@ -73,18 +89,22 @@ type ReporterConfig struct {
// QueueSize controls how many spans the reporter can keep in memory before it starts dropping
// new spans. The queue is continuously drained by a background go-routine, as fast as spans
// can be sent out of process.
// Can be set by exporting an environment variable named JAEGER_REPORTER_MAX_QUEUE_SIZE
QueueSize int `yaml:"queueSize"`

// BufferFlushInterval controls how often the buffer is force-flushed, even if it's not full.
// It is generally not useful, as it only matters for very low traffic services.
// Can be set by exporting an environment variable named JAEGER_REPORTER_FLUSH_INTERVAL
BufferFlushInterval time.Duration

// LogSpans, when true, enables LoggingReporter that runs in parallel with the main reporter
// and logs all submitted spans. Main Configuration.Logger must be initialized in the code
// for this option to have any effect.
// Can be set by exporting an environment variable named JAEGER_REPORTER_LOG_SPANS
LogSpans bool `yaml:"logSpans"`

// LocalAgentHostPort instructs reporter to send spans to jaeger-agent at this address
// Can be set by exporting an environment variable named JAEGER_AGENT_HOST / JAEGER_AGENT_PORT
LocalAgentHostPort string `yaml:"localAgentHostPort"`
}

Expand Down Expand Up @@ -115,9 +135,23 @@ func (c Configuration) New(
serviceName string,
options ...Option,
) (opentracing.Tracer, io.Closer, error) {
if serviceName == "" {
if serviceName == "" && c.ServiceName == "" {
return nil, nil, errors.New("no service name provided")
}

if serviceName != "" {
c.ServiceName = serviceName
}

return c.NewTracer(options...)
}

// NewTracer returns a new tracer based on the current configuration, using the given options
func (c *Configuration) NewTracer(options ...Option) (opentracing.Tracer, io.Closer, error) {
if c.ServiceName == "" {
return nil, nil, errors.New("no service name provided")
}

if c.Disabled {
return &opentracing.NoopTracer{}, &nullCloser{}, nil
}
Expand All @@ -143,7 +177,7 @@ func (c Configuration) New(

sampler := opts.sampler
if sampler == nil {
s, err := c.Sampler.NewSampler(serviceName, tracerMetrics)
s, err := c.Sampler.NewSampler(c.ServiceName, tracerMetrics)
if err != nil {
return nil, nil, err
}
Expand All @@ -152,7 +186,7 @@ func (c Configuration) New(

reporter := opts.reporter
if reporter == nil {
r, err := c.Reporter.NewReporter(serviceName, tracerMetrics, opts.logger)
r, err := c.Reporter.NewReporter(c.ServiceName, tracerMetrics, opts.logger)
if err != nil {
return nil, nil, err
}
Expand All @@ -171,6 +205,10 @@ func (c Configuration) New(
tracerOptions = append(tracerOptions, jaeger.TracerOptions.Tag(tag.Key, tag.Value))
}

for _, tag := range c.Tags {
tracerOptions = append(tracerOptions, jaeger.TracerOptions.Tag(tag.Key, tag.Value))
}

for _, obs := range opts.observers {
tracerOptions = append(tracerOptions, jaeger.TracerOptions.Observer(obs))
}
Expand All @@ -189,7 +227,7 @@ func (c Configuration) New(

if c.BaggageRestrictions != nil {
mgr := remote.NewRestrictionManager(
serviceName,
c.ServiceName,
remote.Options.Metrics(tracerMetrics),
remote.Options.Logger(opts.logger),
remote.Options.HostPort(c.BaggageRestrictions.HostPort),
Expand All @@ -202,7 +240,7 @@ func (c Configuration) New(
}

tracer, closer := jaeger.NewTracer(
serviceName,
c.ServiceName,
sampler,
reporter,
tracerOptions...,
Expand Down
120 changes: 120 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package config

import (
"os"
"testing"
"time"

Expand Down Expand Up @@ -70,6 +71,90 @@ func TestDefaultSampler(t *testing.T) {
require.Error(t, err)
}

func TestServiceNameFromEnv(t *testing.T) {
os.Setenv(envServiceName, "my-service")

_, c, err := FromEnv().New("")
defer c.Close()
assert.NoError(t, err)
os.Unsetenv(envServiceName)
}

func TestFromEnv(t *testing.T) {
os.Setenv(envServiceName, "my-service")
os.Setenv(envDisabled, "false")
os.Setenv(envRPCMetrics, "true")
os.Setenv(envTags, "KEY=VALUE")

cfg := FromEnv()
assert.Equal(t, "my-service", cfg.ServiceName)
assert.Equal(t, false, cfg.Disabled)
assert.Equal(t, true, cfg.RPCMetrics)
assert.Equal(t, "KEY", cfg.Tags[0].Key)
assert.Equal(t, "VALUE", cfg.Tags[0].Value)

os.Unsetenv(envServiceName)
os.Unsetenv(envDisabled)
os.Unsetenv(envRPCMetrics)
}

func TestNoServiceNameFromEnv(t *testing.T) {
os.Unsetenv(envServiceName)
_, _, e := FromEnv().New("")
assert.Error(t, e)
}

func TestSamplerConfigFromEnv(t *testing.T) {
// prepare
os.Setenv(envSamplerType, "const")
os.Setenv(envSamplerParam, "1")
os.Setenv(envSamplerManagerHostPort, "http://themaster")
os.Setenv(envSamplerMaxOperations, "10")
os.Setenv(envSamplerRefreshInterval, "1m1s") // 61 seconds

// test
cfg := FromEnv()

// verify
assert.Equal(t, "const", cfg.Sampler.Type)
assert.Equal(t, float64(1), cfg.Sampler.Param)
assert.Equal(t, "http://themaster", cfg.Sampler.SamplingServerURL)
assert.Equal(t, int(10), cfg.Sampler.MaxOperations)
assert.Equal(t, 61000000000, int(cfg.Sampler.SamplingRefreshInterval))

// cleanup
os.Unsetenv(envSamplerType)
os.Unsetenv(envSamplerParam)
os.Unsetenv(envSamplerManagerHostPort)
os.Unsetenv(envSamplerMaxOperations)
os.Unsetenv(envSamplerRefreshInterval)
}

func TestReporterConfigFromEnv(t *testing.T) {
// prepare
os.Setenv(envReporterMaxQueueSize, "10")
os.Setenv(envReporterFlushInterval, "1m1s") // 61 seconds
os.Setenv(envReporterLogSpans, "true")
os.Setenv(envAgentHost, "nonlocalhost")
os.Setenv(envAgentPort, "6832")

// test
cfg := FromEnv()

// verify
assert.Equal(t, int(10), cfg.Reporter.QueueSize)
assert.Equal(t, 61000000000, int(cfg.Reporter.BufferFlushInterval))
assert.Equal(t, true, cfg.Reporter.LogSpans)
assert.Equal(t, "nonlocalhost:6832", cfg.Reporter.LocalAgentHostPort)

// cleanup
os.Unsetenv(envReporterMaxQueueSize)
os.Unsetenv(envReporterFlushInterval)
os.Unsetenv(envReporterLogSpans)
os.Unsetenv(envAgentHost)
os.Unsetenv(envAgentPort)
}

func TestInvalidSamplerType(t *testing.T) {
cfg := &SamplerConfig{MaxOperations: 10}
s, err := cfg.NewSampler("x", jaeger.NewNullMetrics())
Expand Down Expand Up @@ -302,3 +387,38 @@ func TestConfigWithSampler(t *testing.T) {
require.Equal(t, traceID, sampler.lastTraceID)
require.Equal(t, "test", sampler.lastOperation)
}

func TestNewTracer(t *testing.T) {
cfg := &Configuration{ServiceName: "my-service"}
_, closer, err := cfg.NewTracer(Metrics(metrics.NullFactory), Logger(log.NullLogger))
defer closer.Close()

assert.NoError(t, err)
}

func TestNewTracerWithoutServiceName(t *testing.T) {
cfg := &Configuration{}
_, _, err := cfg.NewTracer(Metrics(metrics.NullFactory), Logger(log.NullLogger))
assert.Contains(t, err.Error(), "no service name provided")
}

func TestParseTags(t *testing.T) {
os.Setenv("existing", "not-default")
tags := "key=value,k1=${nonExisting:default}, k2=${withSpace:default},k3=${existing:default}"
ts := parseTags(tags)
assert.Equal(t, 4, len(ts))

assert.Equal(t, "key", ts[0].Key)
assert.Equal(t, "value", ts[0].Value)

assert.Equal(t, "k1", ts[1].Key)
assert.Equal(t, "default", ts[1].Value)

assert.Equal(t, "k2", ts[2].Key)
assert.Equal(t, "default", ts[2].Value)

assert.Equal(t, "k3", ts[3].Key)
assert.Equal(t, "not-default", ts[3].Value)

os.Unsetenv("existing")
}
31 changes: 31 additions & 0 deletions config/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package config_test

import (
"log"
"os"

opentracing "github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-lib/metrics"

"github.com/uber/jaeger-client-go"
Expand Down Expand Up @@ -82,3 +84,32 @@ func ExampleConfiguration_InitGlobalTracer_production() {

// continue main()
}

func ExampleConfiguration_EnvironmentVariables() {
tracer, closer, err := jaegercfg.FromEnv().NewTracer()
if err != nil {
log.Printf("Could not initialize jaeger tracer: %s", err.Error())
return
}
defer closer.Close()

opentracing.SetGlobalTracer(tracer)
// continue main()
}

func ExampleConfiguration_Override_EnvironmentVariables() {
os.Setenv("JAEGER_SERVICE_NAME", "not-effective")

cfg := jaegercfg.FromEnv()
cfg.ServiceName = "this-will-be-the-service-name"

tracer, closer, err := cfg.NewTracer()
if err != nil {
log.Printf("Could not initialize jaeger tracer: %s", err.Error())
return
}
defer closer.Close()

opentracing.SetGlobalTracer(tracer)
// continue main()
}

0 comments on commit c844e00

Please sign in to comment.