[ python-Feature Requests-1190689 ] logging module root logger name

SourceForge.net noreply at sourceforge.net
Tue May 3 14:07:59 CEST 2005


Feature Requests item #1190689, was opened at 2005-04-27 01:19
Message generated for change (Comment added) made by vsajip
You can respond by visiting: 
https://sourceforge.net/tracker/?func=detail&atid=355470&aid=1190689&group_id=5470

Category: Python Library
Group: None
>Status: Closed
>Resolution: Invalid
Priority: 5
Submitted By: Christopher Dunn (cxdunn)
Assigned to: Vinay Sajip (vsajip)
Summary: logging module root logger name

Initial Comment:
I would like a trailing '.' to be ignored in names
passed to getLogger(), like a trainling '/' in a Unix path.

In module 'foo':
logfoo = getLogger('.foo.')
# logger '"" should be the parent of ".foo"

Elsewhere, controlled by the user of that module:
import foo
logdefault = getLogger('.')
hdlr = StreamHandler()
fmtr = Formatter("%(name)s:%(msg)s")
hdlr.setFormatter(fmtr)
logdefault.addHandler(hdlr)


Given this change, I would also like the name of the
default logger to be displayed as '.', or even "",
rather than 'root'. The current behavior is odd:

logfoo.info("Foo message")
displays
.foo:Foo message
buf
logdefault.info("Default message")
displays
root:Default message

I NEVER mentioned the word "root" anywhere! And I don't
think it's very descriptive.

I would rather see ANY of these:
:Default message
.:Default message
default:Default message
logging:Default message

These changes would make the system more intuitive.

-cxdunn

----------------------------------------------------------------------

>Comment By: Vinay Sajip (vsajip)
Date: 2005-05-03 12:07

Message:
Logged In: YES 
user_id=308438

There's no need to rename the root logger in a configuration 
file, so it can still be called "root" in the configuration file and 
have a different name displayed in messages. The config 
logic specifically looks for the section name "logger_root" and 
associates that section with the root logger. There is no 
reason to change this.

You should not use logging.root to get the root logger. 
Instead, use logging.getLogger().

If you want to make clearer a distinction between root logger 
and other loggers (they're not that different, in my view - see 
the docstring for the RootLogger class), please submit a 
documentation patch.

----------------------------------------------------------------------

Comment By: Christopher Dunn (cxdunn)
Date: 2005-05-02 23:23

Message:
Logged In: YES 
user_id=1267419

There's a bug wrt renaming the root logger:

>>> import logging.config
>>> logging.root.name = "snafu"
>>> logging.config.fileConfig("test.cfg")
Traceback (most recent call last):
  File "python2.3/logging/config.py", line 132, in fileConfig
    llist.remove("root")
ValueError: list.remove(x): x not in list

This is no different in 2.4.
list.remove(root.name)
is an easy fix.

Also,
logging.getLogger() != logging.getLogger("root")
or any other name. I now access the root logger strictly via
logging.root

getRootLogger(), which is deprecated, should be preferred,
since the root logger's name is not actually in the hash-table.

