Ruby logging to several backends using Active Support logger - ruby-on-rails-3

I am using Rails 3.2.12/Ruby 1.9.3 and am trying to set up multiple loggers so I can log both to a file and to a graylog server which we have set up. I have got close using this soltuion but with a Gelf logger - http://railsware.com/blog/2014/08/07/rails-logging-into-several-backends/
So I have back ported the ActiveSupport::Logger to my config/initializers and set up the gelf logger as below
(development.rb)
gelf_logger = GELF::Logger.new("greylogserver", 12201, "WAN", { :host => 'host', :facility => "railslog"})
Rails.logger.extend(ActiveSupport::Logger.broadcast(gelf_logger))
however I am finding that I only get errors logged to the graylog server
ArgumentError: short_message is missing. Options version, short_message and host must be set.
and when I debug the code I can see the args being passed into the Gelf Logger add method (below) always has the 1st element as the level, the 2nd is nil and the 3rd contains the message. This is confusing as the 2nd arg should be the message and the 3rd the progname. The only solution I have come up with is to alter the Gelf-rb gem (as below) by changing the 6th line to use args[1] for message then it works, however this is not ideal and there must be a way to fix this in my code.
def add(level, *args)
raise ArgumentError.new('Wrong arguments.') unless (0..2).include?(args.count)
# Ruby Logger's author is a maniac.
message, progname = if args.count == 2
[args[1], args[1]]
elsif args.count == 0
[yield, default_options['facility']]
elsif block_given?
[yield, args[0]]
else
[args[0], default_options['facility']]
end
....
Just to note when i directly set my Rails logger to use the Gelf logger in development.rb then it works fine
Rails.logger = GELF::Logger.new("greylogserver", 12201, "WAN", { :host => 'host', :facility => "railslog"})
So it has to be something to do with my implementation of ActiveSupport::Logger which is from here - https://github.com/rails/rails/blob/6329d9fa8b2f86a178151be264cccdb805bfaaac/activesupport/lib/active_support/logger.rb
Any help would be much appreciated

As #Leons mentions the same issue was reported to the project as issue #26. The poster wrote a patch with testcases and logged in issue #27 a pull request with a fix to make the interface of the add method identical with the usual definitions.
This was merged in on Dec 22nd, 2014. Since then no new release was made.
I think it is best to compile directly from the github repo with something like:
$ echo "gem 'gelf', :git => 'https://github.com/Graylog2/gelf-rb.git'" >>Gemfile
$ bundle install
or similar.
good luck.

Related

Phoenix Framework: How to Route Custom Media Type?

In the Phoenix Framework, how does one route a custom media type in Accepts?
Phoenix's own code comments indicate the following is all that is necessary—plus a recompile of deps, though the need for that escapes me. But, this seems not to work:
config.exs:
[…]
config :plug, :mimes, %{
"application/vnd.api+json" => ["json-api"]
}
router.ex:
pipeline :api do
plug :accepts, ["json-api"]
end
[…]
scope "/", SomeApp do
pipe_through :api
[…]
some_test.ex:
setup do
conn = conn() |> put_req_header("accept", "application/vnd.api+json")
{:ok, conn: conn}
end
All tests' requests (using conn from setup) receive HTTP 406 responses.
Turns out that the following is inadequate:
% touch deps/plug/mix.exs
% mix deps.compile plug
% mix clean
Instead, as #josé-valim suggests in the question's comments, deleting the entire _build directory did the trick. I went back and forth a few times to be sure, and each time I only touched and deps.compiled, no joy, and each time I removed _build, joy.

How to prevent initializers from running when running `rails generate`

I want to prepopulate my cache with an initializer, but I don't need this code to run every time I run rake or rails g, etc. Rake and Bundler are easy to deal with, but a similar solution does not work for the generators:
# config/initializers/prepop_cache.rb
if !defined?(::Bundler) and !defined?(::Rake) and !defined(Rails::Generators)
# do stuff
end
This must be because rails/generators (or something similar) is requireed at runtime. How can I check to see if the command being run is rails g xyz?
Update:
Two solutions can be found here: Rails 3 initializers that run only on `rails server` and not `rails generate`, etc
Still would like to know if it's possible in the manner I've tried above.
In Rails 3, what you're looking to do is conceivably possible, but in a hacky way. Here's how:
When you make a rails generate call, the callpath looks like this:
bin/rails is called, which eventually routes you to execute script/rails
script/rails is executed which requires rails/commands
rails/commands is loaded, which is the main point of focus.
Within rails/commands the code that runs for generate:
ARGV << '--help' if ARGV.empty?
aliases = {
"g" => "generate",
"c" => "console",
"s" => "server",
"db" => "dbconsole"
}
command = ARGV.shift # <= #1
command = aliases[command] || command
case command
when 'generate', 'destroy', 'plugin', 'benchmarker', 'profiler'
require APP_PATH
Rails.application.require_environment! # <= #2
require "rails/commands/#{command}" # <= #3
The points of interest are numbered above. Namely, that at point #1 the command that you're running is shifting off of ARGV. Which in your case means generate is going to be removed from the command line args.
At point #2 your environment gets loaded, at which point your initializers are going to be executed. And herein is the tough part - because nothing indicating a specific command has been loaded at this point (this occurs at #3) there is no information to determine a generator is being run!
Let's insert a script into config/initializer/debug.rb to see what is available if we ran rails generate model meep:
puts $0 #=> "script/rails"
puts ARGV #=> ["model", "meep"]
As you can see, there is no direct information that a generator is being run. That said, there is indirect information. Namely ARGV[0] #=> "model". Conceivably you could create a list of possible generators and check to see if that generator has been called on ARGV[0]. The responsible developer in me says this is a hack and may break in ways you'd not expect so I'd use this cautiously.
The only other option is to modify script/rails like you suggested -- which isn't too bad a solution, but would likely break when you upgrade to Rails 4.
In Rails 4, you've got more hope! By the time the application environment is being loaded, the generators namespace has already been loaded. This means that in an initializer you could do the following:
if defined? Rails::Generators #=> "constant"
# code to run if generators loaded
else
# code to run if generators not loaded
end

