Adding custom data to bugsnag notifications with middleware in Rails 3.2 - ruby-on-rails

I would like to add a custom Tab to bugsnag for all notifications generated by my rails app.
I cannot use :before_bugsnag_notify because the errors are sometimes generated by models which are being called from resque background jobs.
So, I settled on using middleware and I am running to some issues here:
so I went through this guide here:
https://bugsnag.com/docs/notifiers/ruby#bugsnag-middleware
and added my middleware at app/middleware as follows:
class CustomMiddleware
def initialize(bugsnag)
#bugsnag = bugsnag
end
def call(notification)
puts "doing something"
#bugsnag.call(notification)
end
end
The config file for bugsnag is as follows:
in config/initializers/bugsnag.rb:
Bugsnag.configure do |config|
config.api_key = "#{ENV['BUGSNAG_API_KEY']}"
config.middleware.use "CustomMiddleWare"
end
Eventually, I would like to add a tab using add_tab() method before #bugsnag.call(), but now I keep running into the error that I cannot fix:
** [Bugsnag] Bugsnag middleware error: undefined method `new' for "CustomMiddleWare":String
Any ideas?
Edit: I had to put the method name is strings because of this:
Where do you put your Rack middleware files and requires?

Related

Why is adding a Rails middleware like this causing endless redirects?

I'm trying to add some middleware to a Rails project I'm working on, and when I try to do so, it seems to cause an endless loop.
Specifically, I have the following middleware shell file:
# app/middleware/log_data.rb
class LogData
def initialize(app)
#app = app
end
def call(env)
# To-do: Write code here.
end
end
I then created a new middleware directory under the app directory and put the file in that directory.
After that, I added the following towards the bottom of config/application.rb:
config.middleware.use("LogData")
After restarting the Puma server running on Vagrant with sudo service puma restart, if I run rake middleware, I can see the middleware correctly show up in the list towards the bottom.
However, when I try to refresh the website, it fails with an endless loop, displaying the following in Chrome:
If I comment out the config.middleware.use("LogData") line in config/application.rb, then the middleware disappears from the rake middleware command list, and the website stops crashing and loads properly.
What am I doing wrong? What am I missing? I imagine it's something simple, but I'm not sure why a simple (and empty) shell middleware file would cause the whole site to crash. Thank you.
I should note that I'm using Rails 4.2.11, which I know is old, but upgrading right now is not an option.
Your middleware does nothing, returns nil (which translates to an Incomplete Server Response), and basically the request ends there. It needs to return something (an array of [status, headers, response], or call the env) to allow the request to pass through the middleware chain.
# app/middleware/log_data.rb
class LogData
def initialize(app)
#app = app
end
def call(env)
# To-do: Write code here.
# this should be at the very end of the method
#app.call(env)
end
end
Here is more info about middlewares.

Accessing Rails engine's URL helpers in initializer

I'm trying to access the url helpers in my engine to set up rack-cors. Right now, I've hard-coded the strings for one of the urls in the rack-cors middleware configuration. I have read the order in which Rails initializers are run, and at this point in the load order I should have the engine routes available to me. I thought I would have them available at the event add_routing_paths, but I couldn't find the routes after digging around using pry. Another statement that leads me to think I'm doing this incorrectly is that the docs say: "Some parts of your application, notably routing, are not yet set up at the point where the after_initialize block is called." According to this list:
require "config/boot.rb" to setup load paths
require railties and engines
Define Rails.application as "class MyApp::Application < Rails::Application"
Run config.before_configuration callbacks
Load config/environments/ENV.rb
Run config.before_initialize callbacks
Run Railtie#initializer defined by railties, engines and application.
One by one, each engine sets up its load paths, routes and runs its config/initializers/* files.
Custom Railtie#initializers added by railties, engines and applications are executed
Build the middleware stack and run to_prepare callbacks
Run config.before_eager_load and eager_load! if eager_load is true
Run config.after_initialize callbacks
I'm trying to hook into (7), but perhaps routes aren't available until (11)?
module Zillow
class Engine < ::Rails::Engine
isolate_namespace Zillow
# Rails.application.routes.url_helpers
initializer "zillow.cors", after: :set_routes_reloader do |app|
require 'pry'; binding.pry
app.config.app_middleware.insert_before 0, Rack::Cors do
allow do
origins 'localhost:3000'
resource '/zillow/search_results', methods: :get
end
end
end
end
end
Here is the output of my routes
zillow /zillow Zillow::Engine
Routes for Zillow::Engine:
api_deep_comps GET /api/deep_comps(.:format) zillow/api#deep_comps
api_zestimate GET /api/zestimate(.:format) zillow/api#zestimate
api_search_results GET /api/search_results(.:format) zillow/api#search_results
api_updated_property_details GET /api/updated_property_details(.:format) zillow/api#updated_property_details
You can fire your own event when routes are loaded and then subscribe to that event during initialization to get routes data. For that:
Add this to the end of config/routes.rb file (outside of routes.draw block)
ActiveSupport::Notifications.instrument 'routes_loaded.application'
Subscribe to this event in initialization code and use URL helpers!
ActiveSupport::Notifications.subscribe 'routes_loaded.application' do
Rails.logger.info Rails.application.routes.url_helpers.home_path
end
For more information see:
Subscribing to an event
Creating custom events
Load it just after initialize.
I needed to use root_url in my initializer but it wasn't properly initialized. I tried using the Pub / Sub method that Anton Styagun suggests but even when that's called the URL Helpers were not properly initialized.
Instead, I opted to load my initializer file manually just after the Rails app itself has initialized. This takes a few steps:
Move your initializer from config/initializers/ to lib/.
Require your file in environment.rb just after your application initializes. Ours looks something like this:
CNTRAL::Application.initialize!
require "my_initializer" # Omit the `lib/` because that's the load path it looks in.
Now, inside of my_initializer.rb I was able to call root_url by doing this:
Rails.application.routes.url_helpers.root_url
NOTE: It's important not to just use include Rails.application.routes.url_helpers. It raised an exception on me.
I also added a comment in both the initializer and the environment.rb of where this was loaded/required so it's more obvious to myself in the future and other engineers.
Alternative Method
You can also use the following method and it works well:
In your application.rb file, add:
config.after_initialize do
Rails.application.reload_routes! # Necessary to load the Routes.
require "my_initializer"
end
After looking into this further and reading the rack-cors example done in Rails3, it may not be possible to retrieve the route helpers inside the initializer at any point in time.

Rack middleware "trapping" stack trace

I have a piece of Rack middleware that loads a tenant, via subdomain, and applies some default settings. The middleware, while not pretty, does it's job fine enough. However, when an exception is thrown within the app the middleware "traps" the full stack trace. When I say trap I mean it hides the expected stack trace.
Here is an example.
I am throwing an exception in an a controller action like so:
def index
throw "Exception in a Rails controller action"
#taxonomies = Spree::Taxonomy.all
end
You would expect that the stack trace would reference this location but it does not. Instead it reference a line in the middleware.
Completed 500 Internal Server Error in 139ms
UncaughtThrowError (uncaught throw "Exception in a Rails controller action"):
lib/tenant_manager/middleware/loader.rb:42:in `call'
Why does this happen? Have you seen anything like this before?
Here is the middleware:
# lib/tenant_manager/middleware/loader.rb
module TenantManager
module Middleware
class Loader
# Middleware to detect an tenant via subdomain early in
# the request process
#
# Usage:
# # config/application.rb
# config.middleware.use TenantManager::Middleware::Loader
#
# A scaled down version of https://github.com/radar/houser
def initialize(app)
#app = app
end
def call(env)
domain_parts = env['HTTP_HOST'].split('.')
if domain_parts.length > 2
subdomain = domain_parts.first
tenant = Leafer::Tenant.find_by_database(subdomain)
if tenant
ENV['CURRENT_TENANT_ID'] = tenant.id.to_s
ENV['RAILS_CACHE_ID'] = tenant.database
Spree::Image.change_paths tenant.database
Apartment::Tenant.process(tenant.database) do
country = Spree::Country.find_by_name('United States')
Spree.config do |config|
config.default_country_id = country.id if country.present?
config.track_inventory_levels = false
end
Spree::Auth::Config.set(:registration_step => false)
end
end
else
ENV['CURRENT_TENANT_ID'] = nil
ENV['RAILS_CACHE_ID'] = ""
end
#app.call(env)
end
end
end
end
I am running ruby 2.2.0p0 and rails 4.1.8.
I have searched the webs for this but could not find anything, probably because I'm not serching for the right thing.
Any thoughts on why this is happening and what I am doing wrong?
Cheers!
I finally found the solution to this. It turns out that the last line in what is considered my application is in the middleware. I was running the rest of the code in a local rails engine located in a components directory. All we needed to do was create a new silencer for BacktraceCleaner. Notice components dir is now included.
# config/initializers/backtrace_silencers.rb
Rails.backtrace_cleaner.remove_silencers!
Rails.backtrace_cleaner.add_silencer { |line| line !~ /^\/(app|config|lib|test|components)/}
If you are interested here is an issue I posted on the rails project about how to replicate this in detail. https://github.com/rails/rails/issues/22265
Your middleware seems good. I think you have an issue with your backtrace_cleaner setting. Perhaps the cleaner gets overridden by a 3rd party gem. Try put a breakpoint (debugger) in the controller action method before the error raising, and print:
puts env['action_dispatch.backtrace_cleaner'].instance_variable_get(:#silencers).map(&:source_location).map{|l| l.join(':')}
to see the source locations of all the silencers which strip off non-app traces. By default it should only use Rails::BacktraceCleaner which locates at railties-4.1.8/lib/rails/backtrace_cleaner.rb
To directly see the silencer source code:
puts env['action_dispatch.backtrace_cleaner'].instance_variable_get(:#silencers).map{|s| RubyVM::InstructionSequence.disasm s }
See more from https://github.com/rails/rails/blob/master/railties/lib/rails/backtrace_cleaner.rb
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/backtrace_cleaner.rb
You're not doing anything wrong. But a lot of middleware traps exceptions in order to do cleanup, including middleware that Rack inserts automatically in development mode. There is a specific Rack middleware inserted in development that will catch uncaught exceptions and give a reasonable HTML page instead of a raw stack dump (which you often won't see at all with common app servers.)
You can catch the exception yourself by putting a begin/rescue/end around your top level. Remember to catch "Exception", not just the default "rescue" with no arg, if you want to get everything. And you may want to re-throw the exception if you're going to leave this code in.
You can run in production mode -- that might stop the middleware from being inserted automatically by Rack.
You can find out what middleware is inserted (in Rails: "rake middleware") and then remove the middleware manually (in Rails "config.middleware.delete" or "config.middleware.disable").
There are probably other methods.

Rails Custom Delayed Job - uninitialized constant

I've been successfully using delayed_job for a couple of years now but recently I have a need to implement some kind of success/failure callback/hooks.
Following the delayed_job guide on github i've set up the following custom job:
class XmlImportJob < Struct.new(:file, :supplier_id, :user_id, :directory)
def perform
Product.xml_import(file, supplier_id, user, directory)
end
def success(job)
ProductMailer.xml_import_complete.deliver
end
def failure(job)
ProductMailer.xml_import_failed.deliver
end
end
When running this with Delayed::Job.enqueue XmlImportJob.new(secure_url, 1, 1, directory) for example, I get a Job failed to load: uninitialized constant XmlImportJob. error.
I've tried saving my custom job which is in a file named xml_import.rb under app/jobs and lib and I get the same error.
At the moment i've only tried running this via rails console. Even when explicitly calling require 'xml_import' which returns true I get the same error.
Does anyone with experience of using custom delayed_jobs successfully have any idea what I'm doing wring here?
To answer my own question;
Any custom directories with classes and modules you want to be autoloadable must be added to config/application.rb like so:
config.autoload_paths += %W(
#{config.root}/app/jobs
)
The files contained within these folders must be named according to rails' conventions so XmlImportJob resides in xml_import_job.rb.

Logging in Rails with a per-request ID

I'm looking for a quick and easy way to generate a unique per-request ID in rails that I can then use for logging across a particular request.
Any solution should ideally not make too much use of the default logging code, as I'm running the application under both jruby and ruby.
Backupify produced a great article about this: http://blog.backupify.com/2012/06/27/contextual-logging-with-log4r-and-graylog/
We wanted the request_id (that is generated by rails and available at request.uuid to be present on all messages throughout the request. In order to get it into the rack logging (the list of parameters and the timing among others), we added it to the MDC in a rack middleware.
application.rb:
config.middleware.insert_after "ActionDispatch::RequestId", "RequestIdContext"
app/controllers/request_id_context.rb: (had trouble finding it in lib for some reason)
class RequestIdContext
def initialize(app)
#app = app
end
def call(env)
Log4r::MDC.get_context.keys.each {|k| Log4r::MDC.remove(k) }
Log4r::MDC.put("pid", Process.pid)
Log4r::MDC.put("request_id", env["action_dispatch.request_id"])
#app.call(env)
end
end
If you push jobs onto delay job/resque, put the request_id into the queue. and in your worker pull it off and set into the MDC. Then you can trace the requests the whole way through
It looks like lograge (gem) automatically puts request.uuid in your logs.
They have this pattern:
bfb1bf03-8e12-456e-80f9-85afaf246c7f
This is now a feature of rails:
class WidgetsController < ApplicationController
def get
puts request.request_id
end
end
Maybe the NDC feature of log4r is usefull to you.

Resources