Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7ebfb9e
Add rails.application to process tags when available
wantsui Mar 17, 2026
b549b5a
Merge branch 'master' into add-application-name-rails-process-tags
wantsui Mar 18, 2026
3dbbb52
Refactor app_name from utils because sometimes the namespace can be n…
wantsui Mar 18, 2026
b30dfad
add a failsafe because some of the CI spins up apps that don't have a…
wantsui Mar 19, 2026
4b52af0
Add a recompute of process tags after the rails app has initialized a…
wantsui Mar 19, 2026
1b62ed6
Update lib/datadog/core/contrib/rails/utils.rb
wantsui Mar 20, 2026
56d4346
Refactor a module syntax for the utils spec
wantsui Mar 20, 2026
0ae28c5
Remove the let for the rails major version
wantsui Mar 20, 2026
c8049a0
Add debug log
wantsui Mar 20, 2026
7919c9f
refactor out rails after_initialize logic in one centralized place so…
wantsui Mar 24, 2026
8509732
Swap to if defined because of uninitialized constant Rails
wantsui Mar 24, 2026
b2a67b0
Fix undefined method after_initialize' for Datadog::Core::Contrib::Ra…
wantsui Mar 24, 2026
eefd266
refactor out the public_send
wantsui Mar 24, 2026
a474904
Update comment to be more general
wantsui Mar 24, 2026
e9cf9cb
Remove unneeded import
wantsui Mar 24, 2026
ba91576
Add a future todo for using a real rails app class
wantsui Mar 25, 2026
3574f43
Merge branch 'master' into add-application-name-rails-process-tags
wantsui Mar 31, 2026
f2a8646
Merge branch 'master' into add-application-name-rails-process-tags
wantsui Apr 1, 2026
7f7be92
Update lib/datadog/core/contrib/rails/utils.rb
wantsui Apr 1, 2026
df14399
Update spec/datadog/core/contrib/rails/utils_spec.rb
wantsui Apr 1, 2026
c173fde
Merge branch 'master' into add-application-name-rails-process-tags
wantsui Apr 1, 2026
a2c819d
Fix tests from suggestion
wantsui Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/datadog/core/configuration/components.rb
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ def initialize(settings)

# Configure non-privileged components.
Datadog::Tracing::Contrib::Component.configure(settings)

# Load the core Rails Railtie when Rails is present so all products benefit from Rails-specific setup.
if defined?(::Rails::Railtie)
require_relative '../contrib/rails/railtie'
end
end

# Called when a fork is detected
Expand Down
28 changes: 28 additions & 0 deletions lib/datadog/core/contrib/rails/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

require_relative 'utils'
require_relative '../../environment/process'

module Datadog
module Core
module Contrib
module Rails
# Railtie for core Rails setup that benefits all Datadog products.
class Railtie < ::Rails::Railtie
def self.after_initialize
return unless Datadog.configuration.experimental_propagate_process_tags_enabled

Datadog::Core::Environment::Process.rails_application_name =
Datadog::Core::Contrib::Rails::Utils.app_name
end

# Registered after the method definition so the method exists if on_load fires immediately
# (which happens when the Railtie is loaded into an already-initialized Rails app).
::ActiveSupport.on_load(:after_initialize) do
Datadog::Core::Contrib::Rails::Railtie.after_initialize
end
end
end
end
end
end
10 changes: 7 additions & 3 deletions lib/datadog/core/contrib/rails/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ module Rails
# common utilities for Rails
module Utils
def self.app_name
if ::Rails::VERSION::MAJOR >= 6
::Rails.application.class.module_parent_name.underscore
application_name = if ::Rails::VERSION::MAJOR >= 6
::Rails.application.class.module_parent_name
else
::Rails.application.class.parent_name.underscore
::Rails.application.class.parent_name
end
application_name&.underscore
rescue => e
Datadog.logger.debug("Failed to extract Rails application name: #{e.class}: #{e}")
nil
end

def self.railtie_supported?
Expand Down
1 change: 1 addition & 0 deletions lib/datadog/core/environment/ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ module Ext
TAG_ENTRYPOINT_NAME = "entrypoint.name"
TAG_ENTRYPOINT_WORKDIR = "entrypoint.workdir"
TAG_ENTRYPOINT_TYPE = "entrypoint.type"
TAG_RAILS_APPLICATION = "rails.application"
TAG_PROCESS_TAGS = "_dd.tags.process"
TAG_SERVICE = 'service'
TAG_VERSION = 'version'
Expand Down
12 changes: 12 additions & 0 deletions lib/datadog/core/environment/process.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ def self.tags

tags << "#{Environment::Ext::TAG_ENTRYPOINT_TYPE}:#{TagNormalizer.normalize(entrypoint_type, remove_digit_start_char: false)}"

rails_application_name = TagNormalizer.normalize_process_value(@rails_application_name.to_s)
tags << "#{Environment::Ext::TAG_RAILS_APPLICATION}:#{rails_application_name}" unless rails_application_name.empty?

@tags = tags.freeze
end

