Skip to content

Commit

Permalink
DEBUG-2334 Dynamic instrumentation configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
p committed Sep 17, 2024
1 parent 06aa9ac commit 41980db
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 0 deletions.
1 change: 1 addition & 0 deletions Steepfile
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ target :datadog do
ignore 'lib/datadog/core/workers/polling.rb'
ignore 'lib/datadog/core/workers/queue.rb'
ignore 'lib/datadog/core/workers/runtime_metrics.rb'
ignore 'lib/datadog/di/configuration/settings.rb'
ignore 'lib/datadog/kit/appsec/events.rb' # disabled because of https://github.com/soutaro/steep/issues/701
ignore 'lib/datadog/kit/identity.rb' # disabled because of https://github.com/soutaro/steep/issues/701
ignore 'lib/datadog/opentelemetry.rb'
Expand Down
14 changes: 14 additions & 0 deletions lib/datadog/di.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

require_relative 'di/configuration'
require_relative 'di/extensions'

module Datadog
# Namespace for Datadog dynamic instrumentation.
#
# @api private
module DI
# Expose DI to global shared objects
Extensions.activate!
end
end
11 changes: 11 additions & 0 deletions lib/datadog/di/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Datadog
module DI
# Configuration for DI
module Configuration
end
end
end

require_relative "configuration/settings"
163 changes: 163 additions & 0 deletions lib/datadog/di/configuration/settings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# frozen_string_literal: true

module Datadog
module DI
module Configuration
# Settings
module Settings
def self.extended(base)
base = base.singleton_class unless base.is_a?(Class)
add_settings!(base)
end

def self.add_settings!(base)
base.class_eval do
# The setting has "internal" prefix to prevent it from being
# prematurely turned on by customers.
settings :dynamic_instrumentation do
option :enabled do |o|
o.type :bool
# The environment variable has an "internal" prefix so that
# any customers that have the "proper" environment variable
# turned on (i.e. DD_DYNAMIC_INSTRUMENTATION_ENABLED)
# do not enable Ruby DI until the latter is ready for
# customer testing.
o.env "DD_DYNAMIC_INSTRUMENTATION_ENABLED"
o.default false
end

# This option instructs dynamic instrumentation to use
# untargeted trace points when installing line probes and
# code tracking is not active.
# WARNING: untargeted trace points carry a massive performance
# penalty for the entire file in which a line probe is placed.
#
# If this option is set to false, which is the default,
# dynamic instrumentation will add probes that reference
# unknown files to the list of pending probes, and when
# the respective files are loaded, the line probes will be
# installed using targeted trace points. If the file in
# question is already loaded when the probe is received
# (for example, it is in a third-party library loaded during
# application boot), and code tracking was not active when
# the file was loaded, such files will not be instrumentable
# via line probes.
#
# If this option is set to true
#
# activated, DI will in
# activated or because the files being targeted have beenIf true and code tracking is not enabled, dynamic instrumentation
# will use untargeted trace points.
# If false and code tracking is not enabled, dynamic
# instrumentation will not instrument any files loaded
# WARNING: these trace points will greatly degrade performance
# of all code in the instrumented files.
option :untargeted_trace_points do |o|
o.type :bool
o.default false
end

# If true, all of the catch-all rescue blocks in DI
# will propagate the exceptions onward.
# WARNING: for internal Datadog use only - this will break
# the DI product and potentially the library in general in
# a multitude of ways, cause resource leakage, permanent
# performance decreases, etc.
option :propagate_all_exceptions do |o|
o.type :bool
o.default false
end

# An array of variable and key names to redact in addition to
# the built-in list of identifiers.
#
# The names will be normalized by removing the following
# symbols: _, -, @, $, and then matched to the complete
# variable or key name while ignoring the case.
# For example, specifying pass_word will match password and
# PASSWORD, and specifying PASSWORD will match pass_word.
# Note that, while the at sign (@) is used in Ruby to refer
# to instance variables, it does not have any significance
# for this setting (and is removed before matching identifiers).
option :redacted_identifiers do |o|
o.env "DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS"
o.env_parser do |value|
value&.split(",")&.map(&:strip)
end

o.type :array
o.default []
end

# An array of class names, values of which will be redacted from
# dynamic instrumentation snapshots. Example: FooClass.
# If a name is suffixed by '*', it becomes a wildcard and
# instances of any class whose name begins with the specified
# prefix will be redacted (example: Foo*).
#
# The names must all be fully-qualified, if any prefix of a
# class name is configured to be redacted, the value will be
# subject to redaction. For example, if Foo* is in the
# redacted class name list, instances of Foo, FooBar,
# Foo::Bar are all subject to redaction, but Bar::Foo will
# not be subject to redaction.
#
# Leading double-colon is permitted but has no effect,
# because the names are always considered to be fully-qualified.
# For example, adding ::Foo to the list will redact instances
# of Foo.
#
# Trailing colons should not be used because they will trigger
# exact match behavior but Ruby class names do not have
# trailing colons. For example, Foo:: will not cause anything
# to be redacted. Use Foo::* to redact all classes under
# the Foo module.
option :redacted_type_names do |o|
o.env "DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES"
o.env_parser do |value|
value&.split(",")&.map(&:strip)
end

