Let’s say I have a library with a single interface. It would be a class, which provides a method. That’s it. But I would like to log while the method execute:
module SomeNameSpace
class Library
def the_method
compute_something
logger.info('Work Done')
end
end
end
Then, the client code would use this API as:
SomeNameSpace::Library.new.the_method
And expect to see the message Work Done somewhere.
But, when is the proper time to define that logger object?
I was thinking on defining the initialize method of Library with a default logger for the library developer, but allowing the client code to pass it’s own logger.
module SomeNameSpace
class Library
def initialize(logger: Logger.new(STDERR))
@logger = logger
end
def the_method
compute_something
logger.info('Work Done')
end
end
private
def logger
@logger
end
end
And then it will see the message Work Done in the file a-file.log.
Now, I’m wondering about what other ways to do this you use.
I suppose there must be a dependency injection solution, but I’m not sure if that is a good idea for libraries authors (prove me wrong).
What I’m sure, is that this must have been discussed in the Software Industry Thousands of times, and still I don’t know exactly what is the proper way to do this.
Often, libraries will initialize their own default Logger instance internally and expose it via a public method, while also allowing the injection of a custom Logger externally if needed through a configuration attribute.
For example, Glimmer DSL for SWT initializes its Logger in its Glimmer::Config module while exposing it through the ::logger method to enable customization of the Logger, like the log level:
Glimmer::Config.logger.level = :debug
It also allows replacing the logger completely with your own custom logger:
# use the logging gem Logger instead, which supports async logging
require 'logging'
Glimmer::Config.logger = Logging.logger['custom_logger']
One thing to keep in mind is that the main Logger is usually a singleton in the application, so in general it is better to configure as a singleton class attribute instead of an instance attribute so that it can be accessed globally instead of passing around everywhere (e.g. Glimmer::Config.logger.info('Some Info')). That said, if you need to support multiple loggers in the same app instance for whatever reason, then you can certainly configure as an instance attribute instead.
As an extra note, I forgot to mention that Glimmer DSL for SWT does not initialize its logger directly. It relies on the Glimmer DSL framework logger, and then it extends it with its own extra logic.
Thank you very much. I think I’m getting this idea better. Decades not feeling well about how I’m logging.
I was always mixing ideas.
For one side, I wanted a way to keep functions clean, but to be able to trace them somehow without cluttering their bodies. That’s something I could achieve by AOP or DI, but it is not required.
The other idea is to delegate the Logger implementation to the users of my libraries. That is an independent concept than the first one, and it can be easily achievable as Glimmer does. Finally I GET IT.
Just a singleton service you can use anywhere in the library code to log (e.g. Glimmer::Config.logger.debug {e.message}). Now, how to hide that from functions bodies is another matter.
Thank you SO MUCH! I’m telling you, years thinking about this in the background.