Seeking finer-grained logging options in Rails. How can I log entire requests and/or individual filters to alternative log files? - ruby-on-rails-3

This is a two part question, but may have the same answer.
Part One:
In our app, one particular controller get hit a lot -- so much so that we'd like to it be logged in a file separate from all other requests. Setting the FoosController.logger is not what I'm looking for, because the request exercises some lib files and active record object that have their logger object, and rails will log some info before handing control to the controller in question.
Part Two:
We have a global before filter included in our root application_controller.rb that is run before most actions of most controllers. This before_filter is very wordy in the logs, and is a candidate for having all its logging info sent to a separate file. This before filter also calls out to libs and ActiveRecord code with their own refererences to the logger.
One possible solution is to run the single controller as its own standalone application. I haven't tried it yet, because it's pretty tied into the internals of app. This approach also would not help with the before_filter.
Are there any good solutions for more fine-grained logging in rails apps?
Thanks!

For part I
I recommend creating your own logger class (possibly inheriting from the ruby logger (1), have the logger filter out the request url and based on that log to a specific file.
For part II
the easiest solution would be to just use a seperate logger for these methods. Instead of logger.debug "your message" you just call method_logger.debug "your message".
Creating your own logger is simple, just use the ruby logger class (1). The following code creates a logger that logs to a my_method.log file in the logs dir of your rails application.
method_logger = Logger.new(Rails.root.join('logs','my_method.log')
You can then write to your log with the following command, which should look familiar to you as rails uses the Ruby logger as well.
method_logger.debug "your debug message here"
(1) http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html

Here's the code for a custom logger that you can use to solve Part I. I adapted it for my own use from another Stack Overflow question, even including the answerer's inline comments. The main difference is that you can specify a different log path for development and production:
# app/concerns/event_notifications_logger.rb
class EventNotificationsLogger < Rails::Rack::Logger
def initialize(app, opts = {})
#default_logger = Rails.logger
#notifications_logger = Logger.new(notifications_log_path)
#notifications_logger.formatter = LogFormat.new
#notifications_logger.level = #default_logger.level
#app = app
#opts = opts
end
def call(env)
logger = if env['PATH_INFO'] == '/event_notifications/deliver'
#notifications_logger
else
#default_logger
end
# What?! Why are these all separate?
ActiveRecord::Base.logger = logger
ActionController::Base.logger = logger
Rails.logger = logger
# The Rails::Rack::Logger class is responsible for logging the
# 'starting GET blah blah' log line. We need to call super here (as opposed
# to #app.call) to make sure that line gets output. However, the
# ActiveSupport::LogSubscriber class (which Rails::Rack::Logger inherits
# from) caches the logger, so we have to override that too
#logger = logger
super
end
private
def notifications_log_path
Rails.env.production? ? '/var/log/event-notifications.log' : Rails.root.join('log/event-notifications.log')
end
end
Then include it in your application configuration:
require File.expand_path('../boot', __FILE__)
require 'rails/all'
require File.expand_path('../../app/concerns/event_notifications_logger', __FILE__)
module YourApp
class Application < Rails::Application
config.middleware.swap Rails::Rack::Logger, EventNotificationsLogger
# ...
end
end

Rails 3.2 provides filter options for the logs, called "Tagged Logging".
See the announcement or the new Rails 3.2 guide on ActiveSupport.

Related

Rails 3 ActionMailer class loaded but methods not available in controller

Many simmilar Q/A on this topic here and there, but I was unable to find exact solution for my problem. Using Rails 3.0.9 now, and trying to upgrade existing older application(not Rails).
The goal is to send simple email to new clients created by admins.
Have been following this oficial guide (and many others), but with no success.
The issue is, that method(s) defined in this controller, from class 'UserMailer', aren`t recognised from another controller, while class 'UserMailer' itself recognised is(how do I know this, will be explained below):
/app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
default :from => "info#xxxxx.sk"
def kokotina # << this is just a dummy method for testing
caf = "ssss"
end
def regMailUsr(nazov, priezvisko, email, pass)
#nazov = nazov
#priezvisko = priezvisko
#email = email
#pass = pass
#url = "http://loyalty2.xxxx.sk"
mail(to: email, subject: 'Vaša registrácia bola dokončená.')
end
end
I have also created View for this mail controller but that is not important right now.
The fragments from clients controller are here:
/app/controllers/clients_controller.rb
# encoding: UTF-8
class ClientsController < ApplicationController
load_and_authorize_resource
.......
def new
#noveHeslo = genHeslo(10) # << I defined this in application_controller.rb and it works
UserMailer.kokotina # << just a dummy method from UserMailer
#client = Client.new(params[:client])
.......
end
.......
def create
.......
if #client.save
#send email to new client:
UserMailer.regMailUsr(params[:client][:fname], params[:client][:lname], params[:client][:email], params[:client][:password]).deliver
.....
end ......
Now how do I know that my class is loaded? If in client controller, I change 'UserMailer' to 'xUserMailer', I will get 'no class or method in ...' error, but without 'x', I get only:
'undefined method `kokotina' for UserMailer:Class'
I also tried to define my methods in UserMailer:Class like this:
def self.kokotina # << this is just a dummy method for testing
caf = "ssss"
end
#or even like this
def self <<
def kokotina # << this is just a dummy method for testing
caf = "ssss"
end
end
#and then tried to invoke this method(s) like this:
UserMailer.new.kokotina
#or simply
kokotina
Strange is, that when I put contents of file '/app/mailers/user_mailer.rb' at the end of 'application_helper.rb' file, just after the end of 'module ApplicationHelper', I get no errors but of course, it won`t work.
Please keep in mind that I have no problem coding in another languages, but this mystic/kryptic rules of Ruby on Rails are still a complete mistery to me and unfortunatelly, I don`t have time or even motivation to read time extensive quides or even books for RoR beginners. I have been coding much more difficult applications and implementations, but this heavily discriminating system is driving me nuts.
Thank you all!
Problem solved!
The trick was, that in '/app/mailers/user_mailer.rb', I had multibyte characters. In mail subject.
So I added:
# encoding: UTF-8
at the very first line of '/app/mailers/user_mailer.rb'
I found this by total accident: later my rails app could not start, and server was simply throwing HTTP 500 error. So no trace, error defining etc.
I found out that multibyte string in:
mail(to: email, subject: 'Vaša registrácia bola dokončená.')
Was responsible for crash. When I removed that string, I noticed one important side effect: my methods became magicaly available for another controller!!!!
So if someone could give me at least one reason to lowe Rails...

Resque and Resque_mailer with Devise

I am implementing background email processing with Resque using the resque_mailer gem (https://github.com/zapnap/resque_mailer). I was able to get it to work for all my emails except the ones sent by Devise.
I went through a bunch of SO questions, and blog posts (for instance http://teeparham.posterous.com/send-devise-emails-with-resque) but could not find a way to get it to work.
What are the precise steps to follow to get resque_mailer to work with Devise?
I went through tee's answer and several resources online, but couldn't find a working solution.
After a few days of reading through resque-mailer and devise code, a solution that worked for me. Thanks to tee for gist which put me in right direction.
Assuming your app/mailers/application_mailer.rb looks similar to
class ApplicationMailer < ActionMailer::Base
include Resque::Mailer # This will add a `self.perform` class method, which we will overwrite in DeviseResqueMailer
end
In config/initializers/devise.rb
Devise.parent_mailer = "ApplicationMailer"
Devise.setup do |config|
config.mailer = 'DeviseResqueMailer'
end
In the resource class which uses devise, overwrite the send_devise_notification method to send resource class and id instead of object to prevent marshalling
# app/models/user.rb
protected
def send_devise_notification(notification, *args)
# Based on https://github.com/zapnap/resque_mailer/blob/64d2be9687e320de4295c1bd1b645f42bd547743/lib/resque_mailer.rb#L81
# Mailer may completely skip Resque::Mailer in certain cases - and will fail as we write custom handle in DeviseResqueMailer assuming mails are handled via resque
# So in those cases, don't retain original devise_mailer so things work properly
if ActionMailer::Base.perform_deliveries && Resque::Mailer.excluded_environments.exclude?(Rails.env.to_sym)
# Originally devise_mailer.send(notification, self, *args).deliver
# Modified to ensure devise mails are safely sent via resque
resource_id, resource_class = self.id, self.class.name
devise_mailer.send(notification, {resource_id: resource_id, resource_class: resource_class}, *args).deliver
else
super
end
end
Finally, in app/mailers/devise_resque_mailer.rb, fetch the record again from the database and continue
class DeviseResqueMailer < Devise::Mailer
def self.perform(action, *args)
# Hack to prevent RuntimeError - Could not find a valid mapping for admin.attributes
record_hash = args.shift
record = record_hash["resource_class"].constantize.find(record_hash["resource_id"])
args.unshift(record)
super # From resque-mailer
end
end
I feel this approach is a better than using devise-async as all the mails go through same code path. Its easier to control and overwrite if needed.
I'd take a look at devise-async. Looks like it fits your use case. Devise Async

Disable Rails SQL logging in console

Is there a way to disable SQL query logging when I'm executing commands in the console? Ideally, it would be great if I can just disable it and re-enable it with a command in the console.
I'm trying to debug something and using "puts" to print out some relevant data. However, the sql query output is making it hard to read.
Edit:
I found another solution, since setting the logger to nil sometimes raised an error, if something other than my code tried to call logger.warn
Instead of setting the logger to nil you can set the level of the logger to 1.
ActiveRecord::Base.logger.level = 1 # or Logger::INFO
To turn it off:
old_logger = ActiveRecord::Base.logger
ActiveRecord::Base.logger = nil
To turn it back on:
ActiveRecord::Base.logger = old_logger
This might not be a suitable solution for the console, but Rails has a method for this problem: Logger#silence
ActiveRecord::Base.logger.silence do
# the stuff you want to be silenced
end
Here's a variation I consider somewhat cleaner, that still allows potential other logging from AR. In config/environments/development.rb :
config.after_initialize do
ActiveRecord::Base.logger = Rails.logger.clone
ActiveRecord::Base.logger.level = Logger::INFO
end
For Rails 4 you can put the following in an environment file:
# /config/environments/development.rb
config.active_record.logger = nil
In case someone wants to actually knock out SQL statement logging (without changing logging level, and while keeping the logging from their AR models):
The line that writes to the log (in Rails 3.2.16, anyway) is the call to debug in lib/active_record/log_subscriber.rb:50.
That debug method is defined by ActiveSupport::LogSubscriber.
So we can knock out the logging by overwriting it like so:
module ActiveSupport
class LogSubscriber
def debug(*args, &block)
end
end
end
I used this: config.log_level = :info
edit-in config/environments/performance.rb
Working great for me, rejecting SQL output, and show only rendering and important info.
In Rails 3.2 I'm doing something like this in config/environment/development.rb:
module MyApp
class Application < Rails::Application
console do
ActiveRecord::Base.logger = Logger.new( Rails.root.join("log", "development.log") )
end
end
end
Just as an FYI, in Rails 2 you can do
ActiveRecord::Base.silence { <code you don't want to log goes here> }
Obviously the curly braces could be replaced with a do end block if you wanted.
I use activerecord 6.0.3.3 and I had to include ActiveSupport::LoggerSilence
include ActiveSupport::LoggerSilence
ActiveSupport::LoggerSilence.silence do
## everything you want to silence
end
This however did not work with anything related to creating or deleting SQL tables like ActiveRecord::Migration.drop_table. For this to be silenced I added:
ActiveRecord::Schema.verbose = false
I had to solve this for ActiveRecord 6, and I based my answer on fakeleft's response, but it wasn't quite right, since it was suppressing other logging such as the logging of nested views. What I did was created config/initializers/activerecord_logger.rb:
# Suppress SQL statement logging if necessary
# This is a dirty, dirty trick, but it works:
if ENV["ACTIVERECORD_HIDE_SQL"].present?
module ActiveRecord
class LogSubscriber
def sql(event)
end
end
end
end
The log subscriber in AR 6 has a sql event that we want to hide, so this is very narrowly targeted to skip that event.

How to override the log path for command line jobs invoked using "rails runner"?

My Rails3 application includes several "rails runner" jobs invoked via cron.
I would like to send the output from jobs run via cron to a different log file then the log output from normal web requests.
I'd like to avoid creating a separate environment just to do this because everything is identical except for the location of the log output.
The best suggestion I could cobble together from similar questions was to reset the paths.log config after initialization, by doing something like the following...
class SomeModel < ActiveRecord::Base
def self.some_job
Rails.configuration.paths.log = "/path/to/cron.log"
//do some stuff
end
end
... but unfortunately that doesn't work. The log output still appears in log/{environment}.log.
Any suggestions?
And the answer is:
class SomeModel < ActiveRecord::Base
ActiveRecord::Base.logger = Logger.new('/path/to/cron.log')
def self.some_job
//do some stuff
end
end

How can I minimize logging of monitoring requests?

My rails app is pinged every minute for a health check and I want to keep these out of the log unless there is an error. I was able to do this in Rails 2.3.5 by setting the logger with this in application_controller.rb:
def logger
if params[:__no_logging__] == 'true' && params[:controller] == 'welcome'
&& params[:action] == 'index'
# ignore monitoring requests
RAILS_MONITOR_NULL_LOGGER
else
RAILS_DEFAULT_LOGGER
end
end
But this doesn't work in Rails 3.0.5
I've been able to put together a new solution by monkeypatching before_dispatch and after_dispatch in Rails::Rack::Dispatch:
require 'active_support/core_ext/time/conversions'
module Rails
module Rack
# Log the request started and flush all loggers after it.
class Logger
include ActiveSupport::BufferedLogger::Severity
def before_dispatch(env)
request = ActionDispatch::Request.new(env)
#path = request.filtered_path
path = request.fullpath
if request.path == '/' && request.parameters['__no_logging__'] == 'true'
#log_level = logger.level
logger.level = Logger::ERROR
#logger.level = 3
end
info
"\n\nStarted #{request.request_method}
\"#{path}\" " \
"for #{request.ip} at #{Time.now.to_default_s}"
end
def after_dispatch(env)
logger.level = #log_level unless #log_level.nil?
ActiveSupport::LogSubscriber.flush_all!
end
end
end
end
I put the patch in config/initializers/monkey_patch.rb
This works exactly as I need, I don't see this request in the log:
http://mydomain.com?__no_logging__=true
But all other request remain in the log unaffected
But there are still two problems:
1. I needed to comment out:
path = request.filtered_path
Because it causes this error:
ERROR NoMethodError: undefined method `filtered_path' for #<ActionDispatch::Request:0x105b4c0e8>
/ce_development/Rails/g3/config/initializers/monkey_patches.rb:52:in `before_dispatch'
/ce_development/Rails/g3/.bundle/ruby/1.8/gems/railties-3.0.5/lib/rails/rack/logger.rb:12:in `call'
...
I now understand this is not a problem. The offending method "request.filtered_path" doesn't exist in Rails 3.0.5, which I am using. I inadvertently copied my class from Rails 3.1.0.beta which does define filtered_path. Rails 3.0.5 uses request.fullpath as now shown above.
2. I needed to comment out
logger.level = Logger::ERROR
Because it causes this error:
ERROR NameError: uninitialized constant Rails::Rack::Logger::ERROR
/ce_development/Rails/g3/config/initializers/monkey_patches.rb:57:in `before_dispatch'
/ce_development/Rails/g3/.bundle/ruby/1.8/gems/railties-3.0.5/lib/rails/rack/logger.rb:12:in `call'
...
I solved this second problem by adding this line above
include ActiveSupport::BufferedLogger::Severity
I'm new to monkey patching and I can't figure out how to get filtered_path or Logger::Error defined in my patch. I've tried other requires, but no luck yet.
I'd also like any advice about the robustness of using this monkey patch on my project. Is there a better way to do this?
I know some people don't believe in altering logs, but I don't want all these pings in the log unless there is an error during its request.
A possible solution for Rails 3 where the Logger is replaced by a Custom Logger is described here: Silencing the Rails log on a per-action basis and here: How can I disable logging in Ruby on Rails on a per-action basis?. I had to add require 'rails/all' to the custom_logger.rb class to make it work.