fluyenta-ruby 0.1.14
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 +7 -0
- data/CHANGELOG.md +68 -0
- data/LICENSE +11 -0
- data/README.md +571 -0
- data/lib/brainzlab/beacon/client.rb +227 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +676 -0
- data/lib/brainzlab/context.rb +90 -0
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +159 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +223 -0
- data/lib/brainzlab/debug.rb +305 -0
- data/lib/brainzlab/dendrite/client.rb +250 -0
- data/lib/brainzlab/dendrite/provisioner.rb +44 -0
- data/lib/brainzlab/dendrite.rb +195 -0
- data/lib/brainzlab/development/logger.rb +150 -0
- data/lib/brainzlab/development/store.rb +121 -0
- data/lib/brainzlab/development.rb +72 -0
- data/lib/brainzlab/devtools/assets/devtools.css +1329 -0
- data/lib/brainzlab/devtools/assets/devtools.js +396 -0
- data/lib/brainzlab/devtools/assets/logo.svg +6 -0
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +511 -0
- data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
- data/lib/brainzlab/devtools/data/collector.rb +248 -0
- data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
- data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
- data/lib/brainzlab/devtools.rb +75 -0
- data/lib/brainzlab/errors.rb +490 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +68 -0
- data/lib/brainzlab/flux/provisioner.rb +124 -0
- data/lib/brainzlab/flux.rb +184 -0
- data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
- data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
- data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
- data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
- data/lib/brainzlab/instrumentation/action_mailer.rb +182 -0
- data/lib/brainzlab/instrumentation/action_view.rb +380 -0
- data/lib/brainzlab/instrumentation/active_job.rb +569 -0
- data/lib/brainzlab/instrumentation/active_record.rb +559 -0
- data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
- data/lib/brainzlab/instrumentation/active_support_cache.rb +730 -0
- data/lib/brainzlab/instrumentation/aws.rb +183 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/delayed_job.rb +234 -0
- data/lib/brainzlab/instrumentation/elasticsearch.rb +209 -0
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/faraday.rb +181 -0
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/grape.rb +293 -0
- data/lib/brainzlab/instrumentation/graphql.rb +252 -0
- data/lib/brainzlab/instrumentation/httparty.rb +193 -0
- data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
- data/lib/brainzlab/instrumentation/net_http.rb +114 -0
- data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
- data/lib/brainzlab/instrumentation/railties.rb +134 -0
- data/lib/brainzlab/instrumentation/redis.rb +324 -0
- data/lib/brainzlab/instrumentation/resque.rb +114 -0
- data/lib/brainzlab/instrumentation/sidekiq.rb +265 -0
- data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
- data/lib/brainzlab/instrumentation/stripe.rb +163 -0
- data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
- data/lib/brainzlab/instrumentation.rb +360 -0
- data/lib/brainzlab/nerve/client.rb +235 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/client.rb +203 -0
- data/lib/brainzlab/pulse/instrumentation.rb +401 -0
- data/lib/brainzlab/pulse/propagation.rb +241 -0
- data/lib/brainzlab/pulse/provisioner.rb +114 -0
- data/lib/brainzlab/pulse/tracer.rb +111 -0
- data/lib/brainzlab/pulse.rb +294 -0
- data/lib/brainzlab/rails/log_formatter.rb +807 -0
- data/lib/brainzlab/rails/log_subscriber.rb +334 -0
- data/lib/brainzlab/rails/railtie.rb +606 -0
- data/lib/brainzlab/recall/buffer.rb +66 -0
- data/lib/brainzlab/recall/client.rb +158 -0
- data/lib/brainzlab/recall/logger.rb +116 -0
- data/lib/brainzlab/recall/provisioner.rb +130 -0
- data/lib/brainzlab/recall.rb +175 -0
- data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
- data/lib/brainzlab/reflex/client.rb +150 -0
- data/lib/brainzlab/reflex/provisioner.rb +116 -0
- data/lib/brainzlab/reflex.rb +421 -0
- data/lib/brainzlab/sentinel/client.rb +236 -0
- data/lib/brainzlab/sentinel/provisioner.rb +44 -0
- data/lib/brainzlab/sentinel.rb +165 -0
- data/lib/brainzlab/signal/client.rb +60 -0
- data/lib/brainzlab/signal/provisioner.rb +115 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +308 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/testing/event_store.rb +377 -0
- data/lib/brainzlab/testing/helpers.rb +650 -0
- data/lib/brainzlab/testing/matchers.rb +391 -0
- data/lib/brainzlab/testing.rb +327 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +290 -0
- data/lib/brainzlab/utilities/health_check.rb +294 -0
- data/lib/brainzlab/utilities/log_formatter.rb +254 -0
- data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
- data/lib/brainzlab/utilities.rb +17 -0
- data/lib/brainzlab/vault/cache.rb +80 -0
- data/lib/brainzlab/vault/client.rb +216 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +262 -0
- data/lib/brainzlab/version.rb +5 -0
- data/lib/brainzlab/vision/client.rb +175 -0
- data/lib/brainzlab/vision/provisioner.rb +136 -0
- data/lib/brainzlab/vision.rb +155 -0
- data/lib/brainzlab-sdk.rb +3 -0
- data/lib/brainzlab.rb +306 -0
- data/lib/generators/brainzlab/install/install_generator.rb +63 -0
- data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
- metadata +251 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
class Railties
|
|
6
|
+
# Thresholds for slow initializers (in milliseconds)
|
|
7
|
+
SLOW_INITIALIZER_THRESHOLD = 100 # 100ms
|
|
8
|
+
VERY_SLOW_INITIALIZER_THRESHOLD = 500 # 500ms
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def install!
|
|
12
|
+
return unless defined?(::Rails)
|
|
13
|
+
return if @installed
|
|
14
|
+
|
|
15
|
+
install_load_config_initializer_subscriber!
|
|
16
|
+
|
|
17
|
+
@installed = true
|
|
18
|
+
BrainzLab.debug_log('Railties instrumentation installed')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def installed?
|
|
22
|
+
@installed == true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# ============================================
|
|
28
|
+
# load_config_initializer.railties
|
|
29
|
+
# Fired when each config initializer is loaded
|
|
30
|
+
# ============================================
|
|
31
|
+
def install_load_config_initializer_subscriber!
|
|
32
|
+
ActiveSupport::Notifications.subscribe('load_config_initializer.railties') do |*args|
|
|
33
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
34
|
+
handle_load_config_initializer(event)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def handle_load_config_initializer(event)
|
|
39
|
+
payload = event.payload
|
|
40
|
+
duration = event.duration.round(2)
|
|
41
|
+
|
|
42
|
+
initializer = payload[:initializer]
|
|
43
|
+
|
|
44
|
+
# Extract just the filename for cleaner display
|
|
45
|
+
initializer_name = extract_initializer_name(initializer)
|
|
46
|
+
|
|
47
|
+
# Determine level based on duration
|
|
48
|
+
level = case duration
|
|
49
|
+
when 0...SLOW_INITIALIZER_THRESHOLD then :info
|
|
50
|
+
when SLOW_INITIALIZER_THRESHOLD...VERY_SLOW_INITIALIZER_THRESHOLD then :warning
|
|
51
|
+
else :error
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Record breadcrumb (only for slow initializers to reduce noise during boot)
|
|
55
|
+
if duration >= 10 && BrainzLab.configuration.reflex_effectively_enabled?
|
|
56
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
57
|
+
"Initializer loaded: #{initializer_name} (#{duration}ms)",
|
|
58
|
+
category: 'rails.initializer',
|
|
59
|
+
level: level,
|
|
60
|
+
data: {
|
|
61
|
+
initializer: initializer_name,
|
|
62
|
+
path: initializer,
|
|
63
|
+
duration_ms: duration
|
|
64
|
+
}.compact
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Add Pulse span for tracking boot time
|
|
69
|
+
record_initializer_span(event, initializer_name, initializer, duration)
|
|
70
|
+
|
|
71
|
+
# Log slow initializers
|
|
72
|
+
log_slow_initializer(initializer_name, initializer, duration) if duration >= SLOW_INITIALIZER_THRESHOLD
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
BrainzLab.debug_log("Railties load_config_initializer instrumentation failed: #{e.message}")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# ============================================
|
|
78
|
+
# Span Recording
|
|
79
|
+
# ============================================
|
|
80
|
+
def record_initializer_span(event, name, path, duration)
|
|
81
|
+
return unless BrainzLab.configuration.pulse_effectively_enabled?
|
|
82
|
+
|
|
83
|
+
tracer = BrainzLab::Pulse.tracer
|
|
84
|
+
return unless tracer.current_trace
|
|
85
|
+
|
|
86
|
+
span_data = {
|
|
87
|
+
span_id: SecureRandom.uuid,
|
|
88
|
+
name: "rails.initializer.#{name}",
|
|
89
|
+
kind: 'internal',
|
|
90
|
+
started_at: event.time,
|
|
91
|
+
ended_at: event.end,
|
|
92
|
+
duration_ms: duration,
|
|
93
|
+
error: false,
|
|
94
|
+
data: {
|
|
95
|
+
'rails.initializer.name' => name,
|
|
96
|
+
'rails.initializer.path' => path
|
|
97
|
+
}.compact
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
tracer.current_spans << span_data
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# ============================================
|
|
104
|
+
# Logging
|
|
105
|
+
# ============================================
|
|
106
|
+
def log_slow_initializer(name, path, duration)
|
|
107
|
+
return unless BrainzLab.configuration.recall_effectively_enabled?
|
|
108
|
+
|
|
109
|
+
level = duration >= VERY_SLOW_INITIALIZER_THRESHOLD ? :error : :warn
|
|
110
|
+
|
|
111
|
+
BrainzLab::Recall.send(
|
|
112
|
+
level,
|
|
113
|
+
"Slow initializer: #{name} (#{duration}ms)",
|
|
114
|
+
initializer: name,
|
|
115
|
+
path: path,
|
|
116
|
+
duration_ms: duration,
|
|
117
|
+
threshold_exceeded: duration >= VERY_SLOW_INITIALIZER_THRESHOLD ? 'critical' : 'warning'
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# ============================================
|
|
122
|
+
# Helper Methods
|
|
123
|
+
# ============================================
|
|
124
|
+
def extract_initializer_name(path)
|
|
125
|
+
return 'unknown' unless path
|
|
126
|
+
|
|
127
|
+
# Extract filename without extension from path
|
|
128
|
+
# e.g., "/app/config/initializers/devise.rb" -> "devise"
|
|
129
|
+
File.basename(path.to_s, '.*')
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module RedisInstrumentation
|
|
6
|
+
@installed = false
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def install!
|
|
10
|
+
return unless defined?(::Redis)
|
|
11
|
+
return if @installed
|
|
12
|
+
|
|
13
|
+
# Redis 5+ uses middleware, older versions need patching
|
|
14
|
+
if redis_5_or_newer?
|
|
15
|
+
install_middleware!
|
|
16
|
+
else
|
|
17
|
+
install_patch!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@installed = true
|
|
21
|
+
BrainzLab.debug_log('Redis instrumentation installed')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def installed?
|
|
25
|
+
@installed
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def reset!
|
|
29
|
+
@installed = false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def redis_5_or_newer?
|
|
35
|
+
defined?(::Redis::VERSION) && Gem::Version.new(::Redis::VERSION) >= Gem::Version.new('5.0')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def install_middleware!
|
|
39
|
+
# Redis 5+ uses RedisClient with middleware support
|
|
40
|
+
return unless defined?(::RedisClient)
|
|
41
|
+
|
|
42
|
+
::RedisClient.register(Middleware)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def install_patch!
|
|
46
|
+
# Redis < 5 - patch the client
|
|
47
|
+
::Redis::Client.prepend(LegacyPatch)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Middleware for Redis 5+ (RedisClient)
|
|
52
|
+
module Middleware
|
|
53
|
+
def call(command, redis_config)
|
|
54
|
+
return super unless should_track?
|
|
55
|
+
|
|
56
|
+
track_command(command) { super }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def call_pipelined(commands, redis_config)
|
|
60
|
+
return super unless should_track?
|
|
61
|
+
|
|
62
|
+
track_pipeline(commands) { super }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def should_track?
|
|
68
|
+
BrainzLab.configuration.instrument_redis
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def should_skip_command?(command)
|
|
72
|
+
cmd_name = command.first.to_s.downcase
|
|
73
|
+
ignore = BrainzLab.configuration.redis_ignore_commands || []
|
|
74
|
+
ignore.map(&:downcase).include?(cmd_name)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def track_command(command)
|
|
78
|
+
return yield if should_skip_command?(command)
|
|
79
|
+
|
|
80
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
81
|
+
|
|
82
|
+
begin
|
|
83
|
+
result = yield
|
|
84
|
+
record_command(command, started_at)
|
|
85
|
+
result
|
|
86
|
+
rescue StandardError => e
|
|
87
|
+
error_info = e.class.name
|
|
88
|
+
record_command(command, started_at, error_info)
|
|
89
|
+
raise
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def track_pipeline(commands)
|
|
94
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
95
|
+
|
|
96
|
+
begin
|
|
97
|
+
result = yield
|
|
98
|
+
record_pipeline(commands, started_at)
|
|
99
|
+
result
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
error_info = e.class.name
|
|
102
|
+
record_pipeline(commands, started_at, error_info)
|
|
103
|
+
raise
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def record_command(command, started_at, error = nil)
|
|
108
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
|
|
109
|
+
cmd_name = command.first.to_s.upcase
|
|
110
|
+
key = extract_key(command)
|
|
111
|
+
level = error ? :error : :info
|
|
112
|
+
|
|
113
|
+
# Add breadcrumb for Reflex
|
|
114
|
+
if BrainzLab.configuration.reflex_enabled
|
|
115
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
116
|
+
"Redis #{cmd_name}",
|
|
117
|
+
category: 'redis',
|
|
118
|
+
level: level,
|
|
119
|
+
data: {
|
|
120
|
+
command: cmd_name,
|
|
121
|
+
key: truncate_key(key),
|
|
122
|
+
duration_ms: duration_ms,
|
|
123
|
+
error: error
|
|
124
|
+
}.compact
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Record span for Pulse APM
|
|
129
|
+
record_pulse_span(cmd_name, key, duration_ms, error)
|
|
130
|
+
rescue StandardError => e
|
|
131
|
+
BrainzLab.debug_log("Redis instrumentation error: #{e.message}")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def record_pipeline(commands, started_at, error = nil)
|
|
135
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
|
|
136
|
+
cmd_names = commands.map { |c| c.first.to_s.upcase }.uniq.join(', ')
|
|
137
|
+
level = error ? :error : :info
|
|
138
|
+
|
|
139
|
+
# Add breadcrumb for Reflex
|
|
140
|
+
if BrainzLab.configuration.reflex_enabled
|
|
141
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
142
|
+
"Redis PIPELINE (#{commands.size} commands)",
|
|
143
|
+
category: 'redis',
|
|
144
|
+
level: level,
|
|
145
|
+
data: {
|
|
146
|
+
commands: cmd_names,
|
|
147
|
+
count: commands.size,
|
|
148
|
+
duration_ms: duration_ms,
|
|
149
|
+
error: error
|
|
150
|
+
}.compact
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Record span for Pulse APM
|
|
155
|
+
record_pulse_span('PIPELINE', nil, duration_ms, error, commands.size)
|
|
156
|
+
rescue StandardError => e
|
|
157
|
+
BrainzLab.debug_log("Redis instrumentation error: #{e.message}")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def record_pulse_span(command, key, duration_ms, error, pipeline_count = nil)
|
|
161
|
+
spans = Thread.current[:brainzlab_pulse_spans]
|
|
162
|
+
return unless spans
|
|
163
|
+
|
|
164
|
+
span = {
|
|
165
|
+
span_id: SecureRandom.uuid,
|
|
166
|
+
name: "Redis #{command}",
|
|
167
|
+
kind: 'redis',
|
|
168
|
+
started_at: Time.now.utc - (duration_ms / 1000.0),
|
|
169
|
+
ended_at: Time.now.utc,
|
|
170
|
+
duration_ms: duration_ms,
|
|
171
|
+
data: {
|
|
172
|
+
command: command,
|
|
173
|
+
key: truncate_key(key),
|
|
174
|
+
pipeline_count: pipeline_count
|
|
175
|
+
}.compact
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if error
|
|
179
|
+
span[:error] = true
|
|
180
|
+
span[:error_class] = error
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
spans << span
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def extract_key(command)
|
|
187
|
+
return nil if command.size < 2
|
|
188
|
+
|
|
189
|
+
# Most Redis commands have the key as the second argument
|
|
190
|
+
key = command[1]
|
|
191
|
+
key.is_a?(String) ? key : key.to_s
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def truncate_key(key)
|
|
195
|
+
return nil unless key
|
|
196
|
+
|
|
197
|
+
key.to_s[0, 100]
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Patch for Redis < 5
|
|
202
|
+
module LegacyPatch
|
|
203
|
+
def call(command)
|
|
204
|
+
return super unless should_track?
|
|
205
|
+
return super if should_skip_command?(command)
|
|
206
|
+
|
|
207
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
208
|
+
|
|
209
|
+
begin
|
|
210
|
+
result = super
|
|
211
|
+
record_command(command, started_at)
|
|
212
|
+
result
|
|
213
|
+
rescue StandardError => e
|
|
214
|
+
error_info = e.class.name
|
|
215
|
+
record_command(command, started_at, error_info)
|
|
216
|
+
raise
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def call_pipeline(pipeline)
|
|
221
|
+
return super unless should_track?
|
|
222
|
+
|
|
223
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
224
|
+
commands = pipeline.commands
|
|
225
|
+
|
|
226
|
+
begin
|
|
227
|
+
result = super
|
|
228
|
+
record_pipeline(commands, started_at)
|
|
229
|
+
result
|
|
230
|
+
rescue StandardError => e
|
|
231
|
+
error_info = e.class.name
|
|
232
|
+
record_pipeline(commands, started_at, error_info)
|
|
233
|
+
raise
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
private
|
|
238
|
+
|
|
239
|
+
def should_track?
|
|
240
|
+
BrainzLab.configuration.instrument_redis
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def should_skip_command?(command)
|
|
244
|
+
cmd_name = command.first.to_s.downcase
|
|
245
|
+
ignore = BrainzLab.configuration.redis_ignore_commands || []
|
|
246
|
+
ignore.map(&:downcase).include?(cmd_name)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def record_command(command, started_at, error = nil)
|
|
250
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
|
|
251
|
+
cmd_name = command.first.to_s.upcase
|
|
252
|
+
key = command[1]&.to_s
|
|
253
|
+
level = error ? :error : :info
|
|
254
|
+
|
|
255
|
+
if BrainzLab.configuration.reflex_enabled
|
|
256
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
257
|
+
"Redis #{cmd_name}",
|
|
258
|
+
category: 'redis',
|
|
259
|
+
level: level,
|
|
260
|
+
data: {
|
|
261
|
+
command: cmd_name,
|
|
262
|
+
key: key&.slice(0, 100),
|
|
263
|
+
duration_ms: duration_ms,
|
|
264
|
+
error: error
|
|
265
|
+
}.compact
|
|
266
|
+
)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
record_pulse_span(cmd_name, key, duration_ms, error)
|
|
270
|
+
rescue StandardError => e
|
|
271
|
+
BrainzLab.debug_log("Redis instrumentation error: #{e.message}")
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def record_pipeline(commands, started_at, error = nil)
|
|
275
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
|
|
276
|
+
level = error ? :error : :info
|
|
277
|
+
|
|
278
|
+
if BrainzLab.configuration.reflex_enabled
|
|
279
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
280
|
+
"Redis PIPELINE (#{commands.size} commands)",
|
|
281
|
+
category: 'redis',
|
|
282
|
+
level: level,
|
|
283
|
+
data: {
|
|
284
|
+
count: commands.size,
|
|
285
|
+
duration_ms: duration_ms,
|
|
286
|
+
error: error
|
|
287
|
+
}.compact
|
|
288
|
+
)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
record_pulse_span('PIPELINE', nil, duration_ms, error, commands.size)
|
|
292
|
+
rescue StandardError => e
|
|
293
|
+
BrainzLab.debug_log("Redis instrumentation error: #{e.message}")
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def record_pulse_span(command, key, duration_ms, error, pipeline_count = nil)
|
|
297
|
+
spans = Thread.current[:brainzlab_pulse_spans]
|
|
298
|
+
return unless spans
|
|
299
|
+
|
|
300
|
+
span = {
|
|
301
|
+
span_id: SecureRandom.uuid,
|
|
302
|
+
name: "Redis #{command}",
|
|
303
|
+
kind: 'redis',
|
|
304
|
+
started_at: Time.now.utc - (duration_ms / 1000.0),
|
|
305
|
+
ended_at: Time.now.utc,
|
|
306
|
+
duration_ms: duration_ms,
|
|
307
|
+
data: {
|
|
308
|
+
command: command,
|
|
309
|
+
key: key&.slice(0, 100),
|
|
310
|
+
pipeline_count: pipeline_count
|
|
311
|
+
}.compact
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if error
|
|
315
|
+
span[:error] = true
|
|
316
|
+
span[:error_class] = error
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
spans << span
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module ResqueInstrumentation
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
return unless defined?(::Resque)
|
|
9
|
+
|
|
10
|
+
install_hooks!
|
|
11
|
+
install_failure_backend!
|
|
12
|
+
|
|
13
|
+
BrainzLab.debug_log('[Instrumentation] Resque instrumentation installed')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def install_hooks!
|
|
19
|
+
::Resque.before_fork do |_job|
|
|
20
|
+
# Clear any stale connections before forking
|
|
21
|
+
BrainzLab::Recall.reset! if defined?(BrainzLab::Recall)
|
|
22
|
+
BrainzLab::Pulse.reset! if defined?(BrainzLab::Pulse)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
::Resque.after_fork do |job|
|
|
26
|
+
# Re-establish connections after forking
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def install_failure_backend!
|
|
31
|
+
# Create a custom failure backend
|
|
32
|
+
failure_backend = Class.new do
|
|
33
|
+
def initialize(exception, worker, queue, payload)
|
|
34
|
+
@exception = exception
|
|
35
|
+
@worker = worker
|
|
36
|
+
@queue = queue
|
|
37
|
+
@payload = payload
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def save
|
|
41
|
+
job_class = @payload['class'] || 'Unknown'
|
|
42
|
+
|
|
43
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
44
|
+
BrainzLab::Reflex.capture(@exception,
|
|
45
|
+
tags: { job_class: job_class, queue: @queue, source: 'resque' },
|
|
46
|
+
extra: {
|
|
47
|
+
worker: @worker.to_s,
|
|
48
|
+
args: @payload['args']
|
|
49
|
+
})
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
53
|
+
|
|
54
|
+
BrainzLab::Flux.increment('resque.job.failed', tags: { job_class: job_class, queue: @queue })
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Add our failure backend to the chain
|
|
59
|
+
return unless defined?(::Resque::Failure)
|
|
60
|
+
|
|
61
|
+
::Resque::Failure.backend = ::Resque::Failure::Multiple.new(
|
|
62
|
+
::Resque::Failure.backend,
|
|
63
|
+
failure_backend
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Middleware module to include in Resque jobs
|
|
69
|
+
module Middleware
|
|
70
|
+
def self.included(base)
|
|
71
|
+
base.extend(ClassMethods)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
module ClassMethods
|
|
75
|
+
def around_perform_brainzlab(*_args)
|
|
76
|
+
job_class = name
|
|
77
|
+
queue = Resque.queue_from_class(self) || 'default'
|
|
78
|
+
started_at = Time.now
|
|
79
|
+
|
|
80
|
+
BrainzLab::Context.current.set_context(
|
|
81
|
+
job_class: job_class,
|
|
82
|
+
queue: queue
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
begin
|
|
86
|
+
yield
|
|
87
|
+
ensure
|
|
88
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
89
|
+
|
|
90
|
+
if BrainzLab.configuration.pulse_effectively_enabled?
|
|
91
|
+
BrainzLab::Pulse.record_trace(
|
|
92
|
+
"job.#{job_class}",
|
|
93
|
+
kind: 'job',
|
|
94
|
+
started_at: started_at,
|
|
95
|
+
ended_at: Time.now,
|
|
96
|
+
job_class: job_class,
|
|
97
|
+
queue: queue
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
102
|
+
tags = { job_class: job_class, queue: queue }
|
|
103
|
+
BrainzLab::Flux.distribution('resque.job.duration_ms', duration_ms, tags: tags)
|
|
104
|
+
BrainzLab::Flux.increment('resque.job.processed', tags: tags)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
BrainzLab.clear_context!
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|