We need to make a sharp distinction between the root logger
and the others. There is only one root; you cannot look it
up by name; and the "dot" hierarchy does not apply to the
root (for if it did, we would have to look specify children
as .child, a convention that you've already rejected).

-cxdunn

P.S.
I've posted some useful logging-related stuff at the
ActivePython Cookbook. Feel free to add any of that to the
module. Especially, the Unique filter could be added to
logging.filters

----------------------------------------------------------------------

Comment By: Christopher Dunn (cxdunn)
Date: 2005-04-28 19:23

Message:
Logged In: YES 
user_id=1267419

You're right! That works like a charm:

>>> import logging
>>> logging.getLogger().name = '.'
>>> logging.warning("I am root")
WARNING:.:I am root
>>> sub = logging.getLogger('.sub')
>>> sub.warning("I am a child")
WARNING:.sub:I am a child

Setting the root to "" also works:

>>> import logging
>>> logging.getLogger().name = ""
>>> logging.warning("I am root")
WARNING::I am root
>>> sub = logging.getLogger('sub')
>>> sub.warning("I am a child")
WARNING:sub:I am a child

I agree with your other points. The flexibility would add
little value. I brought this issue up b/c I was confused by
the docs. Clearly,  But it is not so clear that "A" is a
child of "root".

  * "A.B" is obviously a child of "A"
  * ".A" is *clearly* a child of "."
  * And "A" is presumably a child of "".
  * But it is not so clear that "A" is a child of "root"

Since *everything* is a child of the root logger, that's
worth reiterating in the docs. And if there is truly only 1
root logger, then it should be possible to find it by name:

>>> import logging
>>> logging.getLogger().name ="."
>>> logging.warning("I am root")
WARNING:.:I am root
>>> unknown = logging.getLogger(".")
>>> unknown.warning("Who am I?")
WARNING:.:Who am I?
>>> unknown == logging.getLogger()
False

In fact:

>>> import logging
>>> logging.getLogger() == logging.getLogger() #just a test
True
>>> logging.getLogger() == logging.getLogger("root") #should
be same!
False

This is not an easy module to understand, but it's amazingly
powerful.

One last suggestion. You have logging.handlers. You could
also have logging.filters. For example:

class Opaque(Filter):
    """A simple way to prevent any messages from getting
through."""
    def __init__(self, name=None): pass
    def filter(self, rec): return False

class Unique(Filter):
    """Messages are allowed through just once.
    The 'message' includes substitutions, but is not
formatted by the 
    handler. If it were, then practically all messages would
be unique!
    """
    def __init__(self, name=""):
        Filter.__init__(self, name)
        self.reset()
    def reset(self):
        """Act as if nothing has happened."""
        self.__logged = {}
    def filter(self, rec):
        return Filter.filter(self, rec) and
self.__is_first_time(rec)
    def __is_first_time(self, rec):
        """Emit a message only once."""
        msg = rec.msg %(rec.args)
        if msg in self.__logged:
            self.__logged[msg] += 1
            return False
        else:
            self.__logged[msg] = 1
            return True

Actually, this might be Cookbook material. I'll write it up.

Thanks for your time.

-cxdunn

----------------------------------------------------------------------

Comment By: Vinay Sajip (vsajip)
Date: 2005-04-28 07:43

Message:
Logged In: YES 
user_id=308438

Whoops!

I don't quite know what happened, but I think both of us were 
updating this RFE entry concurrently. I only saw your 
followup starting "Novices always ask..." before I posted my 
response.

----------------------------------------------------------------------

Comment By: Vinay Sajip (vsajip)
Date: 2005-04-28 07:36

Message:
Logged In: YES 
user_id=308438

Logger names are hierarchical with dots separating levels in 
the hierarchy. So to me it does not make sense to have 
logger names which end in a dot, and you have given no 
reason why trailing dots should be supported. However, the 
hierarchy is not completely anologous to file system 
hierarchies - there is by design no concept of a "default" 
or "current" logger. I do not propose to make a change to this.

However, I agree that the name of the root logger being "root" 
might be seen as unintuitive by some. Of your alternatives I 
think "logging" is best. I propose to add to the documentation 
the suggestion that users can define their own name for the 
root logger as in the following example:

logging.getLogger().name = "myapp"

People who use the root logger directly typically don't use 
other (named) loggers, because the whole point of using 
named loggers is to pinpoint areas of the application. Those 
users who use the root logger directly are typically not 
interested in finer granularity than the application or script 
itself.


----------------------------------------------------------------------

Comment By: Christopher Dunn (cxdunn)
Date: 2005-04-28 07:29

Message:
Logged In: YES 
user_id=1267419

Oops. Where I wrote abspath.rstrip('.'), I meant
abspath.strip('.')
Drop both leading and trailing dots for the prettified path.
-cdunn

----------------------------------------------------------------------

Comment By: Christopher Dunn (cxdunn)
Date: 2005-04-28 07:21

Message:
Logged In: YES 
user_id=1267419

I am attaching a first pass it it.

I've stored the "absolute" names everywhere, including both
leading and trailing '.'

I call this "absolute" by analogy with os.path.abspath(). I
believe that similarities within the Python library help the
user remember key concepts.

What's missing:

 * I don't have aliases.
 * The "current working logger" is always '.'
 * And I haven't added any extra tags for the Formatter.

But those are all very simple changes. The tough part is
getting the path-searching correct. I have a big UnitTest
suite which I can send to you if you'd like.

The most important thing is that the word "root" is
completely gone, but perhaps %(name)s should translate '.'
to 'root' for backwards compatibility.

The second-most important thing is that getLogger('.')
returns the root logger.

Third is that getLogger("Package.Module") is equivalent to
getLogger(".Package.Module.")

As for tags in the Formatter, after some testing I suggest
these:

%(name)s => abspath.rstrip('.'), but "." becomes "root"
%(absname)s => abspath, with leading AND trailing dot, like
a directory, so there is no question about whether the root
displays as "." or "". It is always just dot in absolute
notation.
%(logger)s => abspath.rstrip('.'), maybe the prettiest

I must tell you that, once I figured out how the logging
module works, I really love it!

Other possible additions:

* Some useful, predefined filter classes: Never, OneTime
(which must have a reset() method to clear its cache). I can
send my version if you want.
* A PipedStreamHandler. I'm not sure how to make this work.
The idea is that, in UnitTesting, I want to read from some
stream immediately after an operation, and I want the logged
data to be immediately available, but to disappear as soon
as I've read it. Does that make sense? Right now, I use a
cStringIO object, with s.seek(0); s.truncate() after every read.

-cxdunn

----------------------------------------------------------------------

Comment By: Christopher Dunn (cxdunn)
Date: 2005-04-27 20:37

Message:
Logged In: YES 
user_id=1267419

Novices always ask, "Why did it print 'root'? Where did that
come from? 
After discussing this with some other "logging" module
users, I think we've come up with a very good idea, which
would maintain BACKWARDS COMPATIBILITY.

Essentially, treat the logging module as a shell and the
logger name as a path. Specifically,

* Let the global logging functions operate on the "current
worrking logger", which by default is "." (Let "root" be an
alias for ".")
* Change getLogger() so that it works on both absolute and
relative logger paths. (Since the default current logger is
"root", we maintain backwards compatibility.)
* Change the format function so that %(name)s shows the
relative path, if the absolute path starts with the current
working logger name.
* Add a format keyword, %(absname)s, which prints the
absolute logger path.
* Add another format keyword, %(logger)s, which prints what
most people expect to see: the absolute logger name, sans
the leading dot. (The "root" or "." logger would display as
"", exactly the way it is usually accessed.)
* Add global functions, change_current_logger() and
get_current_logger().
* Add global function, alias(). Always create an alias for
"root" to "."

