Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEBUG-2334 Dynamic instrumentation configuration #3917

Merged
merged 2 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ AllCops:
- 'vendor/bundle/**/*'
- 'spec/datadog/tracing/contrib/grpc/support/gen/**/*.rb' # Skip protoc autogenerated code
- lib/datadog/di/**/*
- lib/datadog/di.rb
- spec/datadog/di/**/*
NewCops: disable # Don't allow new cops to be enabled implicitly.
SuggestExtensions: false # Stop pushing suggestions constantly.
Expand Down
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
Loading