apm_bro 0.1.14 → 0.1.16

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.
@@ -1,22 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/notifications"
4
- require "thread"
3
+ begin
4
+ require "active_support/notifications"
5
+ rescue LoadError
6
+ # ActiveSupport not available
7
+ end
5
8
 
6
9
  module ApmBro
7
10
  class SqlSubscriber
8
- SQL_EVENT_NAME = "sql.active_record".freeze
11
+ SQL_EVENT_NAME = "sql.active_record"
9
12
  THREAD_LOCAL_KEY = :apm_bro_sql_queries
10
13
  THREAD_LOCAL_ALLOC_START_KEY = :apm_bro_sql_alloc_start
11
14
  THREAD_LOCAL_ALLOC_RESULTS_KEY = :apm_bro_sql_alloc_results
12
15
  THREAD_LOCAL_BACKTRACE_KEY = :apm_bro_sql_backtraces
16
+ THREAD_LOCAL_EXPLAIN_PENDING_KEY = :apm_bro_explain_pending
13
17
 
14
18
  def self.subscribe!
15
19
  # Subscribe with a start/finish listener to measure allocations per query
16
20
  if ActiveSupport::Notifications.notifier.respond_to?(:subscribe)
17
21
  begin
18
22
  ActiveSupport::Notifications.notifier.subscribe(SQL_EVENT_NAME, SqlAllocListener.new)
19
- rescue StandardError
23
+ rescue
20
24
  end
21
25
  end
22
26
 
@@ -30,25 +34,35 @@ module ApmBro
30
34
  begin
31
35
  alloc_results = Thread.current[THREAD_LOCAL_ALLOC_RESULTS_KEY]
32
36
  allocations = alloc_results && alloc_results.delete(unique_id)
33
-
37
+
34
38
  # Get the captured backtrace from when the query started
35
39
  backtrace_map = Thread.current[THREAD_LOCAL_BACKTRACE_KEY]
36
40
  captured_backtrace = backtrace_map && backtrace_map.delete(unique_id)
37
- rescue StandardError
41
+ rescue
38
42
  end
39
43
 
44
+ duration_ms = ((finished - started) * 1000.0).round(2)
45
+ original_sql = data[:sql]
46
+
40
47
  query_info = {
41
- sql: sanitize_sql(data[:sql]),
48
+ sql: sanitize_sql(original_sql),
42
49
  name: data[:name],
43
- duration_ms: ((finished - started) * 1000.0).round(2),
50
+ duration_ms: duration_ms,
44
51
  cached: data[:cached] || false,
45
52
  connection_id: data[:connection_id],
46
53
  trace: safe_query_trace(data, captured_backtrace),
47
54
  allocations: allocations
48
55
  }
56
+
57
+ # Run EXPLAIN ANALYZE for slow queries in the background
58
+ if should_explain_query?(duration_ms, original_sql)
59
+ # Store reference to query_info so we can update it when EXPLAIN completes
60
+ query_info[:explain_plan] = nil # Placeholder
61
+ start_explain_analyze_background(original_sql, data[:connection_id], query_info)
62
+ end
63
+
49
64
  # Add to thread-local storage
50
65
  Thread.current[THREAD_LOCAL_KEY] << query_info
51
-
52
66
  end
53
67
  end
54
68
 
@@ -57,31 +71,266 @@ module ApmBro
57
71
  Thread.current[THREAD_LOCAL_ALLOC_START_KEY] = {}
58
72
  Thread.current[THREAD_LOCAL_ALLOC_RESULTS_KEY] = {}
59
73
  Thread.current[THREAD_LOCAL_BACKTRACE_KEY] = {}
74
+ Thread.current[THREAD_LOCAL_EXPLAIN_PENDING_KEY] = []
60
75
  end
61
76
 
62
77
  def self.stop_request_tracking
