brainzlab 0.1.1 → 0.1.3

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +6 -21
  3. data/README.md +24 -2
  4. data/lib/brainzlab/beacon/client.rb +207 -0
  5. data/lib/brainzlab/beacon/provisioner.rb +44 -0
  6. data/lib/brainzlab/beacon.rb +215 -0
  7. data/lib/brainzlab/configuration.rb +372 -32
  8. data/lib/brainzlab/context.rb +2 -3
  9. data/lib/brainzlab/cortex/cache.rb +59 -0
  10. data/lib/brainzlab/cortex/client.rb +139 -0
  11. data/lib/brainzlab/cortex/provisioner.rb +49 -0
  12. data/lib/brainzlab/cortex.rb +223 -0
  13. data/lib/brainzlab/dendrite/client.rb +230 -0
  14. data/lib/brainzlab/dendrite/provisioner.rb +44 -0
  15. data/lib/brainzlab/dendrite.rb +195 -0
  16. data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
  17. data/lib/brainzlab/devtools/assets/devtools.js +322 -0
  18. data/lib/brainzlab/devtools/assets/logo.svg +6 -0
  19. data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
  20. data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
  21. data/lib/brainzlab/devtools/data/collector.rb +248 -0
  22. data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
  23. data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
  24. data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
  25. data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
  26. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
  27. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
  28. data/lib/brainzlab/devtools.rb +75 -0
  29. data/lib/brainzlab/flux/buffer.rb +96 -0
  30. data/lib/brainzlab/flux/client.rb +68 -0
  31. data/lib/brainzlab/flux/provisioner.rb +57 -0
  32. data/lib/brainzlab/flux.rb +174 -0
  33. data/lib/brainzlab/instrumentation/action_mailer.rb +14 -13
  34. data/lib/brainzlab/instrumentation/active_record.rb +28 -13
  35. data/lib/brainzlab/instrumentation/aws.rb +183 -0
  36. data/lib/brainzlab/instrumentation/dalli.rb +108 -0
  37. data/lib/brainzlab/instrumentation/delayed_job.rb +27 -29
  38. data/lib/brainzlab/instrumentation/elasticsearch.rb +23 -24
  39. data/lib/brainzlab/instrumentation/excon.rb +152 -0
  40. data/lib/brainzlab/instrumentation/faraday.rb +3 -4
  41. data/lib/brainzlab/instrumentation/good_job.rb +102 -0
  42. data/lib/brainzlab/instrumentation/grape.rb +24 -24
  43. data/lib/brainzlab/instrumentation/graphql.rb +24 -23
  44. data/lib/brainzlab/instrumentation/httparty.rb +13 -14
  45. data/lib/brainzlab/instrumentation/mongodb.rb +7 -7
  46. data/lib/brainzlab/instrumentation/net_http.rb +6 -6
  47. data/lib/brainzlab/instrumentation/redis.rb +14 -21
  48. data/lib/brainzlab/instrumentation/resque.rb +114 -0
  49. data/lib/brainzlab/instrumentation/sidekiq.rb +29 -28
  50. data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
  51. data/lib/brainzlab/instrumentation/stripe.rb +163 -0
  52. data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
  53. data/lib/brainzlab/instrumentation.rb +84 -12
  54. data/lib/brainzlab/nerve/client.rb +215 -0
  55. data/lib/brainzlab/nerve/provisioner.rb +44 -0
  56. data/lib/brainzlab/nerve.rb +219 -0
  57. data/lib/brainzlab/pulse/client.rb +15 -11
  58. data/lib/brainzlab/pulse/instrumentation.rb +90 -53
  59. data/lib/brainzlab/pulse/propagation.rb +29 -29
  60. data/lib/brainzlab/pulse/provisioner.rb +12 -12
  61. data/lib/brainzlab/pulse/tracer.rb +4 -4
  62. data/lib/brainzlab/pulse.rb +14 -14
  63. data/lib/brainzlab/rails/log_formatter.rb +127 -121
  64. data/lib/brainzlab/rails/log_subscriber.rb +70 -77
  65. data/lib/brainzlab/rails/railtie.rb +96 -86
  66. data/lib/brainzlab/recall/buffer.rb +1 -1
  67. data/lib/brainzlab/recall/client.rb +14 -10
  68. data/lib/brainzlab/recall/logger.rb +16 -18
  69. data/lib/brainzlab/recall/provisioner.rb +29 -12
  70. data/lib/brainzlab/recall.rb +14 -11
  71. data/lib/brainzlab/reflex/breadcrumbs.rb +2 -2
  72. data/lib/brainzlab/reflex/client.rb +14 -10
  73. data/lib/brainzlab/reflex/provisioner.rb +12 -12
  74. data/lib/brainzlab/reflex.rb +31 -31
  75. data/lib/brainzlab/sentinel/client.rb +216 -0
  76. data/lib/brainzlab/sentinel/provisioner.rb +44 -0
  77. data/lib/brainzlab/sentinel.rb +165 -0
  78. data/lib/brainzlab/signal/client.rb +60 -0
  79. data/lib/brainzlab/signal/provisioner.rb +55 -0
  80. data/lib/brainzlab/signal.rb +136 -0
  81. data/lib/brainzlab/synapse/client.rb +288 -0
  82. data/lib/brainzlab/synapse/provisioner.rb +44 -0
  83. data/lib/brainzlab/synapse.rb +270 -0
  84. data/lib/brainzlab/utilities/circuit_breaker.rb +261 -0
  85. data/lib/brainzlab/utilities/health_check.rb +294 -0
  86. data/lib/brainzlab/utilities/log_formatter.rb +254 -0
  87. data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
  88. data/lib/brainzlab/utilities.rb +17 -0
  89. data/lib/brainzlab/vault/cache.rb +80 -0
  90. data/lib/brainzlab/vault/client.rb +196 -0
  91. data/lib/brainzlab/vault/provisioner.rb +49 -0
  92. data/lib/brainzlab/vault.rb +262 -0
  93. data/lib/brainzlab/version.rb +1 -1
  94. data/lib/brainzlab/vision/client.rb +128 -0
  95. data/lib/brainzlab/vision/provisioner.rb +136 -0
  96. data/lib/brainzlab/vision.rb +155 -0
  97. data/lib/brainzlab-sdk.rb +1 -1
  98. data/lib/brainzlab.rb +112 -13
  99. data/lib/generators/brainzlab/install/install_generator.rb +29 -27
  100. metadata +60 -1
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/log_subscriber"
3
+ require 'active_support/log_subscriber'
4
4
 
