How to access Sinatra Logger from a model? - ruby

Within a request, the Sinatra logger works fine:
get '/' do
logger.info "loading data"
...
However, from within a model, it doesn't work. The model does not have access to the logger:
class Foo
def self.do_something
logger.info "loading data"
end
end
get '/' do
Foo.do_something
...
How to access the logger from within a model?

Seems you want to do
class Foo < Sinatra::Base
because logger is not available in the scope of the Foo class as you defined it.

#Bjoern's answer will work, but I suppose that would make Foo a controller and not a model. Another way would be to initialise a logger before Sinatra sets one up for you and then pass it to it to use. One way of doing this could be:
class Foo
def self.logger
#logger ||= Logger.new STDOUT
end
end
configure do
set :logger, Foo.logger
end

Related

Tagged Logging with Class and method

I want to log using the Ruby Logger but with the classes and methods that are in the call stack.
Things I have tried:
ActiveSupport::TaggedLogger
Kernel#caller and Kernel#caller_locations - only seems to give me the file and line number
TracePoint
Has anyone done something like this or have different ways to use the above? I was thinking there might be something with Binding that might have some information.
Example scenario:
logger = Logger.new(STDOUT)
class A
def initialize
#b = B.new
end
def call_b
b.meth_name('hello world')
end
end
class B
def meth_name(val)
logger.log_with_classes val #made up method name
end
end
A.new.call_b
"[A#call_b][B#meth_name] hello world"
=> nil

Carry logger instance between classes [duplicate]