78
+ # Wait for any pending EXPLAIN ANALYZE queries to complete (with timeout)
79
+ # This must happen BEFORE we get the queries array reference to ensure
80
+ # all explain_plan fields are populated
81
+ wait_for_pending_explains(5.0) # 5 second timeout
82
+
83
+ # Get queries after waiting for EXPLAIN to complete
63
84
  queries = Thread.current[THREAD_LOCAL_KEY]
85
+
64
86
  Thread.current[THREAD_LOCAL_KEY] = nil
65
87
  Thread.current[THREAD_LOCAL_ALLOC_START_KEY] = nil
66
88
  Thread.current[THREAD_LOCAL_ALLOC_RESULTS_KEY] = nil
67
89
  Thread.current[THREAD_LOCAL_BACKTRACE_KEY] = nil
90
+ Thread.current[THREAD_LOCAL_EXPLAIN_PENDING_KEY] = nil
68
91
  queries || []
69
92
  end
70
93
 
94
+ def self.wait_for_pending_explains(timeout_seconds)
95
+ pending = Thread.current[THREAD_LOCAL_EXPLAIN_PENDING_KEY]
96
+ return unless pending && !pending.empty?
97
+
98
+ start_time = Time.now
99
+ pending.each do |thread|
100
+ remaining_time = timeout_seconds - (Time.now - start_time)
101
+ break if remaining_time <= 0
102
+
103
+ begin
104
+ thread.join(remaining_time)
105
+ rescue => e
106
+ ApmBro.logger.debug("Error waiting for EXPLAIN ANALYZE: #{e.message}")
107
+ end
108
+ end
109
+ end
110
+
71
111
  def self.sanitize_sql(sql)
72
112
  return sql unless sql.is_a?(String)
73
113
 
74
114
  # Remove sensitive data patterns
75
115
  sql = sql.gsub(/\b(password|token|secret|key|ssn|credit_card)\s*=\s*['"][^'"]*['"]/i, '\1 = ?')