Expand Down Expand Up @@ -80,6 +83,15 @@ def self.entrypoint_basedir
File.basename(File.expand_path(File.dirname($0)))
end

# Sets the rails application name from other places in code
# @param name [String] the rails application name
# @return [void]
def self.rails_application_name=(name)
@rails_application_name = name
remove_instance_variable(:@tags) if instance_variable_defined?(:@tags)
remove_instance_variable(:@serialized) if instance_variable_defined?(:@serialized)
end

private_class_method :entrypoint_workdir, :entrypoint_type, :entrypoint_name, :entrypoint_basedir
end
end
Expand Down
1 change: 0 additions & 1 deletion lib/datadog/tracing/contrib/rails/patcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
require_relative 'log_injection'
require_relative 'middlewares'
require_relative 'runner'
require_relative '../../../core/contrib/rails/utils'
require_relative '../semantic_logger/patcher'

module Datadog
Expand Down
11 changes: 11 additions & 0 deletions sig/datadog/core/contrib/rails/railtie.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Datadog
module Core
module Contrib
module Rails
class Railtie < ::Rails::Railtie
def self.after_initialize: () -> void
end
end
end
end
end
2 changes: 1 addition & 1 deletion sig/datadog/core/contrib/rails/utils.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Datadog
module Contrib
module Rails
module Utils
def self.app_name: () -> String
def self.app_name: () -> String?

def self.railtie_supported?: () -> bool
end
Expand Down
2 changes: 2 additions & 0 deletions sig/datadog/core/environment/ext.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ module Datadog

TAG_ENTRYPOINT_TYPE: ::String

TAG_RAILS_APPLICATION: ::String

TAG_PROCESS_TAGS: ::String
end
end
Expand Down
3 changes: 3 additions & 0 deletions sig/datadog/core/environment/process.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ module Datadog
module Process
self.@serialized: ::String
self.@tags: ::Array[::String]
self.@rails_application_name: ::String?

def self.serialized: () -> ::String

def self.tags: () -> ::Array[::String]

def self.rails_application_name=: (::String? name) -> void

private

def self.entrypoint_workdir: () -> ::String
Expand Down
48 changes: 48 additions & 0 deletions spec/datadog/core/contrib/rails/railtie_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

require 'spec_helper'
require 'active_support/lazy_load_hooks'

# Provide a minimal stub base class for testing without loading the full Rails framework.
# utils_spec.rb uses the same pattern (stub_const '::Rails') for the same reason.
module Rails
class Railtie; end unless defined?(Railtie)
end

require 'lib/datadog/core/contrib/rails/railtie'

RSpec.describe Datadog::Core::Contrib::Rails::Railtie do
describe '.after_initialize' do
subject(:after_initialize) { described_class.after_initialize }

before do
allow(Datadog::Core::Contrib::Rails::Utils).to receive(:app_name).and_return('test_app')
end

after do
Datadog::Core::Environment::Process.rails_application_name = nil
end

context 'when experimental_propagate_process_tags_enabled is true' do
before do
allow(Datadog.configuration).to receive(:experimental_propagate_process_tags_enabled).and_return(true)
end

it 'includes the rails application name in process tags' do
after_initialize
expect(Datadog::Core::Environment::Process.tags).to include('rails.application:test_app')
end
end

context 'when experimental_propagate_process_tags_enabled is false' do
before do
allow(Datadog.configuration).to receive(:experimental_propagate_process_tags_enabled).and_return(false)
end

it 'does not include the rails application name in process tags' do
after_initialize
expect(Datadog::Core::Environment::Process.tags).not_to include(a_string_starting_with('rails.application:'))
end
end
end
end
51 changes: 51 additions & 0 deletions spec/datadog/core/contrib/rails/utils_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,58 @@
require 'lib/datadog/core/contrib/rails/utils'
require 'rails/version'
require 'active_support/core_ext/string/inflections'

RSpec.describe Datadog::Core::Contrib::Rails::Utils do
describe 'app_name' do
subject(:app_name) { described_class.app_name }

let(:namespace_name) { 'custom_app' }
let(:application_class) { double('custom rails class', module_parent_name: namespace_name) }

let(:application) { double('custom rails', class: application_class) }

# TODO: This can be refactored in the future to use a real Rails application class instead of stubs.
let(:rails_module) do
version_major = 7
application_instance = application
Module.new do
version_module = Module.new do
const_set(:MAJOR, version_major)
end

const_set(:VERSION, version_module)
define_singleton_method(:application) { application_instance }
end
end

before do
stub_const('::Rails', rails_module)
end
Comment thread
wantsui marked this conversation as resolved.

context 'when namespace is available' do
it { is_expected.to eq('custom_app') }
end

context 'when namespace is nil' do
let(:namespace_name) { nil }

it { is_expected.to be_nil }
end

context 'when Rails lookup raises an error' do
before do
allow(rails_module).to receive(:application).and_raise(StandardError)
end

it { is_expected.to be_nil }

it 'returns nil and logs a debug message' do
expect(Datadog.logger).to receive(:debug).with(/Failed to extract Rails application name/)
expect(app_name).to be_nil
end
end
end

