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

Patch Sinatra only once #3515

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
8 changes: 7 additions & 1 deletion lib/datadog/tracing/contrib/configurable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def configurations

# Create or update configuration associated with `matcher` with
# the provided `options` and `&block`.
def configure(matcher = :default, options = {}, &block)
def configure(matcher = :default, options = {}, tracing = nil, &block)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be tracing_configuration or tracing_instrumentation instead maybe?

Copy link
Contributor Author

@lloeki lloeki Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's a bit ambiguous. Since this is a Configuration::Settings object and the other name here is config I went with tracing_config.

config = if matcher == :default
default_configuration
else
Expand All @@ -51,6 +51,12 @@ def configure(matcher = :default, options = {}, &block)

# Apply the settings
config.configure(options, &block)

# Configure dependents
if tracing && respond_to?(:subconfigure)
subconfigure(tracing, config)
end

config
end

Expand Down
6 changes: 5 additions & 1 deletion lib/datadog/tracing/contrib/extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ def configure(&block)
# Reconfigure core settings
super(&block)

# Configuration should be considered RO past this point:
# - Patcher's `patch` must not modify configuration.
# - use Integration's `subconfigure` instead

Comment on lines +66 to +69
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering about calling subconfigure here instead:

  • gives access to full config, not just tracing's, which would allow cross-product integration subconfiguration (e.g appsec could enable tracing)
  • config is final state after whole configure's &block is executed vs at the time the instrument :sinatra line is

# Activate integrations
configuration = self.configuration.tracing

Expand Down Expand Up @@ -158,7 +162,7 @@ def instrument(integration_name, options = {}, &block)
unless integration.nil? || !integration.default_configuration.enabled
configuration_name = options[:describes] || :default
filtered_options = options.reject { |k, _v| k == :describes }
integration.configure(configuration_name, filtered_options, &block)
integration.configure(configuration_name, filtered_options, self, &block)
instrumented_integrations[integration_name] = integration

# Add to activation list
Expand Down
21 changes: 0 additions & 21 deletions lib/datadog/tracing/contrib/sinatra/framework.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,6 @@ module Sinatra
# - handle configuration entries which are specific to Datadog tracing
# - instrument parts of the framework when needed
module Framework
# Configure Rack from Sinatra, but only if Rack has not been configured manually beforehand
def self.setup
Datadog.configure do |datadog_config|
sinatra_config = config_with_defaults(datadog_config)
activate_rack!(datadog_config, sinatra_config)
end
end

def self.config_with_defaults(datadog_config)
datadog_config.tracing[:sinatra]
end

# Apply relevant configuration from Sinatra to Rack
def self.activate_rack!(datadog_config, sinatra_config)
datadog_config.tracing.instrument(
:rack,
service_name: sinatra_config[:service_name],
distributed_tracing: sinatra_config[:distributed_tracing],
)
end

# Add Rack middleware at the top of the stack
def self.add_middleware(middleware, builder, *args, &block)
insert_middleware(builder, middleware, args, block) do |proc_, use|
Expand Down
8 changes: 8 additions & 0 deletions lib/datadog/tracing/contrib/sinatra/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ def new_configuration
Configuration::Settings.new
end

def subconfigure(tracing, sinatra)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lousy name, configure is already used.

I considered merely def configure and supering but that requires giving arguments here that are sorta internal WRT this Integration DSL-ish descriptive class. So I preferred not leaking internal details.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instrument_dependencies or instrument_components? It looks like in most cases this is what we end up doing here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.

Indeed in this case we do c.instrument, but that may not always be the case, it could also do c.some_setting = :foo in which case instrument_* is misleading.

Technically this is really a callback within the Datadog.configure block, so that we don't nest configure anymore. In that case I believe the name should include configure in some way for clarity. Since for Integrations there's a def configure that is called before, so maybe def after_configure? That would give clarity that i.e that it happens after Integration#configure has been called.

Functionally this is about setting up dependencies, so it may go beyond just c.something = :foo or c.instrument. So def dependencies or def configure_dependencies might cut it.

e.g I'll need to apply similar changes to AppSec so it makes me think that I'm yet unsure how that would unfold, therefore I'm trying to keep an open-ended approach where integrations could have dependencies across products. e.g AppSec or CI may decide that they'd want to enable tracing and/or enable specific tracing integrations when tracing is enabled to ensure Things Just Work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points. I like the after_configure, seems like it's a reasonable description for what's happening.

tracing.instrument(
:rack,
service_name: sinatra.service_name,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should deprecate setting the rack service_name from the sinatra service name for 2.0?

E.g. instead suggest customers configure a top-level service name instead, or if they really want to, they can configure a service name in rack directly.

Copy link
Contributor Author

@lloeki lloeki Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not so much about passing a manually configured service name as much as when that sinatra.service_name is defaulted and inferred e.g from the app class or something.

Same for Rails where it's the application name as a default.

Cue autoinstrumentation as well.

Tangentially there may be a conundrum to resolve here in such default cases: ordering may matter when there's both Sinatra and Rails in the same app, as both would set their service name and one would win. But that's a problem for another day.

distributed_tracing: sinatra.distributed_tracing,
)
Comment on lines +35 to +39
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most probably:

  • other integrations are affected by this: Rails, Grape, etc...
  • a similar pattern should be used by e.g AppSec integrations.

end

def patcher
Patcher
end
Expand Down
14 changes: 0 additions & 14 deletions lib/datadog/tracing/contrib/sinatra/patcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,6 @@ module Datadog
module Tracing
module Contrib
module Sinatra
# Set tracer configuration at a late enough time
module TracerSetupPatch
ONLY_ONCE_PER_APP = Hash.new { |h, key| h[key] = Core::Utils::OnlyOnce.new }

def setup_middleware(*args, &block)
super.tap do
ONLY_ONCE_PER_APP[self].run do
Contrib::Sinatra::Framework.setup
end
end
end
end

# Hook into builder before the middleware list gets frozen
module DefaultMiddlewarePatch
ONLY_ONCE_PER_APP = Hash.new { |h, key| h[key] = Core::Utils::OnlyOnce.new }
Expand Down Expand Up @@ -53,7 +40,6 @@ def patch
register_tracer

patch_default_middlewares
setup_tracer
end

def register_tracer
Comment on lines -56 to 45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⬇️ Don't forget to remove setup_tracer, below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, removed.

Expand Down
Loading