Class: Appsignal::Rack::EventHandler

Inherits:
Object
  • Object
show all
Includes:
Rack::Events::Abstract
Defined in:
lib/appsignal/rack/event_handler.rb

Overview

Instrumentation middleware using Rack’s Events module.

We recommend using this in combination with the InstrumentationMiddleware.

This middleware will report the response status code as the ‘response_status` tag on the sample. It will also report the response status as the `response_status` metric.

This middleware will ensure the AppSignal transaction is always completed for every request.

Examples:

Add EventHandler to a Rack app

# Add this middleware as the first middleware of an app
use ::Rack::Events, [Appsignal::Rack::EventHandler.new]

# Then add the InstrumentationMiddleware
use Appsignal::Rack::InstrumentationMiddleware

See Also:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeEventHandler

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of EventHandler.



43
44
45
# File 'lib/appsignal/rack/event_handler.rb', line 43

def initialize
  @id = SecureRandom.uuid
end

Instance Attribute Details

#idObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



40
41
42
# File 'lib/appsignal/rack/event_handler.rb', line 40

def id
  @id
end

Class Method Details

.safe_execution(name) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
34
35
36
37
# File 'lib/appsignal/rack/event_handler.rb', line 31

def self.safe_execution(name)
  yield
rescue => e
  Appsignal.internal_logger.error(
    "Error occurred in #{name}: #{e.class}: #{e}: #{e.backtrace}"
  )
end

Instance Method Details

#on_error(request, _response, error) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/appsignal/rack/event_handler.rb', line 91

def on_error(request, _response, error)
  return unless Appsignal.active?

  self.class.safe_execution("Appsignal::Rack::EventHandler#on_error") do
    return unless request_handler?(request.env[APPSIGNAL_EVENT_HANDLER_ID])

    transaction = request.env[APPSIGNAL_TRANSACTION]
    return unless transaction

    request.env[APPSIGNAL_EVENT_HANDLER_HAS_ERROR] = true
    transaction.set_error(error)
  end
end

#on_finish(request, response) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/appsignal/rack/event_handler.rb', line 106

def on_finish(request, response)
  return unless Appsignal.active?
  return unless request_handler?(request.env[APPSIGNAL_EVENT_HANDLER_ID])

  transaction = request.env[APPSIGNAL_TRANSACTION]
  return unless transaction

  self.class.safe_execution("Appsignal::Rack::EventHandler#on_finish") do
    transaction.finish_event("process_request.rack", "", "")
    transaction.add_params_if_nil { request.params }
    transaction.add_headers_if_nil { request.env }
    transaction.add_session_data_if_nil do
      request.session if request.respond_to?(:session)
    end
    queue_start = Appsignal::Rack::Utils.queue_start_from(request.env)
    transaction.set_queue_start(queue_start) if queue_start
    response_status =
      if response
        response.status
      elsif request.env[APPSIGNAL_EVENT_HANDLER_HAS_ERROR] == true
        500
      end
    if response_status
      transaction.add_tags(:response_status => response_status)
      Appsignal.increment_counter(
        :response_status,
        1,
        :status => response_status,
        :namespace => format_namespace(transaction.namespace)
      )
    end
  end

  # Make sure the current transaction is always closed when the request
  # is finished
  Appsignal::Transaction.complete_current!
end

#on_start(request, _response) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



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
87
88
# File 'lib/appsignal/rack/event_handler.rb', line 53

def on_start(request, _response)
  return unless Appsignal.active?

  event_handler = self
  self.class.safe_execution("Appsignal::Rack::EventHandler#on_start") do
    request.env[APPSIGNAL_EVENT_HANDLER_ID] ||= id
    return unless request_handler?(request.env[APPSIGNAL_EVENT_HANDLER_ID])

    transaction = Appsignal::Transaction.create(Appsignal::Transaction::HTTP_REQUEST)
    request.env[APPSIGNAL_TRANSACTION] = transaction

    request.env[RACK_AFTER_REPLY] ||= []
    request.env[RACK_AFTER_REPLY] << proc do
      next unless event_handler.request_handler?(request.env[APPSIGNAL_EVENT_HANDLER_ID])

      Appsignal::Rack::EventHandler
        .safe_execution("Appsignal::Rack::EventHandler's after_reply") do
        transaction.finish_event("process_request.rack", "", "")
        queue_start = Appsignal::Rack::Utils.queue_start_from(request.env)
        transaction.set_queue_start(queue_start) if queue_start
      end

      # Make sure the current transaction is always closed when the request
      # is finished. This is a fallback for in case the `on_finish`
      # callback is not called. This is supported by servers like Puma and
      # Unicorn.
      #
      # The EventHandler.on_finish callback should be called first, this is
      # just a fallback if that doesn't get called.
      #
      # One such scenario is when a Puma "lowlevel_error" occurs.
      Appsignal::Transaction.complete_current!
    end
    transaction.start_event
  end
end

#request_handler?(given_id) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


48
49
50
# File 'lib/appsignal/rack/event_handler.rb', line 48

def request_handler?(given_id)
  id == given_id
end