Repository: DataDog/dd-trace-rb
Pull request #4671: DEBUG-3573 split telemetry event classes into own files
Author: login: p-datadog
Labels: name: core
description: Involves Datadog core libraries
name: integrations
description: Involves tracing integrations
name: tracing
Created: 2025-05-20T17:49:03Z | Merged: 2025-05-21T15:44:26Z

<!--
Check out the
https://github.com/DataDog/dd-trace-rb/blob/master/docs/DevelopmentGuide.md
for guidance on how to set up your development environment,
run the test suite, write new integrations, and more.
-->

**What does this PR do?**
<!-- A brief description of the change being made with this pull request. -->
Splits telemetry event classes to be in one file per class

**Motivation:**
Dynamic instrumentation requires app started or configuration change events to accurately reflect the state of dynamic instrumentation in the tracer. Currently only app started event is sent, and if dynamic instrumentation is programmatically enabled via `Datadog.configure` this is not reported to telemetry, resulting in broken DI UI.

Repairing this requires hacking the app started and configuration change events more which would be more readable if each event was defined in its own file.
<!-- What inspired you to submit this pull request? -->

**Change log entry**
None
<!-- If this is a customer-visible change, a brief summary to be placed
into the change log.
If you are not a Datadog employee, you can skip this section
and it will be filled or deleted during PR review. -->

**Additional Notes:**
I generated type definitions and steep started complaining about MessageBatch event not deriving from Base, therefore I derived it from Base in this PR.
<!-- Anything else we should know when reviewing? -->

**How to test the change?**
Existing CI
<!--
Describe here how the change can be validated.
You are strongly encouraged to provide automated tests for this PR (unit or integration).
If this change cannot be feasibly tested, please explain why,
unless the change does not modify code (e.g. only modifies docs, comments).
-->

<!-- Unsure? Have a question? Request a review! -->



Diff:
diff --git a/lib/datadog/core/telemetry/event.rb b/lib/datadog/core/telemetry/event.rb
index af217bd19bb..dc1cb6d9fe8 100644
--- a/lib/datadog/core/telemetry/event.rb
+++ b/lib/datadog/core/telemetry/event.rb
@@ -15,480 +15,19 @@ def self.configuration_sequence
           after_fork! { @sequence = Datadog::Core::Utils::Sequence.new(1) }
           @sequence ||= Datadog::Core::Utils::Sequence.new(1)
         end