5
5
  module BrainzLab
6
6
  module Rails
@@ -21,12 +21,11 @@ module BrainzLab
21
21
  params = payload[:params]&.except(*INTERNAL_PARAMS) || {}
22
22
 
23
23
  formatter.start_request(request_id,
24
- method: payload[:method],
25
- path: payload[:path],
26
- params: filter_params(params),
27
- controller: payload[:controller],
28
- action: payload[:action]
29
- )
24
+ method: payload[:method],
25
+ path: payload[:path],
26
+ params: filter_params(params),
27
+ controller: payload[:controller],
28
+ action: payload[:action])
30
29
  end
31
30
 
32
31
  def process_action(event)
@@ -38,18 +37,15 @@ module BrainzLab
38
37
  payload = event.payload
39
38
 
40
39
  formatter.process_action(request_id,
41
- controller: payload[:controller],
42
- action: payload[:action],
43
- status: payload[:status],
44
- duration: event.duration,
45
- view_runtime: payload[:view_runtime],
46
- db_runtime: payload[:db_runtime]
47
- )
40
+ controller: payload[:controller],
41
+ action: payload[:action],
42
+ status: payload[:status],
43
+ duration: event.duration,
44
+ view_runtime: payload[:view_runtime],
45
+ db_runtime: payload[:db_runtime])
48
46
 
49
47
  # Handle exception if present
50
- if payload[:exception_object]
51
- formatter.error(request_id, payload[:exception_object])
52
- end
48
+ formatter.error(request_id, payload[:exception_object]) if payload[:exception_object]
53
49
 
54
50
  # Output the formatted log
55
51
  output = formatter.end_request(request_id)
@@ -81,11 +77,11 @@ module BrainzLab
81
77
  case obj
82
78
  when Hash
83
79
  obj.each_with_object({}) do |(k, v), h|
