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

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 :)

Related

Rescuing error does nothing?

I'm trying to rescue an exception with this method:
def template_deleted
mailchimp_client.templates.info(mailchimp_id)
rescue Mailchimp::InvalidTemplateError => error
puts "Template deleted in Mailchimp: #{error}"
return true
else
return false
end
And no matter what I use to output the message, whether it's STDERR, STDOUT, log.error, p, puts, or print, nothing gets out to the environment's log. This should definitely be returning an error, because the template definitely doesn't exist in Mailchimp.
When I try the same code in the console I can read the error just fine, so either there's something wrong with the rescuing itself (i.e., my method is returning false which it shouldn't), or there's something wrong with the way I'm outputting it.
To output something in the log file of the current environment, use the Rails logger like this:
logger.debug "Template deleted in Mailchimp: #{error}"
You can replace the debug method call with any logging level name, that are briefly described in the link above. Also don't forget to make sure you're running in correct environment!

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.

Logging a certain event only once in 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

Help troubleshoot a consistently repeatable mod_perl2 / $SIG{__DIE__} bug

This is mod_perl2 on Apache 2.2, ActiveState Perl 5.10 for win32.
I override $SIG{__DIE__} and turn on DBI's RaiseError flag, which AFAICT from the docs, should call my override when a database call fails. It seems to almost always, except in one case, and I can't understand why.
My script has an our $page variable, and being mod_perl2, I can get at this from the override like so:
use Carp::Trace;
my $full_trace = Carp::Trace::trace;
$full_trace =~ m/^(ModPerl::ROOT::ModPerl::Registry::.*::)handler .*$/m;
my $page;
if (defined $1)
{
eval '$page = $' . $1 . 'page';
if (defined $page)
{
$json = 1 if defined $$page{json_response};
if (defined $$page{dbh})
{
my $errno = $$page{dbh}->state;
if ($errno ~~ $$page{error_handling}{allowed})
{
# allowed to let it go--no report, expected possible user error at some level that couldn't be caught sooner (usually db level)
my $errmsg = $$page{error_handling}{translation_map}{$errno};
if (defined $errmsg)
{
...
This works fine. Now, within that $page, I have an array ref of 'allowed' error values that I want to do something different with when they come back from the DB. When the DB throws one of these errors, I want to translate it into a user-friendly message, $r->print that in JSON, and stop execution (behaviour A). For some reason, it instead returns control to the script (behaviour B).
Here's the main part of my script:
{
$$page{error_handling}{allowed} = ['22007'];
$$page{json_response}{result} = $page->one_liner("select 'aa'::timestamp");
$$page{json_response}{test} = $$page{error_handling}{state};
}
$page->make_json; # just JSONifies $$page{json_response} and prints it
If I comment out the first line, I get a normal error (handling something unexpected) (behaviour C), which is what I expect, because I haven't added the error that's occurring to the list of allowed errors. What's really strange is, if I cut that first line and paste it into my $SIG{__DIE__} override, it works: the JSON response is overridden, printed, and execution stops before {test} is assigned (behaviour A). Stranger still, I can set {allowed} to any set of numbers, and so long as it contains '22007' in particular, I get behaviour B. If it doesn't, I get behaviour C. Even more strange, I can actually fill my override with anything (warnings, calls to CORE::die, etc.--as long as it compiles) and I get behaviour B still--even though the override no longer contains any of the code that would make it possible! Also I don't get any of the expected results of the calls to warn and CORE::die, just silence in the logs, so I can't even attempt to manually trace the path of execution through my override.
I have restarted Apache2.2 in between every script save. I have even moved the override to the same script file as the script itself, out of the module where it normally is, and commented out the entire module file where the override normally is, and restarted.
If I take out that first line, or take '22007' out of it, I can warn and die and otherwise manually debug all I like, and everything works as expected. What is it about '22007' that it never outputs anything different despite server resets? There are no references to '22007' anywhere else in the entire project, except the translation map, and I can delete it from that file entirely and restart and the result is no different. It's behaving as if it has cached my override from earlier in the day and will never ever forget. It's not a browser cache issue either, because I can add random query strings and the results are no different.
This is the strangest and most frustrating mod_perl2 experience I've ever had, and I've run out of ideas. Does anybody have any hints? The only thing I can think of is that it's a caching problem, yet I've restarted the service countless times.
Since it was the end of the day I thought I would try fully restarting the server computer, and it still didn't change anything. I even, before restarting the server, changed the only line where {state} is assigned to this:
$$page{error_handling}{state} = 'my face'; # $errno;
And yet, the output afterwards had {test} as '22007', which is what it should be only if I had left = $errno intact.
Even if it was, say, the reverse proxy it goes through doing the caching, this situation doesn't make sense to me, since the request can be different. After a full server restart, how can it still be assigning a value that is no longer in the code, i.e., how can it be using my old $SIG{__DIE__} override after a full restart, when it no longer exists in any file?
Update: I also tried changing the allowed errors to '42601' and changing the db call to 'select', which produces that error code, but did not add it to the translation map. It still gives me behaviour B, setting {state} to '42601', so it's not specific to '22007'. Any error code that is put into {allowed}, if that error actually occurs, it's running the old version of the override. Cause an error that's not in {allowed} and it runs the current version. But how does it know whether the current error is in {allowed}, or that that even means anything, before getting to the override? (Because the override is the only place where {allowed} is grepped for the current error.)
This is my temporary workaround, but I would like to solve the mystery and not have to add the extra line everywhere I have a DB call with allowed errors.
package MyModule::ErrorLogging;
sub InsanityWorkaround # duplicates part of $SIG{__DIE__} override for allowed errors
{
my ($page) = #_;
my $r = $$page{r};
my $errno = $$page{error_handling}{state};
if ($errno ~~ $$page{error_handling}{allowed})
{
# allowed to let it go--no report, expected possible user error at some level that couldn't be caught sooner (usually db level)
my $errmsg = $$page{error_handling}{translation_map}{$errno};
if (defined $errmsg)
{
use JSON::XS qw(encode_json);
$$page{json_response} =
{
error => $errmsg,
};
my $response = encode_json($$page{json_response});
$r->content_type("application/json");
$r->print($response);
exit(0);
}
else
{
return 0; # get back to script where {state} can be checked and output can be customized even further
}
}
return;
}
Then my script becomes:
{
$$page{error_handling}{allowed} = ['22007']; # don't be bothered by invalid timestamp error
$$page{json_response}{result} = $page->one_liner("select 'aa'::timestamp");
MyModule::ErrorLogging::InsanityWorkaround($page);
}
This is giving behaviour A.

Ruby append to string if error contains string

I have a Slack Bot that needs to respond in an error condition. If the error has certain text in it, I want to append some additional information to the return message. This block of code works fine if I comment out the message += line but breaks if I do not. When I try to replicate this in irb everything works fine too.
Does something look obviously wrong here?
begin
scan = #nsc.scan_devices(devices)
rescue Nexpose::APIError => e
puts "[!] API ERROR: Most likely caused by an orphaned asset (#{device_ids})"
puts "[!] #{e}"
$slackbot_logger.error("[!] API ERROR: Most likely caused by an orphaned asset (#{device_ids})")
$slackbot_logger.error(e)
# Message back to Slack
message = "<##{user_id}> scan for #{ip_list} *failed* :sob:"
message += 'There is a scheduled blackout Tues/Thurs until 1000 CST' if e.include? 'blackout'
SlackFunctions.slack_send_message(message, channel)
return
end
This particular error object (and maybe all error objects) did not have an include? method. Therefore using e.to_s seems to do the trick.

Resources