# frozen_string_literal: truerequire_relative'tracer'require_relative'configuration/agent_settings_resolver'require_relative'flush'require_relative'sync_writer'require_relative'sampling/span/rule_parser'require_relative'sampling/span/sampler'require_relative'diagnostics/environment_logger'moduleDatadogmoduleTracing# Tracing componentmoduleComponent# Methods that interact with component instance fields.moduleInstanceMethods# Hot-swaps with a new sampler.# This operation acquires the Components lock to ensure# there is no concurrent modification of the sampler.defreconfigure_live_samplersampler=self.class.build_sampler(Datadog.configuration)Datadog.send(:safely_synchronize){tracer.sampler.sampler=sampler}endenddefbuild_tracer(settings,logger:)# If a custom tracer has been provided, use it instead.# Ignore all other options (they should already be configured.)tracer=settings.tracing.instancereturntracerunlesstracer.nil?agent_settings=Configuration::AgentSettingsResolver.call(settings,logger: logger)# Apply test mode settings if test mode is activatedifsettings.tracing.test_mode.enabledtrace_flush=build_test_mode_trace_flush(settings)sampler=build_test_mode_samplerwriter=build_test_mode_writer(settings,agent_settings)elsetrace_flush=build_trace_flush(settings)sampler=build_sampler(settings)writer=build_writer(settings,agent_settings)end# The sampler instance is wrapped in a delegator,# so dynamic instrumentation can hot-swap it.# This prevents full tracer reinitialization on sampling changes.sampler_delegator=SamplerDelegatorComponent.new(sampler)subscribe_to_writer_events!(writer,sampler_delegator,settings.tracing.test_mode.enabled)Tracing::Tracer.new(default_service: settings.service,enabled: settings.tracing.enabled,trace_flush: trace_flush,sampler: sampler_delegator,span_sampler: build_span_sampler(settings),writer: writer,tags: build_tracer_tags(settings),)enddefbuild_trace_flush(settings)ifsettings.tracing.partial_flush.enabledTracing::Flush::Partial.new(min_spans_before_partial_flush: settings.tracing.partial_flush.min_spans_threshold)elseTracing::Flush::Finished.newendend# TODO: Sampler should be a top-level component.# It is currently part of the Tracer initialization# process, but can take a variety of options (including# a fully custom instance) that makes the Tracer# initialization process complex.defbuild_sampler(settings)if(sampler=settings.tracing.sampler)ifsettings.tracing.priority_sampling==falsesamplerelseensure_priority_sampling(sampler,settings)endelsif(rules=settings.tracing.sampling.rules)post_sampler=Tracing::Sampling::RuleSampler.parse(rules,settings.tracing.sampling.rate_limit,settings.tracing.sampling.default_rate)post_sampler||=# Fallback RuleSampler in case `rules` parsing failsTracing::Sampling::RuleSampler.new(rate_limit: settings.tracing.sampling.rate_limit,default_sample_rate: settings.tracing.sampling.default_rate)Tracing::Sampling::PrioritySampler.new(base_sampler: Tracing::Sampling::AllSampler.new,post_sampler: post_sampler)elsifsettings.tracing.priority_sampling==falseTracing::Sampling::RuleSampler.new(rate_limit: settings.tracing.sampling.rate_limit,default_sample_rate: settings.tracing.sampling.default_rate)elseTracing::Sampling::PrioritySampler.new(base_sampler: Tracing::Sampling::AllSampler.new,post_sampler: Tracing::Sampling::RuleSampler.new(rate_limit: settings.tracing.sampling.rate_limit,default_sample_rate: settings.tracing.sampling.default_rate))endenddefensure_priority_sampling(sampler,settings)ifsampler.is_a?(Tracing::Sampling::PrioritySampler)samplerelseTracing::Sampling::PrioritySampler.new(base_sampler: sampler,post_sampler: Tracing::Sampling::RuleSampler.new(rate_limit: settings.tracing.sampling.rate_limit,default_sample_rate: settings.tracing.sampling.default_rate))endend# TODO: Writer should be a top-level component.# It is currently part of the Tracer initialization# process, but can take a variety of options (including# a fully custom instance) that makes the Tracer# initialization process complex.defbuild_writer(settings,agent_settings,options=settings.tracing.writer_options)if(writer=settings.tracing.writer)returnwriterendTracing::Writer.new(agent_settings: agent_settings,**options)enddefsubscribe_to_writer_events!(writer,sampler_delegator,test_mode)returnunlesswriter.respond_to?(:events)# Check if it's a custom, external writerwriter.events.after_send.subscribe(&WRITER_RECORD_ENVIRONMENT_INFORMATION_CALLBACK)# DEV: We need to ignore priority sampling updates coming from the agent in test mode# because test mode wants to *unconditionally* sample all traces.## This can cause trace metrics to be overestimated, but that's a trade-off we take# here to achieve 100% sampling rate.returniftest_modewriter.events.after_send.subscribe(&writer_update_priority_sampler_rates_callback(sampler_delegator))endWRITER_RECORD_ENVIRONMENT_INFORMATION_CALLBACK=lambdado|_,responses|Tracing::Diagnostics::EnvironmentLogger.collect_and_log!(responses: responses)end# Create new lambda for writer callback,# capture the current sampler in the callback closure.defwriter_update_priority_sampler_rates_callback(sampler)lambdado|_,responses|response=responses.lastnextunlessresponse&&!response.internal_error?&&response.service_ratessampler.update(response.service_rates,decision: Tracing::Sampling::Ext::Decision::AGENT_RATE)endenddefbuild_span_sampler(settings)rules=Tracing::Sampling::Span::RuleParser.parse_json(settings.tracing.sampling.span_rules)Tracing::Sampling::Span::Sampler.new(rules||[])end# Sampler wrapper component, to allow for hot-swapping# the sampler instance used by the tracer.# Swapping samplers happens during Dynamic Configuration.classSamplerDelegatorComponentattr_accessor:samplerdefinitialize(sampler)@sampler=samplerenddefsample!(trace)@sampler.sample!(trace)enddefupdate(*args,**kwargs)returnunless@sampler.respond_to?(:update)@sampler.update(*args,**kwargs)endendprivatedefbuild_tracer_tags(settings)settings.tags.dup.tapdo|tags|tags[Core::Environment::Ext::TAG_ENV]=settings.envunlesssettings.env.nil?tags[Core::Environment::Ext::TAG_VERSION]=settings.versionunlesssettings.version.nil?endenddefbuild_test_mode_trace_flush(settings)# If context flush behavior is provided, use it instead.settings.tracing.test_mode.trace_flush||build_trace_flush(settings)enddefbuild_test_mode_sampler# Do not sample any spans for tests; all must be preserved.# Set priority sampler to ensure the agent doesn't drop any traces.Tracing::Sampling::PrioritySampler.new(base_sampler: Tracing::Sampling::AllSampler.new,post_sampler: Tracing::Sampling::AllSampler.new)enddefbuild_test_mode_writer(settings,agent_settings)writer_options=settings.tracing.test_mode.writer_options||{}returnbuild_writer(settings,agent_settings,writer_options)ifsettings.tracing.test_mode.async# Flush traces synchronously, to guarantee they are written.Tracing::SyncWriter.new(agent_settings: agent_settings,**writer_options)endendendend