-
Notifications
You must be signed in to change notification settings - Fork 398
feat: add process tags to traces #5033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 9 commits
1d8bab2
58592a3
7dc9184
cad26a6
f31440a
cfec602
8dae705
055586f
cacb500
7661a3f
7825940
5de6efd
f5ca84a
9ad5be5
a66e635
ec1e930
4073ab5
22a3680
5784833
2b705e3
e3deb4c
4747259
41bc6c0
c3605c0
adfa416
0dff545
7d8da40
31d9796
3672a8a
23d9769
7615906
d4c6a91
433b250
47efb90
a2643a6
ccd4971
be9587d
6042830
4210d74
f9af946
381fbe2
138dff8
2449153
5252259
0eaf302
fbfecfe
cc2225f
62822ab
a77e63b
439e81a
9e45ade
0ab4fef
6577d3f
a336c66
0d229de
e83bc4a
ce1759f
8b978c6
f4c9d49
eae4eb9
f3f1480
b48d20d
3d33291
4e3f8f4
5f6908c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # frozen_string_literal: true | ||
| require_relative 'ext' | ||
| require_relative '../normalizer' | ||
|
|
||
| module Datadog | ||
| module Core | ||
| module Environment | ||
| # Retrieves process level information | ||
wantsui marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| module Process | ||
| module_function | ||
|
|
||
| def entrypoint_workdir | ||
marcotc marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| File.basename(Dir.pwd) | ||
| end | ||
|
|
||
| def entrypoint_type | ||
| Core::Environment::Ext::PROCESS_TYPE | ||
|
||
| end | ||
|
|
||
| def entrypoint_name | ||
| File.basename($0) | ||
| end | ||
|
|
||
| def entrypoint_basedir | ||
| current_basedir = File.expand_path(File.dirname($0)) | ||
| normalized_basedir = current_basedir.tr(File::SEPARATOR, '/') | ||
wantsui marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| normalized_basedir.delete_prefix!('/') | ||
| end | ||
|
|
||
| # Normalize tag key and value using the Datadog Agent's tag normalization logic | ||
| def serialized_kv_helper(key, value) | ||
| key = Core::Normalizer.normalize(key) | ||
| value = Core::Normalizer.normalize(value) | ||
| "#{key}:#{value}" | ||
| end | ||
|
|
||
| # This method returns a key/value part of serialized tags in the format of k1:v1,k2:v2,k3:v3 | ||
| def serialized | ||
| return @serialized if defined?(@serialized) | ||
| tags = [] | ||
| tags << serialized_kv_helper(Core::Environment::Ext::TAG_ENTRYPOINT_WORKDIR, entrypoint_workdir) if entrypoint_workdir | ||
| tags << serialized_kv_helper(Core::Environment::Ext::TAG_ENTRYPOINT_NAME, entrypoint_name) if entrypoint_name | ||
| tags << serialized_kv_helper(Core::Environment::Ext::TAG_ENTRYPOINT_BASEDIR, entrypoint_basedir) if entrypoint_basedir | ||
| tags << serialized_kv_helper(Core::Environment::Ext::TAG_ENTRYPOINT_TYPE, entrypoint_type) if entrypoint_type | ||
|
||
| @serialized = tags.join(',').freeze | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| module Datadog | ||
| module Core | ||
| module Normalizer | ||
wantsui marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
wantsui marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| module_function | ||
| INVALID_TAG_CHARACTERS = %r{[^a-z0-9_\-:./]}.freeze | ||
tlhunter marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # Based on https://docs.datadoghq.com/getting_started/tagging/#defining-tags | ||
| # Currently a reimplementation of the logic in the | ||
| # Datadog::Tracing::Metadata::Ext::HTTP::Headers.to_tag method with some additional items | ||
| # TODO: Swap out the logic in the Datadog Tracing Metadata headers logic | ||
| def self.normalize(original_value) | ||
|
||
| return "" if original_value.nil? || original_value.to_s.strip.empty? | ||
wantsui marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # Removes whitespaces | ||
|
||
| normalized_value = original_value.to_s.strip | ||
| # Lower case characters | ||
| normalized_value.downcase! | ||
| # Invalid characters are replaced with an underscore | ||
| normalized_value.gsub!(INVALID_TAG_CHARACTERS, '_') | ||
| # Merge consecutive underscores with a single underscore | ||
| normalized_value.gsub!(/_+/, '_') | ||
| # Remove leading non-letter characters | ||
| normalized_value.sub!(/\A[^a-z]+/, "") | ||
| # Maximum length is 200 characters | ||
| normalized_value = normalized_value[0...200] if normalized_value.length > 200 | ||
wantsui marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| normalized_value | ||
| end | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require_relative '../../core/environment/identity' | ||
| require_relative '../../core/environment/process' | ||
| require_relative '../../core/environment/socket' | ||
| require_relative '../../core/environment/git' | ||
| require_relative '../../core/git/ext' | ||
|
|
@@ -60,6 +61,7 @@ def format! | |
| tag_sampling_priority! | ||
| tag_profiling_enabled! | ||
| tag_apm_tracing_disabled! | ||
| tag_process_tags! | ||
|
||
|
|
||
| if first_span | ||
| tag_git_repository_url! | ||
|
|
@@ -215,6 +217,12 @@ def tag_git_commit_sha! | |
| first_span.set_tag(Core::Git::Ext::TAG_COMMIT_SHA, git_commit_sha) | ||
| end | ||
|
|
||
| def tag_process_tags! | ||
| return unless Datadog.configuration.experimental_propagate_process_tags_enabled | ||
| process_tags = Core::Environment::Process.serialized | ||
| first_span.set_tag(Core::Environment::Ext::TAG_PROCESS_TAGS, process_tags) | ||
wantsui marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| end | ||
|
|
||
| private | ||
|
|
||
| def partial? | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| require 'spec_helper' | ||
| require 'datadog/core/environment/process' | ||
| require 'open3' | ||
|
|
||
| RSpec.describe Datadog::Core::Environment::Process do | ||
| describe '::entrypoint_workdir' do | ||
| subject(:entrypoint_workdir) { described_class.entrypoint_workdir } | ||
|
|
||
| it { is_expected.to be_a_kind_of(String) } | ||
| end | ||
|
|
||
| describe '::entrypoint_type' do | ||
| subject(:entrypoint_type) { described_class.entrypoint_type } | ||
|
|
||
| it { is_expected.to be_a_kind_of(String) } | ||
| it { is_expected.to eq(Datadog::Core::Environment::Ext::PROCESS_TYPE) } | ||
| end | ||
|
|
||
| describe '::entrypoint_name' do | ||
| subject(:entrypoint_name) { described_class.entrypoint_name } | ||
|
|
||
| it { is_expected.to be_a_kind_of(String) } | ||
| end | ||
|
|
||
| describe '::entrypoint_basedir' do | ||
| subject(:entrypoint_basedir) { described_class.entrypoint_basedir } | ||
|
|
||
| it { is_expected.to be_a_kind_of(String) } | ||
| end | ||
|
|
||
| describe '::serialized' do | ||
| subject(:serialized) { described_class.serialized } | ||
|
|
||
| it { is_expected.to be_a_kind_of(String) } | ||
|
|
||
| it 'returns the same object when called multiple times' do | ||
| # Processes are fixed so no need to recompute this on each call | ||
| first_call = described_class.serialized | ||
| second_call = described_class.serialized | ||
| expect(first_call).to equal(second_call) | ||
| end | ||
| end | ||
|
|
||
| describe 'Scenario: Real applications' do | ||
wantsui marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| context 'when running a real Rails application' do | ||
| it 'detects Rails process information correctly' do | ||
| Dir.mktmpdir do |tmp_dir| | ||
| Dir.chdir(tmp_dir) do | ||
| Bundler.with_unbundled_env do | ||
| skip('rails gem could not be installed') unless system('gem install rails') | ||
wantsui marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| unless system('rails new test_app --minimal --skip-test --skip-keeps --skip-git --skip-docker') | ||
| skip('rails new command failed') | ||
| end | ||
| end | ||
| end | ||
| File.open("#{tmp_dir}/test_app/Gemfile", 'a') do |file| | ||
| file.puts "gem 'datadog', path: '#{Dir.pwd}', require: false" | ||
| end | ||
| File.write("#{tmp_dir}/test_app/config/initializers/process_initializer.rb", <<-RUBY) | ||
| Rails.application.config.after_initialize do | ||
| require 'datadog/core/environment/process' | ||
| STDERR.puts "entrypoint_workdir:\#{Datadog::Core::Environment::Process.entrypoint_workdir}" | ||
| STDERR.puts "entrypoint_type:\#{Datadog::Core::Environment::Process.entrypoint_type}" | ||
| STDERR.puts "entrypoint_name:\#{Datadog::Core::Environment::Process.entrypoint_name}" | ||
| STDERR.puts "entrypoint_basedir:\#{Datadog::Core::Environment::Process.entrypoint_basedir}" | ||
| STDERR.puts "_dd.tags.process:\#{Datadog::Core::Environment::Process.serialized}" | ||
| STDERR.flush | ||
| Thread.new { sleep 1; Process.kill('TERM', Process.pid)}#{' '} | ||
| end | ||
| RUBY | ||
| Bundler.with_unbundled_env do | ||
| Dir.chdir("#{tmp_dir}/test_app") do | ||
| _, _, _ = Open3.capture3('bundle install') | ||
|
||
| _, err, _ = Open3.capture3('bundle exec rails s') | ||
| expect(err).to include('entrypoint_workdir:test_app') | ||
| expect(err).to include('entrypoint_type:script') | ||
| expect(err).to include('entrypoint_name:rails') | ||
| basedir_test = tmp_dir.sub(%r{^/}, '') | ||
| expect(err).to include("entrypoint_basedir:#{basedir_test}/test_app/bin") | ||
| expected_tags = "entrypoint.workdir:test_app,entrypoint.name:rails,entrypoint.basedir:#{basedir_test}/test_app/bin,entrypoint.type:script" | ||
| expect(err).to include("_dd.tags.process:#{expected_tags}") | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| require 'spec_helper' | ||
| require 'datadog/core/normalizer' | ||
|
|
||
| RSpec.describe Datadog::Core::Normalizer do | ||
| describe '.normalize' do | ||
| subject(:normalize) { described_class.normalize(input) } | ||
|
|
||
| context 'keeps normal strings the same' do | ||
| let(:input) {'regulartag'} | ||
| let(:expected_output) {'regulartag'} | ||
| it { is_expected.to eq(expected_output) } | ||
| end | ||
|
|
||
| context 'truncates long strings' do | ||
| let(:input) {'a' * 201} | ||
| let(:expected_output) {'a' * 200} | ||
| it { is_expected.to eq(expected_output) } | ||
| end | ||
|
|
||
| context 'transforms special characters to underscores' do | ||
| let(:input) {'a&**!'} | ||
| let(:expected_output) {'a_'} | ||
| it { is_expected.to eq(expected_output) } | ||
| end | ||
|
|
||
| context 'capital letters are lower cased' do | ||
| let(:input) {'A'*10} | ||
| let(:expected_output) {'a'*10} | ||
| it { is_expected.to eq(expected_output) } | ||
| end | ||
|
|
||
| context 'removes whitespaces' do | ||
| let(:input) {' hi '} | ||
| let(:expected_output) {'hi'} | ||
| it { is_expected.to eq(expected_output) } | ||
| end | ||
|
|
||
| context 'characters must start with a letter' do | ||
| let(:input) {'1hi'} | ||
| let(:expected_output) {'hi'} | ||
| it { is_expected.to eq(expected_output) } | ||
| end | ||
|
|
||
| context 'if none of the characters are valid to start the value, the string is empty' do | ||
| let(:input) {'111111111'} | ||
| let(:expected_output) {''} | ||
| it { is_expected.to eq(expected_output) } | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -237,6 +237,23 @@ | |
| end | ||
| end | ||
|
|
||
| shared_examples 'first span with process tags' do | ||
| it do | ||
| format! | ||
| expect(first_span.meta).to include('_dd.tags.process') | ||
| expect(first_span.meta['_dd.tags.process']).to eq(Datadog::Core::Environment::Process.serialized) | ||
| # TODO figure out if we need an assertion for the value, ie | ||
|
||
| # `"entrypoint.workdir:app,entrypoint.name:rspec,entrypoint.basedir:usr/local/bundle/bin,entrypoint.type:script,server.type:placeholder"` | ||
| end | ||
| end | ||
|
|
||
| shared_examples 'first span without process tags' do | ||
| it do | ||
| format! | ||
| expect(first_span.meta).to_not include('_dd.tags.process') | ||
| end | ||
| end | ||
|
|
||
| context 'with no root span' do | ||
| include_context 'no root span' | ||
|
|
||
|
|
@@ -284,6 +301,18 @@ | |
| include_context 'no git metadata' | ||
| it_behaves_like 'first span with no git metadata' | ||
| end | ||
|
|
||
| context 'with process tags enabled' do | ||
| before do | ||
| allow(Datadog.configuration).to receive(:experimental_propagate_process_tags_enabled).and_return(true) | ||
| end | ||
| it_behaves_like 'first span with process tags' | ||
| end | ||
|
|
||
| context 'without process tags enabled' do | ||
| # default is false | ||
| it_behaves_like 'first span without process tags' | ||
| end | ||
| end | ||
|
|
||
| context 'with missing root span' do | ||
|
|
@@ -333,6 +362,18 @@ | |
| include_context 'no git metadata' | ||
| it_behaves_like 'first span with no git metadata' | ||
| end | ||
|
|
||
| context 'with process tags enabled' do | ||
| before do | ||
| allow(Datadog.configuration).to receive(:experimental_propagate_process_tags_enabled).and_return(true) | ||
| end | ||
| it_behaves_like 'first span with process tags' | ||
| end | ||
|
|
||
| context 'without process tags enabled' do | ||
| # default is false | ||
| it_behaves_like 'first span without process tags' | ||
| end | ||
| end | ||
|
|
||
| context 'with a root span' do | ||
|
|
@@ -384,6 +425,18 @@ | |
| include_context 'no git metadata' | ||
| it_behaves_like 'first span with no git metadata' | ||
| end | ||
|
|
||
| context 'with process tags enabled' do | ||
| before do | ||
| allow(Datadog.configuration).to receive(:experimental_propagate_process_tags_enabled).and_return(true) | ||
| end | ||
| it_behaves_like 'first span with process tags' | ||
| end | ||
|
|
||
| context 'without process tags enabled' do | ||
| # default is false | ||
| it_behaves_like 'first span without process tags' | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.