76
116
  sql = sql.gsub(/\b(password|token|secret|key|ssn|credit_card)\s*=\s*[^'",\s)]+/i, '\1 = ?')
77
-
117
+
78
118
  # Remove specific values in WHERE clauses that might be sensitive
79
119
  sql = sql.gsub(/WHERE\s+[^=]+=\s*['"][^'"]*['"]/i) do |match|
80
- match.gsub(/=\s*['"][^'"]*['"]/, '= ?')
120
+ match.gsub(/=\s*['"][^'"]*['"]/, "= ?")
81
121
  end
82
122
 
83
123
  # Limit query length to prevent huge payloads
84
- sql.length > 1000 ? sql[0..1000] + "..." : sql
124
+ (sql.length > 1000) ? sql[0..1000] + "..." : sql
125
+ end
126
+
127
+ def self.should_explain_query?(duration_ms, sql)
128
+ return false unless ApmBro.configuration.explain_analyze_enabled
129
+ return false if duration_ms < ApmBro.configuration.slow_query_threshold_ms
130
+ return false unless sql.is_a?(String)
131
+ return false if sql.strip.empty?
132
+
133
+ # Skip EXPLAIN for certain query types that don't benefit from it
134
+ sql_upper = sql.upcase.strip
135
+ return false if sql_upper.start_with?("EXPLAIN")
136
+ return false if sql_upper.start_with?("BEGIN")
137
+ return false if sql_upper.start_with?("COMMIT")
138
+ return false if sql_upper.start_with?("ROLLBACK")
139
+ return false if sql_upper.start_with?("SAVEPOINT")
140
+ return false if sql_upper.start_with?("RELEASE")
141
+
142
+ true
143
+ end
144
+
145
+ def self.start_explain_analyze_background(sql, connection_id, query_info)
146
+ return unless defined?(ActiveRecord)
147
+ return unless ActiveRecord::Base.respond_to?(:connection)
148
+
149
+ # Capture the main thread reference to append logs to the correct thread
150
+ main_thread = Thread.current
151
+
152
+ # Run EXPLAIN in a background thread to avoid blocking the main request
153
+ explain_thread = Thread.new do
154
+ connection = nil
155
+ begin
156
+ # Use a separate connection to avoid interfering with the main query
157
+ if ActiveRecord::Base.connection_pool.respond_to?(:checkout)
158
+ connection = ActiveRecord::Base.connection_pool.checkout
159
+ else
160
+ connection = ActiveRecord::Base.connection
161
+ end
162
+
163
+ # Build EXPLAIN query based on database adapter
164
+ explain_sql = build_explain_query(sql, connection)
165
+
166
+ # Execute the EXPLAIN query
167
+ # For PostgreSQL, use select_all which returns ActiveRecord::Result
168
+ # For other databases, use execute
169
+ adapter_name = connection.adapter_name.downcase
170
+ if adapter_name == "postgresql" || adapter_name == "postgis"
171
+ # PostgreSQL: select_all returns ActiveRecord::Result with rows
172
+ result = connection.select_all(explain_sql)
173
+ else
174
+ # Other databases: use execute
175
+ result = connection.execute(explain_sql)
176
+ end
177
+
178
+ # Format the result based on database adapter
179
+ explain_plan = format_explain_result(result, connection)
180
+
181
+ # Update the query_info with the explain plan
182
+ # This updates the hash that's already in the queries array
183
+ if explain_plan && !explain_plan.to_s.strip.empty?
184
+ query_info[:explain_plan] = explain_plan
185
+ append_log_to_thread(main_thread, :debug, "Captured EXPLAIN ANALYZE for slow query (#{query_info[:duration_ms]}ms): #{explain_plan[0..1000]}...")
186
+ else
187
+ query_info[:explain_plan] = nil
188
+ append_log_to_thread(main_thread, :debug, "EXPLAIN ANALYZE returned empty result. Result type: #{result.class}, Result: #{result.inspect[0..200]}")
189
+ end
190
+ rescue => e
191
+ # Silently fail - don't let EXPLAIN break the application
192
+ append_log_to_thread(main_thread, :debug, "Failed to capture EXPLAIN ANALYZE: #{e.message}")
193
+ query_info[:explain_plan] = nil
194
+ ensure
195
+ # Return connection to pool if we checked it out
196
+ if connection && ActiveRecord::Base.connection_pool.respond_to?(:checkin)
197
+ ActiveRecord::Base.connection_pool.checkin(connection) rescue nil
198
+ end
199
+ end
200
+ end
201
+
202
+ # Track the thread so we can wait for it when stopping request tracking
203
+ pending = Thread.current[THREAD_LOCAL_EXPLAIN_PENDING_KEY] ||= []
204
+ pending << explain_thread
205
+ rescue => e
206
+ # Use ApmBro.logger here since we're still in the main thread
207
+ ApmBro.logger.debug("Failed to start EXPLAIN ANALYZE thread: #{e.message}")
208
+ end
209
+
210
+ # Append a log entry directly to a specific thread's log storage
211
+ # This is used when logging from background threads to ensure logs
212
+ # are collected with the main request thread's logs
213
+ def self.append_log_to_thread(thread, severity, message)
214
+ timestamp = Time.now.utc
215
+ log_entry = {
216
+ sev: severity.to_s,
217
+ msg: message.to_s,
218
+ time: timestamp.iso8601(3)
219
+ }
220
+
221
+ # Append to the specified thread's log storage
222
+ thread[:apm_bro_logs] ||= []
223
+ thread[:apm_bro_logs] << log_entry
224
+
225
+ # Also print the message immediately (using current thread's logger)
226
+ begin
227
+ if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
228
+ formatted_message = "[ApmBro] #{timestamp.iso8601(3)} #{severity.to_s.upcase}: #{message}"
229
+ case severity
230
+ when :debug
231
+ Rails.logger.debug(formatted_message)
232
+ when :info
233
+ Rails.logger.info(formatted_message)
234
+ when :warn
235
+ Rails.logger.warn(formatted_message)
236
+ when :error
237
+ Rails.logger.error(formatted_message)
238
+ when :fatal
239
+ Rails.logger.fatal(formatted_message)
240
+ end
241
+ else
242
+ # Fallback to stdout
243
+ $stdout.puts("[ApmBro] #{timestamp.iso8601(3)} #{severity.to_s.upcase}: #{message}")
244
+ end
245
+ rescue
246
+ # Never let logging break the application
247
+ $stdout.puts("[ApmBro] #{severity.to_s.upcase}: #{message}")
248
+ end
249
+ end
250
+
251
+ def self.build_explain_query(sql, connection)
252
+ adapter_name = connection.adapter_name.downcase
253
+
254
+ case adapter_name
255
+ when "postgresql", "postgis"
256
+ # PostgreSQL supports ANALYZE and BUFFERS
257
+ "EXPLAIN (ANALYZE, BUFFERS) #{sql}"
258
+ when "mysql", "mysql2", "trilogy"
259
+ # MySQL uses different syntax - ANALYZE is a separate keyword
260
+ "EXPLAIN ANALYZE #{sql}"
261
+ when "sqlite3"
262
+ # SQLite supports EXPLAIN QUERY PLAN
263
+ "EXPLAIN QUERY PLAN #{sql}"
264
+ else
265
+ # Generic fallback - just EXPLAIN
266
+ "EXPLAIN #{sql}"
267
+ end
268
+ end
269
+
270
+ def self.format_explain_result(result, connection)
271
+ adapter_name = connection.adapter_name.downcase
272
+
273
+ case adapter_name
274
+ when "postgresql", "postgis"
275
+ # PostgreSQL returns ActiveRecord::Result from select_all
276
+ if result.respond_to?(:rows)
277
+ # ActiveRecord::Result object - rows is an array of arrays
278
+ # Each row is [query_plan_string]
279
+ plan_text = result.rows.map { |row| row.is_a?(Array) ? row.first.to_s : row.to_s }.join("\n")
280
+ return plan_text unless plan_text.strip.empty?
281
+ end
282
+
283
+ # Try alternative methods to extract the plan
284
+ if result.respond_to?(:each) && result.respond_to?(:columns)
285
+ # ActiveRecord::Result with columns
286
+ plan_column = result.columns.find { |col| col.downcase.include?("plan") || col.downcase.include?("query") } || result.columns.first
287
+ plan_text = result.map { |row|
288
+ if row.is_a?(Hash)
289
+ row[plan_column] || row[plan_column.to_sym] || row.values.first
290
+ else
291
+ row
292
+ end
293
+ }.join("\n")
294
+ return plan_text unless plan_text.strip.empty?
295
+ end
296
+
297
+ if result.is_a?(Array)
298
+ # Array of hashes or arrays
299
+ plan_text = result.map do |row|
300
+ if row.is_a?(Hash)
301
+ row["QUERY PLAN"] || row["query plan"] || row[:query_plan] || row.values.first.to_s
302
+ elsif row.is_a?(Array)
303
+ row.first.to_s
304
+ else
305
+ row.to_s
306
+ end
307
+ end.join("\n")
308
+ return plan_text unless plan_text.strip.empty?
309
+ end
310
+
311
+ # Fallback to string representation
312
+ result.to_s
313
+ when "mysql", "mysql2", "trilogy"
314
+ # MySQL returns rows
315
+ if result.is_a?(Array)
316
+ result.map { |row| row.is_a?(Hash) ? row.values.join(" | ") : row.to_s }.join("\n")
317
+ else
318
+ result.to_s
319
+ end
320
+ when "sqlite3"
321
+ # SQLite returns rows
322
+ if result.is_a?(Array)
323
+ result.map { |row| row.is_a?(Hash) ? row.values.join(" | ") : row.to_s }.join("\n")
324
+ else
325
+ result.to_s
326
+ end
327
+ else
328
+ # Generic fallback
329
+ result.to_s
330
+ end
331
+ rescue => e
332
+ # Fallback to string representation
333
+ result.to_s
85
334
  end
86
335
 
87
336
  def self.safe_query_trace(data, captured_backtrace = nil)
@@ -89,94 +338,93 @@ module ApmBro
89
338
 
90
339
  # Build trace from available data fields
91
340
  trace = []
92
-
341
+
93
342
  # Use filename, line, and method if available
94
343
  if data[:filename] && data[:line] && data[:method]
95
344
  trace << "#{data[:filename]}:#{data[:line]}:in `#{data[:method]}'"
96
345
  end
97
-
346
+
98
347
  # Use the captured backtrace from when the query started (most accurate)
99
348
  if captured_backtrace && captured_backtrace.is_a?(Array) && !captured_backtrace.empty?
100
349
  # Filter to only include frames that contain "app/" (application code)
101
350
  app_frames = captured_backtrace.select do |frame|
102
- frame.include?('app/') && !frame.include?('/vendor/')
351
+ frame.include?("app/") && !frame.include?("/vendor/")
103
352
  end
104
-
353
+
105
354
  caller_trace = app_frames.map do |line|
106
355
  # Remove any potential sensitive information from file paths
107
- line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, '/[FILTERED]/')
356
+ line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, "/[FILTERED]/")
108
357
  end