-
-        # Base class for all Telemetry V2 events.
-        class Base
-          # The type of the event.
-          # It must be one of the stings defined in the Telemetry V2
-          # specification for event names.
-          def type
-            raise NotImplementedError, 'Must be implemented by subclass'
-          end
-
-          # The JSON payload for the event.
-          def payload
-            {}
-          end
-
-          # Override equality to allow for deduplication
-          # The basic implementation is to check if the other object is an instance of the same class.
-          # This works for events that have no attributes.
-          # For events with attributes, you should override this method to compare the attributes.
-          def ==(other)
-            other.is_a?(self.class)
-          end
-
-          # @see #==
-          alias eql? ==
-
-          # @see #==
-          def hash
-            self.class.hash
-          end
-        end
-
-        # Telemetry class for the 'app-started' event
-        class AppStarted < Base
-          def type
-            'app-started'
-          end
-
-          def payload
-            {
-              products: products,
-              configuration: configuration,
-              install_signature: install_signature,
-              # DEV: Not implemented yet
-              # error: error, # Start-up errors
-            }
-          end
-
-          private
-
-          def products
-            # @type var products: Hash[Symbol, Hash[Symbol, Object]]
-            products = {
-              appsec: {
-                enabled: Datadog::AppSec.enabled?,
-              },
-              profiler: {
-                enabled: Datadog::Profiling.enabled?,
-              },
-              dynamic_instrumentation: {
-                enabled: defined?(Datadog::DI) && Datadog::DI.respond_to?(:enabled?) && Datadog::DI.enabled?,
-              }
-            }
-
-            if (unsupported_reason = Datadog::Profiling.unsupported_reason)
-              products[:profiler][:error] = {
-                code: 1, # Error code. 0 if no error.
-                message: unsupported_reason,
-              }
-            end
-
-            products
-          end
-
-          TARGET_OPTIONS = %w[
-            dynamic_instrumentation.enabled
-            logger.level
-            profiling.advanced.code_provenance_enabled
-            profiling.advanced.endpoint.collection.enabled
-            profiling.enabled
-            runtime_metrics.enabled
-            tracing.analytics.enabled
-            tracing.propagation_style_extract
-            tracing.propagation_style_inject
-            tracing.enabled
-            tracing.log_injection
-            tracing.partial_flush.enabled
-            tracing.partial_flush.min_spans_threshold
-            tracing.report_hostname
-            tracing.sampling.rate_limit
-          ].freeze
-
-          # rubocop:disable Metrics/AbcSize
-          # rubocop:disable Metrics/MethodLength
-          def configuration
-            config = Datadog.configuration
-            seq_id = Event.configuration_sequence.next
-
-            list = [
-              conf_value('DD_GIT_REPOSITORY_URL', Core::Environment::Git.git_repository_url, seq_id, 'env_var'),
-              conf_value('DD_GIT_COMMIT_SHA', Core::Environment::Git.git_commit_sha, seq_id, 'env_var'),
-
-              conf_value('DD_AGENT_HOST', config.agent.host, seq_id),
-              conf_value('DD_AGENT_TRANSPORT', agent_transport(config), seq_id),
-              conf_value('DD_TRACE_SAMPLE_RATE', to_value(config.tracing.sampling.default_rate), seq_id),
-              conf_value(
-                'DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED',
-                config.tracing.contrib.global_default_service_name.enabled,
-                seq_id
-              ),
-              conf_value(
-                'DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED',
-                config.tracing.contrib.peer_service_defaults,
-                seq_id
-              ),
-            ]
-
-            peer_service_mapping_str = ''
-            unless config.tracing.contrib.peer_service_mapping.empty?
-              peer_service_mapping = config.tracing.contrib.peer_service_mapping
-              peer_service_mapping_str = peer_service_mapping.map { |key, value| "#{key}:#{value}" }.join(',')
-            end
-            list << conf_value('DD_TRACE_PEER_SERVICE_MAPPING', peer_service_mapping_str, seq_id)
-
-            # Whitelist of configuration options to send in additional payload object
-            TARGET_OPTIONS.each do |option|
-              split_option = option.split('.')
-              list << conf_value(option, to_value(config.dig(*split_option)), seq_id)
-            end
-
-            # Add some more custom additional payload values here
-            list.push(
-              conf_value('tracing.auto_instrument.enabled', !defined?(Datadog::AutoInstrument::LOADED).nil?, seq_id),
-              conf_value(
-                'tracing.writer_options.buffer_size',
-                to_value(config.tracing.writer_options[:buffer_size]),
-                seq_id
-              ),
-              conf_value(
-                'tracing.writer_options.flush_interval',
-                to_value(config.tracing.writer_options[:flush_interval]),
-                seq_id
-              ),
-              conf_value(
-                'tracing.opentelemetry.enabled',
-                !defined?(Datadog::OpenTelemetry::LOADED).nil?,
-                seq_id
-              ),
-            )
-            list << conf_value('logger.instance', config.logger.instance.class.to_s, seq_id) if config.logger.instance
-            if config.respond_to?('appsec')
-              list << conf_value('appsec.enabled', config.dig('appsec', 'enabled'), seq_id)
-              list << conf_value('appsec.sca_enabled', config.dig('appsec', 'sca_enabled'), seq_id)
-            end
-            list << conf_value('ci.enabled', config.dig('ci', 'enabled'), seq_id) if config.respond_to?('ci')
-
-            list.reject! { |entry| entry[:value].nil? }
-            list
-          end
-          # rubocop:enable Metrics/AbcSize
-          # rubocop:enable Metrics/MethodLength
-
-          def agent_transport(config)
-            adapter = Core::Configuration::AgentSettingsResolver.call(config).adapter
-            if adapter == Datadog::Core::Transport::Ext::UnixSocket::ADAPTER
-              'UDS'
-            else
-              'TCP'
-            end
-          end
-
-          def conf_value(name, value, seq_id, origin = 'code')
-            {
-              name: name,
-              value: value,
-              origin: origin,
-              seq_id: seq_id,
-            }
-          end
-
-          def to_value(value)
-            # TODO: Add float if telemetry starts accepting it
-            case value
-            when Integer, String, true, false, nil
-              value
-            else
-              value.to_s
-            end
-          end
-
-          def install_signature
-            config = Datadog.configuration
-            {
-              install_id: config.dig('telemetry', 'install_id'),
-              install_type: config.dig('telemetry', 'install_type'),
-              install_time: config.dig('telemetry', 'install_time'),
-            }
-          end
-        end
-
-        # Telemetry class for the 'app-dependencies-loaded' event
-        class AppDependenciesLoaded < Base
-          def type
-            'app-dependencies-loaded'
-          end
-
-          def payload
-            { dependencies: dependencies }
-          end
-
-          private
-
-          def dependencies
-            Gem.loaded_specs.collect do |name, gem|
-              {
-                name: name,
-                version: gem.version.to_s,
-                # hash: nil,
-              }
-            end
-          end
-        end
-
-        # Telemetry class for the 'app-integrations-change' event
-        class AppIntegrationsChange < Base
-          def type
-            'app-integrations-change'
-          end
-
-          def payload
-            { integrations: integrations }
-          end
-
-          private
-
-          def integrations
-            instrumented_integrations = Datadog.configuration.tracing.instrumented_integrations
-            Datadog.registry.map do |integration|
-              is_instrumented = instrumented_integrations.include?(integration.name)
-
-              is_enabled = is_instrumented ? !!integration.klass.patcher.patch_successful : false
-
-              version = integration.klass.class.version ? integration.klass.class.version.to_s : nil
-
-              res = {
-                name: integration.name.to_s,
-                enabled: is_enabled,
-                version: version,
-                compatible: integration.klass.class.compatible?,
-                error: (patch_error(integration) if is_instrumented && !is_enabled),
-                # TODO: Track if integration is instrumented by manual configuration or by auto instrumentation
-                # auto_enabled: is_enabled && ???,
-              }
-              res.reject! { |_, v| v.nil? }
-              res
-            end
-          end
-
-          def patch_error(integration)
-            patch_error_result = integration.klass.patcher.patch_error_result
-            return patch_error_result.compact.to_s if patch_error_result
-
-            # If no error occurred during patching, but integration is still not instrumented
-            "Available?: #{integration.klass.class.available?}" \
-            ", Loaded? #{integration.klass.class.loaded?}" \
-            ", Compatible? #{integration.klass.class.compatible?}" \
-            ", Patchable? #{integration.klass.class.patchable?}"
-          end
-        end
-
-        # Telemetry class for the 'app-client-configuration-change' event
-        class AppClientConfigurationChange < Base
-          attr_reader :changes, :origin
-
-          def type
-            'app-client-configuration-change'
-          end
-
-          def initialize(changes, origin)
-            super()
-            @changes = changes
-            @origin = origin
-          end
-
-          def payload
-            { configuration: configuration }
-          end
-
-          def configuration
-            config = Datadog.configuration
-            seq_id = Event.configuration_sequence.next
-
-            res = @changes.map do |name, value|
-              {
-                name: name,
-                value: value,
-                origin: @origin,
-                seq_id: seq_id,
-              }
-            end
-
-            unless config.dig('appsec', 'sca_enabled').nil?
-              res << {
-                name: 'appsec.sca_enabled',
-                value: config.appsec.sca_enabled,
-                origin: 'code',
-                seq_id: seq_id,
-              }
-            end
-
-            res
-          end
-
-          def ==(other)
-            other.is_a?(AppClientConfigurationChange) && other.changes == @changes && other.origin == @origin
-          end
-
-          alias eql? ==
-
-          def hash
-            [self.class, @changes, @origin].hash
-          end
-        end
-
-        # Telemetry class for the 'app-heartbeat' event
-        class AppHeartbeat < Base
-          def type
-            'app-heartbeat'
-          end
-        end
-
-        # Telemetry class for the 'app-closing' event
-        class AppClosing < Base
-          def type
-            'app-closing'
-          end
-        end
-
-        # Telemetry class for the 'generate-metrics' event
-        class GenerateMetrics < Base
-          attr_reader :namespace, :metric_series
-
-          def type
-            'generate-metrics'
-          end
-
-          def initialize(namespace, metric_series)
-            super()
-            @namespace = namespace
-            @metric_series = metric_series
-          end
-
-          def payload
-            {
-              namespace: @namespace,
-              series: @metric_series.map(&:to_h)
-            }
-          end
-
-          def ==(other)
-            other.is_a?(GenerateMetrics) && other.namespace == @namespace && other.metric_series == @metric_series
-          end
-
-          alias eql? ==
-
-          def hash
-            [self.class, @namespace, @metric_series].hash
-          end
-        end
-
-        # Telemetry class for the 'logs' event.
-        # Logs with the same content are deduplicated at flush time.
-        class Log < Base
-          LEVELS = {
-            error: 'ERROR',
-            warn: 'WARN',
-          }.freeze
-
-          LEVELS_STRING = LEVELS.values.freeze
-
-          attr_reader :message, :level, :stack_trace, :count
-
-          def type
-            'logs'
-          end
-
-          # @param message [String] the log message
-          # @param level [Symbol, String] the log level. Either :error, :warn, 'ERROR', or 'WARN'.
-          # @param stack_trace [String, nil] the stack trace
-          # @param count [Integer] the number of times the log was emitted. Used for deduplication.
-          def initialize(message:, level:, stack_trace: nil, count: 1)
-            super()
-            @message = message
-            @stack_trace = stack_trace
-
-            if level.is_a?(String) && LEVELS_STRING.include?(level)
-              # String level is used during object copy for deduplication
-              @level = level
-            elsif level.is_a?(Symbol)
-              # Symbol level is used by the regular log emitter user
-              @level = LEVELS.fetch(level) { |k| raise ArgumentError, "Invalid log level :#{k}" }
-            else
-              raise ArgumentError, "Invalid log level #{level}"
-            end
-
-            @count = count
-          end
-
-          def payload
-            {
-              logs: [
-                {
-                  message: @message,
-                  level: @level,
-                  stack_trace: @stack_trace,
-                  count: @count,
-                }.compact
-              ]
-            }
-          end
-
-          # override equality to allow for deduplication
-          def ==(other)
-            other.is_a?(Log) &&
-              other.message == @message &&
-              other.level == @level && other.stack_trace == @stack_trace && other.count == @count
-          end
-
-          alias eql? ==
-
-          def hash
-            [self.class, @message, @level, @stack_trace, @count].hash
-          end
-        end
-
-        # Telemetry class for the 'distributions' event
-        class Distributions < GenerateMetrics
-          def type
-            'distributions'
-          end
-        end
-
-        # Telemetry class for the 'message-batch' event
-        class MessageBatch
-          attr_reader :events
-
-          def type
-            'message-batch'
-          end
-
-          def initialize(events)
-            @events = events
-          end
-
-          def payload
-            @events.map do |event|
-              {
-                request_type: event.type,
-                payload: event.payload,
-              }
-            end
-          end
-
-          def ==(other)
-            other.is_a?(MessageBatch) && other.events == @events
-          end
-
-          alias eql? ==
-
-          def hash
-            [self.class, @events].hash
-          end
-        end
       end
     end
   end
 end
