Hi Ginnie,

On 03/24/10 09:55 AM, Virginia Wray wrote:
> Hi Keith -
>
> Thanks for the review. See my responses below.
> ginnie
>
>
>
> On 03/23/10 12:41, Keith Mitchell wrote:
>> Hi Ginnie,
>>
>> I think the document is looking fantastic. The detail has helped me 
>> understand the logging module.
>>
>> My remaining concern is with the API for init_logger and add_handler.
>>
>> I had to read ahead all the way down to use cases to fully understand 
>> the "handler" argument. It appears that a string or constant will be 
>> mapped to a handler type by the init_logger call, and instantiated 
>> using args also passed into init_logger. This explains Sarah's desire 
>> to have arbitrary arguments passed in, but even with that, it sounds 
>> overly restrictive. Why not instantiate the handler, and then pass in 
>> the handler reference to init_logger?
>>
>> e.g.
>> handler = logging.FileHandler(...)
>> logging.init_logger(app_name, handler, logging.INFO)
>> In this way, any logging handler, even handlers which are not 
>> directly defined in the logging module (e.g., an application specific 
>> subclass) can be passed in, and the init_logger function doesn't have 
>> to worry about understanding those things.
> The application won't be calling anything with "logging.". The logging 
> interaction is going to be managed directly by the logging module.

I think I may have cornered myself in the language issue. When I stated 
"logging.init_logger()", I should have said 
"InstallLogger.init_logger()". I was a bit ambiguous with my usage of 
the term "logging module" in that response, sometimes referring to 
InstallLogger and sometimes referring to Python's logging module - sorry 
about that. I'll be clearer this time around.

> If a handler isn't defined in the logging module, it couldn't be used 
> for logging.

What is the benefit of imposing this restriction? Part of the 
flexibility of Python and duck-typing is that one can create subclasses 
or even duck-typed pseudo-classes - the benefits of such flexibility 
including making it easier to debug or test odd scenarios by creating 
one-off modifications, for example.

> The init_logger function gives the application an entrance to the 
> logging module. It doesn't have direct access to it. From my 
> understanding
> of how this would be implemented, the application would make a request 
> to the engine for logging, which the engine passes on to the logging 
> module.

This may be part of my confusion. The document states that init_logger 
is a function of InstallLogger, but the implication here is that 
init_logger is actually a function of the execution engine. Can you 
clarify? I don't see an init_logger call in the UML diagram either.

> I don't have a problem with the application calling for a handler 
> first, before initializing the logger, but there is overhead either 
> way. The application would do the following:
> handler = add_handler( file)
> init_logger(app_name, handler, logging.INFO)

I agree there's overhead either way. The difference is, if you create a 
handler first and then pass it in, there's a *much* higher level of 
flexibility for what the application can pass in as a handler.

>
> However, my plan was to allow an application to initialze w/o 
> requiring a handler object, and have a default set up if nothing is 
> provided (this would be to a file handler with a agreed upon location 
> for the file).
>
> init_logger(app_name)
>
> That would be about as simple as it could get.

Excellent. I completely agree with that sort of default.

>>
>> With intelligent defaults for keyword arguments for handlers, this 
>> can even be reduced further to something as simple as:
>>
>> logging.init_logger(app_name, logging.Filehandler()).
>>
>> Similarly, with init_handler - why not call it "add_handler"? 
> I'm fine with changing the name. That's not a problem.
>> Let the consumer pass in a constructed handler, and have the 
>> add_handler code handle any additional registration, such as setting 
>> up the proper formatting, logging level, and the like. Done properly, 
>> this would be far more flexible, yet maintain simplicity for the 
>> default cases. If defaults for kwargs is not sufficient, provide a 
>> 'generator' or 'factory' function for those specific classes. For 
>> example, if the HTTP defaults require logging module context, then a 
>> function such as:
>> logging.generate_http_handler(host, url, ...)
>>
>> If the argument is complexity - I argue that it's more complex to ask 
>> the consumers to construct the **kwargs object, instead of the more 
>> natural method of simply instantiating the desired handler instance.
> The problem with this approach, if I'm  understanding you correctly, 
> is that the consumers aren't directly calling logging module 
> interfaces. The idea is to use the "wrapper functions", which pass the 
> info to the engine, and then, have the engine interact directly with 
> the logging module. I don't think that what you are proposing really 
> shifts the complexity; it just moves the responsibility from the 
> logger to the application. The generate_http_handler function would 
> accomplish the same thing as init_handler(http, hostname, url)   or 
> add_handler(.....), if  I change the name. One way or another the 
> information has to be propagated to the logger.