84
- if filter_keys.include?(k.to_s.downcase)
85
- h[k] = "[FILTERED]"
86
- else
87
- h[k] = deep_filter(v, filter_keys)
88
- end
80
+ h[k] = if filter_keys.include?(k.to_s.downcase)
81
+ '[FILTERED]'
82
+ else
83
+ deep_filter(v, filter_keys)
84
+ end
89
85
  end
90
86
  when Array
91
87
  obj.map { |v| deep_filter(v, filter_keys) }
@@ -121,13 +117,12 @@ module BrainzLab
121
117
  sql_pattern = normalize_sql(payload[:sql])
122
118
 
123
119
  LogSubscriber.formatter.sql_query(request_id,
124
- name: payload[:name],
125
- duration: event.duration,
126
- sql: payload[:sql],
127
- sql_pattern: sql_pattern,
128
- cached: payload[:cached] || payload[:name] == "CACHE",
129
- source: source
130
- )
120
+ name: payload[:name],
121
+ duration: event.duration,
122
+ sql: payload[:sql],
123
+ sql_pattern: sql_pattern,
124
+ cached: payload[:cached] || payload[:name] == 'CACHE',
125
+ source: source)
131
126
  end
132
127
 
133
128
  private
@@ -135,11 +130,11 @@ module BrainzLab
135
130
  def extract_source_location(backtrace)
136
131
  # Find the first line that's in app/ directory
137
132
  backtrace.each do |line|
138
- if line.include?("/app/") && !line.include?("/brainzlab")
139
- # Extract just the relevant part: app/models/user.rb:42
140
- match = line.match(%r{(app/[^:]+:\d+)})
141
- return match[1] if match
142
- end
133
+ next unless line.include?('/app/') && !line.include?('/brainzlab')
134
+
135
+ # Extract just the relevant part: app/models/user.rb:42
136
+ match = line.match(%r{(app/[^:]+:\d+)})
137
+ return match[1] if match
143
138
  end
144
139
  nil
145
140
  end
@@ -148,12 +143,11 @@ module BrainzLab
148
143
  return nil unless sql
149
144
 
150
145
  sql
