Ruby - Share Logger Instance Among Module/Classes

Ruby - share logger instance among module/classes

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.

Share global logger among module/classes

Add a constant in the module:

module Foo
Logger = Logger.new
class A
class B
class C
...
class Z
end

Then you can do Logger.log('blah') in your classes. Since we're shadowing the global constant Logger with Foo::Logger, this means that if you want to refer to the Logger class within the Foo module, you have to use the scope resolution: ::Logger.

How to define a common module logging shared among others module

Just after require you can add this piece of code

$FILE_LOG = Logging.create_log(File.expand_path('LoggingFile.log'), Logger::DEBUG)

Explanation of the above line : It is calling a function in Logging Module, to create File , Level of Logging is debug.

Below is the piece of code for Module

module Logging
def self.create_log(output_location level)
log = Logger.new(output_location, 'weekly').tap do |l|
next unless l

l.level = level

l.progname = File.basename($0)

l.datetime_format = DateTime.iso8601

l.formatter = proc { |severity, datetime, progname, msg| "#{severity}: #{datetime} - (#{progname}) : #{msg}\n" }
end

log.level = level if level < log.level
log
end


def self.log(msg, level)
# Here I am just logging only FATAL and DEBUG, similarly you can add in different level of logs
if level == :FATAL
$FILE_LOG.fatal(msg)
elsif level == :DEBUG
$FILE_LOG.debug(msg)
end
end
end

Then in Every method every Ruby file, we can use this logging as follows

Logging.log("Message",:FATAL)

Logger default for class and new file for subclasses

You tell ruby to do exactly what you need. But beware, multiple loggers on the same file will not play nice with each other, so you'd better set the logger on the class object.

class ModelClass
def logger
self.class.logger
end

def self.logger
@logger ||= begin
target = self == ModelClass ? STDOUT : "#{name}.log"
Logger.new(target)
end
end
end

You will have one logger per class, and all of them will go to files except the base class, which will go to STDOUT.

But why being so complex? You could just write all of them to files, and then tail -f YourFile.log to see the log you want on stdout.

Ruby: partially share initialization logic using Module

super works just fine with modules. Use ** to ignore additional keyword parameters.

module Being
def initialize(age: , name: , **)
@age = age.to_i
@name = name.to_sym
end
end

class Person
include Being

def initialize(height:, **)
super

@height = height
end
end

class Dog
include Being

def initialize(breed: , **)
super

@breed = breed
end
end

#<Dog:0x00007fb0fe80f7f8 @age=6, @name=:"Good Boy", @breed="Good Dog">
#<Person:0x00007fb0fe80f2a8 @age=42, @name=:Bront, @height="6' 2\"">
p Dog.new(age: 6, name: "Good Boy", breed: "Good Dog")
p Person.new(age: 42, name: "Bront", height: %q{6' 2"})

You can get yourself into some trouble mixing super with Modules because it's not always clear which ancestor method super will call. You can check your full inheritance tree with Module#ancestors. This includes Classes because all Classes are Modules.

# [Dog, Being, Object, Kernel, BasicObject]
# [Person, Being, Object, Kernel, BasicObject]
p Dog.ancestors
p Person.ancestors

To avoid this, use composition. Compose your class of several different objects and delegate method calls to them. In this case, have a Being object and delegate method calls to it. We'll use Forwardable to forward method calls to a Being object.

require 'forwardable'

class Being
attr_accessor :age, :name

def initialize(age:, name:)
@age = age.to_i
@name = name.to_sym
end

def greeting
"Hello, my name is #{name} and I am #{age} years old."
end
end

class Person
extend Forwardable

def_delegators :@being, :greeting

def initialize(height:, **args)
@being = Being.new(**args)
@height = height
end

def to_s
self
end
end

class Dog
extend Forwardable

def_delegators :@being, :greeting

def initialize(breed:, **args)
@being = Being.new(**args)
@breed = breed
end

def to_s
self
end
end

#<Dog:0x00007fb87702c060 @being=#<Being:0x00007fb87702e400 @age=6, @name=:"Good Boy">, @breed="Good Dog">
#<Person:0x00007fb87a02f870 @being=#<Being:0x00007fb87a02f7f8 @age=42, @name=:Bront>, @height="6' 2\"">
p dog = Dog.new(age: 6, name: "Good Boy", breed: "Good Dog")
p person = Person.new(age: 42, name: "Bront", height: %q{6' 2"})

# Hello, my name is Good Boy and I am 6 years old.
# Hello, my name is Bront and I am 42 years old.
puts dog.greeting
puts person.greeting

# [Dog, Being, Object, Kernel, BasicObject]
# [Person, Being, Object, Kernel, BasicObject]
p Dog.ancestors
p Person.ancestors

def_delegators :@being, :greeting says that when greeting is called, call @being.greeting instead.


Inheritance is easy, but it can lead to hard to find complications. Composition takes a bit more work, but it is more obvious what is happening, and it allows for more flexible classes. You can swap out what is being delegated to.

For example, say you need to fetch things off the web. You could inherit from Net::HTTP. Or you could delegate to a Net::HTTP object. Then in testing you can replace the Net::HTTP object with one that does dummy network calls.



Related Topics



Leave a reply



Submit