109
-
358
+
110
359
  trace.concat(caller_trace)
111
360
  else
112
361
  # Fallback: try to get backtrace from current context
113
362
  begin
114
363
  # Get all available frames - we'll filter to find application code
115
364
  all_frames = Thread.current.backtrace || []
116
-
365
+
117
366
  if all_frames.empty?
118
367
  # Fallback to caller_locations if backtrace is empty
119
368
  locations = caller_locations(1, 50)
120
369
  all_frames = locations.map { |loc| "#{loc.path}:#{loc.lineno}:in `#{loc.label}'" } if locations
121
370
  end
122
-
371
+
123
372
  # Filter to only include frames that contain "app/" (application code)
124
373
  app_frames = all_frames.select do |frame|
125
- frame.include?('app/') && !frame.include?('/vendor/')
374
+ frame.include?("app/") && !frame.include?("/vendor/")
126
375
  end
127
-
376
+
128
377
  caller_trace = app_frames.map do |line|
129
- line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, '/[FILTERED]/')
378
+ line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, "/[FILTERED]/")
130
379
  end
131
-
380
+
132
381
  trace.concat(caller_trace)
133
- rescue StandardError
382
+ rescue
134
383
  # If backtrace fails, try caller as fallback
135
384
  begin
136
385
  caller_stack = caller(20, 50) # Get more frames to find app/ frames