151
- .gsub(/\b\d+\b/, "?") # Replace numbers
152
- .gsub(/'[^']*'/, "?") # Replace strings
153
- .gsub(/"[^"]*"/, "?") # Replace double-quoted strings
154
- .gsub(/\$\d+/, "?") # Replace positional params
155
- .gsub(/\/\*.*?\*\//, "") # Remove comments
156
- .gsub(/\s+/, " ") # Normalize whitespace
146
+ .gsub(/\b\d+\b/, '?') # Replace numbers
147
+ .gsub(/'[^']*'/, '?') # Replace single-quoted strings
148
+ .gsub(/\$\d+/, '?') # Replace positional params
149
+ .gsub(%r{/\*.*?\*/}, '') # Remove comments
150
+ .gsub(/\s+/, ' ') # Normalize whitespace
157
151
  .strip
158
152
  end
159
153
  end
@@ -183,18 +177,18 @@ module BrainzLab
183
177
  return broadcasting unless broadcasting
184
178
 
185
179
  # Extract channel name from gid format: logs:Z2lkOi8vcmVjYWxsL1Byb2plY3QvNDhi...
186
- if broadcasting.start_with?("logs:")
187
- "LogsChannel"
188
- elsif broadcasting.include?(":")
180
+ if broadcasting.start_with?('logs:')
181
+ 'LogsChannel'
182
+ elsif broadcasting.include?(':')
189
183
  # Generic channel:id format
190
- broadcasting.split(":").first.capitalize + "Channel"
184
+ "#{broadcasting.split(':').first.capitalize}Channel"
191
185
  else
192
186
  broadcasting
193
187
  end
194
188
  end
195
189
 
196
190
  def format_message(message)
197
- return "{}" unless message
191
+ return '{}' unless message
198
192
 
199
193
  case message
200
194
  when Hash
@@ -215,58 +209,60 @@ module BrainzLab
215
209
  parts = []
216
210
 
217
211
  # Show key fields for log entries
218
- if hash["level"] || hash[:level]
219
- level = hash["level"] || hash[:level]
212
+ if hash['level'] || hash[:level]
213
+ level = hash['level'] || hash[:level]
220
214
  parts << colorize_level(level)
221
215
  end
222
216
 
223
- if hash["message"] || hash[:message]
224
- msg = hash["message"] || hash[:message]
217
+ if hash['message'] || hash[:message]
218
+ msg = hash['message'] || hash[:message]
225
219
  parts << truncate(msg.to_s, 50)
226
220
  end
227
221
 
228
- if hash["id"] || hash[:id]
229
- id = hash["id"] || hash[:id]
222
+ if hash['id'] || hash[:id]
223
+ id = hash['id'] || hash[:id]
230
224
  parts << colorize(id.to_s[0..7], :gray)
231
225
  end
232
226
 
233
- parts.any? ? parts.join(" ") : hash.keys.first(3).join(", ")
227
+ parts.any? ? parts.join(' ') : hash.keys.first(3).join(', ')
234
228
  end
235
229
 
236
230
  def build_output(channel, message, duration)
237
- time = Time.current.strftime("%H:%M:%S")
238
- duration_str = duration ? "#{duration.round(1)}ms" : ""
231
+ time = Time.current.strftime('%H:%M:%S')
232
+ duration_str = duration ? "#{duration.round(1)}ms" : ''
239
233
 
240
234
  parts = [
241
235
  colorize(time, :gray),
242
- colorize("", :magenta),
236
+ colorize('', :magenta),
243
237
  colorize(channel, :magenta),
244
- colorize("", :gray),
238
+ colorize('', :gray),
245
239
  message,
246
240
  colorize(duration_str, :gray)
247
241
  ]
248
242
 
249
- " " + parts.join(" ") + "\n"
243
+ " #{parts.join(' ')}\n"
250
244
  end
251
245
 
252
246
  def truncate(text, length)
253
247
  return text if text.nil? || text.length <= length
248
+
254
249
  "#{text[0..(length - 4)]}..."
255
250
  end
256
251
 
257
252
  def colorize(text, color)
258
253
  return text unless $stdout.tty?
259
254
  return text unless COLORS[color]
255
+
260
256
  "#{COLORS[color]}#{text}#{COLORS[:reset]}"
261
257
  end
262
258
 
263
259
  def colorize_level(level)
264
260
  color = case level.to_s.downcase
265
- when "debug" then :gray
266
- when "info" then :green
267
- when "warn", "warning" then :yellow
268
- when "error" then :red
269
- when "fatal" then :red
261
+ when 'debug' then :gray
262
+ when 'info' then :green
263
+ when 'warn', 'warning' then :yellow
264
+ when 'error' then :red
265
+ when 'fatal' then :red
270
266
  else :white
271
267
  end
272
268
  colorize(level.to_s.upcase.ljust(5), color)
@@ -285,10 +281,9 @@ module BrainzLab
285
281
  template = template_name(payload[:identifier])
286
282
 
287
283
  LogSubscriber.formatter.render_template(request_id,
288
- template: template,
289
- duration: event.duration,
290
- layout: payload[:layout]
291
- )
284
+ template: template,
285
+ duration: event.duration,
286
+ layout: payload[:layout])
292
287
  end
293
288
 
294
289
  def render_partial(event)
@@ -301,10 +296,9 @@ module BrainzLab
301
296
  template = template_name(payload[:identifier])
302
297
 
303
298
  LogSubscriber.formatter.render_partial(request_id,
304
- template: template,
305
- duration: event.duration,
306
- count: payload[:count]
307
- )
299
+ template: template,
300
+ duration: event.duration,
301
+ count: payload[:count])
308
302
  end
309
303
 
310
304
  def render_layout(event)
@@ -317,9 +311,8 @@ module BrainzLab
317
311
  layout = template_name(payload[:identifier])
318
312
 
319
313
  LogSubscriber.formatter.render_layout(request_id,
320
- layout: layout,
321
- duration: event.duration
322
- )
314
+ layout: layout,
315
+ duration: event.duration)
323
316
  end
324
317
 
325
318
  private
@@ -328,10 +321,10 @@ module BrainzLab
328
321
  return nil unless identifier
329
322
 
330
323
  # Extract relative path from full identifier
