Class: RailsPulse::Middleware::RequestCollector

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_pulse/middleware/request_collector.rb

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ RequestCollector

Returns a new instance of RequestCollector.



4
5
6
# File 'lib/rails_pulse/middleware/request_collector.rb', line 4

def initialize(app)
  @app = app
end

Instance Method Details

#call(env) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/rails_pulse/middleware/request_collector.rb', line 8

def call(env)
  # Skip if Rails Pulse is disabled
  return @app.call(env) unless RailsPulse.configuration.enabled

  # Skip logging if we are already recording RailsPulse activity. This is to avoid recursion issues
  return @app.call(env) if RequestStore.store[:skip_recording_rails_pulse_activity]

  req = ActionDispatch::Request.new(env)

  # Skip RailsPulse engine requests
  mount_path = RailsPulse.configuration.mount_path || "/rails_pulse"
  if req.path.start_with?(mount_path)
    RequestStore.store[:skip_recording_rails_pulse_activity] = true
    result = @app.call(env)
    RequestStore.store[:skip_recording_rails_pulse_activity] = false
    return result
  end

  # Check if route should be ignored based on configuration
  if should_ignore_route?(req)
    RequestStore.store[:skip_recording_rails_pulse_activity] = true
    result = @app.call(env)
    RequestStore.store[:skip_recording_rails_pulse_activity] = false
    return result
  end

  # Clear any previous request data and set a placeholder ID
  RequestStore.store[:rails_pulse_request_id] = SecureRandom.uuid
  RequestStore.store[:rails_pulse_operations] = []

  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  controller_action = "#{env['action_dispatch.request.parameters']&.[]('controller')&.classify}##{env['action_dispatch.request.parameters']&.[]('action')}"
  occurred_at = Time.current

  # Process request
  status, headers, response = @app.call(env)
  duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)

  # Collect all tracking data
  # Deep copy operations array to prevent race condition in async mode
  operations = RequestStore.store[:rails_pulse_operations] || []
  tracking_data = {
    method: req.request_method,
    path: req.path,
    duration: duration,
    status: status,
    is_error: status.to_i >= 500,
    request_uuid: req.uuid,
    controller_action: controller_action,
    occurred_at: occurred_at,
    operations: operations.map(&:dup)
  }

  # Send to tracker (non-blocking if async mode enabled)
  RailsPulse::Tracker.track_request(tracking_data)

  [ status, headers, response ]
ensure
  RequestStore.store[:skip_recording_rails_pulse_activity] = false
  RequestStore.store[:rails_pulse_request_id] = nil
  RequestStore.store[:rails_pulse_operations] = nil
end