There is a slight difference between the concept of "init_handler" and 
"add_handler", regardless of what exactly each of those functions might 
be named, at least as I understand it.

In either case, the caller has to understand ALL *required* parameters 
of the function, as well as any optional parameters that it wishes to 
modify. That complexity is their either way - as you say, one way or 
another, the application has to propagate that information to the 
logger. And the parameters are the same either way, as well: 
instantiation of a FileHandler requires the application to know it wants 
a FileHandler, and to optionally pass in a filename (getting a default 
of "/tmp/install_log" if no filename is passed in, for example). Whether 
that FileHandler is created via FileHandler("/tmp/install_log") or 
init_handler(FileHandler, "/tmp/install_log"), the same level of 
complexity and flexibility is available *for the handlers that 
init_handler is designed to understand*. If the required parameters to 
FileHandler need to change, then the application has to be updated 
regardless of method; if an optional parameter is added, and the 
application doesn't want it, it doesn't have to be updated in either 
case, and if it wants to make use of (the non-default), the app has to 
be updated.

With the add_handler interface of creating a handler and passing it in, 
the application has the option, but is not required, to modify the 
handler however it chooses prior to registering it with the execution 
engine or InstallLogger (the engine/InstallLogger can still ensure that 
the passed in handler is reasonable, much like it would have to ensure 
that the arguments passed in via init_handler would be reasonable). This 
includes passing in a sub-class, modifying rarely used fields of the 
handler, etc. Additionally, the application, since it is creating the 
handler, will more readily be able to understand the parameters passing 
in. What I'm saying is, with "init_handler(handler_type, **kwargs)", the 
app has to dig through an additional layer to understand how to pass in 
kwargs; with "add_handler(handler_instance)",  whatever mechanism is 
used to create the handler_instance is directly invoked. Even if that 
mechanism is a wrapper provided by InstallLogger of the execution 
engine, there's *immense* flexibility added by breaking it into two 
steps (creation of the handler, either via handler.__init__() or a 
'factory' method, and registering the handler with the 
engine/InstallLogger).

>
> If I'm not understanding you, please let me know.
>>
>> Use cases:
>>
>> Pseudo code for log_messages: I know it's just sample code, but, if 
>> log levels mapped to integers in the same way Python's logging module 
>> does, then it's a much simpler task to do something like:
>>
>> def log_message(...):
>>     for handler in self.handlers:
>>         handler.log(self.logging_level, message, ...)
> The logging levels that exist in the Python logging module will be 
> used. If we need to sub-class additional ones (which we will do for 
> progress reporting), then we'll add other log levels.
> The way the Python logging module works is when you set up a handle 
> with a default logging level, then anytime a message with that logging 
> level is generated, it will be published to that
> handler. So, when the application generates log_message( level, 
> message), it will go to the correct handlers that have been set up to 
> receive messages at that level. I haven't looked specifically
> at how the Python logging module does this, but I wouldn't be 
> surprised if it is implemented similarly to what you've done.

It is; my point here was just a minor nit, in that the long 
"if/elif/else" block can and should be trivialized by mimicking the 
Python logging module's concept of log levels mapping to integers. Being 
blind to the "Level" parameter of "log_message" (and other functions 
accepting a level parameter), like the Python logging.log(level, ...) 
function, is the goal.

>
> Let me know if you have any other questions.
>
> Thanks again for your comments.
>
> ginnie
>>
>> Thanks,
>> Keith
>>
>> On 03/12/10 04:39 PM, Virginia Wray wrote:
>>> Hi -
>>>
>>> I've posted the second draft of the Logging design doc at:
>>>
>>> http://hub.opensolaris.org/bin/view/Project+caiman/Logging
>>>
>>> Select Loggingv1.2.pdf
>>>
>>> Please provide feedback by March 24, 2010.
>>>
>>> thanks,
>>>
>
>

Reply via email to