+
+require_relative 'event/base'
+require_relative 'event/app_client_configuration_change'
+require_relative 'event/app_closing'
+require_relative 'event/app_dependencies_loaded'
+require_relative 'event/app_heartbeat'
+require_relative 'event/app_integrations_change'
+require_relative 'event/app_started'
+require_relative 'event/generate_metrics'
+require_relative 'event/distributions'
+require_relative 'event/log'
+require_relative 'event/message_batch'
diff --git a/lib/datadog/core/telemetry/event/app_client_configuration_change.rb b/lib/datadog/core/telemetry/event/app_client_configuration_change.rb
new file mode 100644
index 00000000000..f25c8481a0d
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/app_client_configuration_change.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-client-configuration-change' event
+        class AppClientConfigurationChange < Base
+          attr_reader :changes, :origin
+
+          def type
+            'app-client-configuration-change'
+          end
+
+          def initialize(changes, origin)
+            super()
+            @changes = changes
+            @origin = origin
+          end
+
+          def payload
+            { configuration: configuration }
+          end
+
+          def configuration
+            config = Datadog.configuration
+            seq_id = Event.configuration_sequence.next
+
+            res = @changes.map do |name, value|
+              {
+                name: name,
+                value: value,
+                origin: @origin,
+                seq_id: seq_id,
+              }
+            end
+
+            unless config.dig('appsec', 'sca_enabled').nil?
+              res << {
+                name: 'appsec.sca_enabled',
+                value: config.appsec.sca_enabled,
+                origin: 'code',
+                seq_id: seq_id,
+              }
+            end
+
+            res
+          end
+
+          def ==(other)
+            other.is_a?(AppClientConfigurationChange) && other.changes == @changes && other.origin == @origin
+          end
+
+          alias eql? ==
+
+          def hash
+            [self.class, @changes, @origin].hash
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/core/telemetry/event/app_closing.rb b/lib/datadog/core/telemetry/event/app_closing.rb
new file mode 100644
index 00000000000..3b7b557fa83
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/app_closing.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-closing' event
+        class AppClosing < Base
+          def type
+            'app-closing'
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb b/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb
new file mode 100644
index 00000000000..1a8b5aa727e
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-dependencies-loaded' event
+        class AppDependenciesLoaded < Base
+          def type
+            'app-dependencies-loaded'
+          end
+
+          def payload
+            { dependencies: dependencies }
+          end
+
+          private
+
+          def dependencies
+            Gem.loaded_specs.collect do |name, gem|
+              {
+                name: name,
+                version: gem.version.to_s,
+              }
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/core/telemetry/event/app_heartbeat.rb b/lib/datadog/core/telemetry/event/app_heartbeat.rb
new file mode 100644
index 00000000000..78c885111ad
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/app_heartbeat.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-heartbeat' event
+        class AppHeartbeat < Base
+          def type
+            'app-heartbeat'
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/core/telemetry/event/app_integrations_change.rb b/lib/datadog/core/telemetry/event/app_integrations_change.rb
new file mode 100644
index 00000000000..cf16a2c71b7
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/app_integrations_change.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-integrations-change' event
+        class AppIntegrationsChange < Base
+          def type
+            'app-integrations-change'
+          end
+
+          def payload
+            { integrations: integrations }
+          end
+
+          private
+
+          def integrations
+            instrumented_integrations = Datadog.configuration.tracing.instrumented_integrations
+            Datadog.registry.map do |integration|
+              is_instrumented = instrumented_integrations.include?(integration.name)
+
+              is_enabled = is_instrumented && integration.klass.patcher.patch_successful
+
+              version = integration.klass.class.version ? integration.klass.class.version.to_s : nil
+
+              res = {
+                name: integration.name.to_s,
+                enabled: is_enabled,
+                version: version,
+                compatible: integration.klass.class.compatible?,
+                error: (patch_error(integration) if is_instrumented && !is_enabled),
+                # TODO: Track if integration is instrumented by manual configuration or by auto instrumentation
+                # auto_enabled: is_enabled && ???,
+              }
+              res.reject! { |_, v| v.nil? }
+              res
+            end
+          end
+
+          def patch_error(integration)
+            patch_error_result = integration.klass.patcher.patch_error_result
+            return patch_error_result.compact.to_s if patch_error_result
+
+            # If no error occurred during patching, but integration is still not instrumented
+            "Available?: #{integration.klass.class.available?}" \
+            ", Loaded? #{integration.klass.class.loaded?}" \
+            ", Compatible? #{integration.klass.class.compatible?}" \
+            ", Patchable? #{integration.klass.class.patchable?}"
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/core/telemetry/event/app_started.rb b/lib/datadog/core/telemetry/event/app_started.rb
new file mode 100644
index 00000000000..5d0d30074fd
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/app_started.rb
@@ -0,0 +1,179 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-started' event
+        class AppStarted < Base
+          def type
+            'app-started'
+          end
+
+          def payload
+            {
+              products: products,
+              configuration: configuration,
+              install_signature: install_signature,
+              # DEV: Not implemented yet
+              # error: error, # Start-up errors
+            }
+          end
+
+          private
+
+          def products
+            # @type var products: Hash[Symbol, Hash[Symbol, Object]]
+            products = {
+              appsec: {
+                enabled: Datadog::AppSec.enabled?,
+              },
+              profiler: {
+                enabled: Datadog::Profiling.enabled?,
+              },
+              dynamic_instrumentation: {
+                enabled: defined?(Datadog::DI) && Datadog::DI.respond_to?(:enabled?) && Datadog::DI.enabled?,
+              }
+            }
+
+            if (unsupported_reason = Datadog::Profiling.unsupported_reason)
+              products[:profiler][:error] = {
+                code: 1, # Error code. 0 if no error.
+                message: unsupported_reason,
+              }
+            end
+
+            products
+          end
+
+          TARGET_OPTIONS = %w[
+            dynamic_instrumentation.enabled
+            logger.level
+            profiling.advanced.code_provenance_enabled
+            profiling.advanced.endpoint.collection.enabled
+            profiling.enabled
+            runtime_metrics.enabled
+            tracing.analytics.enabled
+            tracing.propagation_style_extract
+            tracing.propagation_style_inject
+            tracing.enabled
+            tracing.log_injection
+            tracing.partial_flush.enabled
+            tracing.partial_flush.min_spans_threshold
+            tracing.report_hostname
+            tracing.sampling.rate_limit
+          ].freeze
+
+          # rubocop:disable Metrics/AbcSize
+          # rubocop:disable Metrics/MethodLength
+          def configuration
+            config = Datadog.configuration
+            seq_id = Event.configuration_sequence.next
+
+            list = [
+              conf_value('DD_GIT_REPOSITORY_URL', Core::Environment::Git.git_repository_url, seq_id, 'env_var'),
+              conf_value('DD_GIT_COMMIT_SHA', Core::Environment::Git.git_commit_sha, seq_id, 'env_var'),
+
+              conf_value('DD_AGENT_HOST', config.agent.host, seq_id),
+              conf_value('DD_AGENT_TRANSPORT', agent_transport(config), seq_id),
+              conf_value('DD_TRACE_SAMPLE_RATE', to_value(config.tracing.sampling.default_rate), seq_id),
+              conf_value(
+                'DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED',
+                config.tracing.contrib.global_default_service_name.enabled,
+                seq_id
+              ),
+              conf_value(
+                'DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED',
+                config.tracing.contrib.peer_service_defaults,
+                seq_id
+              ),
+            ]
+
+            peer_service_mapping_str = ''
+            unless config.tracing.contrib.peer_service_mapping.empty?
+              peer_service_mapping = config.tracing.contrib.peer_service_mapping
+              peer_service_mapping_str = peer_service_mapping.map { |key, value| "#{key}:#{value}" }.join(',')
+            end
+            list << conf_value('DD_TRACE_PEER_SERVICE_MAPPING', peer_service_mapping_str, seq_id)
+
+            # Whitelist of configuration options to send in additional payload object
+            TARGET_OPTIONS.each do |option|
+              split_option = option.split('.')
+              list << conf_value(option, to_value(config.dig(*split_option)), seq_id)
+            end
+
+            # Add some more custom additional payload values here
+            list.push(
+              conf_value('tracing.auto_instrument.enabled', !defined?(Datadog::AutoInstrument::LOADED).nil?, seq_id),
+              conf_value(
+                'tracing.writer_options.buffer_size',
+                to_value(config.tracing.writer_options[:buffer_size]),
+                seq_id
+              ),
+              conf_value(
+                'tracing.writer_options.flush_interval',
+                to_value(config.tracing.writer_options[:flush_interval]),
+                seq_id
+              ),
+              conf_value(
+                'tracing.opentelemetry.enabled',
+                !defined?(Datadog::OpenTelemetry::LOADED).nil?,
+                seq_id
+              ),
+            )
+            list << conf_value('logger.instance', config.logger.instance.class.to_s, seq_id) if config.logger.instance
+            if config.respond_to?('appsec')
+              list << conf_value('appsec.enabled', config.dig('appsec', 'enabled'), seq_id)
+              list << conf_value('appsec.sca_enabled', config.dig('appsec', 'sca_enabled'), seq_id)
+            end
+            list << conf_value('ci.enabled', config.dig('ci', 'enabled'), seq_id) if config.respond_to?('ci')
+
+            list.reject! { |entry| entry[:value].nil? }
+            list
+          end
+          # rubocop:enable Metrics/AbcSize
+          # rubocop:enable Metrics/MethodLength
+
+          def agent_transport(config)
+            adapter = Core::Configuration::AgentSettingsResolver.call(config).adapter
+            if adapter == Datadog::Core::Transport::Ext::UnixSocket::ADAPTER
+              'UDS'
+            else
+              'TCP'
+            end
+          end
+
+          def conf_value(name, value, seq_id, origin = 'code')
+            {
+              name: name,
+              value: value,
+              origin: origin,
+              seq_id: seq_id,
+            }
+          end
+
+          def to_value(value)
+            # TODO: Add float if telemetry starts accepting it
+            case value
+            when Integer, String, true, false, nil
+              value
+            else
+              value.to_s
+            end
+          end
+
+          def install_signature
+            config = Datadog.configuration
+            {
+              install_id: config.dig('telemetry', 'install_id'),
+              install_type: config.dig('telemetry', 'install_type'),
+              install_time: config.dig('telemetry', 'install_time'),
+            }
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/core/telemetry/event/base.rb b/lib/datadog/core/telemetry/event/base.rb
new file mode 100644
index 00000000000..38d18202090
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/base.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Base class for all Telemetry V2 events.
+        class Base
+          # The type of the event.
+          # It must be one of the stings defined in the Telemetry V2
+          # specification for event names.
+          def type
+            raise NotImplementedError, 'Must be implemented by subclass'
+          end
+
+          # The JSON payload for the event.
+          def payload
+            {}
+          end
+
+          # Override equality to allow for deduplication
+          # The basic implementation is to check if the other object is an instance of the same class.
+          # This works for events that have no attributes.
+          # For events with attributes, you should override this method to compare the attributes.
+          def ==(other)
+            other.is_a?(self.class)
+          end
+
+          # @see #==
+          alias eql? ==
+
+          # @see #==
+          def hash
+            self.class.hash
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/core/telemetry/event/distributions.rb b/lib/datadog/core/telemetry/event/distributions.rb
new file mode 100644
index 00000000000..8840849273b
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/distributions.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require_relative 'generate_metrics'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'distributions' event
+        class Distributions < GenerateMetrics
+          def type
+            'distributions'
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/core/telemetry/event/generate_metrics.rb b/lib/datadog/core/telemetry/event/generate_metrics.rb
new file mode 100644
index 00000000000..e417e5492fd
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/generate_metrics.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'generate-metrics' event
+        class GenerateMetrics < Base
+          attr_reader :namespace, :metric_series
+
+          def type
+            'generate-metrics'
+          end
+
+          def initialize(namespace, metric_series)
+            super()
+            @namespace = namespace
+            @metric_series = metric_series
+          end
+
+          def payload
+            {
+              namespace: @namespace,
+              series: @metric_series.map(&:to_h)
+            }
+          end
+
+          def ==(other)
+            other.is_a?(GenerateMetrics) && other.namespace == @namespace && other.metric_series == @metric_series
+          end
+
+          alias eql? ==
+
+          def hash
+            [self.class, @namespace, @metric_series].hash
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/core/telemetry/event/log.rb b/lib/datadog/core/telemetry/event/log.rb
new file mode 100644
index 00000000000..93070102905
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/log.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'logs' event.
+        # Logs with the same content are deduplicated at flush time.
+        class Log < Base
+          LEVELS = {
+            error: 'ERROR',
+            warn: 'WARN',
+          }.freeze
+
+          LEVELS_STRING = LEVELS.values.freeze
+
+          attr_reader :message, :level, :stack_trace, :count
+
+          def type
+            'logs'
+          end
+
+          # @param message [String] the log message
+          # @param level [Symbol, String] the log level. Either :error, :warn, 'ERROR', or 'WARN'.
+          # @param stack_trace [String, nil] the stack trace
+          # @param count [Integer] the number of times the log was emitted. Used for deduplication.
+          def initialize(message:, level:, stack_trace: nil, count: 1)
+            super()
+            @message = message
+            @stack_trace = stack_trace
+
+            if level.is_a?(String) && LEVELS_STRING.include?(level)
+              # String level is used during object copy for deduplication
+              @level = level
+            elsif level.is_a?(Symbol)
+              # Symbol level is used by the regular log emitter user
+              @level = LEVELS.fetch(level) { |k| raise ArgumentError, "Invalid log level :#{k}" }
+            else
+              raise ArgumentError, "Invalid log level #{level}"
+            end
+
+            @count = count
+          end
+
+          def payload
+            {
+              logs: [
+                {
+                  message: @message,
+                  level: @level,
+                  stack_trace: @stack_trace,
+                  count: @count,
+                }.compact
+              ]
+            }
+          end
+
+          # override equality to allow for deduplication
+          def ==(other)
+            other.is_a?(Log) &&
+              other.message == @message &&
+              other.level == @level && other.stack_trace == @stack_trace && other.count == @count
+          end
+
+          alias eql? ==
+
+          def hash
+            [self.class, @message, @level, @stack_trace, @count].hash
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/core/telemetry/event/message_batch.rb b/lib/datadog/core/telemetry/event/message_batch.rb
new file mode 100644
index 00000000000..d14a55cb72f
--- /dev/null
+++ b/lib/datadog/core/telemetry/event/message_batch.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'message-batch' event.
+        class MessageBatch < Base
+          attr_reader :events
+
+          def type
+            'message-batch'
+          end
+
+          def initialize(events)
+            super()
+            @events = events
+          end
+
+          def payload
+            @events.map do |event|
+              {
+                request_type: event.type,
+                payload: event.payload,
+              }
+            end
+          end
+
+          def ==(other)
+            other.is_a?(MessageBatch) && other.events == @events
+          end
+
+          alias eql? ==
+
+          def hash
+            [self.class, @events].hash
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/datadog/tracing/contrib/patcher.rb b/lib/datadog/tracing/contrib/patcher.rb
index 85156da3cef..e47a0862795 100644
--- a/lib/datadog/tracing/contrib/patcher.rb
+++ b/lib/datadog/tracing/contrib/patcher.rb
@@ -20,8 +20,11 @@ def self.included(base)
         # @public_api
         module CommonMethods
           attr_accessor \