Working on a little Ruby script that goes out to the web and crawls various services. I've got a module with several classes inside:
module Crawler
class Runner
class Options
class Engine
end
I want to share one logger among all those of those classes. Normally I'd just put this in a constant in the module and reference it like so:
Crawler::LOGGER.info("Hello, world")
The problem is that I can't create my logger instance until I know where the output is going. You start the crawler via command line and at that point you can tell it you want to run in development (log output goes to STDOUT) or production (log output goes to a file, crawler.log):
crawler --environment=production
I have a class Options that parses the options passed in through the command line. Only at that point do I know how to instantiate the logger with the correct output location.
So, my question is: how/where to I put my logger object so that all my classes have access to it?
I could pass my logger instance to each new() call for every class instance I create, but I know there has to be a better, Rubyish way to do it. I'm imagining some weird class variable on the module that shared with class << self or some other magic. :)
A little more detail: Runner starts everything by passing the command line options to the Options class and gets back an object with a couple of instance variables:
module Crawler
class Runner
def initialize(argv)
#options = Options.new(argv)
# feels like logger initialization should go here
# #options.log_output => STDOUT or string (log file name)
# #options.log_level => Logger::DEBUG or Logger::INFO
#engine = Engine.new()
end
def run
#engine.go
end
end
end
runner = Runner.new(ARGV)
runner.run
I need the code in Engine to be able to access the logger object (along with a couple more classes that are initialized inside Engine). Help!
All of this could be avoided if you could just dynamically change the output location of an already-instantiated Logger (similar to how you change the log level). I'd instantiate it to STDOUT and then change over to a file if I'm in production. I did see a suggestion somewhere about changing Ruby's $stdout global variable, which would redirect output somewhere other than STDOUT, but this seems pretty hacky.
Thanks!
I like to have a logger method available in my classes, but I don't like sprinkling #logger = Logging.logger in all my initializers. Usually, I do this:
module Logging
# This is the magical bit that gets mixed into your classes
def logger
Logging.logger
end
# Global, memoized, lazy initialized instance of a logger
def self.logger
#logger ||= Logger.new(STDOUT)
end
end
Then, in your classes:
class Widget
# Mix in the ability to log stuff ...
include Logging
# ... and proceed to log with impunity:
def discombobulate(whizbang)
logger.warn "About to combobulate the whizbang"
# commence discombobulation
end
end
Because the Logging#logger method can access the instance that the module is mixed into, it is trivial to extend your logging module to record the classname with log messages:
module Logging
def logger
#logger ||= Logging.logger_for(self.class.name)
end
# Use a hash class-ivar to cache a unique Logger per class:
#loggers = {}
class << self
def logger_for(classname)
#loggers[classname] ||= configure_logger_for(classname)
end
def configure_logger_for(classname)
logger = Logger.new(STDOUT)
logger.progname = classname
logger
end
end
end
Your Widget now logs messages with its classname, and didn't need to change one bit :)
With the design you've laid out, it looks like the easiest solution is to give Crawler a module method that returns a module ivar.
module Crawler
def self.logger
#logger
end
def self.logger=(logger)
#logger = logger
end
end
Or you could use "class <<self magic" if you wanted:
module Crawler
class <<self
attr_accessor :logger
end
end
It does the exact same thing.
As Zenagray points out, logging from class methods was left out of Jacob's answer. A small addition solves that:
require 'logger'
module Logging
class << self
def logger
#logger ||= Logger.new($stdout)
end
def logger=(logger)
#logger = logger
end
end
# Addition
def self.included(base)
class << base
def logger
Logging.logger
end
end
end
def logger
Logging.logger
end
end
The intended use is via "include":
class Dog
include Logging
def self.bark
logger.debug "chirp"
puts "#{logger.__id__}"
end
def bark
logger.debug "grrr"
puts "#{logger.__id__}"
end
end
class Cat
include Logging
def self.bark
logger.debug "chirp"
puts "#{logger.__id__}"
end
def bark
logger.debug "grrr"
puts "#{logger.__id__}"
end
end
Dog.new.bark
Dog.bark
Cat.new.bark
Cat.bark
Produces:
D, [2014-05-06T22:27:33.991454 #2735] DEBUG -- : grrr
70319381806200
D, [2014-05-06T22:27:33.991531 #2735] DEBUG -- : chirp
70319381806200
D, [2014-05-06T22:27:33.991562 #2735] DEBUG -- : grrr
70319381806200
D, [2014-05-06T22:27:33.991588 #2735] DEBUG -- : chirp
70319381806200
Note the id of the logger is the same in all four cases. If you want a different instance for each class, then don't use Logging.logger, rather use self.class.logger:
require 'logger'
module Logging
def self.included(base)
class << base
def logger
#logger ||= Logger.new($stdout)
end
def logger=(logger)
#logger = logger
end
end
end
def logger
self.class.logger
end
end
The same program now produces:
D, [2014-05-06T22:36:07.709645 #2822] DEBUG -- : grrr
70350390296120
D, [2014-05-06T22:36:07.709723 #2822] DEBUG -- : chirp
70350390296120
D, [2014-05-06T22:36:07.709763 #2822] DEBUG -- : grrr
70350390295100
D, [2014-05-06T22:36:07.709791 #2822] DEBUG -- : chirp
70350390295100
Note that the first two id's are the same but are different from the 2nd two ids showing that we have two instances -- one for each class.
Inspired by this thread I created the easy_logging gem.
It combines all the features discussed such as:
Adds logging functionality anywhere with one, short,
self-descriptive command
Logger works in both class and instance methods
Logger is specific to class and contains class name
Installation:
gem install 'easy_logging
Usage:
require 'easy_logging'
class YourClass
include EasyLogging
def do_something
# ...
logger.info 'something happened'
end
end
class YourOtherClass
include EasyLogging
def self.do_something
# ...
logger.info 'something happened'
end
end
YourClass.new.do_something
YourOtherClass.do_something
Output
I, [2017-06-03T21:59:25.160686 #5900] INFO -- YourClass: something happened
I, [2017-06-03T21:59:25.160686 #5900] INFO -- YourOtherClass: something happened
More details on GitHub.
The may be some weird Ruby magic that could let you avoid it, but there's a fairly simple solution that doesn't need weird. Just put the logger into the module and access it directly, with a mechanism to set it. If you want to be cool about it, define a "lazy logger" that keeps a flag to say if it has a logger yet, and either silently drops messages until the logger is set, throws an exception of something is logged before the logger is set, or adds the log message to a list so it can be logged once the logger is defined.
A little chunk of code to demonstrate how this works. I'm simply creating a new basic Object so that I can observe that the object_id remains the same throughout the calls:
module M
class << self
attr_accessor :logger
end
#logger = nil
class C
def initialize
puts "C.initialize, before setting M.logger: #{M.logger.object_id}"
M.logger = Object.new
puts "C.initialize, after setting M.logger: #{M.logger.object_id}"
#base = D.new
end
end
class D
def initialize
puts "D.initialize M.logger: #{M.logger.object_id}"
end
end
end
puts "M.logger (before C.new): #{M.logger.object_id}"
engine = M::C.new
puts "M.logger (after C.new): #{M.logger.object_id}"
The output of this code looks like (an object_id of 4 means nil):
M.logger (before C.new): 4
C.initialize, before setting M.logger: 4
C.initialize, after setting M.logger: 59360
D.initialize M.logger: 59360
M.logger (after C.new): 59360
Thanks for the help guys!
How about wrapping the logger in a singleton then you could access it using MyLogger.instance
Based on your comment
All of this could be avoided if you could just dynamically change the output location of an already-instantiated Logger (similar to how you change the log level).
If you are not restricted to the default logger you may use another log-gem.
As an example with log4r:
require 'log4r'
module Crawler
LOGGER = Log4r::Logger.new('mylog')
class Runner
def initialize
LOGGER.info('Created instance for %s' % self.class)
end
end
end
ARGV << 'test' #testcode
#...
case ARGV.first
when 'test'
Crawler::LOGGER.outputters = Log4r::StdoutOutputter.new('stdout')
when 'prod'
Crawler::LOGGER.outputters = Log4r::FileOutputter.new('file', :filename => 'test.log') #append to existing log
end
#...
Crawler::Runner.new
In prod mode the logging data are stored in a file (attached to existing file, but there are options to create new log files or implement rolling log files).
The result:
INFO main: Created instance for Crawler::Runner
If you use the inheritance mechanism of log4r (a), you may define a logger for each class (or in my following example for each instance) and share the outputter.
The example:
require 'log4r'
module Crawler
LOGGER = Log4r::Logger.new('mylog')
class Runner
def initialize(id)
#log = Log4r::Logger.new('%s::%s %s' % [LOGGER.fullname,self.class,id])
#log.info('Created instance for %s with id %s' % [self.class, id])
end
end
end
ARGV << 'test' #testcode
#...
case ARGV.first
when 'test'
Crawler::LOGGER.outputters = Log4r::StdoutOutputter.new('stdout')
when 'prod'
Crawler::LOGGER.outputters = Log4r::FileOutputter.new('file', :filename => 'test.log') #append to existing log
end
#...
Crawler::Runner.new(1)
Crawler::Runner.new(2)
The result:
INFO Runner 1: Created instance for Crawler::Runner with id 1
INFO Runner 2: Created instance for Crawler::Runner with id 2
(a) A logger name like A::B has the name B and is a child of the logger with the name A. As far as I know this is no object inheritance.
One advantage of this approach: If you want to use a single logger for each class, you need only to change the name of the logger.
Although an old question, I thought it worthwhile to document a different approach.
Building on Jacob's answer, I would suggest a module that you can add in as and when needed.
My version is this:
# saved into lib/my_log.rb
require 'logger'
module MyLog
def self.logger
if #logger.nil?
#logger = Logger.new( STDERR)
#logger.datetime_format = "%H:%M:%S "
end
#logger
end
def self.logger=( logger)
#logger = logger
end
levels = %w(debug info warn error fatal)
levels.each do |level|
define_method( "#{level.to_sym}") do |msg|
self.logger.send( level, msg)
end
end
end
include MyLog
I have this saved into a library of handy modules, and I would use it like this:
#! /usr/bin/env ruby
#
require_relative '../lib/my_log.rb'
MyLog.debug "hi"
# => D, [19:19:32 #31112] DEBUG -- : hi
MyLog.warn "ho"
# => W, [19:20:14 #31112] WARN -- : ho
MyLog.logger.level = Logger::INFO
MyLog.logger = Logger.new( 'logfile.log')
MyLog.debug 'huh'
# => no output, sent to logfile.log instead
I find this a lot easier and more versatile than other options I've looked at so far, so I hope it helps you with yours.

How to get `prints` method outside the method using logger

I am using gem 'logging'. And I have bunch of methods where I can not use logger instance, but I would like to get log message from this method using logger outside of the class. For example:
class Main
def method
p 'First log message'
execute some steps
p 'Another log message'
end
end
How can I log those two messages outside of the class using my logger like this: logger.warn(method) but as separate logs:
... WARN: 'First log message'
... WARN: 'Another log message'
UPDATE:
Possible solution is to make logger global:
module Kernel
def logger
#logger ||= Logging.logger(STDOUT)
end
end
UPDATE 2:
module Logging
module Loggable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def logger
Logging.logger(STDOUT)
end
end
def logger
self.class.logger
end
end
end
Object.send :include, Logging::Loggable
As by documentation, you might call the respective methods directly on Logging class:
Logging.logger(STDOUT).warn 'ghgh'
W, [2015-03-07T09:04:30.601189 #19126] WARN : ghgh
or you might declare global variable:
$logger = Logging.logger(STDOUT)
$logger.warn 'ghgh'
or you might query the logger instance from within your method:
lg = Logging.logger['my-logger']
lg.warn 'ghgh'
Hope it helps.
UPD To make an instance of Logging available in any instance of any class, one might use something like:
module IncludeLogger
def logger
#logger ||= Logging.logger(STDOUT)
end
end
Object.send :include, IncludeLogger
The above would inject logger method into any object.

the instance method not included by mixin

I got add_log method not defined error When I call add_log in the AlarmLogger,
But the AlarmLogger has already inherited Logger
How do I fix it, Thanks
test.rb
include TestingDataLoad
class AlarmLogger < Logger
def run()
add_log do | new_log, response|
new_log.brief_content = hash_values_to_int(response[:values])
end
end
testing_data_load.rb
module TestingDataLoad
class Logger
def add_log
end
end
I suspect you may be inheriting from the standard library Logger class.
You can easily verify this by adding
puts Logger.inspect
after your include. If it outputs "TestingDataLoad::Logger" then it has picked your class, if not it hasn't.
The easiest fix would be to have AlarmLogger inherit from TestingDataLoad::Logger
I think you should require the file before you include it
require 'testing_data_load'
include TestingDataLoad
class AlarmLogger < Logger
def run
add_log do | new_log, response|
new_log.brief_content = hash_values_to_int(response[:values])
end
end
end

Ruby - share logger instance among module/classes

Working on a little Ruby script that goes out to the web and crawls various services. I've got a module with several classes inside:
module Crawler
class Runner
class Options
class Engine
end
I want to share one logger among all those of those classes. Normally I'd just put this in a constant in the module and reference it like so:
Crawler::LOGGER.info("Hello, world")
The problem is that I can't create my logger instance until I know where the output is going. You start the crawler via command line and at that point you can tell it you want to run in development (log output goes to STDOUT) or production (log output goes to a file, crawler.log):
crawler --environment=production
I have a class Options that parses the options passed in through the command line. Only at that point do I know how to instantiate the logger with the correct output location.
So, my question is: how/where to I put my logger object so that all my classes have access to it?
I could pass my logger instance to each new() call for every class instance I create, but I know there has to be a better, Rubyish way to do it. I'm imagining some weird class variable on the module that shared with class << self or some other magic. :)
A little more detail: Runner starts everything by passing the command line options to the Options class and gets back an object with a couple of instance variables:
module Crawler
class Runner
def initialize(argv)
#options = Options.new(argv)
# feels like logger initialization should go here
# #options.log_output => STDOUT or string (log file name)
# #options.log_level => Logger::DEBUG or Logger::INFO
#engine = Engine.new()
end
def run
#engine.go
end
end
end
runner = Runner.new(ARGV)
runner.run
I need the code in Engine to be able to access the logger object (along with a couple more classes that are initialized inside Engine). Help!
All of this could be avoided if you could just dynamically change the output location of an already-instantiated Logger (similar to how you change the log level). I'd instantiate it to STDOUT and then change over to a file if I'm in production. I did see a suggestion somewhere about changing Ruby's $stdout global variable, which would redirect output somewhere other than STDOUT, but this seems pretty hacky.
Thanks!
I like to have a logger method available in my classes, but I don't like sprinkling #logger = Logging.logger in all my initializers. Usually, I do this:
module Logging
# This is the magical bit that gets mixed into your classes
def logger
Logging.logger
end
# Global, memoized, lazy initialized instance of a logger
def self.logger
#logger ||= Logger.new(STDOUT)
end
end
Then, in your classes:
class Widget
# Mix in the ability to log stuff ...
include Logging
# ... and proceed to log with impunity:
def discombobulate(whizbang)
logger.warn "About to combobulate the whizbang"
# commence discombobulation
end
end
Because the Logging#logger method can access the instance that the module is mixed into, it is trivial to extend your logging module to record the classname with log messages:
module Logging
def logger
#logger ||= Logging.logger_for(self.class.name)
end
# Use a hash class-ivar to cache a unique Logger per class:
#loggers = {}
class << self
def logger_for(classname)
#loggers[classname] ||= configure_logger_for(classname)
end
def configure_logger_for(classname)
logger = Logger.new(STDOUT)
logger.progname = classname
logger
end
end
end
Your Widget now logs messages with its classname, and didn't need to change one bit :)
With the design you've laid out, it looks like the easiest solution is to give Crawler a module method that returns a module ivar.
module Crawler
def self.logger
#logger
end
def self.logger=(logger)
#logger = logger
end
end
Or you could use "class <<self magic" if you wanted:
module Crawler
class <<self
attr_accessor :logger
end
end
It does the exact same thing.
As Zenagray points out, logging from class methods was left out of Jacob's answer. A small addition solves that:
require 'logger'
module Logging
class << self
def logger
#logger ||= Logger.new($stdout)
end
def logger=(logger)
#logger = logger
end
end
# Addition
def self.included(base)
class << base
def logger
Logging.logger
end
end
end
def logger
Logging.logger
end
end
The intended use is via "include":
class Dog
include Logging
def self.bark
logger.debug "chirp"
puts "#{logger.__id__}"
end
def bark
logger.debug "grrr"
puts "#{logger.__id__}"
end
end
class Cat
include Logging
def self.bark
logger.debug "chirp"
puts "#{logger.__id__}"
end
def bark
logger.debug "grrr"
puts "#{logger.__id__}"
end
end
Dog.new.bark
Dog.bark
Cat.new.bark
Cat.bark
Produces:
D, [2014-05-06T22:27:33.991454 #2735] DEBUG -- : grrr
70319381806200
D, [2014-05-06T22:27:33.991531 #2735] DEBUG -- : chirp
70319381806200
D, [2014-05-06T22:27:33.991562 #2735] DEBUG -- : grrr
70319381806200
D, [2014-05-06T22:27:33.991588 #2735] DEBUG -- : chirp
70319381806200
Note the id of the logger is the same in all four cases. If you want a different instance for each class, then don't use Logging.logger, rather use self.class.logger:
require 'logger'
module Logging
def self.included(base)
class << base
def logger
#logger ||= Logger.new($stdout)
end
def logger=(logger)
#logger = logger
end
end
end
def logger
self.class.logger
end
end
The same program now produces:
D, [2014-05-06T22:36:07.709645 #2822] DEBUG -- : grrr
70350390296120
D, [2014-05-06T22:36:07.709723 #2822] DEBUG -- : chirp
70350390296120
D, [2014-05-06T22:36:07.709763 #2822] DEBUG -- : grrr
70350390295100
D, [2014-05-06T22:36:07.709791 #2822] DEBUG -- : chirp
70350390295100
Note that the first two id's are the same but are different from the 2nd two ids showing that we have two instances -- one for each class.
Inspired by this thread I created the easy_logging gem.
It combines all the features discussed such as:
Adds logging functionality anywhere with one, short,
self-descriptive command
Logger works in both class and instance methods
Logger is specific to class and contains class name
Installation:
gem install 'easy_logging
Usage:
require 'easy_logging'
class YourClass
include EasyLogging
def do_something
# ...
logger.info 'something happened'
end
end
class YourOtherClass
include EasyLogging
def self.do_something
# ...
logger.info 'something happened'
end
end
YourClass.new.do_something
YourOtherClass.do_something
Output
I, [2017-06-03T21:59:25.160686 #5900] INFO -- YourClass: something happened
I, [2017-06-03T21:59:25.160686 #5900] INFO -- YourOtherClass: something happened
More details on GitHub.
The may be some weird Ruby magic that could let you avoid it, but there's a fairly simple solution that doesn't need weird. Just put the logger into the module and access it directly, with a mechanism to set it. If you want to be cool about it, define a "lazy logger" that keeps a flag to say if it has a logger yet, and either silently drops messages until the logger is set, throws an exception of something is logged before the logger is set, or adds the log message to a list so it can be logged once the logger is defined.
A little chunk of code to demonstrate how this works. I'm simply creating a new basic Object so that I can observe that the object_id remains the same throughout the calls:
module M
class << self
attr_accessor :logger
end
#logger = nil
class C
def initialize
puts "C.initialize, before setting M.logger: #{M.logger.object_id}"
M.logger = Object.new
puts "C.initialize, after setting M.logger: #{M.logger.object_id}"
#base = D.new
end
end
class D
def initialize
puts "D.initialize M.logger: #{M.logger.object_id}"
end
end
end
puts "M.logger (before C.new): #{M.logger.object_id}"
engine = M::C.new
puts "M.logger (after C.new): #{M.logger.object_id}"
The output of this code looks like (an object_id of 4 means nil):
M.logger (before C.new): 4
C.initialize, before setting M.logger: 4
C.initialize, after setting M.logger: 59360
D.initialize M.logger: 59360
M.logger (after C.new): 59360
Thanks for the help guys!
How about wrapping the logger in a singleton then you could access it using MyLogger.instance
Based on your comment
All of this could be avoided if you could just dynamically change the output location of an already-instantiated Logger (similar to how you change the log level).
If you are not restricted to the default logger you may use another log-gem.
As an example with log4r:
require 'log4r'
module Crawler
LOGGER = Log4r::Logger.new('mylog')
class Runner
def initialize
LOGGER.info('Created instance for %s' % self.class)
end
end
end
ARGV << 'test' #testcode
#...
case ARGV.first
when 'test'
Crawler::LOGGER.outputters = Log4r::StdoutOutputter.new('stdout')
when 'prod'
Crawler::LOGGER.outputters = Log4r::FileOutputter.new('file', :filename => 'test.log') #append to existing log
end
#...
Crawler::Runner.new
In prod mode the logging data are stored in a file (attached to existing file, but there are options to create new log files or implement rolling log files).
The result:
INFO main: Created instance for Crawler::Runner
If you use the inheritance mechanism of log4r (a), you may define a logger for each class (or in my following example for each instance) and share the outputter.
The example:
require 'log4r'
module Crawler
LOGGER = Log4r::Logger.new('mylog')
class Runner
def initialize(id)
#log = Log4r::Logger.new('%s::%s %s' % [LOGGER.fullname,self.class,id])
#log.info('Created instance for %s with id %s' % [self.class, id])
end
end
end
ARGV << 'test' #testcode
#...
case ARGV.first
when 'test'
Crawler::LOGGER.outputters = Log4r::StdoutOutputter.new('stdout')
when 'prod'
Crawler::LOGGER.outputters = Log4r::FileOutputter.new('file', :filename => 'test.log') #append to existing log
end
#...
Crawler::Runner.new(1)
Crawler::Runner.new(2)
The result:
INFO Runner 1: Created instance for Crawler::Runner with id 1
INFO Runner 2: Created instance for Crawler::Runner with id 2
(a) A logger name like A::B has the name B and is a child of the logger with the name A. As far as I know this is no object inheritance.
One advantage of this approach: If you want to use a single logger for each class, you need only to change the name of the logger.
Although an old question, I thought it worthwhile to document a different approach.
Building on Jacob's answer, I would suggest a module that you can add in as and when needed.
My version is this:
# saved into lib/my_log.rb
require 'logger'
module MyLog
def self.logger
if #logger.nil?
#logger = Logger.new( STDERR)
#logger.datetime_format = "%H:%M:%S "
end
#logger
end
def self.logger=( logger)
#logger = logger
end
levels = %w(debug info warn error fatal)
levels.each do |level|
define_method( "#{level.to_sym}") do |msg|
self.logger.send( level, msg)
end
end
end
include MyLog
I have this saved into a library of handy modules, and I would use it like this:
#! /usr/bin/env ruby
#
require_relative '../lib/my_log.rb'
MyLog.debug "hi"
# => D, [19:19:32 #31112] DEBUG -- : hi
MyLog.warn "ho"
# => W, [19:20:14 #31112] WARN -- : ho
MyLog.logger.level = Logger::INFO
MyLog.logger = Logger.new( 'logfile.log')
MyLog.debug 'huh'
# => no output, sent to logfile.log instead
I find this a lot easier and more versatile than other options I've looked at so far, so I hope it helps you with yours.

Resources