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 (#275)
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 authored and black-adder committed Apr 3, 2018
1 parent 3516374 commit 6cc524e
Show file tree
Hide file tree
Showing 7 changed files with 516 additions and 11 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. If true, the default `opentracing.NoopTracer` is used.
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
52 changes: 44 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 All @@ -111,13 +131,25 @@ func (*nullCloser) Close() error { return nil }

// New creates a new Jaeger Tracer, and a closer func that can be used to flush buffers
// before shutdown.
// Deprecated: use NewTracer() function
func (c Configuration) New(
serviceName string,
options ...Option,
) (opentracing.Tracer, io.Closer, error) {
if serviceName == "" {
if serviceName != "" {
c.ServiceName = serviceName
}

return c.NewTracer(options...)
}

// NewTracer returns a new tracer based on the current configuration, using the given options,
// and a closer func that can be used to flush buffers before shutdown.
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 +175,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 +184,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 +203,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 +225,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 +238,7 @@ func (c Configuration) New(
}

tracer, closer := jaeger.NewTracer(
serviceName,
c.ServiceName,
sampler,
reporter,
tracerOptions...,
Expand Down
205 changes: 205 additions & 0 deletions config/config_env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Copyright (c) 2018 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
"fmt"
"os"
"strconv"
"strings"
"time"

opentracing "github.com/opentracing/opentracing-go"
"github.com/pkg/errors"

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

const (
// environment variable names
envServiceName = "JAEGER_SERVICE_NAME"
envDisabled = "JAEGER_DISABLED"
envRPCMetrics = "JAEGER_RPC_METRICS"
envTags = "JAEGER_TAGS"
envSamplerType = "JAEGER_SAMPLER_TYPE"
envSamplerParam = "JAEGER_SAMPLER_PARAM"
envSamplerManagerHostPort = "JAEGER_SAMPLER_MANAGER_HOST_PORT"
envSamplerMaxOperations = "JAEGER_SAMPLER_MAX_OPERATIONS"
envSamplerRefreshInterval = "JAEGER_SAMPLER_REFRESH_INTERVAL"
envReporterMaxQueueSize = "JAEGER_REPORTER_MAX_QUEUE_SIZE"
envReporterFlushInterval = "JAEGER_REPORTER_FLUSH_INTERVAL"
envReporterLogSpans = "JAEGER_REPORTER_LOG_SPANS"
envAgentHost = "JAEGER_AGENT_HOST"
envAgentPort = "JAEGER_AGENT_PORT"
)

// FromEnv uses environment variables to set the tracer's Configuration
func FromEnv() (*Configuration, error) {
c := &Configuration{}

if e := os.Getenv(envServiceName); e != "" {
c.ServiceName = e
}

if e := os.Getenv(envRPCMetrics); e != "" {
if value, err := strconv.ParseBool(e); err == nil {
c.RPCMetrics = value
} else {
return nil, errors.Wrapf(err, "cannot parse env var %s=%s", envRPCMetrics, e)
}
}

if e := os.Getenv(envDisabled); e != "" {
if value, err := strconv.ParseBool(e); err == nil {
c.Disabled = value
} else {
return nil, errors.Wrapf(err, "cannot parse env var %s=%s", envDisabled, e)
}
}

if e := os.Getenv(envTags); e != "" {
c.Tags = parseTags(e)
}

if s, err := samplerConfigFromEnv(); err == nil {
c.Sampler = s
} else {
return nil, errors.Wrap(err, "cannot obtain sampler config from env")
}

if r, err := reporterConfigFromEnv(); err == nil {
c.Reporter = r
} else {
return nil, errors.Wrap(err, "cannot obtain reporter config from env")
}

return c, nil
}

// samplerConfigFromEnv creates a new SamplerConfig based on the environment variables
func samplerConfigFromEnv() (*SamplerConfig, error) {
sc := &SamplerConfig{}

if e := os.Getenv(envSamplerType); e != "" {
sc.Type = e
}

if e := os.Getenv(envSamplerParam); e != "" {
if value, err := strconv.ParseFloat(e, 64); err == nil {
sc.Param = value
} else {
return nil, errors.Wrapf(err, "cannot parse env var %s=%s", envSamplerParam, e)
}
}

if e := os.Getenv(envSamplerManagerHostPort); e != "" {
sc.SamplingServerURL = e
}

if e := os.Getenv(envSamplerMaxOperations); e != "" {
if value, err := strconv.ParseInt(e, 10, 0); err == nil {
sc.MaxOperations = int(value)
} else {
return nil, errors.Wrapf(err, "cannot parse env var %s=%s", envSamplerMaxOperations, e)
}
}

if e := os.Getenv(envSamplerRefreshInterval); e != "" {
if value, err := time.ParseDuration(e); err == nil {
sc.SamplingRefreshInterval = value
} else {
return nil, errors.Wrapf(err, "cannot parse env var %s=%s", envSamplerRefreshInterval, e)
}
}

return sc, nil
}

// reporterConfigFromEnv creates a new ReporterConfig based on the environment variables
func reporterConfigFromEnv() (*ReporterConfig, error) {
rc := &ReporterConfig{}

if e := os.Getenv(envReporterMaxQueueSize); e != "" {
if value, err := strconv.ParseInt(e, 10, 0); err == nil {
rc.QueueSize = int(value)
} else {
return nil, errors.Wrapf(err, "cannot parse env var %s=%s", envReporterMaxQueueSize, e)
}
}

if e := os.Getenv(envReporterFlushInterval); e != "" {
if value, err := time.ParseDuration(e); err == nil {
rc.BufferFlushInterval = value
} else {
return nil, errors.Wrapf(err, "cannot parse env var %s=%s", envReporterFlushInterval, e)
}
}

if e := os.Getenv(envReporterLogSpans); e != "" {
if value, err := strconv.ParseBool(e); err == nil {
rc.LogSpans = value
} else {
return nil, errors.Wrapf(err, "cannot parse env var %s=%s", envReporterLogSpans, e)
}
}

host := jaeger.DefaultUDPSpanServerHost
if e := os.Getenv(envAgentHost); e != "" {
host = e
}

port := jaeger.DefaultUDPSpanServerPort
if e := os.Getenv(envAgentPort); e != "" {
if value, err := strconv.ParseInt(e, 10, 0); err == nil {
port = int(value)
} else {
return nil, errors.Wrapf(err, "cannot parse env var %s=%s", envAgentPort, e)
}
}

// the side effect of this is that we are building the default value, even if none of the env vars
// were not explicitly passed
rc.LocalAgentHostPort = fmt.Sprintf("%s:%d", host, port)

return rc, nil
}

// parseTags parses the given string into a collection of Tags.
// Spec for this value:
// - comma separated list of key=value
// - value can be specified using the notation ${envVar:defaultValue}, where `envVar`
// is an environment variable and `defaultValue` is the value to use in case the env var is not set
func parseTags(sTags string) []opentracing.Tag {
pairs := strings.Split(sTags, ",")
tags := make([]opentracing.Tag, 0)
for _, p := range pairs {
kv := strings.SplitN(p, "=", 2)
k, v := strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])

if strings.HasPrefix(v, "${") && strings.HasSuffix(v, "}") {
ed := strings.SplitN(v[2:len(v)-1], ":", 2)
e, d := ed[0], ed[1]
v = os.Getenv(e)
if v == "" && d != "" {
v = d
}
}

tag := opentracing.Tag{Key: k, Value: v}
tags = append(tags, tag)
}

return tags
}
Loading

0 comments on commit 6cc524e

Please sign in to comment.