-            :patch_error_result,
-            :patch_successful
+            :patch_error_result
+
+          def patch_successful
+            !!@patch_successful
+          end
 
           def patch_name
             self.class != Class && self.class != Module ? self.class.name : name
diff --git a/sig/datadog/core/telemetry/event.rbs b/sig/datadog/core/telemetry/event.rbs
index 4489b440843..659beaafa32 100644
--- a/sig/datadog/core/telemetry/event.rbs
+++ b/sig/datadog/core/telemetry/event.rbs
@@ -7,90 +7,6 @@ module Datadog
         self.@sequence: Datadog::Core::Utils::Sequence
 
         def self.configuration_sequence: () -> Datadog::Core::Utils::Sequence
-
-        class Base
-          def payload: () -> (Hash[Symbol, untyped] | Array[Hash[Symbol, untyped]])
-          def type: -> String
-        end
-
-        class AppStarted < Base
-          TARGET_OPTIONS: Array[String]
-
-          private
-
-          def products: -> Hash[Symbol, untyped]
-
-          def configuration: -> Array[Hash[Symbol, untyped]]
-
-          def agent_transport: (untyped config) -> String
-
-          def conf_value: (String name, untyped value, Integer seq_id, ?String origin) -> Hash[Symbol, untyped]
-
-          def to_value: (Object value) -> Object
-
-          def install_signature: -> Hash[Symbol, Object]
-        end
-
-        class AppDependenciesLoaded < Base
-          private
-
-          def dependencies: -> Array[Hash[Symbol, String]]
-        end
-
-        class AppIntegrationsChange < Base
-          private
-
-          def integrations: -> Array[Hash[Symbol, string]]
-
-          def patch_error: (untyped integration) -> String
-        end
-
-        class AppClientConfigurationChange < Base
-          attr_reader changes: Array[[String, Numeric | bool | String | int]]
-          attr_reader origin: String
-
-          def initialize: (Array[[String, Numeric | bool | String]] changes, String origin) -> void
-
-          def configuration: () -> Array[Hash[Symbol, untyped]]
-        end
-
-        class AppHeartbeat < Base
-        end
-
-        class AppClosing < Base
-        end
-
-        class GenerateMetrics < Base
-          attr_reader namespace: String
-          attr_reader metric_series: Array[Metric::Base]
-
-          def initialize: (String namespace, Array[Metric::Base] metric_series) -> void
-        end
-
-        class Log < Base
-          LEVELS: Hash[Symbol, String]
-
-          LEVELS_STRING: Array[String]
-
-          attr_reader count: Integer
-          attr_reader message: String
-          attr_reader level: String
-          attr_reader stack_trace: String?
-
-          def initialize: (message: String, level: (Symbol|String), ?stack_trace: String?, ?count: Integer) -> void
-
-          def payload: () -> { logs: [Hash[Symbol, (String|Integer)]] }
-        end
-
-        class Distributions < GenerateMetrics
-        end
-
-        class MessageBatch < Base
-          attr_reader events: Array[Datadog::Core::Telemetry::Event::Base]
-          @events: Array[Datadog::Core::Telemetry::Event::Base]
-
-          def initialize: (Array[Datadog::Core::Telemetry::Event::Base] events) -> void
-        end
       end
     end
   end
