diff --git a/CHANGELOG.md b/CHANGELOG.md index b1b24f17..f2269049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Changes by Version 2.5.1 (unreleased) ------------------ -- Nothing yet +- Add config option to initialize RPC Metrics feature 2.5.0 (2017-03-23) diff --git a/config/config.go b/config/config.go index 20735bfa..8be3c291 100644 --- a/config/config.go +++ b/config/config.go @@ -30,6 +30,7 @@ import ( "github.com/opentracing/opentracing-go" "github.com/uber/jaeger-client-go" + "github.com/uber/jaeger-client-go/rpcmetrics" "github.com/uber/jaeger-client-go/transport" "github.com/uber/jaeger-client-go/transport/udp" ) @@ -38,11 +39,10 @@ const defaultSamplingProbability = 0.001 // Configuration configures and creates Jaeger Tracer type Configuration struct { - ClientOptions - - Disabled bool `yaml:"disabled"` - Sampler *SamplerConfig `yaml:"sampler"` - Reporter *ReporterConfig `yaml:"reporter"` + Disabled bool `yaml:"disabled"` + Sampler *SamplerConfig `yaml:"sampler"` + Reporter *ReporterConfig `yaml:"reporter"` + RPCMetrics bool `yaml:"rpc_metrics"` } // SamplerConfig allows initializing a non-default sampler. All fields are optional. @@ -101,19 +101,23 @@ func (*nullCloser) Close() error { return nil } // before shutdown. func (c Configuration) New( serviceName string, - options ...ClientOption, + options ...Option, ) (opentracing.Tracer, io.Closer, error) { - for _, option := range options { - option(&c.ClientOptions) - } - if c.metrics == nil { - c.metrics = jaeger.NewNullMetrics() + if serviceName == "" { + return nil, nil, errors.New("no service name provided") } if c.Disabled { return &opentracing.NoopTracer{}, &nullCloser{}, nil } - if serviceName == "" { - return nil, nil, errors.New("no service name provided") + opts := applyOptions(options...) + tracerMetrics := jaeger.NewMetrics(opts.metrics, nil) + if c.RPCMetrics { + Observer( + rpcmetrics.NewObserver( + opts.metrics.Namespace("jaeger-rpc", map[string]string{"component": "jaeger"}), + rpcmetrics.DefaultNameNormalizer, + ), + )(&opts) // adds to c.observers } if c.Sampler == nil { c.Sampler = &SamplerConfig{ @@ -125,22 +129,26 @@ func (c Configuration) New( c.Reporter = &ReporterConfig{} } - sampler, err := c.Sampler.NewSampler(serviceName, c.metrics) + sampler, err := c.Sampler.NewSampler(serviceName, tracerMetrics) if err != nil { return nil, nil, err } - reporter, err := c.Reporter.NewReporter(serviceName, c.metrics, c.logger) - if err != nil { - return nil, nil, err + reporter := opts.reporter + if reporter == nil { + r, err := c.Reporter.NewReporter(serviceName, tracerMetrics, opts.logger) + if err != nil { + return nil, nil, err + } + reporter = r } tracerOptions := []jaeger.TracerOption{ - jaeger.TracerOptions.Metrics(c.metrics), - jaeger.TracerOptions.Logger(c.logger), + jaeger.TracerOptions.Metrics(tracerMetrics), + jaeger.TracerOptions.Logger(opts.logger), } - for _, obs := range c.ClientOptions.observers { + for _, obs := range opts.observers { tracerOptions = append(tracerOptions, jaeger.TracerOptions.Observer(obs)) } @@ -157,7 +165,7 @@ func (c Configuration) New( // It returns a closer func that can be used to flush buffers before shutdown. func (c Configuration) InitGlobalTracer( serviceName string, - options ...ClientOption, + options ...Option, ) (io.Closer, error) { if c.Disabled { return &nullCloser{}, nil diff --git a/config/config_test.go b/config/config_test.go index b8060376..09ae0486 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -24,8 +24,11 @@ import ( "testing" "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/uber/jaeger-lib/metrics" + "github.com/uber/jaeger-lib/metrics/testutils" "github.com/uber/jaeger-client-go" "github.com/uber/jaeger-client-go/log" @@ -136,3 +139,44 @@ func TestInitGlobalTracer(t *testing.T) { } } } + +func TestConfigWithReporter(t *testing.T) { + c := Configuration{ + Sampler: &SamplerConfig{ + Type: "const", + Param: 1, + }, + } + r := jaeger.NewInMemoryReporter() + tracer, closer, err := c.New("test", Reporter(r)) + require.NoError(t, err) + defer closer.Close() + + tracer.StartSpan("test").Finish() + assert.Len(t, r.GetSpans(), 1) +} + +func TestConfigWithRPCMetrics(t *testing.T) { + metrics := metrics.NewLocalFactory(0) + c := Configuration{ + Sampler: &SamplerConfig{ + Type: "const", + Param: 1, + }, + RPCMetrics: true, + } + r := jaeger.NewInMemoryReporter() + tracer, closer, err := c.New("test", Reporter(r), Metrics(metrics)) + require.NoError(t, err) + defer closer.Close() + + tracer.StartSpan("test", ext.SpanKindRPCServer).Finish() + + testutils.AssertCounterMetrics(t, metrics, + testutils.ExpectedMetric{ + Name: "jaeger-rpc.requests", + Tags: map[string]string{"component": "jaeger", "endpoint": "test", "error": "false"}, + Value: 1, + }, + ) +} diff --git a/config/options.go b/config/options.go index 3a14bdf8..47c396e7 100644 --- a/config/options.go +++ b/config/options.go @@ -26,35 +26,58 @@ import ( "github.com/uber/jaeger-client-go" ) -// ClientOption is a function that sets some option on the client. -type ClientOption func(c *ClientOptions) +// Option is a function that sets some option on the client. +type Option func(c *Options) -// ClientOptions control behavior of the client. -type ClientOptions struct { - metrics *jaeger.Metrics +// Options control behavior of the client. +type Options struct { + metrics metrics.Factory logger jaeger.Logger + reporter jaeger.Reporter observers []jaeger.Observer } -// Metrics creates a ClientOption that initializes Metrics in the client, -// which is used to emit statistics. -func Metrics(factory metrics.Factory) ClientOption { - return func(c *ClientOptions) { - c.metrics = jaeger.NewMetrics(factory, nil) +// Metrics creates an Option that initializes Metrics in the tracer, +// which is used to emit statistics about spans. +func Metrics(factory metrics.Factory) Option { + return func(c *Options) { + c.metrics = factory } } // Logger can be provided to log Reporter errors, as well as to log spans // if Reporter.LogSpans is set to true. -func Logger(logger jaeger.Logger) ClientOption { - return func(c *ClientOptions) { +func Logger(logger jaeger.Logger) Option { + return func(c *Options) { c.logger = logger } } +// Reporter can be provided explicitly to override the configuration. +// Useful for testing, e.g. by passing InMemoryReporter. +func Reporter(reporter jaeger.Reporter) Option { + return func(c *Options) { + c.reporter = reporter + } +} + // Observer can be registered with the Tracer to receive notifications about new Spans. -func Observer(observer jaeger.Observer) ClientOption { - return func(c *ClientOptions) { +func Observer(observer jaeger.Observer) Option { + return func(c *Options) { c.observers = append(c.observers, observer) } } + +func applyOptions(options ...Option) Options { + opts := Options{} + for _, option := range options { + option(&opts) + } + if opts.metrics == nil { + opts.metrics = metrics.NullFactory + } + if opts.logger == nil { + opts.logger = jaeger.NullLogger + } + return opts +} diff --git a/config/options_test.go b/config/options_test.go new file mode 100644 index 00000000..06ba5fe4 --- /dev/null +++ b/config/options_test.go @@ -0,0 +1,56 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package config + +import ( + "testing" + + opentracing "github.com/opentracing/opentracing-go" + "github.com/stretchr/testify/assert" + "github.com/uber/jaeger-lib/metrics" + + "github.com/uber/jaeger-client-go" +) + +func TestApplyOptions(t *testing.T) { + metricsFactory := metrics.NewLocalFactory(0) + observer := fakeObserver{} + opts := applyOptions( + Metrics(metricsFactory), + Logger(jaeger.StdLogger), + Observer(observer), + ) + assert.Equal(t, jaeger.StdLogger, opts.logger) + assert.Equal(t, metricsFactory, opts.metrics) + assert.Equal(t, []jaeger.Observer{observer}, opts.observers) +} + +func TestApplyOptionsDefaults(t *testing.T) { + opts := applyOptions() + assert.Equal(t, jaeger.NullLogger, opts.logger) + assert.Equal(t, metrics.NullFactory, opts.metrics) +} + +type fakeObserver struct{} + +func (o fakeObserver) OnStartSpan(operationName string, options opentracing.StartSpanOptions) jaeger.SpanObserver { + return nil +}