137
- app_frames = caller_stack.select { |frame| frame.include?('app/') && !frame.include?('/vendor/') }
386
+ app_frames = caller_stack.select { |frame| frame.include?("app/") && !frame.include?("/vendor/") }
138
387
  caller_trace = app_frames.map do |line|
139
- line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, '/[FILTERED]/')
388
+ line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, "/[FILTERED]/")
140
389
  end
141
390
  trace.concat(caller_trace)
142
- rescue StandardError
391
+ rescue
143
392
  # If caller also fails, we still have the immediate location
144
393
  end
145
394
  end
146
395
  end
147
-
396
+
148
397
  # If we have a backtrace in the data, use it (but it's usually nil for SQL events)
149
398
  if data[:backtrace] && data[:backtrace].is_a?(Array)
150
399
  # Filter to only include frames that contain "app/"
151
400
  app_backtrace = data[:backtrace].select do |line|
152
- line.is_a?(String) && line.include?('app/') && !line.include?('/vendor/')
401
+ line.is_a?(String) && line.include?("app/") && !line.include?("/vendor/")
153
402
  end
154
-
403
+
155
404
  backtrace_trace = app_backtrace.map do |line|
156
405
  case line
157
406
  when String
158
- line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, '/[FILTERED]/')
407
+ line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, "/[FILTERED]/")
159
408
  else
