How to log something in Rails in an independent log file? - ruby-on-rails

In rails I want to log some information in a different log file and not the standard development.log or production.log. I want to do this logging from a model class.

You can create a Logger object yourself from inside any model. Just pass the file name to the constructor and use the object like the usual Rails logger:
class User < ActiveRecord::Base
def my_logger
##my_logger ||= Logger.new("#{Rails.root}/log/my.log")
end
def before_save
my_logger.info("Creating user with name #{self.name}")
end
end
Here I used a class attribute to memoize the logger. This way it won't be created for every single User object that gets created, but you aren't required to do that. Remember also that you can inject the my_logger method directly into the ActiveRecord::Base class (or into some superclass of your own if you don't like to monkey patch too much) to share the code between your app's models.

Update
I made a gem based on the solution below, called multi_logger. Just do this in the initializer:
MultiLogger.add_logger('post')
and call
Rails.logger.post.error('hi')
# or call logger.post.error('hi') if it is accessible.
and you are done.
If you want to code it yourself, see below:
A more complete solution would be to place the following in your lib/ or config/initializers/ directory.
The benefit is that you can setup formatter to prefix timestamps or severity to the logs automatically. This is accessible from anywhere in Rails, and looks neater by using the singleton pattern.
# Custom Post logger
require 'singleton'
class PostLogger < Logger
include Singleton
def initialize
super(Rails.root.join('log/post_error.log'))
self.formatter = formatter()
self
end
# Optional, but good for prefixing timestamps automatically
def formatter
Proc.new{|severity, time, progname, msg|
formatted_severity = sprintf("%-5s",severity.to_s)
formatted_time = time.strftime("%Y-%m-%d %H:%M:%S")
"[#{formatted_severity} #{formatted_time} #{$$}] #{msg.to_s.strip}\n"
}
end
class << self
delegate :error, :debug, :fatal, :info, :warn, :add, :log, :to => :instance
end
end
PostLogger.error('hi')
# [ERROR 2012-09-12 10:40:15] hi

A decent option that works for me is to just add a fairly plain class to your app/models folder such as app/models/my_log.rb
class MyLog
def self.debug(message=nil)
#my_log ||= Logger.new("#{Rails.root}/log/my.log")
#my_log.debug(message) unless message.nil?
end
end
then in your controller, or really almost anywhere that you could reference a model's class from within your rails app, i.e. anywhere you could do Post.create(:title => "Hello world", :contents => "Lorum ipsum"); or something similar you can log to your custom file like this
MyLog.debug "Hello world"

Define a logger class in (say) app/models/special_log.rb:
class SpecialLog
LogFile = Rails.root.join('log', 'special.log')
class << self
cattr_accessor :logger
delegate :debug, :info, :warn, :error, :fatal, :to => :logger
end
end
initialize the logger in (say) config/initializers/special_log.rb:
SpecialLog.logger = Logger.new(SpecialLog::LogFile)
SpecialLog.logger.level = 'debug' # could be debug, info, warn, error or fatal
Anywhere in your app, you can log with:
SpecialLog.debug("something went wrong")
# or
SpecialLog.info("life is good")

Here is my custom logger:
class DebugLog
def self.debug(message=nil)
return unless Rails.env.development? and message.present?
#logger ||= Logger.new(File.join(Rails.root, 'log', 'debug.log'))
#logger.debug(message)
end
end

class Article < ActiveRecord::Base
LOGFILE = File.join(RAILS_ROOT, '/log/', "article_#{RAILS_ENV}.log")
def validate
log "was validated!"
end
def log(*args)
args.size == 1 ? (message = args; severity = :info) : (severity, message = args)
Article.logger severity, "Article##{self.id}: #{message}"
end
def self.logger(severity = nil, message = nil)
#article_logger ||= Article.open_log
if !severity.nil? && !message.nil? && #article_logger.respond_to?(severity)
#article_logger.send severity, "[#{Time.now.to_s(:db)}] [#{severity.to_s.capitalize}] #{message}\n"
end
message or #article_logger
end
def self.open_log
ActiveSupport::BufferedLogger.new(LOGFILE)
end
end

I would suggest using Log4r gem for custom logging. Quoting description from its page:
Log4r is a comprehensive and flexible logging library written in Ruby for use
in Ruby programs. It features a hierarchical logging system of any number of
levels, custom level names, logger inheritance, multiple output destinations
per log event, execution tracing, custom formatting, thread safteyness, XML
and YAML configuration, and more.

class Post < ActiveRecord::Base
def initialize(attributes)
super(attributes)
#logger = Logger.new("#{Rails.root}/log/post.log")
end
def logger
#logger
end
def some_method
logger.info('Test 1')
end
end
ps = Post.new
ps.some_method
ps.logger.info('Test 2')
Post.new.logger.info('Test 3')

The Logging framework, with its deceptively simple name, has the sophistication you crave!
Follow the very short instructions of logging-rails to get started filtering out noise, getting alerts, and choosing output in a fine-grained and high-level way.
Pat yourself on the back when you are done. Log-rolling, daily. Worth it for that alone.

Related

How to make logging in rails libs works like in models and controllers

So far I always used "puts" to add custom logging infos to my code. But now it is kind of a pain. When I run rspec for exemple I'm not interested in all the verbose that I added with puts. So I installed the "logging and logging-rails gem" because its installation is real fast and satisfying.
It works well when I call logger from models and controller, but not when I'm using logger inside libraries. I get that error : NameError - undefined local variable or method `logger' for CustomLib:Class.
The easiest thing I succeeded is to call 'Rails.logger' instead of just 'logger'. But in that way in my logfile, the class referring to that line will be 'Rails' but I want 'CustomLib'. For models and controller the right classname is displayed without any intervention from myself.
# config/environnement/test.rb
# Set the logging destination(s)
config.log_to = %w[stdout]
config.log_level = :info
# Show the logging configuration on STDOUT
config.show_log_configuration = false
# lib/custom_lib.rb
class CustomLib
def initialize
Rails.logger.info 'foo'
end
end
When I will use or test my customlib class I'll get :
[2019-06-21T16:26:41] INFO Rails : foo
Instead I would like to see :
[2019-06-21T16:26:41] INFO CustomLib : foo
I'm a bit lost in all that log management in rails, I have no idea what to try next to reach that goal...
 Edit
When I put a byebug just before the "logger.info 'foo' " line and enter into it via 'step', I got two different results if whether I'm in a model/controller or a custom lib.
# In custom lib, step enters this file "gems/logging-2.2.2/lib/logging/logger.rb"
# And Rails.logger returns an object like this one beloow
Logging::Logger:0x000055a2182f8f40
#name="Rails",
#parent=#<Logging::RootLogger:0x000055a2182e7ee8
#name="root",
#level=1>,
# In model/controller, step enters this file "gems/logging-rails-0.6.0/lib/logging/rails/mixin.rb"
# And Rails.logger returns an object like this one beloow
Logging::Logger:0x0000557aed75d7b8
#name="Controller",
#parent=#<Logging::RootLogger:0x0000557aedfcf630
#name="root",
#level=0>,
At the end I found a better way, I just need to include Logging.globally on top of the module where I want that behavior:
# lib/custom_lib.rb
class CustomLib
include Logging.globally
def initialize
logger.info 'foo'
end
end
The Rails in the log line is the progname of the logger. You could create a new logger that sets the progname to 'CustomLib'with (say) Rails.logger.clone.tap {|l| l.progname = 'CustomLib' }, but that isn't really the purpose of progname, which is to specify the name of the program, not the name of a class.
You could instead include the class name in the log line:
Rails.logger.info "[#{self.class}] - some message"
Or, for a bit more effort, you could define your own formatter. Here's one with a convenience method for wrapping an existing logger:
class ClassNameFormatter
def self.wrap_logger(klass, logger)
logger.clone.tap { |l| l.formatter = new(klass, logger.formatter) }
end
def initialize(klass, formatter=nil)
#klass = klass
#formatter = formatter ||= Logger::Formatter.new
end
def call(severity, timestamp, progname, msg)
#formatter.call severity, timestamp, progname, "[#{#klass.name}] - #{msg}"
end
end
To use it:
class CustomLib
def initialize
#logger = ClassNameFormatter.wrap_logger self.class, Rails.logger
end
def call
#logger.info 'test log please ignore'
end
end

Best approach to mark specific Rails Models to be used in a module

I am trying to write a lib plugin/extension to perform an action where I need to know which Models have been marked for use with this plugin.
Currently, I am marking the models in the fashion of acts_as_something method which is added to each Model intended to be used with the plugin.
The main file of the plugin looks like this
# lib/foo.rb
module Foo
class << self
attr_accessor :models
end
self.models = []
module Model
def acts_as_foo
Foo.models << self
end
end
ActiveSupport.on_load(:active_record) do
extend Foo::Model
end
The intended use is to then call in a controller Foo.perform, which needs to know the marked models in order to carry out the intended action, the idea being getting the list of models from Foo.models.
It works as intended if when config.eager_load is set to true in development.rb , otherwise the files of the models have not been used/loaded yet and therefore Foo.models is an empty Array.
My goal is to be able to add more models to Foo without having to change Foo's code like this.
#app/models/bar.rb
class Bar < ApplicationRecord
acts_as_foo
end
Any ideas on the best way to implement this?
I had a similar problem before in my gem.
I ended up loading ONLY model files (which is the minimum of my requirement same as yours because the DSL code is there like your acts_as_foo), and not immediately eager loading all Rails-related files using Rails.application.eager_load!.
# lib/foo.rb
module Foo
class << self
attr_accessor :models
end
self.models = []
class Engine < Rails::Engine
initializer 'foo.eager_load_models' do |app|
unless app.config.eager_load
models_load_path = File.join(Rails.root, 'app', 'models')
# copied from https://apidock.com/rails/Rails/Engine/eager_load%21/class
matcher = /\A#{Regexp.escape(models_load_path.to_s)}\/(.*)\.rb\Z/
Dir.glob("#{models_load_path}/**/*.rb").sort.each do |file|
app.require_dependency file.sub(matcher, '\1')
end
end
end
end
module Model
def acts_as_foo
Foo.models << self
end
end
end
ActiveSupport.on_load(:active_record) do
extend Foo::Model
end

Dynamic registering classes in factory

I'm currently trying to achieve something similar to what is proposed in the chosen answer of this question: Ruby design pattern: How to make an extensible factory class?
class LogFileReader
##subclasses = { }
def self.create type
c = ##subclasses[type]
if c
c.new
else
raise "Bad log file type: #{type}"
end
end
def self.register_reader name
##subclasses[name] = self
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
register_reader :git
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
register_reader :bzr
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class SvnLogFileReader < LogFileReader
def display
puts "Subersion reader, at your service."
end
register_reader :svn
end
LogFileReader.create(:svn).display
The unit tests work flawlessly, but when I start the server no class is being registered. May I be missing something about how the static method call is working? When is the register_reader call made by each subclass?
To answer the OP's question about when the classes call register_reader, it happens when the file is loaded. Add this code to an initializer to load the files yourself.
Dir[Rails.root.join('path', 'to', 'log_file_readers', '**', '*.rb').to_s].each { |log_file_reader| require log_file_reader }

About strategy pattern and Rails

I want to incorporate the strategy pattern in my application.
I have stored under lib the following classes.
class Network
def search
raise "NO"
end
def w_read
raise "NO"
end
#...
end
AND
class FacebookClass < Network
def search
# FacebookClass specific...
end
def w_read
raise OneError.new("...")
end
end
AND
class TwitterClass < Network
def search
# TwitterClass specific...
end
def w_read
# TwitterClass specific...
end
def write
# TwitterClass specific...
end
end
Now I want to call the method search of TwitterClass from app/model/network_searcher.rb. How can I do that? Did I implemented the strategy pattern here successfully?
Going by the example in the Wikipedia, I think your app/model/network_searcher should be something like this
class NetworkSearcher
def initialize(search_class)
#search_class = search_class
end
def search_social
#search_class.search
end
def w_read_social
#search_class.w_read
end
def write_social
#search_class.write
end
end
Then in controller or where you want to invoke it, you can call like this:
search_class = TwitterClass.new # or FacebookClass.new
network_searcher = NetworkSearch.new(search_class)
network_searcher.search_social # or network_searcher.w_read_social or network_searcher.write_social
Also if you are keeping these classes in lib, for Rails 3, inorder to get these classes autoloaded, you need to add this line to config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
and also follow the naming convention for the filenames in Rails (for example TwitterClass should be named twitter_class.rb). Otherwise you will have to require these files wherever you are using these classes.
The strategy pattern is used to allow the algorithm to use to be selected at runtime. Without more details it's hard to say if this is appropriate to your problem. Assuming that it is then what you need is a way to set the search on your model and you can then use the selected algorithm elsewhere in your model. e.g.
class TheInformation
attr_writer :searcher
def other_method
..
# can use the selected searcher here
#searcher.search
..
end
end
Does that help?

Layer Supertype in ActiveRecord (Rails)

I'm developing a ruby on rails app and I want to be able to excecute a method on every AR object before each save.
I thought I'd create a layer-super-type like this:
MyObject << DomainObject << ActiveRecord::Base
and put in DomainObject a callback (before_save) with my special method (which basically strips all tags like "H1" from the string attributes of the object).
The catch is that rails is asking for the domain_object table, which I obviously don't have.
My second attempt was to monkeypatch active record, like this:
module ActiveRecord
class Base
def my_method .... end
end
end
And put that under the lib folder.
This doesnt work, it tells me that my_method is undefined.
Any ideas?
Try using an abstract class for your domain object.
class DomainObject < ActiveRecord::Base
self.abstract_class = true
# your stuff goes here
end
With an abstract class, you are creating a model which cannot have objects (cannot be instantiated) and don't have an associated table.
From reading Rails: Where to put the 'other' files from Strictly Untyped,
Files in lib are not loaded when Rails starts. Rails has overridden both Class.const_missing and Module.const_missing to dynamically load the file based on the class name. In fact, this is exactly how Rails loads your models and controllers.
so placing the file in the lib folder, it will not be run when Rails starts and won't monkey patch ActiveRecord::Base. You could place the file in config/initializers, but I think there are better alternatives.
Another method that I used at a previous job for stripping HTML tags from models is to create a plugin. We stripped a lot more than just HTML tags, but here is the HTML stripping portion:
The initializer (vendor/plugins/stripper/init.rb):
require 'active_record/stripper'
ActiveRecord::Base.class_eval do
include ActiveRecord::Stripper
end
The stripping code (vendor/plugins/stripper/lib/active_record/stripper.rb):
module ActiveRecord
module Stripper
module ClassMethods
def strip_html(*args)
opts = args.extract_options!
self.strip_html_fields = args
before_validation :strip_html
end
end
module InstanceMethods
def strip_html
self.class.strip_html_fields.each{ |field| strip_html_field(field) }
end
private
def strip_html_field(field)
clean_attribute(field, /<\/?[^>]*>/, "")
end
def clean_attribute(field, regex, replacement)
self[field].gsub!(regex, replacement) rescue nil
end
end
def self.included(receiver)
receiver.class_inheritable_accessor :strip_html_fields
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
end
end
Then in your MyObject class, you can selectively strip html from fields by calling:
class MyObject < ActiveRecord::Base
strip_html :first_attr, :second_attr, :etc
end
The HTML stripping plugin code already given would handle the specific use mentioned in the question. In general, to add the same code to a number of classes, including a module will do this easily without requiring everything to inherit from some common base, or adding any methods to ActiveRecord itself.
module MyBeforeSave
def self.included(base)
base.before_save :before_save_tasks
end
def before_save_tasks
puts "in module before_save tasks"
end
end
class MyModel < ActiveRecord::Base
include MyBeforeSave
end
>> m = MyModel.new
=> #<MyModel id: nil>
>> m.save
in module before_save tasks
=> true
I'd monkeypatch ActiveRecord::Base and put the file in config/initializers:
class ActiveRecord::Base
before_create :some_method
def some_method
end
end

Resources