Logging a certain event only once in ruby - ruby

Are there any logging frameworks in ruby that allow you to log a specific event type only once?
logger = IdealLogger.new
logger.log(:happy_path, "We reached the happy path") # => logs this message
logger.log(:happy_path, "We reached the happy path yet again") # => Doesn't log this
logger.log(:sad_path, "We've encountered a sad path!") # => logs this message
Also, is there a term for the concept of logging a certain event type only once?
Edit: I'm using Plain Old Ruby Objects, not Rails. I had in mind "once per time the script is run" for "once".

I'm not aware of one, but extending Logger to make your own isn't too difficult. It's essentially implementing caching for your logging, but instead of fetching from the cache and returning it like you would with a normal app, you quash it when it's been cached. Implementation and expiration strategy of this log cache are left as an exercise for the reader.
something like:
class IdealLogger < Logger
def info(event = nil, progname = nil, &block)
super(progname, &block) unless event_is_cached(event)
end
# define debug, warn, error, fatal, and unknown the same way, override others
# as you wish.
end

Related

How can I intercept ActiveRecord::Base.logger output?

Context: I am constructing a really long sql string and executing it with ActiveRecord. When it fails, it logs out the error (which includes the original query) and takes up 5 pages of screen space. Because I am already catching the exception, I don't need to be notified there was an error, and it just clutters the logging. All attempts to temporarily turn off the logger or to hijack IO streams have been futile.
Problem: How do I prevent logging of that one exception?
Example: (I know a lot of this code is redundant, but my point is that even all together it doesn't work)
really_long_query = "select * from posts where ..."
ActiveRecord::Base.logger.level = 10
$stderr = $stdout = $stdin = STDOUT = STDERR = STDIN = IO.new(IO.sysopen('/dev/null', 'w+'))
silence_stream(STDOUT){
ActiveRecord::Base.connection.execute really_long_query # TURN LOGGING OFF FOR THIS LINE
} # => still logs the exception to the console, despite all the above code
My Conclusions: Based on the above results, I assume that ActiveRecord must be using
A different logger AND
A stream not included on line 3
ActiveRecord exceptions are like normal exceptions, you can handle them, so please try:
begin
sql = ActiveRecord::Base.connection.execute really_long_query
rescue => e
# do what ever you want to do with error
end
I am no able to try it now, but I guess it may be help for you :)

Rails logger formatting - insert method name calling logger method

I want to insert name of the method calling the logger methods into my log files. Not the whole stack trace, but the class, method and/or line number would be great.
In any method, one can use caller to get an array of strings, each of which contains the file, line number and method name. I've come up with a pretty awful kludge using regexes and Enumerable#find to try to return the first non-logger stack frame. I guess it works, but if the locations of the logging Ruby files change in a different version or Rails, or I name my files something to do with logs, it will break. Same with if I take a given index from the top of the stack (I did this at first, then refactored one thing and naturally it gave me the wrong frame).
Note that I'm not looking to just log the controller or action, as those can be retrieved easily. Mostly this is for stuff in the lib/ directory.
Isn't there an easy way to do this? I don't want to have to pass in __method__ every time I make a logging statement.
I've looked all over at different solutions for capturing the exact place (file, line number, method name) where I invoke any given logger instance method from within my rails app. To do this, you need to override Logger's format_message method, and a good place to do this is in your rails project's config/environment.rb file.
This is what I've come up with, which is good enough for me ;o)
class Logger
def format_message(severity, timestamp, progname, msg)
line = ''
Kernel.caller.each{|entry|
if (entry.include? Rails.root.to_s)
line = " #{entry.gsub(Rails.root.to_s,'').gsub(/\/(.+)\:in `(.+)'/, "\\1 -> \\2")}"
break
end
}
"[#{timestamp.strftime("%Y%m%d.%H:%M:%S")}] #{severity}#{line}: #{msg}\n"
end
end
Kernel.caller holds an enumerable array of the entire backtrace. If you look at it in its entirety, you'll see most calls are internal inside of a gem somewhere well outside your project. I've found that by looping through the Kernel.caller until I find the first place that includes my Rails.root, I can get the line with the information I want to parse.
Example:
If I call Rails.logger.debug("Streamer class started!") from the start method of my Streamer class, the raw entry would look like this:
/Users/chikoon/www/my_rails_app/lib/streamer.rb:7:in `start'
so by the time it makes it through my formatter, I've got the timestamp, severity mode, the file path, line number, method name, and message:
[20140919.19:23:44] DEBUG lib/streamer.rb:7 -> start: Streamer class started!
I hope that helps get your wheels turning.
How about setting up log_tags to call the __method__?
Blog::Application.configure do
config.log_tags = [lambda { |req| __method__ }]
end

Different log level for Sidekiq workers - Ruby approach?

I have a running environment with a Rails application, Sidekiq and clockwork mod for scheduling purposes.
I have many different workers, filled with logger.debug and logger.info instructions, and I occasionally need to activate debug logging on some of them to know what's going on.
I like the Sidekiq logger, and I would like to utilize it because it just need a "logger.debug" instruction in the workers to do its job.
What I miss with my current setup is the possibility to activate the DEBUG level for some workers, while leaving the others in standard INFO.
Now in each of my workers I have this initialize method:
class SendMailOnStart
include Sidekiq::Worker
sidekiq_options :retry => false, :queue => :critical
def initialize
logger.level = Logger::INFO
end
.... ...
But if a change the level in one worker, this level will be overwritten by the level specified in the next one - e.g. if two workers are processed together, the second one will "win".
What's the best way to achieve this in an elegant way?
Coming from Java world, I can think only to create a custom logger and putting it in each worker, copying output format used by Sidekiq logger, adding a logger method in each worker like
def logger
logger = MyLogger.new
end
and changing the level when I neeed it in initialize method
Is this the best approach in Ruby?
I had a similar question and I found this thread more useful:
Log to different logger based on call from Sidekiq or Rails
You should be able set the log level for Sidekiq workers specifically in the block mentioned there by altering Rails.logger.
I have no clue if that’s the best approach, but I would do the following. First of all, let’s prepare the function to retrieve the caller’s filename and/or method:
def parse_caller
# magic number 7 below is the amount of calls
# on stack to unwind to get your caller
if /^(?<file>.+?):(?<line>\d+)(?::in `(?<method>.*)')?/ =~ caller(7).first
file = Regexp.last_match[:file]
line = Regexp.last_match[:line].to_i
method = Regexp.last_match[:method]
[file, line, method]
end
end
Then I would override the default formatter of Logger instance, compelling it to check the caller:
logger.formatter = lambda do |severity, datetime, progname, msg|
f,l,m = parse_caller
# HERE GOES YOUR CHECK
if f =~ /…/
…
end
end
I know it looks a weird hack, but it works fine for me.