160
409
  line.to_s
161
410
  end
162
411
  end
163
412
  trace.concat(backtrace_trace)
164
413
  end
165
-
414
+
166
415
  # Remove duplicates and return all app/ frames (no limit)
167
416
  trace.uniq.map do |line|
168
417
  case line
169
418
  when String
170
419
  # Remove any potential sensitive information from file paths
171
- line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, '/[FILTERED]/')
420
+ line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, "/[FILTERED]/")
172
421
  else
173
422
  line.to_s
174
423
  end
175
424
  end
176
- rescue StandardError
425
+ rescue
177
426
  []
178
427
  end
179
-
180
428
  end
181
429
  end
182
430
 
@@ -184,36 +432,36 @@ module ApmBro
184
432
  # Listener that records GC allocation deltas per SQL event id
185
433
  class SqlAllocListener
186
434
  def start(name, id, payload)
187
- begin
188
- map = (Thread.current[ApmBro::SqlSubscriber::THREAD_LOCAL_ALLOC_START_KEY] ||= {})
189
- map[id] = GC.stat[:total_allocated_objects] if defined?(GC) && GC.respond_to?(:stat)
190
-
191
- # Capture the backtrace at query start time (before notification system processes it)
192
- # This gives us the actual call stack where the SQL was executed
193
- backtrace_map = (Thread.current[ApmBro::SqlSubscriber::THREAD_LOCAL_BACKTRACE_KEY] ||= {})
194
- captured_backtrace = Thread.current.backtrace
195
- if captured_backtrace && captured_backtrace.is_a?(Array)
196
- # Skip the first few frames (our listener code) to get to the actual query execution
197
- backtrace_map[id] = captured_backtrace[5..-1] || captured_backtrace
198
- end
199
- rescue StandardError
435
+ map = (Thread.current[ApmBro::SqlSubscriber::THREAD_LOCAL_ALLOC_START_KEY] ||= {})
436
+ map[id] = GC.stat[:total_allocated_objects] if defined?(GC) && GC.respond_to?(:stat)
437
+
438
+ # Capture the backtrace at query start time (before notification system processes it)
439
+ # This gives us the actual call stack where the SQL was executed
440
+ backtrace_map = (Thread.current[ApmBro::SqlSubscriber::THREAD_LOCAL_BACKTRACE_KEY] ||= {})
441
+ captured_backtrace = Thread.current.backtrace
442
+ if captured_backtrace && captured_backtrace.is_a?(Array)
443
+ # Skip the first few frames (our listener code) to get to the actual query execution
444
+ backtrace_map[id] = captured_backtrace[5..-1] || captured_backtrace
200
445
  end
446
+ rescue
201
447
  end
202
448
 
203
449
  def finish(name, id, payload)
204
- begin
205
- start_map = Thread.current[ApmBro::SqlSubscriber::THREAD_LOCAL_ALLOC_START_KEY]
206
- return unless start_map && start_map.key?(id)
207
-
208
- start_count = start_map.delete(id)
209
- end_count = (GC.stat[:total_allocated_objects] rescue nil)
210
- return unless start_count && end_count
450
+ start_map = Thread.current[ApmBro::SqlSubscriber::THREAD_LOCAL_ALLOC_START_KEY]
451
+ return unless start_map && start_map.key?(id)
211
452
 
212
- delta = end_count - start_count
213
- results = (Thread.current[ApmBro::SqlSubscriber::THREAD_LOCAL_ALLOC_RESULTS_KEY] ||= {})
214
- results[id] = delta
215
- rescue StandardError
453
+ start_count = start_map.delete(id)
454
+ end_count = begin
455
+ GC.stat[:total_allocated_objects]
456
+ rescue
457
+ nil
216
458
  end
459
+ return unless start_count && end_count
460
+
461
+ delta = end_count - start_count
462
+ results = (Thread.current[ApmBro::SqlSubscriber::THREAD_LOCAL_ALLOC_RESULTS_KEY] ||= {})
463
+ results[id] = delta
464
+ rescue
217
465
  end
