class AppsignalExtensions::Middleware

Used to open an Appsignal transaction, but to let the callee close it when it is done. The standard Rack middleware for Appsignal closes the transaction as soon as the response triplet gets returned, we need to keep the transaction open as long as the response is being read.

Public Class Methods

new(app) click to toggle source

Creates a new Appsignal middleware handler with the given Rack app as a callee

@param app the Rack app

# File lib/appsignal_extensions/middleware.rb, line 64
def initialize(app)
  @app = app
end

Public Instance Methods

call(env) click to toggle source

Calls the application, captures errors, sets up wrappers and so forth

@param env the Rack env @return [Array] the Rack response triplet from upstream

# File lib/appsignal_extensions/middleware.rb, line 72
def call(env)
  request = ::Rack::Request.new(env)
  env['action_dispatch.request_id'] ||= SecureRandom.uuid
  if Appsignal.active?
    call_with_appsignal(env, request)
  else
    call_with_null_transaction(env, request)
  end
end

Private Instance Methods

call_and_capture(env, transaction, request) click to toggle source
# File lib/appsignal_extensions/middleware.rb, line 84
def call_and_capture(env, transaction, request)
  env['appsignal.transaction'] = transaction
  app_name = @app.is_a?(Module) ? @app.to_s : @app.class.to_s # Set the class name properly
  transaction.set_action('%s#%s' % [app_name, 'call'])
  transaction.set_metadata('path', request.path)
  transaction.set_metadata('method', request.request_method)
  transaction.set_http_or_background_queue_start
  s, h, b = @app.call(env)
  
  # If the app we called wants to close the transaction on it's own, return the response. This
  # is useful if the app will clean up or close the transaction within an async.callback block,
  # or within the long response body, or within a hijack proc.
  return [s, h, b] if h.delete('appsignal.suspend')
  
  # If the app didn't ask for the explicit suspend, Wrap the response in a self-closing wrapper
  # so that the transaction is closed once the response is read in full. This wrapper only works
  # with response bodies that support #each().
  closing_wrapper = TransactionClosingBody.new(b, transaction)
  [s, h, closing_wrapper]
rescue Exception => e
  # If the raise happens immediately (not in the response read cycle)
  # set the error and close the transaction so that the data gets sent
  # to Appsignal right away, and ensure it gets closed
  transaction.set_error(e)
  transaction.close
  raise e
end
call_with_appsignal(env, request) click to toggle source
# File lib/appsignal_extensions/middleware.rb, line 117
def call_with_appsignal(env, request)
  bare_transaction = Appsignal::Transaction.create(
    env.fetch('action_dispatch.request_id'),
    Appsignal::Transaction::HTTP_REQUEST,
    request
  )
  
  # Let the app do something to the appsignal transaction if it wants to
  # Instrument a `process_action`, to set params/action name
  transaction = Close.new(bare_transaction)
  status, headers, body = Appsignal.instrument('process_action.rack') do
    call_and_capture(env, transaction, request)
  end
  
  [status, headers, body]
end
call_with_null_transaction(env, request) click to toggle source
# File lib/appsignal_extensions/middleware.rb, line 112
def call_with_null_transaction(env, request)
  # Supply the app with a null transaction
  call_and_capture(env, NullTransaction.new, request)
end