331
- if identifier.include?("/app/views/")
332
- identifier.split("/app/views/").last
333
- elsif identifier.include?("/views/")
334
- identifier.split("/views/").last
324
+ if identifier.include?('/app/views/')
325
+ identifier.split('/app/views/').last
326
+ elsif identifier.include?('/views/')
327
+ identifier.split('/views/').last
335
328
  else
336
329
  File.basename(identifier)
337
330
  end
@@ -1,13 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'English'
3
4
  module BrainzLab
4
5
  module Rails
5
6
  class Railtie < ::Rails::Railtie
6
7
  generators do
7
- require "generators/brainzlab/install/install_generator"
8
+ require 'generators/brainzlab/install/install_generator'
8
9
  end
9
10
 
10
- initializer "brainzlab.configure_rails_initialization" do |app|
11
+ # Load Vault secrets early, before configuration
12
+ # This allows secrets to be used in config files
13
+ initializer 'brainzlab.load_vault_secrets', before: :load_environment_config do
14
+ if BrainzLab.configuration.vault_enabled && BrainzLab.configuration.vault_auto_load
15
+ BrainzLab.debug_log('[Vault] Auto-loading secrets into ENV...')
16
+ BrainzLab::Vault.load!(
17
+ provider_keys: BrainzLab.configuration.vault_load_provider_keys
18
+ )
19
+ end
20
+ end
21
+
22
+ initializer 'brainzlab.configure_rails_initialization' do |app|
11
23
  # Set defaults from Rails
12
24
  BrainzLab.configure do |config|
13
25
  config.environment ||= ::Rails.env.to_s
@@ -20,11 +32,31 @@ module BrainzLab
20
32
 
21
33
  # Add request context middleware (runs early)
22
34
  app.middleware.insert_after ActionDispatch::RequestId, BrainzLab::Rails::Middleware
35
+
36
+ # Add DevTools middlewares if enabled
37
+ if BrainzLab.configuration.devtools_enabled
38
+ require_relative '../devtools'
39
+
40
+ # Asset server (handles /__brainzlab__/* requests)
41
+ app.middleware.insert_before ActionDispatch::Static, BrainzLab::DevTools::Middleware::AssetServer
42
+
43
+ # Database handler (handles /_brainzlab/devtools/database POST requests)
44
+ # Allows running migrations from the error page
45
+ app.middleware.insert_before ActionDispatch::Static, BrainzLab::DevTools::Middleware::DatabaseHandler
46
+
47
+ # Error page (catches exceptions and renders branded error page)
48
+ # Insert BEFORE DebugExceptions so we can intercept the HTML error page
49
+ # that DebugExceptions renders and replace it with our own
50
+ app.middleware.insert_before ActionDispatch::DebugExceptions, BrainzLab::DevTools::Middleware::ErrorPage if defined?(ActionDispatch::DebugExceptions)
51
+
52
+ # Debug panel (injects panel into HTML responses)
53
+ app.middleware.use BrainzLab::DevTools::Middleware::DebugPanel
54
+ end
23
55
  end
24
56
 
25
57
  config.after_initialize do
26
58
  # Set up custom log formatter
27
- setup_log_formatter if BrainzLab.configuration.log_formatter_enabled
59
+ BrainzLab::Rails::Railtie.setup_log_formatter if BrainzLab.configuration.log_formatter_enabled
28
60
 
29
61
  # Install instrumentation (HTTP tracking, etc.)
30
62
  BrainzLab::Instrumentation.install!
@@ -38,14 +70,10 @@ module BrainzLab
38
70
  end
39
71
 
40
72
  # Hook into ActiveJob
41
- if defined?(ActiveJob::Base)
42
- ActiveJob::Base.include(BrainzLab::Rails::ActiveJobExtension)
43
- end
73
+ ActiveJob::Base.include(BrainzLab::Rails::ActiveJobExtension) if defined?(ActiveJob::Base)
44
74
 
45
75
  # Hook into ActionController for rescue_from fallback
46
- if defined?(ActionController::Base)
47
- ActionController::Base.include(BrainzLab::Rails::ControllerExtension)
48
- end
76
+ ActionController::Base.include(BrainzLab::Rails::ControllerExtension) if defined?(ActionController::Base)
49
77
 
50
78
  # Hook into Sidekiq if available
51
79
  if defined?(Sidekiq)
@@ -58,8 +86,8 @@ module BrainzLab
58
86
  class << self