218
466
  end
219
467
  end
@@ -12,37 +12,37 @@ module ApmBro
12
12
 
13
13
  # Start SQL tracking for this request
14
14
  if defined?(ApmBro::SqlSubscriber)
15
- puts "Starting SQL tracking for request: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
15
+ puts "Starting SQL tracking for request: #{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"
16
16
  ApmBro::SqlSubscriber.start_request_tracking
17
17
  end
18
18
 
19
19
  # Start cache tracking for this request
20
20
  if defined?(ApmBro::CacheSubscriber)
21
- puts "Starting cache tracking for request: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
21
+ puts "Starting cache tracking for request: #{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"
22
22
  ApmBro::CacheSubscriber.start_request_tracking
23
23
  end
24
24
 
25
25
  # Start Redis tracking for this request
26
26
  if defined?(ApmBro::RedisSubscriber)
27
- puts "Starting redis tracking for request: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
27
+ puts "Starting redis tracking for request: #{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"
28
28
  ApmBro::RedisSubscriber.start_request_tracking
29
29
  end
30
30
 
31
31
  # Start view rendering tracking for this request
32
32
  if defined?(ApmBro::ViewRenderingSubscriber)
33
- puts "Starting view rendering tracking for request: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
33
+ puts "Starting view rendering tracking for request: #{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"
34
34
  ApmBro::ViewRenderingSubscriber.start_request_tracking
35
35
  end
36
36
 
37
37
  # Start lightweight memory tracking for this request
38
38
  if defined?(ApmBro::LightweightMemoryTracker)
39
- puts "Starting lightweight memory tracking for request: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
39
+ puts "Starting lightweight memory tracking for request: #{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"
40
40
  ApmBro::LightweightMemoryTracker.start_request_tracking
41
41
  end
42
42
 
43
43
  # Start detailed memory tracking when allocation tracking is enabled
44
44
  if ApmBro.configuration.allocation_tracking_enabled && defined?(ApmBro::MemoryTrackingSubscriber)
45
- puts "Starting detailed memory tracking for request: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
45
+ puts "Starting detailed memory tracking for request: #{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"
46
46
  ApmBro::MemoryTrackingSubscriber.start_request_tracking
47
47
  end
48
48
 
@@ -53,27 +53,27 @@ module ApmBro
53
53
  ensure
54
54
  # Clean up thread-local storage
55
55
  if defined?(ApmBro::SqlSubscriber)
56
- queries = Thread.current[:apm_bro_sql_queries]
56
+ Thread.current[:apm_bro_sql_queries]
57
57
  Thread.current[:apm_bro_sql_queries] = nil
58
58
  end
59
59
 
60
60
  if defined?(ApmBro::CacheSubscriber)
61
- cache_events = Thread.current[:apm_bro_cache_events]
61
+ Thread.current[:apm_bro_cache_events]
62
62
  Thread.current[:apm_bro_cache_events] = nil
63
63
  end
64
64
 
65
65
  if defined?(ApmBro::RedisSubscriber)
66
- redis_events = Thread.current[:apm_bro_redis_events]
66
+ Thread.current[:apm_bro_redis_events]
67
67
  Thread.current[:apm_bro_redis_events] = nil
68
68
  end
69
69
 
70
70
  if defined?(ApmBro::ViewRenderingSubscriber)
71
- view_events = Thread.current[:apm_bro_view_events]
71
+ Thread.current[:apm_bro_view_events]
72
72
  Thread.current[:apm_bro_view_events] = nil
73
73
  end
74
74
 
75
75
  if defined?(ApmBro::LightweightMemoryTracker)
76
- memory_events = Thread.current[:apm_bro_lightweight_memory]
76
+ Thread.current[:apm_bro_lightweight_memory]
77
77
  Thread.current[:apm_bro_lightweight_memory] = nil
78
78
  end
79
79