From fa17e9ccec2515f70217e622a6d5b208c2d1c813 Mon Sep 17 00:00:00 2001 From: David Elner Date: Tue, 14 Apr 2020 17:31:04 -0400 Subject: [PATCH 1/3] Changed: Populate #env, #service, #version from #tags if not defined. --- lib/ddtrace/configuration/settings.rb | 13 ++++++++ lib/ddtrace/ext/environment.rb | 1 + spec/ddtrace/configuration/settings_spec.rb | 36 +++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/lib/ddtrace/configuration/settings.rb b/lib/ddtrace/configuration/settings.rb index e1c52692475..971a1d0729f 100644 --- a/lib/ddtrace/configuration/settings.rb +++ b/lib/ddtrace/configuration/settings.rb @@ -158,6 +158,19 @@ def runtime_metrics(options = nil) # Coerce keys to strings string_tags = Hash[new_value.collect { |k, v| [k.to_s, v] }] + # Cross-populate tag values with other settings + if env.nil? && string_tags.key?(Ext::Environment::TAG_ENV) + self.env = string_tags[Ext::Environment::TAG_ENV] + end + + if version.nil? && string_tags.key?(Ext::Environment::TAG_VERSION) + self.version = string_tags[Ext::Environment::TAG_VERSION] + end + + if service.nil? && string_tags.key?(Ext::Environment::TAG_SERVICE) + self.service = string_tags[Ext::Environment::TAG_SERVICE] + end + # Merge with previous tags (old_value || {}).merge(string_tags) end diff --git a/lib/ddtrace/ext/environment.rb b/lib/ddtrace/ext/environment.rb index 4db999489e0..a258742ce96 100644 --- a/lib/ddtrace/ext/environment.rb +++ b/lib/ddtrace/ext/environment.rb @@ -7,6 +7,7 @@ module Environment ENV_VERSION = 'DD_VERSION'.freeze TAG_ENV = 'env'.freeze + TAG_SERVICE = 'service'.freeze TAG_VERSION = 'version'.freeze end end diff --git a/spec/ddtrace/configuration/settings_spec.rb b/spec/ddtrace/configuration/settings_spec.rb index 12e0649a4e8..46cbeae65a7 100644 --- a/spec/ddtrace/configuration/settings_spec.rb +++ b/spec/ddtrace/configuration/settings_spec.rb @@ -544,6 +544,18 @@ end end + context 'defines :env with missing #env' do + let(:env_tags) { "env:#{tag_env_value}" } + let(:tag_env_value) { 'tag-env-value' } + + it 'populates #env from the tag' do + expect { tags } + .to change { settings.env } + .from(nil) + .to(tag_env_value) + end + end + context 'conflicts with #env' do let(:env_tags) { "env:#{tag_env_value}" } let(:tag_env_value) { 'tag-env-value' } @@ -554,6 +566,18 @@ it { is_expected.to include('env' => env_value) } end + context 'defines :service with missing #service' do + let(:env_tags) { "service:#{tag_service_value}" } + let(:tag_service_value) { 'tag-service-value' } + + it 'populates #service from the tag' do + expect { tags } + .to change { settings.service } + .from(nil) + .to(tag_service_value) + end + end + context 'conflicts with #version' do let(:env_tags) { "env:#{tag_version_value}" } let(:tag_version_value) { 'tag-version-value' } @@ -563,6 +587,18 @@ it { is_expected.to include('version' => version_value) } end + + context 'defines :version with missing #version' do + let(:env_tags) { "version:#{tag_version_value}" } + let(:tag_version_value) { 'tag-version-value' } + + it 'populates #version from the tag' do + expect { tags } + .to change { settings.version } + .from(nil) + .to(tag_version_value) + end + end end end From 02710c1bc91f119f561d720924b310871395968c Mon Sep 17 00:00:00 2001 From: David Elner Date: Wed, 15 Apr 2020 07:17:11 -0400 Subject: [PATCH 2/3] Added: #service to Correlation::Identifier, omit blank attributes. --- lib/ddtrace/correlation.rb | 15 +- lib/ddtrace/ext/correlation.rb | 1 + spec/ddtrace/correlation_spec.rb | 236 +++++++++++++++++++++++-------- 3 files changed, 187 insertions(+), 65 deletions(-) diff --git a/lib/ddtrace/correlation.rb b/lib/ddtrace/correlation.rb index a10afb9577f..65fb951b077 100644 --- a/lib/ddtrace/correlation.rb +++ b/lib/ddtrace/correlation.rb @@ -6,21 +6,24 @@ module Datadog # e.g. Retrieve a correlation to the current trace for logging, etc. module Correlation # Struct representing correlation - Identifier = Struct.new(:trace_id, :span_id, :env, :version) do + Identifier = Struct.new(:trace_id, :span_id, :env, :service, :version) do def initialize(*args) super self.trace_id = trace_id || 0 self.span_id = span_id || 0 self.env = env || Datadog.configuration.env + self.service = service || Datadog.configuration.service self.version = version || Datadog.configuration.version end def to_s - str = "#{Ext::Correlation::ATTR_TRACE_ID}=#{trace_id}" - str += " #{Ext::Correlation::ATTR_SPAN_ID}=#{span_id}" - str += " #{Ext::Correlation::ATTR_ENV}=#{env}" - str += " #{Ext::Correlation::ATTR_VERSION}=#{version}" - str + attributes = [] + attributes << "#{Ext::Correlation::ATTR_ENV}=#{env}" unless env.nil? + attributes << "#{Ext::Correlation::ATTR_SERVICE}=#{service}" unless service.nil? + attributes << "#{Ext::Correlation::ATTR_VERSION}=#{version}" unless version.nil? + attributes << "#{Ext::Correlation::ATTR_TRACE_ID}=#{trace_id}" + attributes << "#{Ext::Correlation::ATTR_SPAN_ID}=#{span_id}" + attributes.join(' ') end end.freeze diff --git a/lib/ddtrace/ext/correlation.rb b/lib/ddtrace/ext/correlation.rb index caf2c9c13ce..30f52c4139c 100644 --- a/lib/ddtrace/ext/correlation.rb +++ b/lib/ddtrace/ext/correlation.rb @@ -2,6 +2,7 @@ module Datadog module Ext module Correlation ATTR_ENV = 'dd.env'.freeze + ATTR_SERVICE = 'dd.service'.freeze ATTR_SPAN_ID = 'dd.span_id'.freeze ATTR_TRACE_ID = 'dd.trace_id'.freeze ATTR_VERSION = 'dd.version'.freeze diff --git a/spec/ddtrace/correlation_spec.rb b/spec/ddtrace/correlation_spec.rb index 59ed2bfe1bc..977d9fa7308 100644 --- a/spec/ddtrace/correlation_spec.rb +++ b/spec/ddtrace/correlation_spec.rb @@ -4,48 +4,29 @@ require 'ddtrace/context' RSpec.describe Datadog::Correlation do - # Expect string to contain the attribute, at the beginning/end of the string, - # or buffered by a whitespace character to delimit it. - def have_attribute(attribute) - match(/(?<=\A|\s)#{Regexp.escape(attribute)}(?=\z|\s)/) + let(:default_env) { 'default-env' } + let(:default_service) { 'default-service' } + let(:default_version) { 'default-version' } + + before do + allow(Datadog.configuration).to receive(:env).and_return(default_env) + allow(Datadog.configuration).to receive(:service).and_return(default_service) + allow(Datadog.configuration).to receive(:version).and_return(default_version) end describe '::identifier_from_context' do - subject(:correlation_ids) { described_class.identifier_from_context(context) } - let(:environment) { nil } - let(:version) { nil } - - before do - allow(Datadog.configuration).to receive(:env).and_return(environment) - allow(Datadog.configuration).to receive(:version).and_return(version) - end + subject(:identifier_from_context) { described_class.identifier_from_context(context) } context 'given nil' do let(:context) { nil } - shared_examples_for 'an empty correlation identifier' do - it { is_expected.to be_a_kind_of(Datadog::Correlation::Identifier) } - it { expect(correlation_ids.trace_id).to be 0 } - it { expect(correlation_ids.span_id).to be 0 } - it { expect(correlation_ids.env).to eq environment } - it { expect(correlation_ids.version).to eq version } - it { expect(correlation_ids.to_s).to have_attribute("#{Datadog::Ext::Correlation::ATTR_TRACE_ID}=0") } - it { expect(correlation_ids.to_s).to have_attribute("#{Datadog::Ext::Correlation::ATTR_SPAN_ID}=0") } - it { expect(correlation_ids.to_s).to have_attribute("#{Datadog::Ext::Correlation::ATTR_ENV}=#{environment}") } - it { expect(correlation_ids.to_s).to have_attribute("#{Datadog::Ext::Correlation::ATTR_VERSION}=#{version}") } - end - - it_behaves_like 'an empty correlation identifier' - - context 'after Datadog::Environment::env has changed' do - let(:environment) { 'my-env' } - it_behaves_like 'an empty correlation identifier' - end - - context 'after Datadog::Environment::version has changed' do - let(:version) { 'my-version' } - it_behaves_like 'an empty correlation identifier' - end + it { is_expected.to be_a_kind_of(described_class::Identifier) } + it { expect(identifier_from_context.frozen?).to be true } + it { expect(identifier_from_context.trace_id).to be 0 } + it { expect(identifier_from_context.span_id).to be 0 } + it { expect(identifier_from_context.env).to be default_env } + it { expect(identifier_from_context.service).to be default_service } + it { expect(identifier_from_context.version).to be default_version } end context 'given a Context object' do @@ -57,44 +38,181 @@ def have_attribute(attribute) ) end - let(:trace_id) { double('trace id') } - let(:span_id) { double('span id') } - - shared_examples_for 'a correlation identifier with basic properties' do - it { is_expected.to be_a_kind_of(Datadog::Correlation::Identifier) } - it { expect(correlation_ids.trace_id).to eq(trace_id) } - it { expect(correlation_ids.span_id).to eq(span_id) } - it { expect(correlation_ids.env).to eq environment } - it { expect(correlation_ids.version).to eq version } - it { expect(correlation_ids.to_s).to have_attribute("#{Datadog::Ext::Correlation::ATTR_TRACE_ID}=#{trace_id}") } - it { expect(correlation_ids.to_s).to have_attribute("#{Datadog::Ext::Correlation::ATTR_SPAN_ID}=#{span_id}") } - it { expect(correlation_ids.to_s).to have_attribute("#{Datadog::Ext::Correlation::ATTR_ENV}=#{environment}") } - it { expect(correlation_ids.to_s).to have_attribute("#{Datadog::Ext::Correlation::ATTR_VERSION}=#{version}") } + let(:trace_id) { double('trace ID') } + let(:span_id) { double('span ID') } + + it { is_expected.to be_a_kind_of(described_class::Identifier) } + it { expect(identifier_from_context.frozen?).to be true } + it { expect(identifier_from_context.trace_id).to be trace_id } + it { expect(identifier_from_context.span_id).to be span_id } + it { expect(identifier_from_context.env).to be default_env } + it { expect(identifier_from_context.service).to be default_service } + it { expect(identifier_from_context.version).to be default_version } + end + end + + describe described_class::Identifier do + describe '#new' do + context 'given no arguments' do + subject(:identifier) { described_class.new } + + it do + is_expected.to have_attributes( + trace_id: 0, + span_id: 0, + env: default_env, + service: default_service, + version: default_version + ) + end + end + + context 'given full arguments' do + subject(:identifier) do + described_class.new( + trace_id, + span_id, + env, + service, + version + ) + end + + let(:trace_id) { double('trace_id') } + let(:span_id) { double('span_id') } + let(:env) { double('env') } + let(:service) { double('service') } + let(:version) { double('version') } + + it do + is_expected.to have_attributes( + trace_id: trace_id, + span_id: span_id, + env: env, + service: service, + version: version + ) + end end + end + + describe '#to_s' do + shared_examples_for 'an identifier string' do + subject(:string) { identifier.to_s } + + let(:identifier) do + described_class.new( + trace_id, + span_id, + env, + service, + version + ) + end - it_behaves_like 'a correlation identifier with basic properties' + let(:trace_id) { double('trace_id') } + let(:span_id) { double('span_id') } + let(:env) { double('env') } + let(:service) { double('service') } + let(:version) { double('version') } + + it 'doesn\'t have attributes without values' do + is_expected.to_not match(/.*=(?=\z|\s)/) + end + end + + # Expect string to contain the attribute, at the beginning/end of the string, + # or buffered by a whitespace character to delimit it. + def have_attribute(attribute) + match(/(?<=\A|\s)#{Regexp.escape(attribute)}(?=\z|\s)/) + end + + context 'when #trace_id' do + context 'is defined' do + it_behaves_like 'an identifier string' do + let(:trace_id) { double('trace_id') } + it { is_expected.to have_attribute("#{Datadog::Ext::Correlation::ATTR_TRACE_ID}=#{trace_id}") } + end + end - context 'when #env configuration setting' do context 'is not defined' do - let(:environment) { nil } - it_behaves_like 'a correlation identifier with basic properties' + it_behaves_like 'an identifier string' do + let(:trace_id) { nil } + it { is_expected.to have_attribute("#{Datadog::Ext::Correlation::ATTR_TRACE_ID}=0") } + end end + end + context 'when #span_id' do context 'is defined' do - let(:environment) { 'my-env' } - it_behaves_like 'a correlation identifier with basic properties' + it_behaves_like 'an identifier string' do + let(:span_id) { double('span_id') } + it { is_expected.to have_attribute("#{Datadog::Ext::Correlation::ATTR_SPAN_ID}=#{span_id}") } + end + end + + context 'is not defined' do + it_behaves_like 'an identifier string' do + let(:span_id) { nil } + it { is_expected.to have_attribute("#{Datadog::Ext::Correlation::ATTR_SPAN_ID}=0") } + end end end - context 'when #version configuration setting' do + context 'when #env' do + context 'is defined' do + it_behaves_like 'an identifier string' do + let(:env) { double('env') } + it { is_expected.to have_attribute("#{Datadog::Ext::Correlation::ATTR_ENV}=#{env}") } + it 'puts the env attribute before trace ID and span ID' do + is_expected.to match(/(dd\.env=).*(dd\.trace_id=).*(dd\.span_id=).*/) + end + end + end + context 'is not defined' do - let(:version) { nil } - it_behaves_like 'a correlation identifier with basic properties' + it_behaves_like 'an identifier string' do + let(:env) { nil } + it { is_expected.to_not have_attribute("#{Datadog::Ext::Correlation::ATTR_ENV}=#{env}") } + end end + end + context 'when #service' do context 'is defined' do - let(:version) { 'my-version' } - it_behaves_like 'a correlation identifier with basic properties' + it_behaves_like 'an identifier string' do + let(:service) { double('service') } + it { is_expected.to have_attribute("#{Datadog::Ext::Correlation::ATTR_SERVICE}=#{service}") } + it 'puts the service attribute before trace ID and span ID' do + is_expected.to match(/(dd\.service=).*(dd\.trace_id=).*(dd\.span_id=).*/) + end + end + end + + context 'is not defined' do + it_behaves_like 'an identifier string' do + let(:service) { nil } + it { is_expected.to_not have_attribute("#{Datadog::Ext::Correlation::ATTR_SERVICE}=#{service}") } + end + end + end + + context 'when #version' do + context 'is defined' do + it_behaves_like 'an identifier string' do + let(:version) { double('version') } + it { is_expected.to have_attribute("#{Datadog::Ext::Correlation::ATTR_VERSION}=#{version}") } + it 'puts the version attribute before trace ID and span ID' do + is_expected.to match(/(dd\.version=).*(dd\.trace_id=).*(dd\.span_id=).*/) + end + end + end + + context 'is not defined' do + it_behaves_like 'an identifier string' do + let(:version) { nil } + it { is_expected.to_not have_attribute("#{Datadog::Ext::Correlation::ATTR_VERSION}=#{version}") } + end end end end From 70863c48fecf7d6a1416293b3cb0c88c1ebe0af4 Mon Sep 17 00:00:00 2001 From: David Elner Date: Wed, 15 Apr 2020 16:33:06 -0400 Subject: [PATCH 3/3] Changed: Documentation to reflect environment and correlation changes. --- docs/GettingStarted.md | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index a7add12aefe..9e27fd426c3 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -1603,9 +1603,11 @@ By default, the trace agent (not this library, but the program running in the ba You can configure the application to automatically tag your traces and metrics, using the following environment variables: - `DD_ENV`: Your application environment (e.g. `production`, `staging`, etc.) + - `DD_SERVICE`: Your application's default service name (e.g. `billing-api`) - `DD_VERSION`: Your application version (e.g. `2.5`, `202003181415`, `1.3-alpha`, etc.) - `DD_TAGS`: Custom tags in value pairs separated by `,` (e.g. `layer:api,team:intake`) - - If `DD_ENV` or `DD_VERSION`, it will override any `env` or `version` tag defined in `DD_TAGS`. + - If `DD_ENV`, `DD_SERVICE` or `DD_VERSION` are set, it will override any respective `env`/`service`/`version` tag defined in `DD_TAGS`. + - If `DD_ENV`, `DD_SERVICE` or `DD_VERSION` are NOT set, tags defined in `DD_TAGS` will be used to populate `env`/`service`/`version` respectively. These values can also be overridden at the tracer level: @@ -1618,9 +1620,9 @@ Datadog.configure do |c| end ``` -This enables you to set this value on a per tracer basis, so you can have for example several applications reporting for different environments on the same host. +This enables you to set this value on a per application basis, so you can have for example several applications reporting for different environments on the same host. -Ultimately, tags can be set per span, but `env` should typically be the same for all spans belonging to a given trace. +Tags can also be set directly on individual spans, which will supersede any conflicting tags defined at the application level. ### Sampling @@ -1910,6 +1912,7 @@ Datadog.tracer.trace('correlation.example') do correlation.trace_id # => 5963550561812073440 correlation.span_id # => 2232727802607726424 correlation.env # => 'production' (derived from DD_ENV) + correlation.service # => 'billing-api' (derived from DD_SERVICE) correlation.version # => '2.5.17' (derived from DD_VERSION) end @@ -1919,8 +1922,9 @@ correlation = Datadog.tracer.active_correlation correlation = Datadog.tracer.active_correlation correlation.trace_id # => 0 correlation.span_id # => 0 -correlation.trace_id # => nil -correlation.span_id # => nil +correlation.env # => 'production' (derived from DD_ENV) +correlation.service # => 'billing-api' (derived from DD_SERVICE) +correlation.version # => '2.5.17' (derived from DD_VERSION) ``` #### For logging in Rails applications using Lograge (recommended) @@ -1939,6 +1943,7 @@ config.lograge.custom_options = lambda do |event| :trace_id => correlation.trace_id.to_s, :span_id => correlation.span_id.to_s, :env => correlation.env.to_s, + :service => correlation.service.to_s, :version => correlation.version.to_s }, :ddsource => ["ruby"], @@ -1960,27 +1965,29 @@ end # Given: # DD_ENV = 'production' (The name of the environment your application is running in.) +# DD_SERVICE = 'billing-api' (Default service name of your application.) # DD_VERSION = '2.5.17' (The version of your application.) # Web requests will produce: -# [dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206 dd.env=production dd.version=2.5.17] Started GET "/articles" for 172.22.0.1 at 2019-01-16 18:50:57 +0000 -# [dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206 dd.env=production dd.version=2.5.17] Processing by ArticlesController#index as */* -# [dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206 dd.env=production dd.version=2.5.17] Article Load (0.5ms) SELECT "articles".* FROM "articles" -# [dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206 dd.env=production dd.version=2.5.17] Completed 200 OK in 7ms (Views: 5.5ms | ActiveRecord: 0.5ms) +# [dd.env=production dd.service=billing-api dd.version=2.5.17 dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206] Started GET "/articles" for 172.22.0.1 at 2019-01-16 18:50:57 +0000 +# [dd.env=production dd.service=billing-api dd.version=2.5.17 dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206] Processing by ArticlesController#index as */* +# [dd.env=production dd.service=billing-api dd.version=2.5.17 dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206] Article Load (0.5ms) SELECT "articles".* FROM "articles" +# [dd.env=production dd.service=billing-api dd.version=2.5.17 dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206] Completed 200 OK in 7ms (Views: 5.5ms | ActiveRecord: 0.5ms) ``` #### For logging in Ruby applications To add correlation IDs to your logger, add a log formatter which retrieves the correlation IDs with `Datadog.tracer.active_correlation`, then add them to the message. -To properly correlate with Datadog logging, be sure the following is present in the log message: +To properly correlate with Datadog logging, be sure the following is present in the log message, in order as they appear: + - `dd.env=`: Where `` is equal to `Datadog.tracer.active_correlation.env`. Omit if no environment is configured. + - `dd.service=`: Where `` is equal to `Datadog.tracer.active_correlation.service`. Omit if no default service name is configured. + - `dd.version=`: Where `` is equal to `Datadog.tracer.active_correlation.version`. Omit if no application version is configured. - `dd.trace_id=`: Where `` is equal to `Datadog.tracer.active_correlation.trace_id` or `0` if no trace is active during logging. - `dd.span_id=`: Where `` is equal to `Datadog.tracer.active_correlation.span_id` or `0` if no trace is active during logging. - - `dd.env=`: Where `` is equal to `Datadog.tracer.active_correlation.env` or empty string (no quotes) if no environment name is configured. - - `dd.version=`: Where `` is equal to `Datadog.tracer.active_correlation.version` or empty string (no quotes) if no application version is configured. -By default, `Datadog::Correlation::Identifier#to_s` will return `dd.trace_id= dd.span_id= dd.env= dd.version=`. +By default, `Datadog::Correlation::Identifier#to_s` will return `dd.env= dd.service= dd.version= dd.trace_id= dd.span_id=`. If a trace is not active and the application environment & version is not configured, it will return `dd.trace_id=0 dd.span_id=0 dd.env= dd.version=`. @@ -1991,6 +1998,7 @@ require 'ddtrace' require 'logger' ENV['DD_ENV'] = 'production' +ENV['DD_SERVICE'] = 'billing-api' ENV['DD_VERSION'] = '2.5.17' logger = Logger.new(STDOUT) @@ -2001,11 +2009,11 @@ end # When no trace is active logger.warn('This is an untraced operation.') -# [2019-01-16 18:38:41 +0000][my_app][WARN][dd.trace_id=0 dd.span_id=0 dd.env=production dd.version=2.5.17] This is an untraced operation. +# [2019-01-16 18:38:41 +0000][my_app][WARN][dd.env=production dd.service=billing-api dd.version=2.5.17 dd.trace_id=0 dd.span_id=0] This is an untraced operation. # When a trace is active Datadog.tracer.trace('my.operation') { logger.warn('This is a traced operation.') } -# [2019-01-16 18:38:41 +0000][my_app][WARN][dd.trace_id=8545847825299552251 dd.span_id=3711755234730770098 dd.env=production dd.version=2.5.17] This is a traced operation. +# [2019-01-16 18:38:41 +0000][my_app][WARN][dd.env=production dd.service=billing-api dd.version=2.5.17 dd.trace_id=8545847825299552251 dd.span_id=3711755234730770098] This is a traced operation. ``` ### Configuring the transport layer