Examples::
  from logging import *
  log = getLogger() #or getLogger(".") or getLogger("root")
  h1 = StreamHandler()
  f1 = Formatter("[%(name)s]%(message)s")
  h1.setFormatter(f1)
  log.addHandler(h1)
  h2 = StreamHandler()
  f2 = Formatter("[%(absname)s]%(message)s")
  h2.setFormatter(f2)
  log.addHandler(h2)
  h3 = StreamHandler()
  f3 = Formatter("[%(logger)s]%(message)s")
  h3.setFormatter(f3)
  log.addHandler(h3)
  log.error("First message")

  # ...
  child = getLogger("child") # or getLogger(".child")
  child.error("Bad news")

This should print:

[root]First message
[.]First message
[]First message
[child]Bad news
[.child]Bad news
[child]Bad news


This would create tremendous flexibility, add some clarity
to the meaning of the "root" logger, and still maintain
complete backwards compatibility.

I am willing to make the changes myself, including
UnitTests, if there is agreement that they would be adopted.
(Note that String.before() and String.after() would make the
coding a little easier/clearer, but that's a different
feature request.)

-cxdunn

----------------------------------------------------------------------

You can respond by visiting: 
https://sourceforge.net/tracker/?func=detail&atid=355470&aid=1190689&group_id=5470


More information about the Python-bugs-list mailing list