59
87
  def setup_log_formatter
60
88
  # Lazy require to ensure Rails is fully loaded
61
- require_relative "log_formatter"
62
- require_relative "log_subscriber"
89
+ require_relative 'log_formatter'
90
+ require_relative 'log_subscriber'
63
91
 
64
92
  config = BrainzLab.configuration
65
93
 
@@ -91,37 +119,23 @@ module BrainzLab
91
119
  null_logger.level = Logger::FATAL
92
120
 
93
121
  # Silence ActiveRecord SQL logging
94
- if defined?(ActiveRecord::Base)
95
- ActiveRecord::Base.logger = null_logger
96
- end
122
+ ActiveRecord::Base.logger = null_logger if defined?(ActiveRecord::Base)
97
123
 
98
124
  # Silence ActionController logging (the "Completed" message)
99
- if defined?(ActionController::Base)
100
- ActionController::Base.logger = null_logger
101
- end
125
+ ActionController::Base.logger = null_logger if defined?(ActionController::Base)
102
126
 
103
127
  # Silence ActionView logging
104
- if defined?(ActionView::Base)
105
- ActionView::Base.logger = null_logger
106
- end
128
+ ActionView::Base.logger = null_logger if defined?(ActionView::Base)
107
129
 
108
130
  # Silence the class-level loggers for specific subscribers
109
- if defined?(ActionController::LogSubscriber)
110
- ActionController::LogSubscriber.logger = null_logger
111
- end
131
+ ActionController::LogSubscriber.logger = null_logger if defined?(ActionController::LogSubscriber)
112
132
 
113
- if defined?(ActionView::LogSubscriber)
114
- ActionView::LogSubscriber.logger = null_logger
115
- end
133
+ ActionView::LogSubscriber.logger = null_logger if defined?(ActionView::LogSubscriber)
116
134
 
117
- if defined?(ActiveRecord::LogSubscriber)
118
- ActiveRecord::LogSubscriber.logger = null_logger
119
- end
135
+ ActiveRecord::LogSubscriber.logger = null_logger if defined?(ActiveRecord::LogSubscriber)
120
136
 
121
137
  # Silence ActionCable logging
122
- if defined?(ActionCable::Server::Base)
123
- ActionCable.server.config.logger = null_logger
124
- end
138
+ ActionCable.server.config.logger = null_logger if defined?(ActionCable::Server::Base)
125
139
 
126
140
  if defined?(ActionCable::Connection::TaggedLoggerProxy)
127
141
  # ActionCable uses a tagged logger proxy that we need to quiet
@@ -151,7 +165,7 @@ module BrainzLab
151
165
  /^Rendering/,
152
166
  /^Rendered/,
153
167
  /^\[ActionCable\] Broadcasting/,
154
- /^\s*$/ # Empty lines
168
+ /^\s*$/ # Empty lines
155
169
  ].freeze
156
170
 
157
171
  def call(severity, datetime, progname, msg)
@@ -182,7 +196,7 @@ module BrainzLab
182
196
 
183
197
  # Set request context
184
198
  context = BrainzLab::Context.current
185
- request_id = request.request_id || env["action_dispatch.request_id"]
199
+ request_id = request.request_id || env['action_dispatch.request_id']
186
200
  context.request_id = request_id
187
201
 
188
202
  # Store request_id in thread local for log subscriber
@@ -191,7 +205,11 @@ module BrainzLab
191
205
  # Capture session_id - access session to ensure it's loaded
192
206
  if request.session.respond_to?(:id)
193
207
  # Force session load by accessing it
194
- session_id = request.session.id rescue nil
208
+ session_id = begin
209
+ request.session.id
210
+ rescue StandardError
211
+ nil
212
+ end
195
213
  context.session_id = session_id.to_s if session_id.present?
196
214
  end
197
215
 
@@ -205,7 +223,7 @@ module BrainzLab
205
223
  # Add breadcrumb for request start
206
224
  BrainzLab::Reflex.add_breadcrumb(
207
225
  "#{request.request_method} #{request.path}",
208
- category: "http.request",
226
+ category: 'http.request',
209
227
  level: :info,
210
228
  data: { url: request.url }
211
229
  )
@@ -229,7 +247,7 @@ module BrainzLab
229
247
  Thread.current[:brainzlab_pulse_breakdown] = nil
