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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# 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 ID to avoid conflicts
  RequestStore.store[:rails_pulse_request_id] = nil

  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)

  # Temporarily skip recording while we create the route and request
  RequestStore.store[:skip_recording_rails_pulse_activity] = true
  route = find_or_create_route(req)
  controller_action = "#{env['action_dispatch.request.parameters']&.[]('controller')&.classify}##{env['action_dispatch.request.parameters']&.[]('action')}"
  occurred_at = Time.current

  request = nil
  if route
    request = RailsPulse::Request.create!(
      route: route,
      duration: 0, # will update after response
      status: 0, # will update after response
      is_error: false,
      request_uuid: req.uuid,
      controller_action: controller_action,
      occurred_at: occurred_at
    )
    RequestStore.store[:rails_pulse_request_id] = request.id
  end

  # Re-enable recording for the actual request processing
  RequestStore.store[:skip_recording_rails_pulse_activity] = false

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

  # Temporarily skip recording while we update the request and save operations
  RequestStore.store[:skip_recording_rails_pulse_activity] = true
  if request
    request.update(duration: duration, status: status, is_error: status.to_i >= 500)

    # Save collected operations
    operations_data = RequestStore.store[:rails_pulse_operations] || []
    operations_data.each do |operation_data|
      begin
        RailsPulse::Operation.create!(operation_data)
      rescue => e
        Rails.logger.error "[RailsPulse] Failed to save operation: #{e.message}"
      end
    end
  end

  [ 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