sqreen 1.20.4-java → 1.21.0.beta3-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -24
  3. data/lib/sqreen/condition_evaluator.rb +6 -5
  4. data/lib/sqreen/conditionable.rb +24 -6
  5. data/lib/sqreen/deliveries/batch.rb +8 -1
  6. data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
  7. data/lib/sqreen/ecosystem/exception_reporting.rb +26 -0
  8. data/lib/sqreen/ecosystem/http/net_http.rb +50 -0
  9. data/lib/sqreen/ecosystem/http/rack_request.rb +39 -0
  10. data/lib/sqreen/ecosystem/loggable.rb +13 -0
  11. data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
  12. data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
  13. data/lib/sqreen/ecosystem/module_api/message_producer.rb +51 -0
  14. data/lib/sqreen/ecosystem/module_api/signal_producer.rb +24 -0
  15. data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +31 -0
  16. data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +27 -0
  17. data/lib/sqreen/ecosystem/module_api/tracing.rb +45 -0
  18. data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +16 -0
  19. data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
  20. data/lib/sqreen/ecosystem/module_api.rb +30 -0
  21. data/lib/sqreen/ecosystem/module_registry.rb +44 -0
  22. data/lib/sqreen/ecosystem/redis/redis_connection.rb +43 -0
  23. data/lib/sqreen/ecosystem/tracing/modules/client.rb +31 -0
  24. data/lib/sqreen/ecosystem/tracing/modules/server.rb +30 -0
  25. data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
  26. data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
  27. data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
  28. data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
  29. data/lib/sqreen/ecosystem/tracing_broker.rb +101 -0
  30. data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
  31. data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
  32. data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +13 -0
  33. data/lib/sqreen/ecosystem.rb +96 -0
  34. data/lib/sqreen/ecosystem_integration/around_callbacks.rb +99 -0
  35. data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +42 -0
  36. data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +58 -0
  37. data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
  38. data/lib/sqreen/ecosystem_integration.rb +87 -0
  39. data/lib/sqreen/frameworks/generic.rb +15 -1
  40. data/lib/sqreen/graft/call.rb +30 -1
  41. data/lib/sqreen/graft/hook.rb +88 -78
  42. data/lib/sqreen/graft/hook_point.rb +17 -10
  43. data/lib/sqreen/legacy/old_event_submission_strategy.rb +7 -1
  44. data/lib/sqreen/metrics/req_detailed.rb +41 -0
  45. data/lib/sqreen/metrics.rb +1 -0
  46. data/lib/sqreen/remote_command.rb +3 -0
  47. data/lib/sqreen/rules/rule_cb.rb +2 -2
  48. data/lib/sqreen/runner.rb +44 -15
  49. data/lib/sqreen/session.rb +2 -0
  50. data/lib/sqreen/signals/conversions.rb +6 -1
  51. data/lib/sqreen/version.rb +1 -1
  52. data/lib/sqreen/weave/budget.rb +3 -14
  53. data/lib/sqreen/weave/legacy/instrumentation.rb +145 -94
  54. metadata +41 -5