diff --git a/sig/datadog/core/telemetry/event/app_client_configuration_change.rbs b/sig/datadog/core/telemetry/event/app_client_configuration_change.rbs
new file mode 100644
index 00000000000..2e940f61e50
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/app_client_configuration_change.rbs
@@ -0,0 +1,31 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class AppClientConfigurationChange < Base
+          @changes: untyped
+
+          @origin: untyped
+
+          attr_reader changes: Array[[String, Numeric | bool | String | int]]
+
+          attr_reader origin: String
+
+          def type: () -> "app-client-configuration-change"
+
+          def initialize: (Array[[String, Numeric | bool | String]] changes, String origin) -> void
+
+          def payload: () -> { configuration: untyped }
+
+          def configuration: () -> Array[Hash[Symbol, untyped]]
+
+          def ==: (untyped other) -> untyped
+
+          alias eql? ==
+
+          def hash: () -> untyped
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/core/telemetry/event/app_closing.rbs b/sig/datadog/core/telemetry/event/app_closing.rbs
new file mode 100644
index 00000000000..876e96fa72d
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/app_closing.rbs
@@ -0,0 +1,11 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class AppClosing < Base
+          def type: () -> "app-closing"
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/core/telemetry/event/app_dependencies_loaded.rbs b/sig/datadog/core/telemetry/event/app_dependencies_loaded.rbs
new file mode 100644
index 00000000000..29e29f73e33
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/app_dependencies_loaded.rbs
@@ -0,0 +1,17 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class AppDependenciesLoaded < Base
+          def type: () -> "app-dependencies-loaded"
+
+          def payload: () -> { dependencies: untyped }
+
+          private
+
+          def dependencies: -> Array[Hash[Symbol, String]]
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/core/telemetry/event/app_heartbeat.rbs b/sig/datadog/core/telemetry/event/app_heartbeat.rbs
new file mode 100644
index 00000000000..f44d455e97e
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/app_heartbeat.rbs
@@ -0,0 +1,11 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class AppHeartbeat < Base
+          def type: () -> "app-heartbeat"
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/core/telemetry/event/app_integrations_change.rbs b/sig/datadog/core/telemetry/event/app_integrations_change.rbs
new file mode 100644
index 00000000000..fe60ad64b06
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/app_integrations_change.rbs
@@ -0,0 +1,19 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class AppIntegrationsChange < Base
+          def type: () -> "app-integrations-change"
+
+          def payload: () -> { integrations: untyped }
+
+          private
+
+          def integrations: -> Array[Hash[Symbol, string]]
+
+          def patch_error: (untyped integration) -> String
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/core/telemetry/event/app_started.rbs b/sig/datadog/core/telemetry/event/app_started.rbs
new file mode 100644
index 00000000000..7d14f83e788
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/app_started.rbs
@@ -0,0 +1,28 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class AppStarted < Base
+          def type: () -> "app-started"
+
+          def payload: () -> { products: untyped, configuration: untyped, install_signature: untyped }
+
+          private
+
+          def products: -> Hash[Symbol, untyped]
+
+          TARGET_OPTIONS: Array[String]
+          def configuration: -> Array[Hash[Symbol, untyped]]
+
+          def agent_transport: (untyped config) -> String
+
+          def conf_value: (String name, untyped value, Integer seq_id, ?String origin) -> Hash[Symbol, untyped]
+
+          def to_value: (Object value) -> Object
+
+          def install_signature: -> Hash[Symbol, Object]
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/core/telemetry/event/base.rbs b/sig/datadog/core/telemetry/event/base.rbs
new file mode 100644
index 00000000000..2b65c6713e3
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/base.rbs
@@ -0,0 +1,15 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class Base
+          def payload: () -> (Hash[Symbol, untyped] | Array[Hash[Symbol, untyped]])
+          def type: -> String
+          def ==: (untyped other) -> untyped
+          alias eql? ==
+          def hash: () -> untyped
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/core/telemetry/event/distributions.rbs b/sig/datadog/core/telemetry/event/distributions.rbs
new file mode 100644
index 00000000000..6f0693d7c66
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/distributions.rbs
@@ -0,0 +1,11 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class Distributions < GenerateMetrics
+          def type: () -> "distributions"
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/core/telemetry/event/generate_metrics.rbs b/sig/datadog/core/telemetry/event/generate_metrics.rbs
new file mode 100644
index 00000000000..3e9c1069130
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/generate_metrics.rbs
@@ -0,0 +1,29 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class GenerateMetrics < Base
+          @namespace: untyped
+
+          @metric_series: untyped
+
+          attr_reader namespace: String
+
+          attr_reader metric_series: Array[Metric::Base]
+
+          def type: () -> "generate-metrics"
+
+          def initialize: (String namespace, Array[Metric::Base] metric_series) -> void
+
+          def payload: () -> { namespace: untyped, series: untyped }
+
+          def ==: (untyped other) -> untyped
+
+          alias eql? ==
+
+          def hash: () -> untyped
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/core/telemetry/event/log.rbs b/sig/datadog/core/telemetry/event/log.rbs
new file mode 100644
index 00000000000..c92e48cf532
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/log.rbs
@@ -0,0 +1,35 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class Log < Base
+          @message: untyped
+
+          @stack_trace: untyped
+          @level: untyped
+
+          @count: untyped
+
+          LEVELS: Hash[Symbol, String]
+
+          LEVELS_STRING: Array[String]
+
+          attr_reader count: Integer
+          attr_reader message: String
+          attr_reader level: String
+          attr_reader stack_trace: String?
+
+          def type: () -> "logs"
+          def initialize: (message: String, level: (Symbol|String), ?stack_trace: String?, ?count: Integer) -> void
+
+          def payload: () -> { logs: [Hash[Symbol, (String|Integer)]] }
+          def ==: (untyped other) -> untyped
+
+          alias eql? ==
+
+          def hash: () -> untyped
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/core/telemetry/event/message_batch.rbs b/sig/datadog/core/telemetry/event/message_batch.rbs
new file mode 100644
index 00000000000..0f0efa67d89
--- /dev/null
+++ b/sig/datadog/core/telemetry/event/message_batch.rbs
@@ -0,0 +1,25 @@
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        class MessageBatch < Base
+          @events: Array[Datadog::Core::Telemetry::Event::Base]
+
+          attr_reader events: Array[Datadog::Core::Telemetry::Event::Base]
+
+          def type: () -> "message-batch"
+
+          def initialize: (Array[Datadog::Core::Telemetry::Event::Base] events) -> void
+
+          def payload: () -> untyped
+
+          def ==: (untyped other) -> untyped
+
+          alias eql? ==
+
+          def hash: () -> untyped
+        end
+      end
+    end
+  end
+end
diff --git a/sig/datadog/tracing/contrib/patcher.rbs b/sig/datadog/tracing/contrib/patcher.rbs
index 01414556aff..63a77728e61 100644
--- a/sig/datadog/tracing/contrib/patcher.rbs
+++ b/sig/datadog/tracing/contrib/patcher.rbs
@@ -6,7 +6,7 @@ module Datadog
         module CommonMethods
           attr_accessor patch_error_result: untyped
 
