Is it ellegant to use a global variable to store a Logger object?

Hi, I use Logger class in a programm and since I need to log in lot of
different places (into classes, into methods…) I use a global variable
in
this way:

$log = Logger.new
$log.debug …

so I can use $log anywhere in the code. Is it ellegant and “the Ruby
way”?

The other possibility I see is creating a class that stores the logger
instance into a @@class_variable (@@logger) and call class method
anywhere in
the code:

class MyLogger

@@logger = Logger.new

def self.debug(x)
  @@logger.debug(x)
end

...

end

MyLogger.debug …

Which is a more ellegant way? is there other option?

Thanks for any advice I could receive from you.

Iñaki Baz C. wrote:

$log = Logger.new
$log.debug …

The other possibility I see is

class MyLogger

@@logger = Logger.new

def self.debug(x)
  @@logger.debug(x)
end

end

MyLogger.debug …

is there other option?

You could add a debug() method to the Kernel module:

module Kernel
@@logger = Logger.new

def debug message
@@logger.debug message
end
end

Then you could call it from anywhere in your code.

El Lunes, 23 de Junio de 2008, Suraj K. escribió:

def self.debug(x)

module Kernel
@@logger = Logger.new

def debug message
@@logger.debug message
end
end

Then you could call it from anywhere in your code.

Oh, interesting. Anyway let me a question:
How ellegant is using Kernel module? Imagine you are programming a
framework
that others can use in their programms. Isn’t dangerous to extend/modify
Kernel module since it will be shared by all the code?

Thanks a lot.

Iñaki Baz C. wrote:

Imagine you are programming a framework
that others can use in their programms.
Isn’t dangerous to extend/modify
Kernel module since it will be shared by all the code?

True. In that case, I would put the debug() method or the LOG object in
the top-level module of my library/framework:

Using a debug() method

module FooBar
module Logging
@@logger = Logger.new

def debug msg
  @@logger.debug msg
end

end

class FooBar::Baz
include Logging # <== NOTE this!

def oh_no
  debug "oh no!"
end

end
end

Using a LOG object

module FooBar
LOG = Logger.new

class FooBar::Baz
def oh_no
LOG.debug “oh no!”
end
end
end

Iñaki Baz C. wrote:

Humm, I don’t like adding “include Logging” to every classes I use since
sometimes I use objects, sometimes classes, so I’d also need to “extend”
some classes not just “include”.

You could use meta programming to include Logging in all nested classes
and modules of your top-level module:

module FooBar

… code from previous e-mail …

recursively adds Logging functionality to all nested classes

extender = lambda do |k|
k.constants.map {|c| k.const_get c }.
select {|c| c.is_a? Class or c.is_a? Module }.
each do |c|
c.extend Logging
extender[c]
end
end

extender[self]
end

Using a LOG object

module FooBar
LOG = Logger.new

class FooBar::Baz
def oh_no
LOG.debug “oh no!”
end
end
end

Ok, in this case you use a constant (LOG). This is the same I do now but
I use a global variable ($log). Is more ellegant using a constant?

Yes, in my opinion. A constant is constrained to the walls of your
library’s top-level module, whereas a global variable is not.
Therefore, unless someone was modifying your library, they would not be
able to access your constant.

El Lunes, 23 de Junio de 2008, Suraj K. escribió:

module FooBar

def oh_no
  debug "oh no!"
end

end
end

Humm, I don’t like adding “include Logging” to every classes I use since
sometimes I use objects, sometimes classes, so I’d also need to “extend”
some
classes not just “include”.

Using a LOG object

module FooBar
LOG = Logger.new

class FooBar::Baz
def oh_no
LOG.debug “oh no!”
end
end
end

Ok, in this case you use a constant (LOG). This is the same I do now but
I use
a global variable ($log). Is more ellegant using a constant?

Thanks a lot.

On 23.06.2008 03:29, Suraj K. wrote:

Iñaki Baz C. wrote:

Humm, I don’t like adding “include Logging” to every classes I use since
sometimes I use objects, sometimes classes, so I’d also need to “extend”
some classes not just “include”.

You could use meta programming to include Logging in all nested classes
and modules of your top-level module:

Now things are becoming quite complex.
Logging is a global task and thusly some form of global handling is
perfectly ok IMHO. Using a global variable has the advantage that you
do not collide with instance methods (which you do if you place #debug
etc. in all classes either via inheritance, meta programming or other
means). IMHO the only bad aspect about the global is that you cannot
ensure automatic initialization. An alternative would be to add a
single method to Kernel that lazily initializes a Logger instance.

My 0.02 EUR.

Kind regards

robert

Suraj K. wrote:

recursively adds Logging functionality to all nested classes

extender = lambda do |k|
k.constants.map {|c| k.const_get c }.
select {|c| c.is_a? Class or c.is_a? Module }.

Hmm, I should have used duck-typing in the above line (all the Java
programming I’ve done recently has corrupted me ;-). So the above line
should be written as:

select {|c| c.respond_to? :constants and c.respond_to? :const_get }.

each do |c|
  c.extend Logging
  extender[c]
end

end

2008/6/23, Robert K. [email protected]:

Now things are becoming quite complex.
Logging is a global task and thusly some form of global handling is
perfectly ok IMHO. Using a global variable has the advantage that you do
not collide with instance methods (which you do if you place #debug etc. in
all classes either via inheritance, meta programming or other means). IMHO
the only bad aspect about the global is that you cannot ensure automatic
initialization. An alternative would be to add a single method to Kernel
that lazily initializes a Logger instance.

Ok, thanks all for your help and advices :slight_smile:

On Jun 23, 2008, at 9:10 AM, Suraj K. wrote:

end
Note that the include musts be done after all nested classes have
been
defined. That is why I put that include() statement at the bottom of
the module’s code.

this is a bad idea. try to use rails’ code with other code that uses
ruby’s logging class and you’ll see what i mean. it’s better just to
keep things simple imho - your meta-programming bit will fail, for
example, if someone does

module M
extend AddLoggingToNested
end

i agree with robert that logging is (typically) a global task and thus
the interface should reflect that. i’d use something like

module Namespace

module Logger
Instance = ::Logger.new STDERR

 %w( debug info warn error fatal ).each do |m|
   module_eval <<-code
     def #{ m }(*a, &b)
       Instance.#{ m }(*a, &b)
     end
     def self.#{ m }(*a, &b)
       Instance.#{ m }(*a, &b)
      end
   code
  end

end

end

which does three things

  • box your methods in a namespace

  • allow access via: Logger.info …

  • allow access via: include Logger, info …

perhaps it might make sense to auto-mixin, but tha’ts a layer on top
of this.

kind regards.

a @ http://codeforpeople.com/

El Lunes, 23 de Junio de 2008, ara.t.howard
escribió:

i agree with robert that logging is (typically) a global task and thus
the interface should reflect that. i’d use something like

module Namespace

module Logger
Instance = ::Logger.new STDERR

Could you please explain me what the last line does? (::Logger.new)

Thanks a lot.

ara.t.howard wrote:

this is a bad idea. try to use rails’ code with other code that uses
ruby’s logging class and you’ll see what i mean.

Perhaps they modify ruby’s logging class directly? I am proposing
something different: a selective injection of logging functionality into
a library’s top-level module.

it’s better just to keep things simple imho

I agree, but keep what simple? Implementation or interface?

Iñaki seems to want the interface to be simple, i.e. an automatic
logging facility that is (1) private to his library and (2) does not
require any explicit enabling (include Logging) or addressing (a LOG
constant).

your meta-programming bit will fail, for example, if someone does

module M
extend AddLoggingToNested
end

That is easily solved by aliasing included() to extended():

module AddLoggingToNested
# … code from previous email …

class << self
  alias extended included
end

end

On Jun 23, 2008, at 2:59 PM, Suraj K. wrote:

ara.t.howard wrote:

this is a bad idea. try to use rails’ code with other code that uses
ruby’s logging class and you’ll see what i mean.

Perhaps they modify ruby’s logging class directly?

yup.

Rails.send(‘bad!’)

logging facility that is (1) private to his library and (2) does not

module AddLoggingToNested

… code from previous email …

class << self
alias extended included
end
end

Posted via http://www.ruby-forum.com/.

yup i understand it all - because i’ve done it :wink: in the end i just
decided it was easier to use a singleton/module level methods - not
least of which because

grep -R Logger .

ends up being used a ton in debugging the logs of a crashed program.
anyhow, i’m not disagreeing - just noting that i’ve done both
extensive and tend towards the simple and explicit pattern with
logging - YMMV.

