sqreen 1.19.1-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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +34 -0
- data/lib/sqreen/actions/block_user.rb +1 -1
- data/lib/sqreen/actions/redirect_ip.rb +1 -1
- data/lib/sqreen/actions/redirect_user.rb +1 -1
- data/lib/sqreen/agent_message.rb +20 -0
- data/lib/sqreen/aggregated_metric.rb +25 -0
- data/lib/sqreen/attack_detected.html +1 -2
- data/lib/sqreen/ca.crt +24 -0
- data/lib/sqreen/condition_evaluator.rb +9 -2
- data/lib/sqreen/conditionable.rb +24 -6
- data/lib/sqreen/configuration.rb +11 -5
- data/lib/sqreen/deferred_logger.rb +50 -14
- data/lib/sqreen/deliveries/batch.rb +12 -2
- data/lib/sqreen/deliveries/simple.rb +4 -0
- data/lib/sqreen/deprecation.rb +38 -0
- data/lib/sqreen/ecosystem.rb +96 -0
- data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
- data/lib/sqreen/ecosystem/exception_reporting.rb +26 -0
- data/lib/sqreen/ecosystem/http/net_http.rb +50 -0
- data/lib/sqreen/ecosystem/http/rack_request.rb +39 -0
- data/lib/sqreen/ecosystem/loggable.rb +13 -0
- data/lib/sqreen/ecosystem/module_api.rb +30 -0
- data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
- data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
- data/lib/sqreen/ecosystem/module_api/message_producer.rb +51 -0
- data/lib/sqreen/ecosystem/module_api/signal_producer.rb +24 -0
- data/lib/sqreen/ecosystem/module_api/tracing.rb +45 -0
- data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +31 -0
- data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +27 -0
- data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +16 -0
- data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
- data/lib/sqreen/ecosystem/module_registry.rb +44 -0
- data/lib/sqreen/ecosystem/redis/redis_connection.rb +43 -0
- data/lib/sqreen/ecosystem/tracing/modules/client.rb +31 -0
- data/lib/sqreen/ecosystem/tracing/modules/server.rb +30 -0
- data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
- data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
- data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
- data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
- data/lib/sqreen/ecosystem/tracing_broker.rb +101 -0
- data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
- data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
- data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +13 -0
- data/lib/sqreen/ecosystem_integration.rb +87 -0
- data/lib/sqreen/ecosystem_integration/around_callbacks.rb +99 -0
- data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +42 -0
- data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +58 -0
- data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
- data/lib/sqreen/endpoint_testing.rb +184 -0
- data/lib/sqreen/event.rb +7 -5
- data/lib/sqreen/events/attack.rb +23 -18
- data/lib/sqreen/events/remote_exception.rb +0 -22
- data/lib/sqreen/events/request_record.rb +15 -71
- data/lib/sqreen/frameworks/generic.rb +24 -1
- data/lib/sqreen/frameworks/rails.rb +0 -7
- data/lib/sqreen/frameworks/request_recorder.rb +15 -2
- data/lib/sqreen/graft/call.rb +106 -19
- data/lib/sqreen/graft/callback.rb +1 -1
- data/lib/sqreen/graft/hook.rb +212 -100
- data/lib/sqreen/graft/hook_point.rb +18 -11
- data/lib/sqreen/kit/signals/specialized/aggregated_metric.rb +72 -0
- data/lib/sqreen/kit/signals/specialized/attack.rb +57 -0
- data/lib/sqreen/kit/signals/specialized/binning_metric.rb +76 -0
- data/lib/sqreen/kit/signals/specialized/http_trace.rb +26 -0
- data/lib/sqreen/kit/signals/specialized/sdk_track_call.rb +50 -0
- data/lib/sqreen/kit/signals/specialized/sqreen_exception.rb +57 -0
- data/lib/sqreen/legacy/instrumentation.rb +22 -10
- data/lib/sqreen/legacy/old_event_submission_strategy.rb +228 -0
- data/lib/sqreen/legacy/waf_redactions.rb +49 -0
- data/lib/sqreen/log.rb +3 -2
- data/lib/sqreen/log/loggable.rb +2 -1
- data/lib/sqreen/logger.rb +24 -0
- data/lib/sqreen/metrics.rb +1 -0
- data/lib/sqreen/metrics/base.rb +3 -0
- data/lib/sqreen/metrics/req_detailed.rb +41 -0
- data/lib/sqreen/metrics_store.rb +33 -12
- data/lib/sqreen/null_logger.rb +22 -0
- data/lib/sqreen/performance_notifications/binned_metrics.rb +8 -2
- data/lib/sqreen/remote_command.rb +4 -0
- data/lib/sqreen/rules.rb +12 -6
- data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
- data/lib/sqreen/rules/custom_error_cb.rb +3 -3
- data/lib/sqreen/rules/not_found_cb.rb +2 -0
- data/lib/sqreen/rules/rule_cb.rb +6 -2
- data/lib/sqreen/rules/waf_cb.rb +16 -13
- data/lib/sqreen/runner.rb +138 -16
- data/lib/sqreen/sensitive_data_redactor.rb +19 -31
- data/lib/sqreen/session.rb +53 -43
- data/lib/sqreen/signals/conversions.rb +288 -0
- data/lib/sqreen/signals/http_trace_redaction.rb +111 -0
- data/lib/sqreen/signals/signals_submission_strategy.rb +78 -0
- data/lib/sqreen/version.rb +1 -1
- data/lib/sqreen/weave/budget.rb +35 -0
- data/lib/sqreen/weave/legacy/instrumentation.rb +277 -135
- data/lib/sqreen/worker.rb +6 -2
- metadata +86 -10
- data/lib/sqreen/backport.rb +0 -9
- data/lib/sqreen/backport/clock_gettime.rb +0 -74
- data/lib/sqreen/backport/original_name.rb +0 -88
- data/lib/sqreen/encoding_sanitizer.rb +0 -27
@@ -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
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'sqreen/ecosystem/loggable'
|
2
|
+
|
3
|
+
module Sqreen
|
4
|
+
module Ecosystem
|
5
|
+
# The transaction storage is a mechanism for the modules to share
|
6
|
+
# request-scoped data with each other or to keep request-scoped objects
|
7
|
+
# themselves, although, for this last use case to be effective,
|
8
|
+
# propagation of request start/end events to the modules will likely be
|
9
|
+
# needed. A more generic notification of data availability to modules
|
10
|
+
# also may be needed in the future.
|
11
|
+
#
|
12
|
+
# This is not be used to share data with the Ecosystem client
|
13
|
+
#
|
14
|
+
# This class is not thread safe because it can call the lazy getter
|
15
|
+
# twice if [] is called concurrently.
|
16
|
+
class TransactionStorage
|
17
|
+
include Loggable
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# @return [Sqreen::Ecosystem::TransactionStorage]
|
21
|
+
def create_thread_local
|
22
|
+
Thread.current[:tx_storage] = new
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Sqreen::Ecosystem::TransactionStorage]
|
26
|
+
def fetch_thread_local
|
27
|
+
Thread.current[:tx_storage]
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy_thread_local
|
31
|
+
Thread.current[:tx_storage] = nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@values = {}
|
37
|
+
@values_lazy = {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def []=(key, value)
|
41
|
+
@values[key] = value
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_lazy(key, &block)
|
45
|
+
@values_lazy[key] = block
|
46
|
+
end
|
47
|
+
|
48
|
+
def [](key)
|
49
|
+
v = @values[key]
|
50
|
+
return v unless v.nil?
|
51
|
+
|
52
|
+
v = @values_lazy[key]
|
53
|
+
return nil if v.nil?
|
54
|
+
|
55
|
+
begin
|
56
|
+
@values[key] = v.call
|
57
|
+
rescue StandardError => e
|
58
|
+
logger.warn { "Error resolving key #{e} with lazy value: #{e.message}" }
|
59
|
+
raise
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|