describe 'railtie_supported?' do
subject(:railtie_supported?) { described_class.railtie_supported? }

Expand Down
61 changes: 59 additions & 2 deletions spec/datadog/core/environment/process_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,23 @@
expect(described_class.serialized).to include('entrypoint.type:script')
end
end

context 'when Rails application name is available' do
include_context 'with mocked process environment'
let(:program_name) { 'bin/rails' }

before do
described_class.rails_application_name = 'Test::App'
end

after do
described_class.rails_application_name = nil
end

it 'includes rails.application in serialized tags' do
expect(serialized).to include('rails.application:test_app')
end
end
end

describe 'Scenario: Real applications' do
Expand All @@ -98,8 +115,9 @@
file.puts "gem 'datadog', path: '#{project_root_directory}', require: false"
end
File.write("test@_app/config/initializers/process_initializer.rb", <<-RUBY)
Rails.application.config.after_initialize do
require 'datadog/core/environment/process'
require 'datadog'
Datadog.configure { }
ActiveSupport.on_load(:after_initialize) do
STDERR.puts "_dd.tags.process:\#{Datadog::Core::Environment::Process.serialized}"
STDERR.flush
Thread.new { Process.kill('TERM', Process.pid) }
Expand All @@ -114,6 +132,7 @@
expect(err).to include('entrypoint.type:script')
expect(err).to include('entrypoint.name:rails')
expect(err).to include('entrypoint.basedir:bin')
expect(err).to include('rails.application:test_app')
end
end
end
Expand Down Expand Up @@ -201,5 +220,43 @@
expect(described_class.tags).to include('entrypoint.type:script')
end
end

context 'when Rails application name is available' do
include_context 'with mocked process environment'
let(:program_name) { 'bin/rails' }

before { described_class.rails_application_name = 'test_app' }
after { described_class.rails_application_name = nil }

it 'includes rails.application in tag array' do
expect(tags.length).to eq(5)
expect(tags).to include('rails.application:test_app')
end
end
end
describe '::rails_application_name=' do
include_context 'with mocked process environment'
let(:program_name) { 'bin/rails' }

after do
described_class.rails_application_name = nil
end

it 'includes the rails app name in the tags' do
described_class.rails_application_name = "Test::App"
expect(described_class.tags).to include('rails.application:test_app')
end

it 'invalidates the cached tags' do
described_class.tags
described_class.rails_application_name = "Test::App"
expect(described_class.tags).to include('rails.application:test_app')
end

it 'invalidates the serialized cache' do
described_class.serialized
described_class.rails_application_name = "Test::App"
expect(described_class.serialized).to include('rails.application:test_app')
end
end
end
10 changes: 7 additions & 3 deletions spec/datadog/core/runtime/metrics_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,16 @@
before do
allow(runtime_metrics).to receive(:statsd).and_return(statsd)
allow(statsd).to receive(:gauge)
allow(Datadog::Core::Environment::Process).to receive(:tags).and_return(['entrypoint.workdir:test'])
allow(Datadog::Core::Environment::Process).to receive(:tags)
.and_return(['entrypoint.workdir:test', 'rails.application:test_app'])
runtime_metrics.enabled = true
end

it 'sends metrics with the process tags' do
flush

expect(statsd).to have_received(:gauge).with(anything, anything, hash_including(tags: array_including('entrypoint.workdir:test'))).at_least(:once)
expect(statsd).to have_received(:gauge).with(anything, anything, hash_including(tags: array_including('rails.application:test_app'))).at_least(:once)
end
end
end
Expand Down Expand Up @@ -328,12 +330,13 @@
context 'when :experimental_propagate_process_tags_enabled is true' do
before do
allow(Datadog::Core::Environment::Process).to receive(:tags)
.and_return(['entrypoint.workdir:test', 'entrypoint.name:test_script'])
.and_return(['entrypoint.workdir:test', 'entrypoint.name:test_script', 'rails.application:test_app'])
end

it 'includes process tags by default' do
is_expected.to include('entrypoint.workdir:test')
is_expected.to include('entrypoint.name:test_script')
is_expected.to include('rails.application:test_app')
end
end

Expand Down Expand Up @@ -373,14 +376,15 @@
let(:options) { super().merge(experimental_propagate_process_tags_enabled: true) }

before do
expect(Datadog::Core::Environment::Process).to receive(:tags).and_return(['entrypoint.workdir:test', 'entrypoint.name:test_script', 'entrypoint.basedir:test', 'entrypoint.type:script'])
expect(Datadog::Core::Environment::Process).to receive(:tags).and_return(['entrypoint.workdir:test', 'entrypoint.name:test_script', 'entrypoint.basedir:test', 'entrypoint.type:script', 'rails.application:test_app'])
end

it 'includes process tags when enabled' do
is_expected.to include('entrypoint.workdir:test')
is_expected.to include('entrypoint.name:test_script')
is_expected.to include('entrypoint.basedir:test')
is_expected.to include('entrypoint.type:script')
is_expected.to include('rails.application:test_app')
end
end

Expand Down
Loading
Loading