cheers.

a @ http://codeforpeople.com/

Iñaki Baz C. ha scritto:

instance into a @@class_variable (@@logger) and call class method anywhere in

Maybe and IMHO you can use a simple dependency injection pattern. You
pass a Logger instance to the constructor of your classes. In this way
you keep the logger object decoupled from other objects in the system.
Moreover, testing will be easier (you can mock the logger
implementation).

require ‘logger’

class Container
def logger
@logger ||= Logger.new STDOUT
end
def foo
Foo.new(logger)
end
end

class Foo
def initialize(logger)
@logger = logger
end
def bar
@logger.info(‘Foo#bar invoked.’)
end
end

c = Container.new
c.foo.bar

El Martes, 24 de Junio de 2008, Andrea F. escribió:

  @@logger.debug(x)

end
@logger.info(‘Foo#bar invoked.’)
end
end

c = Container.new
c.foo.bar

That’s a cool solution :slight_smile:

Robert K. wrote:

On 23.06.2008 03:29, Suraj K. wrote:

I�aki Baz C. wrote:

Humm, I don’t like adding “include Logging” to every classes I use since
sometimes I use objects, sometimes classes, so I’d also need to “extend”
some classes not just “include”.

You could use meta programming to include Logging in all nested classes
and modules of your top-level module:

Now things are becoming quite complex.

Not really. You can refactor that bit of meta programming into its own
module:

module AddLoggingToNested
def self.included target
# … code from previous e-mail …

  extender[target]
end

end

Now, you can add this propagation of logging functionality to any
top-level module or class by simply doing:

module MyProjectTopLevel
# …

include AddLoggingToNested

end

Note that the include musts be done after all nested classes have been
defined. That is why I put that include() statement at the bottom of
the module’s code.

2008/6/24 Iñaki Baz C. [email protected]:

c.foo.bar

That’s a cool solution :slight_smile:

I do not think so. Reasons: it clutters every instance with a
reference which can have a significant impact on memory if there are a
lot objects. Then, you have to change a class’s #initialize signature
for all classes that do want to do logging and also manually code the
assignment inside the class. Accessing a logger from a global context
(whichever way you do it) saves memory and is less tedious.

Kind regards

robert

2008/6/25 Robert D. [email protected]:

Could not agree more with you. May I add some other reasons.
This approach vioaltes principles we regard rather highly on this list
It is not DRY, you are really repeating yourself and in case of the
metaprogramming solutions you let Ruby repeat itself.
It is just much less simple than necessary.
You spread code dependencies all over the place, as a matter of fact
the expression “dependency injection” says it allready it is almost as
putting a virus (with constructive behavior) into your code, but can
you imagine how much more work refactoring will become?

Absolutely!

My order of preference would be

  1. $logger or $LOGGER

  2. Logger

  3. Kernel::log (or Object.log)

Now, this really makes me wonder about the position before the last
one in that list…
:slight_smile:

Kind regards

robert

On Wed, Jun 25, 2008 at 11:22 AM, Robert K.
[email protected] wrote:

end

(whichever way you do it) saves memory and is less tedious.

Could not agree more with you. May I add some other reasons.
This approach vioaltes principles we regard rather highly on this list
It is not DRY, you are really repeating yourself and in case of the
metaprogramming solutions you let Ruby repeat itself.
It is just much less simple than necessary.
You spread code dependencies all over the place, as a matter of fact
the expression “dependency injection” says it allready it is almost as
putting a virus (with constructive behavior) into your code, but can
you imagine how much more work refactoring will become?

My order of preference would be

  1. $logger or $LOGGER

  2. Logger

  3. Kernel::log (or Object.log)

Cheers


http://ruby-smalltalk.blogspot.com/


Les mêmes questions qu’on se pose
On part vers où et vers qui
Et comme indice pas grand-chose
Des roses et des orties.

Francis Cabrel

Robert D. ha scritto:

@logger ||= Logger.new STDOUT

def bar
reference which can have a significant impact on memory if there are a

It is just much less simple than necessary.

Cheers

Hi Robert,

I would known if, in your opinion, DI is not DRY in general or you are
referring to the particular case of logging. Moreover, which are
alternative DRY solutions to DI that guarantees loose coupling between
objects?

Andrea