Ruby Mongo: How to disable logging on certain collections?

I am using Mongoid for interacting with MongoDB. In development I usually like to see the logs of what Mongo is doing. However, there is one instance where there is an excessive amount of redundant logging that I simply don't want to see. How can I disable logging in this specific case?
There doesn't appear to be any clean way to control logging in Mongoid on a per collection basis.
However, for your purposes, if you are able to identify the individual calls,
you can turn off logging temporarily by raising the level.
Hope that this is good enough for your purposes.
require 'test_helper'
def dont_log(temp_level = Logger::Severity::UNKNOWN)
logger = Rails.logger
old_level, logger.level = logger.level, temp_level
yield
logger.level = old_level
end
class LogDoTest < ActiveSupport::TestCase
def setup
LogDo.delete_all
LogDont.delete_all
end
test "log do dont" do
LogDo.create(text: 'Log me')
dont_log{ LogDont.create(text: 'Dont log me') }
end
end

Using Ruby's Logger to have errors go to standard error, but other messages to file/stdout?

I'd like to use Ruby's logger in command-line apps; it beats puts and has good flexibility for logging things.
One thing that I would like is to be able to have error/fatal messages go to the standard error (as is customary) in addition to where the logger's messages are configured to go.
logger = Logger.new(some_file)
logger.debug("This goes to some_file, if debug is set")
logger.info("This goes to some_file, if info is set")
logger.error("This goes to some_file, AND stderr")
One way I've done this is to hack the formatter:
logger.formatter = Proc.new do |severity,time,progname,msg|
message = format_message(severity,time,progname,msg)
if severity == ERROR
$stderr.puts message
end
message
end
This seems hacky. Another way might be to create a Logger that proxies its calls to an underlying real logger, but intercepting the error messages.
Anyone done this, and is there maybe something already that does this?
Anyone done this, and is there maybe something already that does this?
Ruby is cool, and flexible, and powerful, and stuff. Just with these monkey-patching things and abilities to open a class and add methods, it's too easy to forget about plain old inheritance...
class CopyLogger < Logger
def error message
# Print to standard error...
$stderr.puts message
# ...*and* to wherever you specified as well
super message
end
end
logger = CopyLogger.new(some_file)
# ...
You may put additional parameters into the constructor of your new class, such as the desired severity to tee messages of, etc.
I usually tail the log file to monitor while developing and put custom, user friendly messages for the masses.

Resources