-          attr_accessor patch_successful: untyped
+          attr_accessor patch_successful: (true | false)
 
           def patch_name: () -> untyped
 


Reviews:
user: login: y9v
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T13:37:24Z
user: login: y9v
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T13:38:09Z
user: login: marcotc
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T13:39:28Z
user: login: p-datadog
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T14:23:07Z
user: login: p-datadog
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T14:46:59Z
user: login: p-datadog
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T14:58:32Z
user: login: y9v
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T14:58:45Z
user: login: p-datadog
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T15:01:25Z

Review comments:
diff_hunk: @@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-dependencies-loaded' event
+        class AppDependenciesLoaded < Base
+          def type
+            'app-dependencies-loaded'
+          end
+
+          def payload
+            { dependencies: dependencies }
+          end
+
+          private
+
+          def dependencies
+            Gem.loaded_specs.collect do |name, gem|
+              {
+                name: name,
+                version: gem.version.to_s,
+                # hash: nil,
path: lib/datadog/core/telemetry/event/app_dependencies_loaded.rb
user: login: y9v
body: I think this could be deleted?
created_at: 2025-05-21T13:32:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-integrations-change' event
+        class AppIntegrationsChange < Base
+          def type
+            'app-integrations-change'
+          end
+
+          def payload
+            { integrations: integrations }
+          end
+
+          private
+
+          def integrations
+            instrumented_integrations = Datadog.configuration.tracing.instrumented_integrations
+            Datadog.registry.map do |integration|
+              is_instrumented = instrumented_integrations.include?(integration.name)
+
+              is_enabled = is_instrumented ? !!integration.klass.patcher.patch_successful : false
path: lib/datadog/core/telemetry/event/app_integrations_change.rb
user: login: y9v
body: I think this might be easier to read:

```ruby
is_enabled = is_instrumented && integration.klass.patcher.patch_successful
```
created_at: 2025-05-21T13:33:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-client-configuration-change' event
+        class AppClientConfigurationChange < Base
+          attr_reader :changes, :origin
+
+          def type
+            'app-client-configuration-change'
+          end
+
+          def initialize(changes, origin)
+            super()
+            @changes = changes
+            @origin = origin
+          end
+
+          def payload
+            { configuration: configuration }
+          end
+
+          def configuration
+            config = Datadog.configuration
+            seq_id = Event.configuration_sequence.next
+
+            res = @changes.map do |name, value|
+              {
+                name: name,
+                value: value,
+                origin: @origin,
+                seq_id: seq_id,
+              }
+            end
+
+            unless config.dig('appsec', 'sca_enabled').nil?
+              res << {
+                name: 'appsec.sca_enabled',
+                value: config.appsec.sca_enabled,
+                origin: 'code',
+                seq_id: seq_id,
+              }
+            end
+
+            res
+          end
+
+          def ==(other)
+            other.is_a?(AppClientConfigurationChange) && other.changes == @changes && other.origin == @origin
+          end
+
+          alias eql? ==
path: lib/datadog/core/telemetry/event/app_client_configuration_change.rb
user: login: y9v
body: I think this alias is defined in the `Event::Base` class already
created_at: 2025-05-21T13:36:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-client-configuration-change' event
+        class AppClientConfigurationChange < Base
+          attr_reader :changes, :origin
+
+          def type
+            'app-client-configuration-change'
+          end
+
+          def initialize(changes, origin)
+            super()
+            @changes = changes
+            @origin = origin
+          end
+
+          def payload
+            { configuration: configuration }
+          end
+
+          def configuration
+            config = Datadog.configuration
+            seq_id = Event.configuration_sequence.next
+
+            res = @changes.map do |name, value|
+              {
+                name: name,
+                value: value,
+                origin: @origin,
+                seq_id: seq_id,
+              }
+            end
+
+            unless config.dig('appsec', 'sca_enabled').nil?
+              res << {
+                name: 'appsec.sca_enabled',
+                value: config.appsec.sca_enabled,
+                origin: 'code',
+                seq_id: seq_id,
+              }
+            end
+
+            res
+          end
+
+          def ==(other)
+            other.is_a?(AppClientConfigurationChange) && other.changes == @changes && other.origin == @origin
+          end
+
+          alias eql? ==
path: lib/datadog/core/telemetry/event/app_client_configuration_change.rb
user: login: p-datadog
body: The `alias` in parent does not apply to the child:

```
class A
  def a
    'a'
  end

  alias b a
end

class B < A
  def a
    'b'
  end
end

p B.new.b

```

```
big% ruby test.rb
"a"
```

Probably a good reason to use `alias_method` instead of `alias`.
created_at: 2025-05-21T14:23:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-client-configuration-change' event
+        class AppClientConfigurationChange < Base
+          attr_reader :changes, :origin
+
+          def type
+            'app-client-configuration-change'
+          end
+
+          def initialize(changes, origin)
+            super()
+            @changes = changes
+            @origin = origin
+          end
+
+          def payload
+            { configuration: configuration }
+          end
+
+          def configuration
+            config = Datadog.configuration
+            seq_id = Event.configuration_sequence.next
+
+            res = @changes.map do |name, value|
+              {
+                name: name,
+                value: value,
+                origin: @origin,
+                seq_id: seq_id,
+              }
+            end
+
+            unless config.dig('appsec', 'sca_enabled').nil?
+              res << {
+                name: 'appsec.sca_enabled',
+                value: config.appsec.sca_enabled,
+                origin: 'code',
+                seq_id: seq_id,
+              }
+            end
+
+            res
+          end
+
+          def ==(other)
+            other.is_a?(AppClientConfigurationChange) && other.changes == @changes && other.origin == @origin
+          end
+
+          alias eql? ==
path: lib/datadog/core/telemetry/event/app_client_configuration_change.rb
user: login: p-datadog
body: Actually `alias_method` has the same behavior, the aliases need to be defined in each class if the method they are aliasing changed.
created_at: 2025-05-21T14:46:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-dependencies-loaded' event
+        class AppDependenciesLoaded < Base
+          def type
+            'app-dependencies-loaded'
+          end
+
+          def payload
+            { dependencies: dependencies }
+          end
+
+          private
+
+          def dependencies
+            Gem.loaded_specs.collect do |name, gem|
+              {
+                name: name,
+                version: gem.version.to_s,
+                # hash: nil,
path: lib/datadog/core/telemetry/event/app_dependencies_loaded.rb
user: login: p-datadog
body: :+1:
created_at: 2025-05-21T14:58:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-client-configuration-change' event
+        class AppClientConfigurationChange < Base
+          attr_reader :changes, :origin
+
+          def type
+            'app-client-configuration-change'
+          end
+
+          def initialize(changes, origin)
+            super()
+            @changes = changes
+            @origin = origin
+          end
+
+          def payload
+            { configuration: configuration }
+          end
+
+          def configuration
+            config = Datadog.configuration
+            seq_id = Event.configuration_sequence.next
+
+            res = @changes.map do |name, value|
+              {
+                name: name,
+                value: value,
+                origin: @origin,
+                seq_id: seq_id,
+              }
+            end
+
+            unless config.dig('appsec', 'sca_enabled').nil?
+              res << {
+                name: 'appsec.sca_enabled',
+                value: config.appsec.sca_enabled,
+                origin: 'code',
+                seq_id: seq_id,
+              }
+            end
+
+            res
+          end
+
+          def ==(other)
+            other.is_a?(AppClientConfigurationChange) && other.changes == @changes && other.origin == @origin
+          end
+
+          alias eql? ==
path: lib/datadog/core/telemetry/event/app_client_configuration_change.rb
user: login: y9v
body: wow, nice to know, thanks!
created_at: 2025-05-21T14:58:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require_relative 'base'
+
+module Datadog
+  module Core
+    module Telemetry
+      class Event
+        # Telemetry class for the 'app-integrations-change' event
+        class AppIntegrationsChange < Base
+          def type
+            'app-integrations-change'
+          end
+
+          def payload
+            { integrations: integrations }
+          end
+
+          private
+
+          def integrations
+            instrumented_integrations = Datadog.configuration.tracing.instrumented_integrations
+            Datadog.registry.map do |integration|
+              is_instrumented = instrumented_integrations.include?(integration.name)
+
+              is_enabled = is_instrumented ? !!integration.klass.patcher.patch_successful : false
path: lib/datadog/core/telemetry/event/app_integrations_change.rb
user: login: p-datadog
body: :+1:

created_at: 2025-05-21T15:01:25Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-20T17:49:18Z
body: <!-- CHANGELOG_HIDDEN_MARKER -->
Thank you for updating **Change log entry** section :clap:

<sup>Visited at: 2025-05-20 19:38:08 UTC</sup>
author_association: NONE
user: login: codecov-commenter
created_at: 2025-05-20T18:04:32Z
body: ## [Codecov](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) Report
:x: Patch coverage is `96.44128%` with `10 lines` in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 97.73%. Comparing base ([`2deebe6`](https://app.codecov.io/gh/DataDog/dd-trace-rb/commit/2deebe6eeac70da8e3908c0a185f1f9f1ca8781a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog)) to head ([`749a655`](https://app.codecov.io/gh/DataDog/dd-trace-rb/commit/749a6559c57fddc76017df05d8c9b8b4685fd2bf?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog)).
:warning: Report is 2615 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | Patch % | Lines |
|---|---|---|
| [lib/datadog/core/telemetry/event/app\_started.rb](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&filepath=lib%2Fdatadog%2Fcore%2Ftelemetry%2Fevent%2Fapp_started.rb&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog#diff-bGliL2RhdGFkb2cvY29yZS90ZWxlbWV0cnkvZXZlbnQvYXBwX3N0YXJ0ZWQucmI=) | 97.43% | [2 Missing :warning: ](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) |
| [lib/datadog/core/telemetry/event/message\_batch.rb](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&filepath=lib%2Fdatadog%2Fcore%2Ftelemetry%2Fevent%2Fmessage_batch.rb&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog#diff-bGliL2RhdGFkb2cvY29yZS90ZWxlbWV0cnkvZXZlbnQvbWVzc2FnZV9iYXRjaC5yYg==) | 90.47% | [2 Missing :warning: ](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) |
| [...telemetry/event/app\_client\_configuration\_change.rb](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&filepath=lib%2Fdatadog%2Fcore%2Ftelemetry%2Fevent%2Fapp_client_configuration_change.rb&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog#diff-bGliL2RhdGFkb2cvY29yZS90ZWxlbWV0cnkvZXZlbnQvYXBwX2NsaWVudF9jb25maWd1cmF0aW9uX2NoYW5nZS5yYg==) | 96.55% | [1 Missing :warning: ](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) |
| [...og/core/telemetry/event/app\_integrations\_change.rb](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&filepath=lib%2Fdatadog%2Fcore%2Ftelemetry%2Fevent%2Fapp_integrations_change.rb&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog#diff-bGliL2RhdGFkb2cvY29yZS90ZWxlbWV0cnkvZXZlbnQvYXBwX2ludGVncmF0aW9uc19jaGFuZ2UucmI=) | 96.66% | [1 Missing :warning: ](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) |
| [lib/datadog/core/telemetry/event/base.rb](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&filepath=lib%2Fdatadog%2Fcore%2Ftelemetry%2Fevent%2Fbase.rb&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog#diff-bGliL2RhdGFkb2cvY29yZS90ZWxlbWV0cnkvZXZlbnQvYmFzZS5yYg==) | 92.85% | [1 Missing :warning: ](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) |
| [lib/datadog/core/telemetry/event/distributions.rb](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&filepath=lib%2Fdatadog%2Fcore%2Ftelemetry%2Fevent%2Fdistributions.rb&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog#diff-bGliL2RhdGFkb2cvY29yZS90ZWxlbWV0cnkvZXZlbnQvZGlzdHJpYnV0aW9ucy5yYg==) | 87.50% | [1 Missing :warning: ](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) |
| [...b/datadog/core/telemetry/event/generate\_metrics.rb](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&filepath=lib%2Fdatadog%2Fcore%2Ftelemetry%2Fevent%2Fgenerate_metrics.rb&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog#diff-bGliL2RhdGFkb2cvY29yZS90ZWxlbWV0cnkvZXZlbnQvZ2VuZXJhdGVfbWV0cmljcy5yYg==) | 95.45% | [1 Missing :warning: ](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) |
| [lib/datadog/core/telemetry/event/log.rb](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&filepath=lib%2Fdatadog%2Fcore%2Ftelemetry%2Fevent%2Flog.rb&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog#diff-bGliL2RhdGFkb2cvY29yZS90ZWxlbWV0cnkvZXZlbnQvbG9nLnJi) | 97.05% | [1 Missing :warning: ](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) |

<details><summary>Additional details and impacted files</summary>



```diff
@@            Coverage Diff             @@
##           master    #4671      +/-   ##
==========================================
- Coverage   97.73%   97.73%   -0.01%     
==========================================
  Files        1442     1453      +11     
  Lines       87249    87316      +67     
  Branches     4488     4487       -1     
==========================================
+ Hits        85271    85336      +65     
- Misses       1978     1980       +2     
```
</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/DataDog/dd-trace-rb/pull/4671?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog).
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: datadog-datadog-prod-us1[bot]
created_at: 2025-05-20T18:04:45Z
body: 
## Datadog Report

Branch report: [`telemetry-split`](https://app.datadoghq.com/ci/test-branch/github.com%2Fdatadog%2Fdd-trace-rb/dd-trace-rb/telemetry-split?env=ci&index=citest&_user_click_origin_=github)
Commit report: [`749a655`](https://app.datadoghq.com/ci/test-commit/github.com%2Fdatadog%2Fdd-trace-rb/dd-trace-rb/telemetry-split/749a6559c57fddc76017df05d8c9b8b4685fd2bf?env=ci&index=citest&_user_click_origin_=github)
Test service: `dd-trace-rb`

:white_check_mark: 0 Failed, 21465 Passed, 1378 Skipped, 4m 0.9s Total Time




author_association: CONTRIBUTOR
user: login: p-datadog
created_at: 2025-05-21T15:01:43Z
body: Made the suggested changes except for the alias one which needs to stay the way it is now.
author_association: MEMBER
user: login: pr-commenter[bot]
created_at: 2025-05-21T15:31:48Z
body: ## Benchmarks

Benchmark execution time: 2025-05-21 15:31:24

Comparing candidate commit 749a6559c57fddc76017df05d8c9b8b4685fd2bf in PR branch `telemetry-split` with baseline commit 2deebe6eeac70da8e3908c0a185f1f9f1ca8781a in branch `master`.

Found 0 performance improvements and 1 performance regressions! Performance is the same for 38 metrics, 6 unstable metrics.

#### scenario:profiling - Allocations ()

- 🟥 `throughput` [-340746.728op/s; -298663.150op/s] or [-10.361%; -9.082%]




author_association: NONE