230
248
  BrainzLab::Pulse.start_trace(
231
249
  "#{request.request_method} #{request.path}",
232
- kind: "request",
250
+ kind: 'request',
233
251
  parent_context: parent_context
234
252
  )
235
253
  end
@@ -239,7 +257,7 @@ module BrainzLab
239
257
  # Add breadcrumb for response
240
258
  BrainzLab::Reflex.add_breadcrumb(
241
259
  "Response #{status}",
242
- category: "http.response",
260
+ category: 'http.response',
243
261
  level: status >= 400 ? :error : :info,
244
262
  data: { status: status }
245
263
  )
@@ -257,9 +275,7 @@ module BrainzLab
257
275
  raise
258
276
  ensure
259
277
  # Finish Pulse trace for successful requests
260
- if should_trace && !$!
261
- record_pulse_trace(request, started_at, status)
262
- end
278
+ record_pulse_trace(request, started_at, status) if should_trace && !$ERROR_INFO
263
279
 
264
280
  Thread.current[:brainzlab_request_id] = nil
265
281
  BrainzLab::Context.clear!
@@ -273,8 +289,8 @@ module BrainzLab
273
289
  path = request.path
274
290
 
275
291
  # Check if path matches any excluded pattern
276
- !excluded.any? do |pattern|
277
- if pattern.include?("*")
292
+ excluded.none? do |pattern|
293
+ if pattern.include?('*')
278
294
  File.fnmatch?(pattern, path)
279
295
  else
280
296
  path.start_with?(pattern)
@@ -290,7 +306,6 @@ module BrainzLab
290
306
  spans = Thread.current[:brainzlab_pulse_spans] || []
291
307
  breakdown = Thread.current[:brainzlab_pulse_breakdown] || {}
292
308
 
293
-
294
309
  # Format spans for API
295
310
  formatted_spans = spans.map do |span|