How do I prepend a session-id to every log message (Rails 3)?

My thinking is to capture the session_id and store it in thread local storage, e.g.
Thread.current[:session_id] = session[:session_id]
But some rails logging occurs before my filter is called.
I thought I might capture the session_id by writing a middleware plug-in. But again, I don't seem to get it early enough for all logging.
What is the earliest that I can capture session_id?
Thanks!
Since Rails 3.2 support tagged logging using log_tags configuration array, log_tags array accept a Proc object, and the proc object is invoked with a request object, I could configure the log_tags following way:
config.log_tags = [ :uuid, :remote_ip,
lambda {|req| "#{req.cookie_jar["_session_id"]}" } ]
It's working with Rails 3.2.3, with ActiveRecord's session store is in use.
Okay, I finally figured this out.
I moved ActionDispatch::Cookies and ActionDispatch::Session::CookieStore way earlier in the rack stack. This appears to be safe, and is necessary because otherwise some logging happens before the session is initialized.
I added my own rack middleware component that sets the session_id in thread local storage.
I override the rails logger and prepend the session_id to each log message.
This is very helpful in being able to separate out and analyze all logs for particular user session.
I'd be interested to know how anyone else accomplishes this.
Based on #Felix's answer. i've done these in rails 4:
# config/application.rb
config.middleware.delete "ActionDispatch::Cookies"
config.middleware.delete "ActionDispatch::Session::CookieStore"
config.middleware.insert_before Rails::Rack::Logger, ActionDispatch::Cookies
config.middleware.insert_before Rails::Rack::Logger, ActionDispatch::Session::CookieStore
# config/environment/development.rb and production.rb
config.log_tags = [
lambda {|req| "#{req.subdomain}/#{req.session["user_id"]}" },
:uuid
]
config.log_formatter = Logger::Formatter.new
This produces logs like this:
I, [2015-11-05T15:45:42.617759 #22056] INFO -- : [verimor/2] [77e593dc-c852-4102-a999-5c90ea0c9d66] Started GET "/home/dashboard" for 192.168.1.37 at 2015-11-05 15:45:42 +0200
[verimor/2] is subdomain/user_id (this is a multitenant app).
[77e593dc-c852-4102-a999-5c90ea0c9d66] is a unique id for this request. Useful for keeping track of the lifecycle of requests.
HTH.
For Rails 3.2 with ActiveSupport::TaggedLogging, if you're using :cookie_store:
config.log_tags = [ :uuid, :remote_ip,
lambda { |r| "#{r.cookie_jar.signed["_session_id"]["session_id"]}" } ]
Note: Change the "_session_id" with your :key value at config/initializers/session_store.rb
Related: https://stackoverflow.com/a/22487360/117382

Ruby on Rails with Sorcery reset password email has a undefined method error

Using Sorcery 0.7.4 with Rails 3.1.1 for authentication.
Everything was going well until I tried to setup password resetting.
Activation works perfectly and emails are sent, but for some reason I get this error when trying to send the reset password email.
undefined method `reset_password_email' for nil:NilClass
I copied the tutorial exactly, and when I did a quick test in the console it shot off the email as expected. In console:
user = User.find(1)
user.deliver_reset_password_instructions!
In the actual controller, it finds the user by the email submitted from the form and in the log I can see it is retrieving the right user and setting the token, but errors out as above and rolls back.
I checked the gem's code for deliver_reset_password_instructions! and there seems to be no reason for it to fail.
PasswordResetsController:
#user = User.find_by_email(params[:email])
#user.deliver_reset_password_instructions! if #user
The following is copied from the gem code:
Instance Method in Gem:
def deliver_reset_password_instructions!
config = sorcery_config
# hammering protection
return false if config.reset_password_time_between_emails && self.send(config.reset_password_email_sent_at_attribute_name) && self.send(config.reset_password_email_sent_at_attribute_name) > config.reset_password_time_between_emails.ago.utc
self.send(:"#{config.reset_password_token_attribute_name}=", TemporaryToken.generate_random_token)
self.send(:"#{config.reset_password_token_expires_at_attribute_name}=", Time.now.in_time_zone + config.reset_password_expiration_period) if config.reset_password_expiration_period
self.send(:"#{config.reset_password_email_sent_at_attribute_name}=", Time.now.in_time_zone)
self.class.transaction do
self.save!(:validate => false)
generic_send_email(:reset_password_email_method_name, :reset_password_mailer)
end
end
The method called above for mailing:
def generic_send_email(method, mailer)
config = sorcery_config
mail = config.send(mailer).send(config.send(method),self)
if defined?(ActionMailer) and config.send(mailer).superclass == ActionMailer::Base
mail.deliver
end
end
Again all the required mailer bits and pieces are there and work from the console.
Uncomment this lines in the sorcery initializer
user.reset_password_mailer = UserMailer
user.reset_password_email_method_name = :reset_password_email
Check your app/mailers/user_mailer.rb file.
If you were following the tutorial you probably did something like copy and paste the method definition from the wiki (which takes one parameter) into the generated method definition (which doesn't take any parameter), hence the 1 for 0 ArgumentError.
In other words, you likely have something that looks like this:
def reset_password_email
def reset_password_email(user)
This is bad, but an easy fix :-)

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.