From 054fa5e35760b6e61fae450efbcecf550589fd01 Mon Sep 17 00:00:00 2001 From: Kyle Nusbaum Date: Tue, 17 Mar 2020 19:03:47 -0500 Subject: [PATCH] ddtrace/tracer: add service version to tracer. (#607) This commit adds the concept of a service version to the tracer. The service version should be the current version of the application (service). the service version will be picked up from the DD_VERSION environment variable, or can be set with the WithServiceVersion StartOption when starting the tracer. WithServiceVersion takes priority over the environment variable. Spans that have a service name equal to the application's service name (set with DD_SERVICE or the WithServiceName StartOption) will be given a "version" tag with the service version as its value. --- .../grpc/stats_client_test.go | 17 +++--- .../grpc/stats_server_test.go | 15 +++-- ddtrace/ext/tags.go | 3 + ddtrace/tracer/option.go | 14 +++++ ddtrace/tracer/tracer.go | 3 + ddtrace/tracer/tracer_test.go | 56 +++++++++++++++++++ 6 files changed, 91 insertions(+), 17 deletions(-) diff --git a/contrib/google.golang.org/grpc/stats_client_test.go b/contrib/google.golang.org/grpc/stats_client_test.go index 5384ff0aab..6fb71f33a4 100644 --- a/contrib/google.golang.org/grpc/stats_client_test.go +++ b/contrib/google.golang.org/grpc/stats_client_test.go @@ -48,15 +48,14 @@ func TestClientStatsHandler(t *testing.T) { assert.NotZero(span.StartTime()) assert.True(span.FinishTime().After(span.StartTime())) assert.Equal("grpc.client", span.OperationName()) - assert.Equal(map[string]interface{}{ - "span.type": ext.AppTypeRPC, - "grpc.code": codes.OK.String(), - "service.name": serviceName, - "resource.name": "/grpc.Fixture/Ping", - tagMethodName: "/grpc.Fixture/Ping", - ext.TargetHost: "127.0.0.1", - ext.TargetPort: server.port, - }, span.Tags()) + tags := span.Tags() + assert.Equal(ext.AppTypeRPC, tags["span.type"]) + assert.Equal(codes.OK.String(), tags["grpc.code"]) + assert.Equal(serviceName, tags["service.name"]) + assert.Equal("/grpc.Fixture/Ping", tags["resource.name"]) + assert.Equal("/grpc.Fixture/Ping", tags[tagMethodName]) + assert.Equal("127.0.0.1", tags[ext.TargetHost]) + assert.Equal(server.port, tags[ext.TargetPort]) } func newClientStatsHandlerTestServer(statsHandler stats.Handler) (*rig, error) { diff --git a/contrib/google.golang.org/grpc/stats_server_test.go b/contrib/google.golang.org/grpc/stats_server_test.go index 2ad0c9779e..91171e9739 100644 --- a/contrib/google.golang.org/grpc/stats_server_test.go +++ b/contrib/google.golang.org/grpc/stats_server_test.go @@ -44,14 +44,13 @@ func TestServerStatsHandler(t *testing.T) { assert.NotZero(span.StartTime()) assert.True(span.FinishTime().After(span.StartTime())) assert.Equal("grpc.server", span.OperationName()) - assert.Equal(map[string]interface{}{ - "span.type": ext.AppTypeRPC, - "grpc.code": codes.OK.String(), - "service.name": serviceName, - "resource.name": "/grpc.Fixture/Ping", - tagMethodName: "/grpc.Fixture/Ping", - "_dd.measured": 1, - }, span.Tags()) + tags := span.Tags() + assert.Equal(ext.AppTypeRPC, tags["span.type"]) + assert.Equal(codes.OK.String(), tags["grpc.code"]) + assert.Equal(serviceName, tags["service.name"]) + assert.Equal("/grpc.Fixture/Ping", tags["resource.name"]) + assert.Equal("/grpc.Fixture/Ping", tags[tagMethodName]) + assert.Equal(1, tags["_dd.measured"]) } func newServerStatsHandlerTestServer(statsHandler stats.Handler) (*rig, error) { diff --git a/ddtrace/ext/tags.go b/ddtrace/ext/tags.go index 206b0da427..d6c44bf344 100644 --- a/ddtrace/ext/tags.go +++ b/ddtrace/ext/tags.go @@ -43,6 +43,9 @@ const ( // ServiceName defines the Service name for this Span. ServiceName = "service.name" + // Version is a tag that specifies the current application version. + Version = "version" + // ResourceName defines the Resource name for the Span. ResourceName = "resource.name" diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index e66f0e65ec..ec651b3473 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -30,6 +30,9 @@ type config struct { // serviceName specifies the name of this application. serviceName string + // version specifies the version of this application + version string + // sampler specifies the sampler that will be used for sampling traces. sampler Sampler @@ -110,6 +113,9 @@ func defaults(c *config) { } else { c.serviceName = filepath.Base(os.Args[0]) } + if ver := os.Getenv("DD_VERSION"); ver != "" { + c.version = ver + } if v := os.Getenv("DD_TAGS"); v != "" { for _, tag := range strings.Split(v, ",") { tag = strings.TrimSpace(tag) @@ -283,6 +289,14 @@ func WithSamplingRules(rules []SamplingRule) StartOption { } } +// WithServiceVersion specifies the version of the service that is running. This will +// be included in spans from this service in the "version" tag. +func WithServiceVersion(version string) StartOption { + return func(cfg *config) { + cfg.version = version + } +} + // StartSpanOption is a configuration option for StartSpan. It is aliased in order // to help godoc group all the functions returning it together. It is considered // more correct to refer to it as the type as the origin, ddtrace.StartSpanOption. diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 7fa5a7a780..ecdda30448 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -321,6 +321,9 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt for k, v := range t.config.globalTags { span.SetTag(k, v) } + if t.config.version != "" && span.Service == t.config.serviceName { + span.SetTag(ext.Version, t.config.version) + } if context == nil { // this is a brand new trace, sample it t.sample(span) diff --git a/ddtrace/tracer/tracer_test.go b/ddtrace/tracer/tracer_test.go index b57a51f0bd..b9bfd9a310 100644 --- a/ddtrace/tracer/tracer_test.go +++ b/ddtrace/tracer/tracer_test.go @@ -1014,6 +1014,62 @@ func TestTracerReportsHostname(t *testing.T) { }) } +func TestVersion(t *testing.T) { + t.Run("env", func(t *testing.T) { + os.Setenv("DD_VERSION", "1.2.3") + defer os.Unsetenv("DD_VERSION") + + tracer, _, _, stop := startTestTracer(t) + defer stop() + + assert := assert.New(t) + sp := tracer.StartSpan("http.request").(*span) + v := sp.Meta[ext.Version] + assert.Equal("1.2.3", v) + }) + + t.Run("option", func(t *testing.T) { + os.Setenv("DD_VERSION", "1.2.3") + defer os.Unsetenv("DD_VERSION") + + tracer, _, _, stop := startTestTracer(t, WithServiceVersion("4.5.6")) + defer stop() + + assert := assert.New(t) + sp := tracer.StartSpan("http.request").(*span) + v := sp.Meta[ext.Version] + assert.Equal("4.5.6", v) + }) + + t.Run("unset", func(t *testing.T) { + os.Setenv("DD_VERSION", "1.2.3") + defer os.Unsetenv("DD_VERSION") + + tracer, _, _, stop := startTestTracer(t, WithService("servenv")) + defer stop() + + assert := assert.New(t) + sp := tracer.StartSpan("http.request", ServiceName("otherservenv")).(*span) + _, ok := sp.Meta[ext.Version] + assert.False(ok) + }) + + t.Run("unset2", func(t *testing.T) { + os.Setenv("DD_SERVICE", "servenv") + defer os.Unsetenv("DD_SERVICE") + os.Setenv("DD_VERSION", "1.2.3") + defer os.Unsetenv("DD_VERSION") + + tracer, _, _, stop := startTestTracer(t) + defer stop() + + assert := assert.New(t) + sp := tracer.StartSpan("http.request", ServiceName("otherservenv")).(*span) + _, ok := sp.Meta[ext.Version] + assert.False(ok) + }) +} + // BenchmarkConcurrentTracing tests the performance of spawning a lot of // goroutines where each one creates a trace with a parent and a child. func BenchmarkConcurrentTracing(b *testing.B) {