@@ -0,0 +1,44 @@
1
+ require 'sqreen/ecosystem/loggable'
2
+
3
+ module Sqreen
4
+ module Ecosystem
5
+ class ModuleRegistry
6
+ include Sqreen::Ecosystem::Loggable
7
+
8
+ def initialize
9
+ @mods = []
10
+ end
11
+
12
+ def register(mod)
13
+ @mods << mod
14
+ end
15
+
16
+ def init_all
17
+ logger.info { "Initializing #{@mods.size} ecosystem modules" }
18
+ each_module do |mod|
19
+ next unless mod.respond_to? :setup
20
+ logger.debug { "Initializing module with type #{mod.class}" }
21
+ mod.setup
22
+ end
23
+ end
24
+
25
+ def destroy_all
26
+ # not implemented
27
+ end
28
+
29
+ # @param [Class] type
30
+ def each_module(type = nil, &block)
31
+ selected_mods = type ? (@mods.select { |mod| mod.is_a?(type) }) : @mods
32
+ if block_given?
33
+ selected_mods.each(&block)
34
+ else
35
+ selected_mods.each
36
+ end
37
+ end
38
+
39
+ def module_subset(type)
40
+ each_module(type).to_a
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ require 'sqreen/ecosystem/module_api'
2
+ require 'sqreen/ecosystem/module_api/instrumentation'
3
+ require 'sqreen/ecosystem/module_api/message_producer'
4
+ require 'sqreen/ecosystem/module_api/tracing_id_generation'
5
+ require 'sqreen/ecosystem/module_api/tracing/client_data'
6
+
7
+ module Sqreen
8
+ module Ecosystem
9
+ module Redis
10
+ class RedisConnection
11
+ class RedisConnectionData
12
+ include ModuleApi::Tracing::ClientData
13
+
14
+ attr_accessor :port
15
+ end
16
+
17
+ include ModuleApi::Instrumentation
18
+ include ModuleApi::MessageProducer
19
+ include ModuleApi::TracingIdGeneration
20
+
21
+ def setup
22
+ advice = wrap_for_interest(ModuleApi::Tracing::ClientData, &method(:before_advice))
23
+ instrument 'Redis::Connection::TCPSocket#connect',
24
+ before: advice
25
+ end
26
+
27
+ private
28
+
29
+ def before_advice
30
+ host = call.args[0]
31
+ port = call.args[1]
32
+
33
+ RedisConnectionData.new(
34
+ transport: 'redis',
35
+ host: host,
36
+ port: port,
37
+ tracing_identifier: create_tracing_id
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ require 'sqreen/ecosystem/tracing/signals/tracing_client'
2
+ require 'sqreen/ecosystem/module_api/tracing'
3
+ require 'sqreen/ecosystem/module_api/tracing/client_data'
4
+
5
+ module Sqreen
6
+ module Ecosystem
7
+ module Tracing
8
+ module Modules
9
+ class Client
10
+ include ModuleApi::Tracing
11
+
12
+ consumes ModuleApi::Tracing::ClientData
13
+ fixed_scope 'client'
14
+
15
+ # @param [Sqreen::Ecosystem::ModuleApi::Tracing::ClientData] data
16
+ def receive(data)
17
+ signal = Tracing::Signals::TracingClient.new
18
+ signal.payload = Tracing::Signals::TracingClient::Payload.new(
19
+ transport: data.transport,
20
+ host: data.host,
21
+ ip: data.ip,
22
+ tracing_identifier: data.tracing_identifier
23
+ )
24
+
25
+ submit_signal signal
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ require 'sqreen/ecosystem/tracing/signals/tracing_server'
2
+ require 'sqreen/ecosystem/module_api/tracing'
3
+ require 'sqreen/ecosystem/module_api/tracing/server_data'
4
+
5
+ module Sqreen
6
+ module Ecosystem
7
+ module Tracing
8
+ module Modules
9
+ class Server
10
+ include ModuleApi::Tracing
11
+
12
+ consumes ModuleApi::Tracing::ServerData
13
+ fixed_scope 'server'
14
+
15
+ # @param [Sqreen::Ecosystem::ModuleApi::Tracing::ServerData] data
16
+ def receive(data)
17
+ signal = Tracing::Signals::TracingServer.new
18
+ signal.payload = Tracing::Signals::TracingServer::Payload.new(
19
+ transport: data.transport,
20
+ client_ip: data.client_ip,
21
+ tracing_identifier: data.tracing_identifier
22
+ )
23
+
24
+ submit_signal signal
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,160 @@
1
+ require 'thread' # for Mutex
2
+ require 'singleton'
3
+ require 'sqreen/ecosystem/loggable'
4
+
5
+ # see https://github.com/sqreen/TechDoc/blob/master/content/specs/spec000024-sampling.md
6
+
7
+ module Sqreen
8
+ module Ecosystem
9
+ module Tracing
10
+ class Sampler
11
+ # @param [Array<Hash{String=>Object}>] definition
12
+ def initialize(definition)
13
+ @lines = definition.map { |h| Line.new(h) }
14
+ end
15
+
16
+ def should_sample?
17
+ line = @lines.find(&:triggers?)
18
+ line ? line.saved_definition : false
19
+ end
20
+
21
+ class Line
22
+ include Loggable
23
+
24
+ attr_reader :saved_definition
25
+
26
+ # @param [Hash{String=>Object}] definition
27
+ def initialize(definition)
28
+ @saved_definition = definition
29
+ @primitives = []
30
+
31
+ unknown = definition.keys - PRIMITIVES_MAP.keys
32
+ unless unknown.empty?
33
+ logger.warn "Unknown primitives: #{unknown}"
34
+ @primitives << AlwaysFalsePrimitive.instance
35
+ return
36
+ end
37
+
38
+ PRIMITIVES_MAP.each do |key, prim_class|
39
+ next unless definition[key]
40
+ @primitives << prim_class.new(definition[key])
41
+ end
42
+ # if @primitives is empty the line will always
43
+ # return true: [].all?(&:triggers?) is true
44
+ end
45
+
46
+ def triggers?
47
+ @primitives.all?(&:triggers?)
48
+ end
49
+ end
50
+
51
+ class AlwaysFalsePrimitive
52
+ include Singleton
53
+
54
+ def triggers?
55
+ false
56
+ end
57
+ end
58
+
59
+ class CallsPrimitive
60
+ def initialize(calls_period)
61
+ @calls_period = calls_period
62
+ @count = 0
63
+ @mutex = Mutex.new
64
+ end
65
+
66
+ def triggers?
67
+ prev_count = nil
68
+ @mutex.synchronize do
69
+ prev_count = @count
70
+ @count += 1
71
+ end
72
+
73
+ (prev_count % @calls_period).zero?
74
+ end
75
+ end
76
+
77
+ class RandomPrimitive
78
+ def initialize(probability)
79
+ @probability = probability
80
+ end
81
+
82
+ def triggers?
83
+ @probability >= rand
84
+ end
85
+ end
86
+
87
+ class MaxDurationMinutesPrimitive
88
+ def initialize(time_in_minutes)
89
+ @deadline = Sqreen.time + time_in_minutes * 60
90
+ @passed = false # no locking needed
91
+ end
92
+
93
+ def triggers?
94
+ return false if @passed
95
+ if Sqreen.time > @deadline
96
+ @passed = true
97
+ return false
98
+ end
99
+ true
100
+ end
101
+ end
102
+
103
+ class TargetPerMinutePrimitive
104
+ def initialize(max_calls)
105
+ @max_calls = max_calls
106
+ @minute_last_call = cur_minute
107
+ @calls_accumulated = 0
108
+ @mutex = Mutex.new
109
+ end
110
+
111
+ def triggers?
112
+ this_minute = cur_minute
113
+ calls_cur_minute = @mutex.synchronize do
114
+ if @minute_last_call == this_minute
115
+ @calls_accumulated += 1
116
+ else
117
+ @minute_last_call = this_minute
118
+ @calls_accumulated = 1
119
+ end
120
+ end
121
+
122
+ calls_cur_minute <= @max_calls
123
+ end
124
+
125
+ private
126
+
127
+ def cur_minute
128
+ (Sqreen.time / 60).floor
129
+ end
130
+ end
131
+
132
+ class MaxCallsPrimitive
133
+ def initialize(max_calls)
134
+ @max_calls = max_calls
135
+ @disabled = false # to avoid lock
136
+ @mutex = Mutex.new
137
+ @num_calls = 0
138
+ end
139
+
140
+ def triggers?
141
+ return false if @disabled
142
+ num_calls = @mutex.synchronize do
143
+ @num_calls += 1
144
+ end
145
+
146
+ num_calls <= @max_calls
147
+ end
148
+ end
149
+
150
+ PRIMITIVES_MAP = {
151
+ "calls" => CallsPrimitive,
152
+ "random" => RandomPrimitive,
153
+ "max_duration_minutes" => MaxDurationMinutesPrimitive,
154
+ "target_per_minute" => TargetPerMinutePrimitive,
155
+ "max_calls" => MaxCallsPrimitive,
156
+ }.freeze
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,150 @@
1
+ require 'thread'
2
+ require 'sqreen/ecosystem/loggable'
3
+ require 'sqreen/ecosystem/tracing/sampler'
4
+
5
+ module Sqreen
6
+ module Ecosystem
7
+ module Tracing
8
+ # tracing sampling configuration, as specified by the 2nd argument of the
9
+ # tracing_enable command.
10
+ # See https://github.com/sqreen/TechDoc/blob/master/content/specs/spec000025-enabling-tracing.md
11
+ class SamplingConfiguration
12
+ include Loggable
13
+
14
+ DEFAULT_SCOPE = '*'.freeze
15
+
16
+ def initialize(sampling_config)
17
+ @sampling_config = sampling_config
18
+ @samplers = {}
19
+ @samplers_virtual = build_virtual_holders(sampling_config)
20
+ end
21
+
22
+ # @param [String] scope either the scope or the part behind @ in a virtual scope
23
+ # @param [String] qualifier the part after @ in a virtual scope or nil
24
+ def should_sample?(scope, qualifier = nil)
25
+ if qualifier
26
+ fetch_sampler_virtual(scope, qualifier).should_sample?
27
+ else
28
+ fetch_sampler(scope).should_sample?
29
+ end
30
+ end
31
+
32
+ def forget_virtual_scope(scope, qualifier)
33
+ holder = @samplers_virtual[scope]
34
+ return unless holder.delete!(qualifier)
35
+ end
36
+
37
+ private
38
+
39
+ def fetch_sampler_virtual(scope, qualifier)
40
+ holder = @samplers_virtual[scope]
41
+
42
+ # no virtual scope configured, fallback to plain scope
43
+ return fetch_sampler(scope) unless holder
44
+
45
+ holder[qualifier]
46
+ end
47
+
48
+ def fetch_sampler(scope)
49
+ sampler = @samplers[scope]
50
+ return sampler if sampler
51
+
52
+ cfg = @sampling_config[scope] || @sampling_config[DEFAULT_SCOPE]
53
+ if cfg
54
+ @samplers[scope] = SamplingConfiguration.build_sampler(scope, cfg)
55
+ else
56
+ logger.info { "Disabling scope #{scope} due to its not being configured" }
57
+ @samplers[scope] = DisabledScopeSampler
58
+ end
59
+ end
60
+
61
+ def build_virtual_holders(sampling_config)
62
+ Hash[
63
+ sampling_config
64
+ .select { |scope, _cfg| scope.end_with?('@*') }
65
+ .map do |scope, cfg|
66
+ parent = scope[0...-2] # remove trailing @*
67
+ [parent, VirtualScopesHolder.new(parent, cfg)]
68
+ end
69
+ ]
70
+ end
71
+
72
+ class << self
73
+ include Loggable
74
+
75
+ def build_sampler(scope, cfg)
76
+ do_build_sampler(cfg['enabled'], cfg['sampling'] || [{}])
77
+ rescue StandardError => e
78
+ logger.warn "Invalid sampling configuration for #{scope}: #{e.inspect}"
79
+ logger.debug { e.backtrace }
80
+ DisabledScopeSampler
81
+ end
82
+
83
+ private
84
+
85
+ def do_build_sampler(enabled, sampling)
86
+ if enabled
87
+ Sampler.new(sampling)
88
+ else
89
+ logger.debug { "Disabling scope #{scope}" }
90
+ DisabledScopeSampler
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ class VirtualScopesHolder
97
+ include Loggable
98
+
99
+ MAX_VIRTUAL_SCOPES = 120
100
+ DISCARD_SIZE = 20
101
+
102
+ def initialize(parent, cfg)
103
+ @parent = parent
104
+ @cfg = cfg
105
+ @virtual_scopes = {}
106
+ @mutex = Mutex.new
107
+ end
108
+
109
+ def [](qualifier)
110
+ return false unless @cfg['enabled']
111
+
112
+ @mutex.synchronize do
113
+ sampler = @virtual_scopes[qualifier]
114
+ return sampler if sampler
115
+
116
+ @virtual_scopes[qualifier] = create_virtual(qualifier)
117
+ end
118
+ end
119
+
120
+ def delete!(qualifier)
121
+ @virtual_scopes.delete(qualifier)
122
+ end
123
+
124
+ private
125
+
126
+ def create_virtual(qualifier)
127
+ discard if @virtual_scopes.size >= MAX_VIRTUAL_SCOPES
128
+
129
+ @virtual_scopes[qualifier] =
130
+ SamplingConfiguration.build_sampler("#{@parent}@#{qualifier}", @cfg)
131
+ end
132
+
133
+ def discard
134
+ logger.info { "Discarding excess virtual scopes for scope '#{@parent}'" }
135
+ discard_keys = @virtual_scopes.keys.sample(DISCARD_SIZE)
136
+ @virtual_scopes.delete_if { |k, _v| discard_keys.include? k }
137
+ end
138
+ end
139
+
140
+ # fake sampler that always returns false
141
+ class DisabledScopeSampler
142
+ class << self
143
+ def should_sample?
144
+ false
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,53 @@
1
+ require 'sqreen/kit/configuration'
2
+ require 'sqreen/kit/signals/point'
3
+ require 'sqreen/kit/signals/dto_helper'
4
+
5
+ # reference: https://github.com/sqreen/SignalsSchemas/blob/master/schemas/payload/tracing/client-2020-04-21/schema.cue
6
+
7
+ module Sqreen
8
+ module Ecosystem
9
+ module Tracing
10
+ module Signals
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ class Sqreen::Ecosystem::Tracing::Signals::TracingClient < Sqreen::Kit::Signals::Point
17
+ readonly_attrs :payload_schema, :source, :signal_name
18
+
19
+ def initialize(values = {})
20
+ self.payload_schema = Payload::SCHEMA_VERSION
21
+ self.source = Sqreen::Kit::Configuration.default_source
22
+ self.signal_name = 'tracing.client'
23
+ self.time = values[:time] || Time.now
24
+ super
25
+ end
26
+
27
+ def payload=(payload)
28
+ unless payload.is_a?(Payload)
29
+ raise ArgumentError, "Payload should be a #{Payload}"
30
+ end
31
+ super
32
+ end
33
+
34
+ class Payload
35
+ include Sqreen::Kit::Signals::DtoHelper
36
+
37
+ add_mandatory_attrs :transport, :host
38
+
39
+ SCHEMA_VERSION = 'tracing/client-2020-04-21'.freeze
40
+
41
+ # @return [Symbol]
42
+ attr_accessor :transport
43
+
44
+ # @return [String]
45
+ attr_accessor :host
46
+
47
+ # @return [String]
48
+ attr_accessor :ip
49
+
50
+ # @return [String]
51
+ attr_accessor :tracing_identifier
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ require 'sqreen/kit/configuration'
2
+ require 'sqreen/kit/signals/point'
3
+ require 'sqreen/kit/signals/dto_helper'
4
+
5
+ # reference: https://github.com/sqreen/SignalsSchemas/blob/master/schemas/payload/tracing/server-2020-04-21/schema.cue
6
+
7
+ module Sqreen
8
+ module Ecosystem
9
+ module Tracing
10
+ module Signals
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ class Sqreen::Ecosystem::Tracing::Signals::TracingServer < Sqreen::Kit::Signals::Point
17
+ readonly_attrs :payload_schema, :source, :signal_name
18
+
19
+ def initialize(values = {})
20
+ self.payload_schema = Payload::SCHEMA_VERSION
21
+ self.source = Sqreen::Kit::Configuration.default_source
22
+ self.signal_name = 'tracing.server'
23
+ self.time = values[:time] || Time.now
24
+ super
25
+ end
26
+
27
+ def payload=(payload)
28
+ unless payload.is_a?(Payload)
29
+ raise ArgumentError, "Payload should be a #{Payload}"
30
+ end
31
+ super
32
+ end
33
+
34
+ class Payload
35
+ include Sqreen::Kit::Signals::DtoHelper
36
+
37
+ add_mandatory_attrs :transport, :client_ip
38
+
39
+ SCHEMA_VERSION = 'tracing/server-2020-04-21'.freeze
40
+
41
+ # @return [Symbol]
42
+ attr_accessor :transport
43
+
44
+ # @return [String]
45
+ attr_accessor :client_ip
46
+
47
+ # @return [Array<String>]
48
+ attr_accessor :previous_hops
49
+
50
+ # @return [String]
51
+ attr_accessor :tracing_identifier
52
+ end
53
+ end
@@ -0,0 +1,101 @@
1
+ require 'sqreen/ecosystem/loggable'
2
+ require 'sqreen/ecosystem/exception_reporting'
3
+
4
+ module Sqreen
5
+ module Ecosystem
6
+ class TracingBroker
7
+ include Loggable
8
+ include ExceptionReporting
9
+
10
+ # Stores a lookup resolution so that lookup (incl. sampling) is done
11
+ # only once, not in the beginning of the producer code (when it asks
12
+ # whether it should proceed) and again once it delivers the data
13
+ ObserverLookup = Struct.new(:modules)
14
+
15
+ # @return [Sqreen::Ecosystem::Tracing::SamplingConfiguration]
16
+ attr_writer :sampling_configuration
17
+
18
+ # @param [Array<Sqreen::Ecosystem::ModuleApi::Tracing>] tracing_modules
19
+ def initialize(tracing_modules)
20
+ @sampling_configuration = nil
21
+ @type_to_subscribers = {}
22
+ tracing_modules.each do |mod|
23
+ consumed_type = mod.consumed_type
24
+ @type_to_subscribers[consumed_type] ||= []
25
+ @type_to_subscribers[consumed_type] << mod
26
+ end
27
+ end
28
+
29
+ # @param [Object] data
30
+ # @param [Sqreen::Ecosystem::TracingBroker::ObserverLookup] prev_lookup
31
+ def publish(data, prev_lookup)
32
+ prev_lookup.modules.each do |mod|
33
+ mod_process_data mod, data
34
+ end
35
+ end
36
+
37
+ # @param [Module] data_type
38
+ # @param [Hash] _hints reserved for future use, e.g. virtual scopes
39
+ # @return [Sqreen::Ecosystem::TracingBroker::ObserverLookup]
40
+ def interested_consumers(data_type, _hints = {})
41
+ unless @sampling_configuration
42
+ logger.debug do
43
+ "Declaring no one is interested in #{data_type} " \
44
+ "because tracing hasn't been enabled yet"
45
+ end
46
+ return false
47
+ end
48
+
49
+ # if we have several modules with the same scope, we
50
+ # should ask whether we should sample only once
51
+ scope_to_should_sample = Hash.new do |hash, scope|
52
+ result = @sampling_configuration.should_sample?(scope)
53
+ if result
54
+ logger.debug { "Will sample scope #{scope}. Sampling line: #{result}" }
55
+ else
56
+ logger.debug { "Will NOT sample scope #{scope}" }
57
+ end
58
+
59
+ hash[scope] = result
60
+ end
61
+
62
+ res = subscribers(data_type).select do |mod|
63
+ scope_to_should_sample[mod.scope]
64
+ end
65
+
66
+ res.empty? ? false : ObserverLookup.new(res)
67
+ end
68
+
69
+ private
70
+
71
+ # @param [Sqreen::Ecosystem::ModuleApi::Tracing] mod
72
+ def mod_process_data(mod, data)
73
+ mod.receive(data)
74
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
75
+ report_exception("Error invoking tracing module #{mod}", e)
76
+ end
77
+
78
+ # @param [Module] data_type
79
+ # @return Array<Sqreen::Ecosystem::ModuleApi::Tracing>
80
+ def subscribers(data_type)
81
+ subscribers = @type_to_subscribers[data_type]
82
+
83
+ # None of the modules subscribes to data_type directly,
84
+ # but maybe they subscribe to one of the ancestors
85
+ # Cache this lookup
86
+ unless subscribers
87
+ subscribers = parents(data_type).inject([]) do |accum, type|
88
+ accum + (@type_to_subscribers[type] || [])
89
+ end
90
+ @type_to_subscribers[data_type] = subscribers
91
+ end
92
+
93
+ subscribers
94
+ end
95
+
96
+ def parents(type)
97
+ type.ancestors - Object.ancestors - [type]
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,34 @@
1
+ require 'sqreen/ecosystem/module_registry'
2
+ require 'sqreen/ecosystem/transaction_storage'
3
+ require 'sqreen/ecosystem/module_api/signal_producer'
4
+
5
+ module Sqreen
6
+ module Ecosystem
7
+ class TracingIdSetup
8
+ # @param [Array<Sqreen::Ecosystem::ModuleApi::SignalProducer>] signal_producer_modules
9
+ def initialize(signal_producer_modules)
10
+ @modules = signal_producer_modules
11
+ @tracing_id_prefix = nil
12
+ end
13
+
14
+ def setup_modules
15
+ inject_out_of_tx_tracing_id_gen
16
+ end
17
+
18
+ attr_writer :tracing_id_prefix
19
+
20
+ private
21
+
22
+ def inject_out_of_tx_tracing_id_gen
23
+ @modules.each do |mod|
24
+ mod.tracing_id_producer = method(:generate_tracing_id)
25
+ end
26
+ end
27
+
28
+ def generate_tracing_id
29
+ return nil unless @tracing_id_prefix
30
+ "#{@tracing_id_prefix}.#{SecureRandom.uuid}"
31
+ end
32
+ end
33
+ end
34
+ end