Add session id to each log in Rails - ruby-on-rails-3

Is there a way to add the session id to each log in Rails.
Now, I've added this in my environment.rb:
class Logger
def format_message(severity, timestamp, progname, msg)
"#{timestamp.to_formatted_s(:db)} #{severity} #{msg}\n"
end
end
First of all, is this best-practice? And how do I add the session id?

Unfortunately, giving :session_id to log_tags don't work yet (as of May 23 2012), since there is no session_id in Rack middleware.
Since 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.

when you are working with rails 3.2 you can use log tags:
Update Rails::Rack::Logger middleware to apply any tags set in
config.log_tags to the newly ActiveSupport::TaggedLogging
Rails.logger. This makes it easy to tag log lines with debug
information like subdomain and request id -- both very helpful in
debugging multi-user production applications DHH
with some luck, adding this to your environment might work:
config.log_tags = [:uuid, :remote_ip, :session_id]
UPDATE
for the unlucky, the solution is from the answer of #shigeya.
unfortunately rails does NOT provide a way to access your sessionid in a sane manner.
you always have to rely on what rack is doing in it's internals... accessing the cookie_jar with your cookie key is the "recommended" way of doing so.
this is what i use for my application:
config.log_tags = [
:host,
:remote_ip,
lambda { |request| "#{request.uuid}"[0..15] },
lambda { |request| "#{request.cookie_jar["_session_id"]}"[0..15] },
]
output then looks like this:
[hamburg.onruby.dev] [127.0.0.1] [27666d0897c88b32] [BAh7B0kiD3Nlc3Np] Completed 200 OK in 298ms (Views: 286.2ms | ActiveRecord: 9.2ms)

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

Unfortunately this isn't easy with Rails log tags. Moreover, it clutters your logs to the point where they are unreadable.
I'd recommend something like timber, it captures session ids (and more) by simply augmenting your logs with metadata. It's automatic and you don't lose readability.

Related

Ruby logging to several backends using Active Support logger

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.

rails: resque worker log doesn't honor log_tags

I have the following line in my config/environments/development.rb:
config.log_tags = [lambda { |req| Time.now }]
which works perfectly for the rails log (it prepends every entry with a timestamp).
I'd like to have the same behaviour in my resque workers.
The output of
p Rails.application.config.log_tags
in a resque job's perform method shows me, that there is a proc in it:
[#<Proc:0x007ffb36c14920#/.../config/environments/development.rb:49 (lambda)>]
But still, the log entries don't have timestamps.
I'm running out of ideas on why this happens...
Log tags are evaluated once per request in Rack middleware (Rails::Rack::Logger#call). There is no Web request when Resque runs and so you see nothing.
To get what you want, you should add code similar to Rails::Rack::Logger#compute_tags to Resque as a job hook, e.g., in before_perform.

Rails Tagged Logging

I am using Tagged Logging with Unicorn and using the following configuration in my environment file.
config.logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
config.log_tags = [:uuid]
So far so good.
When it comes to tags, is there a way to -
Print out specific request headers
Print a custom UUID, i.e something that I can generate. The default UUID that rails spits out is too long.
See some examples from this Gist here https://gist.github.com/2513183
You can add a proc to the log_tags array, which has access to the request object.
You can generate a UUID in that proc, or you can pass something through the request.env from your ApplicationController in a before_filter, like so:
#application_controller.rb
before_filter :set_some_request_env
def set_some_request_env
request.env['some_var'] = "Happy"
end
# application.rb
config.log_tags = [
-> request {
request.env['some_var']
}
]
Update
You can use the #tagged method to add tags to all log messages sent within a given block.
To get a parameter from your request or controller params into the tagged output, you can do the following:
#application_controller.rb
around_filter :add_tags_to_logs
def add_tags_to_logs
Rails.logger.tagged(custom_uuid_for_current_user) do
yield
end
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 - Suppress an error message

I am using Rails 3 and AJAX and have a parent object which is being created through and AJAX request. This parent is sent with children and saves them all. However, if a child has an error, Rails will stop the request. Is there any way to tell Rails to ignore this? I understand the proper thing to do is find the problem within the Javascript sending the request and fix it. However, for the sake of learning, is there a way to tell Rails that some errors might be ignorable?
To save without validating use:
#parent.save(:validate => false)
Also, don't forget you can create conditional validation rules if needs be. For example, add a virtual attribute (an instance variable that is not persisted to the DB) accessible via bare_bones?. Then modify a validator like so:
validates_presence_of :nickname, :unless => "bare_bones?"
Then, in your controller you would do something like this:
#parent = Parent.new params[:parent]
#parent.bare_bones = true
#parent.save()
Hope this helps.
You are looking for exception handling.
begin
#code that may contain errors
rescue
#what to do if an error is encountered
end