o.type :array
o.default []
end

# Maximum number of object or collection traversals that
# will be permitted when serializing captured values.
option :max_capture_depth do |o|
o.type :int
o.default 3
end

# Maximum number of collection (Array and Hash) elements
# that will be captured. Arrays and hashes that have more
# elements will be truncated to this many elements.
option :max_capture_collection_size do |o|
o.type :int
o.default 100
end

# Strings longer than this length will be truncated to this
# length in dynamic instrumentation snapshots.
#
# Note that while all values are stringified during
# serialization, only values which are originally instances
# of the String class are subject to this length limit.
option :max_capture_string_length do |o|
o.type :int
o.default 255
end

# Maximim number of attributes that will be captured for
# a single non-primitive value.
option :max_capture_attribute_count do |o|
o.type :int
o.default 20
end
end
end
end
end
end
end
end
16 changes: 16 additions & 0 deletions lib/datadog/di/extensions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

require_relative "../core/configuration"
require_relative "configuration"

module Datadog
module DI
# Extends Datadog tracing with DI features
module Extensions
# Inject DI into global objects.
def self.activate!
Core::Configuration::Settings.extend(Configuration::Settings)
end
end
end
end
4 changes: 4 additions & 0 deletions sig/datadog/di.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Datadog
module DI
end
end
6 changes: 6 additions & 0 deletions sig/datadog/di/configuration.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Datadog
module DI
module Configuration
end
end
end
10 changes: 10 additions & 0 deletions sig/datadog/di/configuration/settings.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Datadog
module DI
module Configuration
module Settings
def self.extended: (untyped base) -> untyped
def self.add_settings!: (untyped base) -> untyped
end
end
end
end
7 changes: 7 additions & 0 deletions sig/datadog/di/extensions.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Datadog
module DI
module Extensions
def self.activate!: () -> untyped
end
end
end
78 changes: 78 additions & 0 deletions spec/datadog/di/configuration/settings_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
require "datadog/di"

RSpec.describe Datadog::DI::Configuration::Settings do
subject(:settings) { Datadog::Core::Configuration::Settings.new }

describe "dynamic_instrumentation" do
context "programmatic configuration" do
[
["enabled", true],
["enabled", false],
["untargeted_trace_points", true],
["untargeted_trace_points", false],
["propagate_all_exceptions", true],
["propagate_all_exceptions", false],
["redacted_identifiers", ["foo"]],
["redacted_identifiers", []],
["redacted_type_names", ["foo*", "bar"]],
["redacted_type_names", []],
["max_capture_depth", 5],
["max_capture_collection_size", 10],
["max_capture_string_length", 20],
["max_capture_attribute_count", 4],
].each do |(name_, value_)|
name = name_
value = value_.freeze

context "when #{name} set to #{value}" do
let(:value) { _value }

before do
settings.dynamic_instrumentation.public_send("#{name}=", value)
end

it "returns the value back" do
expect(settings.dynamic_instrumentation.public_send(name)).to eq(value)
end
end
end
end

context "environment variable configuration" do
[
["DD_DYNAMIC_INSTRUMENTATION_ENABLED", "true", "enabled", true],
["DD_DYNAMIC_INSTRUMENTATION_ENABLED", "false", "enabled", false],
["DD_DYNAMIC_INSTRUMENTATION_ENABLED", nil, "enabled", false],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS", "foo", "redacted_identifiers", %w[foo]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS", "foo,bar", "redacted_identifiers", %w[foo bar]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS", "foo, bar", "redacted_identifiers", %w[foo bar]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS", "", "redacted_identifiers", %w[]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS", ",", "redacted_identifiers", %w[]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS", "~?", "redacted_identifiers", %w[~?]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES", "foo", "redacted_type_names", %w[foo]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES", "foo,bar", "redacted_type_names", %w[foo bar]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES", "foo, bar", "redacted_type_names", %w[foo bar]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES", "", "redacted_type_names", %w[]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES", ",", "redacted_type_names", %w[]],
["DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES", ".!", "redacted_type_names", %w[.!]],
].each do |(env_var_name_, env_var_value_, setting_name_, setting_value_)|
env_var_name = env_var_name_
env_var_value = env_var_value_
setting_name = setting_name_
setting_value = setting_value_

context "when #{env_var_name}=#{env_var_value}" do
around do |example|
ClimateControl.modify(env_var_name => env_var_value) do
example.run
end
end

it "sets dynamic_instrumentation.#{setting_name}=#{setting_value}" do
expect(settings.dynamic_instrumentation.public_send(setting_name)).to eq setting_value
end
end
end
end
end
end

0 comments on commit 41980db

Please sign in to comment.