NameError: undefined local variable or method `logger' - ruby-on-rails

When I run 'script/server' everything works fine, but when I run my unit tests (rake test:units), I get the error below, and am not sure how to solve this.
Error
NameError: undefined local variable or method `logger' for #<GiveawayEligibleMemberTest:0x10477dff8>
/Users/kamilski81/Sites/pe/vitality_mall/vendor/rails/actionpack/lib/action_controller/test_process.rb:471:in `method_missing'
/Users/kamilski81/Sites/pe/vitality_mall/lib/update_giveaway_eligible_members.rb:17:in `is_valid_checksum?'
/Users/kamilski81/Sites/pe/vitality_mall/test/unit/giveaway_eligible_member_test.rb:26:in `test_that_checksum_is_valid'
/Users/kamilski81/Sites/pe/vitality_mall/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:60:in `__send__'
/Users/kamilski81/Sites/pe/vitality_mall/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:60:in `run'
I tried putting:
class Test::Unit::TestCase
RAILS_DEFAULT_LOGGER = Logger.new(STDOUT)
RAILS_DEFAULT_LOGGER.level = Logger::WARN
logger = Logger.new(STDOUT)
logger.level = Logger::WARN
end
Here is the code that is using my logger:
def is_valid_checksum?(csv_arr)
expected_row_count = csv_arr[0][3].to_i
logger.debug "Expected record count: #{expected_row_count}"
actual_row_count = csv_arr.nitems - 1
logger.debug "Actual record count: #{actual_row_count}"
checksum_valid = false
if expected_row_count == actual_row_count
logger.debug "Checksum is valid"
checksum_valid = true
end
return checksum_valid
end
But this still does not solve the error

You can use the Rails logger outside of models and controllers:
Rails.logger.info "..."
Source

The logger method isn't available to test cases instance methods.
You have defined a local variable in your class definition but this isn't enough. The logger variable falls out of scope once the class is initialized. You may be looking for a class variable (##logger). But a cleaner solution would be wrapping it in a method like this.
class Test::Unit::TestCase
def logger
RAILS_DEFAULT_LOGGER ||= Logger.new(STDOUT)
end
end
Notice this code will use the default logger if it is available (which it should be). If this isn't desired, you can make your own just as easily.
def logger
#logger ||= Logger.new(STDOUT)
end

you should use RAILS_DEFAULT_LOGGER.debug the constant in your test case is_valid_checksum? not the variable logger(class level variable ) which cannot be used in a instance method.
I would suggest to wrap the code in a instance method something like
def logger
logger = Logger.new(STDOUT)
logger.level = Logger::WARN
logger
end
and then use logger

Related

Overriding const_missing returns `NameError uninitialized constant` in non-dev environments

I have the following code in my rails app
# app/models/test_module/text_class.rb
module TestModule
class TestClass
end
end
# app/models/test_module.rb
module TestModule
def self.const_missing(name)
super(delete_end_number(name.to_s).to_sym)
end
def self.delete_end_number(str)
str.gsub(/\d+$/,'')
end
end
When it runs in development it works
>> TestModule::TestClass1
=> TestModule::TestClass
When I run it in production however I get
NameError (uninitialized constant TestModule::TestClass)
If I just copy TestModule::TestClass into the console it works. It seems just to not work with the const_missing method.
I suspect it may have something to do with the autoloading as when I set config.cache_classes and config.eager_load to true in development.rb it happens there too. I can't seem to figure out how to get it to work in cached environments though.
Change from
super(delete_end_number(name.to_s).to_sym)
To
const = delete_end_number(name.to_s).to_sym
ActiveSupport::Dependencies.load_missing_constant(self, const)

notify_airbrake throws error on custom classes

We use airbrake throughout our codebase which works great except for custom classes that do not inherit from a Rails hierarchy (i.e. ActiveRecord).
Some code:
class Offer
def counter(amount, qty=1)
begin
offers_response = viyet_mage_rest_client.counter_offer(#id.to_i, amount, qty.to_i)
rescue => e
notify_airbrake(e)
offers_response = []
end
end
end
In airbrake we see:
NoMethodError: undefined method `notify_airbrake' for Offer:Class
As the error. I want the actual error and not an error for reporting an error!
Our airbrake.rb file:
if Rails.env.production? || Rails.env.staging?
Airbrake.configure do |config|
config.api_key = Settings.airbrake.api_key
config.secure = true
end
end
Any help would be appreciated!
The method you need is Airbrake.notify_or_ignore(). For your given example:
class Offer
def counter(amount, qty=1)
begin
offers_response = viyet_mage_rest_client.counter_offer(#id.to_i, amount, qty.to_i)
rescue => e
Airbrake.notify_or_ignore(e)
offers_response = []
end
end
end
More detail can be found here: https://github.com/airbrake/airbrake/wiki/Using-Airbrake-with-plain-Ruby

Cannot require file in application.rb

I'm trying to simplify our configuration by creating small configuration classes that can be included in our application.rb.
lib/logging.rb
class << Logging
def configure(config)
# ... configure logging stuff
end
end
application.rb
require 'lib'
module MyApp
class Application < Rails::Application
Logging.configure(config)
end
end
The problem is if I don't use require "lib" then I get an Undefined Constant Logging error. But if I try to require it I get:
bin/rails:6: warning: already initialized constant APP_PATH
/opt/qtip/bin/rails:6: warning: previous definition of APP_PATH was here
The only way I've been able to get it to work is by doing this which is very limiting.
config.autoload_paths = %w(lib)
config.after_initialize do
::Logging.configure(config)
end
You have wrong class declaration.
Instead
class << Logging
you should use
class Logging
class << self
def configure(config)
end
end
end

Rails: How to raise I18n translation is missing exceptions in the testing environment

I want Rails to raise an exception when an I18n translation is missing in the testing environment (instead of rendering text 'translation missing'). Is there a simple way to achieve this?
As of Rails 4.1.0, there's now a better solution than the 4 year-old answers to this question: add the following line to your config file:
config.action_view.raise_on_missing_translations = true
I like to set this in the test environment only, but you might also want to set it in development. I would strongly advise against setting it to true in production.
To raise exceptions, you can define a class to handle localization errors.
class TestExceptionLocalizationHandler
def call(exception, locale, key, options)
raise exception.to_exception
end
end
Then you attach it to the desired test cases with
I18n.exception_handler = TestExceptionLocalizationHandler.new
This way you get exceptions raised. I don't know how to raise failures (with flunk) to get better results.
Or you can just add those lines to your config/test.rb
config.action_view.raise_on_missing_translations = true
config.i18n.exception_handler = Proc.new { |exception| raise exception.to_exception }
Rails 4.1+
To raise i18n translation missing exceptions you need two things:
1) An initializer config/initializers/i18n_force_exceptions.rb:
module I18n
class ForceMissingTranslationsHandler < ExceptionHandler
def call(exception, locale, key, options)
if Rails.env.test?
raise exception.to_exception
else
super
end
end
end
end
I18n.exception_handler = I18n::ForceMissingTranslationsHandler.new
2) A config setting in config/environments/test.rb (and other environments as needed):
config.action_view.raise_on_missing_translations = true
Note: The config setting is needed in addition to the exception handler because rails wraps calls to I18n.translate in it's view and helpers preventing exceptions from triggering.
I've created this initializer to raise an exception - args are passed so you will know which i18n key is missing!
# only for test
if Rails.env.test?
# raises exception when there is a wrong/no i18n key
module I18n
class JustRaiseExceptionHandler < ExceptionHandler
def call(exception, locale, key, options)
if exception.is_a?(MissingTranslation)
raise exception.to_exception
else
super
end
end
end
end
I18n.exception_handler = I18n::JustRaiseExceptionHandler.new
end
Source
If you're using rails between 4.0.0 to 4.1.0 you should monkey patch this way:
module ActionView::Helpers::TranslationHelper
def t_with_raise(*args)
value = t_without_raise(*args)
if value.to_s.match(/title="translation missing: (.+)"/)
raise "Translation missing: #{$1}"
else
value
end
end
alias_method :translate_with_raise, :t_with_raise
alias_method_chain :t, :raise
alias_method_chain :translate, :raise
end