296
311
  {
@@ -306,7 +321,7 @@ module BrainzLab
306
321
 
307
322
  BrainzLab::Pulse.record_trace(
308
323
  "#{request.request_method} #{request.path}",
309
- kind: "request",
324
+ kind: 'request',
310
325
  started_at: started_at,
311
326
  ended_at: ended_at,
312
327
  request_id: context.request_id,
@@ -344,11 +359,11 @@ module BrainzLab
344
359
  case obj
345
360
  when Hash
346
361
  obj.each_with_object({}) do |(k, v), h|
347
- if BrainzLab::Reflex::FILTERED_PARAMS.include?(k.to_s)
348
- h[k] = "[FILTERED]"
349
- else
350
- h[k] = deep_filter(v)
351
- end
362
+ h[k] = if BrainzLab::Reflex::FILTERED_PARAMS.include?(k.to_s)
363
+ '[FILTERED]'
364
+ else
365
+ deep_filter(v)
366
+ end
352
367
  end
353
368
  when Array
354
369
  obj.map { |v| deep_filter(v) }
@@ -375,11 +390,11 @@ module BrainzLab
375
390
  def extract_headers(env)
376
391
  headers = {}
377
392
  env.each do |key, value|
378
- next unless key.start_with?("HTTP_")
379
- next if key == "HTTP_COOKIE"
380
- next if key == "HTTP_AUTHORIZATION"
393
+ next unless key.start_with?('HTTP_')
394
+ next if key == 'HTTP_COOKIE'
395
+ next if key == 'HTTP_AUTHORIZATION'
381
396
 
382
- header_name = key.sub("HTTP_", "").split("_").map(&:capitalize).join("-")
397
+ header_name = key.sub('HTTP_', '').split('_').map(&:capitalize).join('-')
383
398
  headers[header_name] = value
384
399
  end
385
400
  headers
@@ -391,11 +406,10 @@ module BrainzLab
391
406
  def report(error, handled:, severity:, context: {}, source: nil)
392
407
  # Capture both handled and unhandled, but mark them
393
408
  BrainzLab::Reflex.capture(error,
394
- handled: handled,
395
- severity: severity.to_s,
396
- source: source,
397
- extra: context
398
- )
409
+ handled: handled,
410
+ severity: severity.to_s,
411
+ source: source,
412
+ extra: context)
399
413
  rescue StandardError => e
400
414
  BrainzLab.configuration.logger&.error("[BrainzLab] ErrorSubscriber failed: #{e.message}")
401
415
  end
@@ -421,7 +435,7 @@ module BrainzLab
421
435
  # Add breadcrumb
422
436
  BrainzLab::Reflex.add_breadcrumb(
423
437
  "#{self.class.name}##{action_name}",
424
- category: "controller",
438
+ category: 'controller',
425
439
  level: :info
426
440
  )
427
441
 
@@ -458,7 +472,7 @@ module BrainzLab
458
472
 
459
473
  BrainzLab::Reflex.add_breadcrumb(
460
474
  "Job #{self.class.name}",
461
- category: "job",
475
+ category: 'job',
462
476
  level: :info,
463
477
  data: { job_id: job_id, queue: queue_name }
464
478
  )
@@ -468,7 +482,7 @@ module BrainzLab
468
482
  if should_trace
469
483
  Thread.current[:brainzlab_pulse_spans] = []
470
484
  Thread.current[:brainzlab_pulse_breakdown] = nil
471
- BrainzLab::Pulse.start_trace(self.class.name, kind: "job")
485
+ BrainzLab::Pulse.start_trace(self.class.name, kind: 'job')
472
486
  end
473
487
 
474
488
  error_occurred = nil
@@ -480,9 +494,7 @@ module BrainzLab
480
494
  end
481
495
  ensure
482
496
  # Record Pulse trace for job
483
- if should_trace
484
- record_pulse_job_trace(started_at, error_occurred)
485
- end
497
+ record_pulse_job_trace(started_at, error_occurred) if should_trace
486
498
 
487
499
  BrainzLab::Context.clear!
488
500
  end
@@ -517,7 +529,7 @@ module BrainzLab
517
529
 
518
530
  BrainzLab::Pulse.record_trace(
519
531
  self.class.name,
520
- kind: "job",
532
+ kind: 'job',
521
533
  started_at: started_at,
522
534
  ended_at: ended_at,
523
535
  job_class: self.class.name,
@@ -556,32 +568,30 @@ module BrainzLab
556
568
 
557
569
  def brainzlab_rescue_job(exception)
558
570
  BrainzLab::Reflex.capture(exception,
559
- tags: { type: "background_job" },
560
- extra: {
561
- job_class: self.class.name,
562
- job_id: job_id,
563
- queue_name: queue_name,
564
- executions: executions,
565
- arguments: arguments.map(&:to_s).first(5)
566
- }
567
- )
571
+ tags: { type: 'background_job' },
572
+ extra: {
573
+ job_class: self.class.name,
574
+ job_id: job_id,
575
+ queue_name: queue_name,
576
+ executions: executions,
577
+ arguments: arguments.map(&:to_s).first(5)
578
+ })
568
579
  raise exception # Re-raise to let ActiveJob handle retries
569
580
  end
570
581
  end
571
582
 
572
- # Sidekiq error handler
583
+ # Sidekiq error handler - Sidekiq 7.x+ requires 3 arguments
573
584
  class SidekiqErrorHandler
574
- def call(exception, context)
585
+ def call(exception, context, _config = nil)
575
586
  BrainzLab::Reflex.capture(exception,
576
- tags: { type: "sidekiq" },
577
- extra: {
578
- job_class: context[:job]["class"],
579
- job_id: context[:job]["jid"],
580
- queue: context[:job]["queue"],
581
- args: context[:job]["args"]&.map(&:to_s)&.first(5),
582
- retry_count: context[:job]["retry_count"]
583
- }
584
- )
587
+ tags: { type: 'sidekiq' },
588
+ extra: {
589
+ job_class: context[:job]['class'],
590
+ job_id: context[:job]['jid'],
591
+ queue: context[:job]['queue'],
592
+ args: context[:job]['args']&.map(&:to_s)&.first(5),
593
+ retry_count: context[:job]['retry_count']
594
+ })
585
595
  rescue StandardError => e
586
596
  BrainzLab.configuration.logger&.error("[BrainzLab] Sidekiq handler failed: #{e.message}")
587
597
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "concurrent"
3
+ require 'concurrent'
4
4
 
5
5
  module BrainzLab
6
6
  module Recall