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

  • Thread starter Iñaki Baz Castillo
  • Start date
I

Iñaki Baz Castillo

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

$log =3D 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=20
instance into a @@class_variable (@@logger) and call class method anywhere =
in=20
the code:


class MyLogger

@@logger =3D 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.


=20
=2D-=20
I=C3=B1aki Baz Castillo
 
S

Suraj Kurapati

Iñaki Baz Castillo said:
$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.
 
I

Iñaki Baz Castillo

El Lunes, 23 de Junio de 2008, Suraj Kurapati escribi=C3=B3:
You could add a debug() method to the Kernel module:

module Kernel
@@logger =3D 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 framewor=
k=20
that others can use in their programms. Isn't dangerous to extend/modify=20
Kernel module since it will be shared by *all* the code?

Thanks a lot.

=2D-=20
I=C3=B1aki Baz Castillo
 
S

Suraj Kurapati

Iñaki Baz Castillo said:
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

Iñaki Baz Castillo

El Lunes, 23 de Junio de 2008, Suraj Kurapati escribi=C3=B3:
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 =3D Logger.new

def debug msg
@@logger.debug msg
end
end

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

def oh_no
debug "oh no!"
end
end
end

Humm, I don't like adding "include Logging" to every classes I use since=20
sometimes I use objects, sometimes classes, so I'd also need to "extend" so=
me=20
classes not just "include".

# Using a LOG object
module FooBar
LOG =3D 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=20
a global variable ($log). Is more ellegant using a constant?

Thanks a lot.

=2D-=20
I=C3=B1aki Baz Castillo
 
S

Suraj Kurapati

Iñaki Baz Castillo said:
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
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.
 
S

Suraj Kurapati

Suraj said:
# 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
 
R

Robert Klemme

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
 
I

Iñaki Baz Castillo

MjAwOC82LzIzLCBSb2JlcnQgS2xlbW1lIDxzaG9ydGN1dHRlckBnb29nbGVtYWlsLmNvbT46Cj4g
IE5vdyB0aGluZ3MgYXJlIGJlY29taW5nIHF1aXRlIGNvbXBsZXguCj4gIExvZ2dpbmcgaXMgYSBn
bG9iYWwgdGFzayBhbmQgdGh1c2x5IHNvbWUgZm9ybSBvZiBnbG9iYWwgaGFuZGxpbmcgaXMKPiBw
ZXJmZWN0bHkgb2sgSU1ITy4gIFVzaW5nIGEgZ2xvYmFsIHZhcmlhYmxlIGhhcyB0aGUgYWR2YW50
YWdlIHRoYXQgeW91IGRvCj4gbm90IGNvbGxpZGUgd2l0aCBpbnN0YW5jZSBtZXRob2RzICh3aGlj
aCB5b3UgZG8gaWYgeW91IHBsYWNlICNkZWJ1ZyBldGMuIGluCj4gYWxsIGNsYXNzZXMgZWl0aGVy
IHZpYSBpbmhlcml0YW5jZSwgbWV0YSBwcm9ncmFtbWluZyBvciBvdGhlciBtZWFucykuICBJTUhP
Cj4gdGhlIG9ubHkgYmFkIGFzcGVjdCBhYm91dCB0aGUgZ2xvYmFsIGlzIHRoYXQgeW91IGNhbm5v
dCBlbnN1cmUgYXV0b21hdGljCj4gaW5pdGlhbGl6YXRpb24uICBBbiBhbHRlcm5hdGl2ZSB3b3Vs
ZCBiZSB0byBhZGQgYSBzaW5nbGUgbWV0aG9kIHRvIEtlcm5lbAo+IHRoYXQgbGF6aWx5IGluaXRp
YWxpemVzIGEgTG9nZ2VyIGluc3RhbmNlLgoKT2ssIHRoYW5rcyBhbGwgZm9yIHlvdXIgaGVscCBh
bmQgYWR2aWNlcyA6KQoKCi0tIApJw7Fha2kgQmF6IENhc3RpbGxvCjxpYmNAYWxpYXgubmV0Pgo=
 
S

Suraj Kurapati

Robert said:
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.
 
A

ara.t.howard

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.


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/
 
I

Iñaki Baz Castillo

El Lunes, 23 de Junio de 2008, ara.t.howard escribi=F3:
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 =3D ::Logger.new STDERR

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

Thanks a lot.


=2D-=20
I=F1aki Baz Castillo
 
S

Suraj Kurapati

ara.t.howard said:
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
 
A

ara.t.howard

Perhaps they modify ruby's logging class directly?
yup.

Rails.send('bad!')


I am proposing
something different: a selective injection of logging functionality =20=
into
a library's top-level module.


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

I=F1aki 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).


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

module AddLoggingToNested
# ... code from previous email ...

class << self
alias extended included
end
end
--=20
Posted via http://www.ruby-forum.com/.



yup i understand it all - because i've done it ;-) in the end i just =20=

decided it was easier to use a singleton/module level methods - not =20
least of which because

grep -R Logger .

ends up being used a ton in debugging the logs of a crashed program. =20=

anyhow, i'm not disagreeing - just noting that i've done both =20
extensive and tend towards the simple and explicit pattern with =20
logging - YMMV.

cheers.


a @ http://codeforpeople.com/
 
A

Andrea Fazzi

Iñaki Baz Castillo ha scritto:
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.

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
 
I

Iñaki Baz Castillo

El Martes, 24 de Junio de 2008, Andrea Fazzi escribi=C3=B3:
I=C3=B1aki Baz Castillo ha scritto:

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 ||=3D Logger.new STDOUT
end
def foo
Foo.new(logger)
end
end

class Foo
def initialize(logger)
@logger =3D logger
end
def bar
@logger.info('Foo#bar invoked.')
end
end

c =3D Container.new
c.foo.bar

That's a cool solution :)



=2D-=20
I=C3=B1aki Baz Castillo
 
R

Robert Klemme

2008/6/24 I=F1aki Baz Castillo said:
El Martes, 24 de Junio de 2008, Andrea Fazzi escribi=F3:

That's a cool solution :)

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



--=20
use.inject do |as, often| as.you_can - without end
 
R

Robert Dober

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

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

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

Cheers




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

---
Les m=EAmes questions qu'on se pose
On part vers o=F9 et vers qui
Et comme indice pas grand-chose
Des roses et des orties.
-
Francis Cabrel
 
R

Robert Klemme

2008/6/25 Robert Dober said:
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

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

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

Kind regards

robert
 
A

Andrea Fazzi

Robert Dober ha scritto:
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

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

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,260
Messages
2,571,039
Members
48,768
Latest member
first4landlord

Latest Threads

Top