How can I disable logging in Ruby on Rails on a per-action basis?

I have a Rails application that has an action invoked frequently enough to be inconvenient when I am developing, as it results in a lot of extra log output I don't care about. How can I get rails not to log anything (controller, action, parameters, completion time, etc.) for just this one action? I'd like to conditionalize it on RAILS_ENV as well, so logs in production are complete.
Thanks!
You can silence the Rails logger object:
def action
Rails.logger.silence do
# Things within this block will not be logged...
end
end
Use lograge gem.
Gemfile:
gem 'lograge'
config/application.rb:
config.lograge.enabled = true
config.lograge.ignore_actions = ['StatusController#nginx', ...]
The following works with at least Rails 3.1.0:
Make a custom logger that can be silenced:
# selective_logger.rb
class SelectiveLogger < Rails::Rack::Logger
def initialize app, opts = {}
#app = app
#opts = opts
#opts[:silenced] ||= []
end
def call env
if #opts[:silenced].include?(env['PATH_INFO']) || #opts[:silenced].any? {|silencer| silencer.is_a?( Regexp) && silencer.match( env['PATH_INFO']) }
Rails.logger.silence do
#app.call env
end
else
super env
end
end
end
Tell Rails to use it:
# application.rb
config.middleware.swap Rails::Rack::Logger, SelectiveLogger, :silenced => ["/remote/every_minute", %r"^/assets/"]
The example above shows silencing asset serving requests, which in the development environment means less ( and sometimes no) scrolling back is required to see the actual request.
The answer turns out to be a lot harder than I expected, since rails really does provide no hook to do this. Instead, you need to wrap some of the guts of ActionController::Base. In the common base class for my controllers, I do
def silent?(action)
false
end
# this knows more than I'd like about the internals of process, but
# the other options require knowing even more. It would have been
# nice to be able to use logger.silence, but there isn't a good
# method to hook that around, due to the way benchmarking logs.
def log_processing_with_silence_logs
if logger && silent?(action_name) then
#old_logger_level, logger.level = logger.level, Logger::ERROR
end
log_processing_without_silence_logs
end
def process_with_silence_logs(request, response, method = :perform_action, *arguments)
ret = process_without_silence_logs(request, response, method, *arguments)
if logger && silent?(action_name) then
logger.level = #old_logger_level
end
ret
end
alias_method_chain :log_processing, :silence_logs
alias_method_chain :process, :silence_logs
then, in the controller with the method I want to suppress logging on:
def silent?(action)
RAILS_ENV == "development" && ['my_noisy_action'].include?(action)
end
You can add the gem to the Gemfile silencer.
gem 'silencer', '>= 1.0.1'
And in your config/initializers/silencer.rb :
require 'silencer/logger'
Rails.application.configure do
config.middleware.swap Rails::Rack::Logger, Silencer::Logger, silence: ['/api/notifications']
end
The following works with Rails 2.3.14:
Make a custom logger that can be silenced:
#selective_logger.rb
require "active_support"
class SelectiveLogger < ActiveSupport::BufferedLogger
attr_accessor :silent
def initialize path_to_log_file
super path_to_log_file
end
def add severity, message = nil, progname = nil, &block
super unless #silent
end
end
Tell Rails to use it:
#environment.rb
config.logger = SelectiveLogger.new config.log_path
Intercept the log output at the beginning of each action and (re)configure the logger depending on whether the action should be silent or not:
#application_controller.rb
# This method is invoked in order to log the lines that begin "Processing..."
# for each new request.
def log_processing
logger.silent = %w"ping time_zone_table".include? params[:action]
super
end
With Rails 5 it gets more complicated request processing is logged in several classes. Firstly we need to override call_app in Logger class, let's call this file lib/logger.rb:
# original class:
# https://github.com/rails/rails/blob/master/railties/lib/rails/rack/logger.rb
require 'rails/rack/logger'
module Rails
module Rack
class Logger < ActiveSupport::LogSubscriber
def call_app(request, env) # :doc:
unless Rails.configuration.logger_exclude.call(request.filtered_path)
instrumenter = ActiveSupport::Notifications.instrumenter
instrumenter.start "request.action_dispatch", request: request
logger.info { started_request_message(request) }
end
status, headers, body = #app.call(env)
body = ::Rack::BodyProxy.new(body) { finish(request) }
[status, headers, body]
rescue Exception
finish(request)
raise
ensure
ActiveSupport::LogSubscriber.flush_all!
end
end
end
end
Then follow with lib/silent_log_subscriber.rb:
require 'active_support/log_subscriber'
require 'action_view/log_subscriber'
require 'action_controller/log_subscriber'
# original class:
# https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/log_subscriber.rb
class SilentLogSubscriber < ActiveSupport::LogSubscriber
def start_processing(event)
return unless logger.info?
payload = event.payload
return if Rails.configuration.logger_exclude.call(payload[:path])
params = payload[:params].except(*ActionController::LogSubscriber::INTERNAL_PARAMS)
format = payload[:format]
format = format.to_s.upcase if format.is_a?(Symbol)
info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
info " Parameters: #{params.inspect}" unless params.empty?
end
def process_action(event)
return if Rails.configuration.logger_exclude.call(event.payload[:path])
info do
payload = event.payload
additions = ActionController::Base.log_process_action(payload)
status = payload[:status]
if status.nil? && payload[:exception].present?
exception_class_name = payload[:exception].first
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
additions << "Allocations: #{event.allocations}" if event.respond_to? :allocations
message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
message << " (#{additions.join(" | ")})" unless additions.empty?
message << "\n\n" if defined?(Rails.env) && Rails.env.development?
message
end
end
def self.setup
# unsubscribe default processors
ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber|
case subscriber
when ActionView::LogSubscriber
self.unsubscribe(:action_view, subscriber)
when ActionController::LogSubscriber
self.unsubscribe(:action_controller, subscriber)
end
end
end
def self.unsubscribe(component, subscriber)
events = subscriber.public_methods(false).reject { |method| method.to_s == 'call' }
events.each do |event|
ActiveSupport::Notifications.notifier.listeners_for("#{event}.#{component}").each do |listener|
if listener.instance_variable_get('#delegate') == subscriber
ActiveSupport::Notifications.unsubscribe listener
end
end
end
end
end
# subscribe this class
SilentLogSubscriber.attach_to :action_controller
SilentLogSubscriber.setup
Make sure to load modified modules e.g. in config/application.rb after loading rails:
require_relative '../lib/logger'
require_relative '../lib/silent_log_subscriber'
Finally configure excluded paths:
Rails.application.configure do
config.logger_exclude = ->(path) { path == "/health" }
end
As we're modifying core code of Rails it's always good idea to check original classes in Rails version you're using.
If this looks like too many modifications, you can simply use lograge gem which does pretty much the same with few other modifications. Although the Rack::Loggger code has changed since Rails 3, so you might be loosing some functionality.
#neil-stockbridge 's answer not worked for Rails 6.0, I edit some to make it work
# selective_logger.rb
class SelectiveLogger
def initialize app, opts = {}
#app = app
#opts = opts
#opts[:silenced] ||= []
end
def call env
if #opts[:silenced].include?(env['PATH_INFO']) || #opts[:silenced].any? {|silencer| silencer.is_a?( Regexp) && silencer.match( env['PATH_INFO']) }
Rails.logger.silence do
#app.call env
end
else
#app.call env
end
end
end
Test rails app to use it:
# application.rb
config.middleware.swap Rails::Rack::Logger, SelectiveLogger, :silenced => ["/remote/every_minute", %r"^/assets/"]
Sprockets-rails gem starting from version 3.1.0 introduces implementation of quiet assets. Unfortunately it's not flexible at this moment, but can be extended easy enough.
Create config/initializers/custom_quiet_assets.rb file:
class CustomQuietAssets < ::Sprockets::Rails::QuietAssets
def initialize(app)
super
#assets_regex = %r(\A/{0,2}#{quiet_paths})
end
def quiet_paths
[
::Rails.application.config.assets.prefix, # remove if you don't need to quiet assets
'/ping',
].join('|')
end
end
Add it to middleware in config/application.rb:
# NOTE: that config.assets.quiet must be set to false (its default value).
initializer :quiet_assets do |app|
app.middleware.insert_before ::Rails::Rack::Logger, CustomQuietAssets
end
Tested with Rails 4.2
Rails 6. I had to put this in config/application.rb, inside my app's class definition:
require 'silencer/logger'
initializer 'my_app_name.silence_health_check_request_logging' do |app|
app.config.middleware.swap(
Rails::Rack::Logger,
Silencer::Logger,
app.config.log_tags,
silence: %w[/my_health_check_path /my_other_health_check_path],
)
end
That leaves the log_tags config intact and modifies the middleware before it gets frozen. I would like to put it in config/initializers/ somewhere tucked away but haven't figured out how to do that yet.

Resources