python / cpython

The Python programming language
https://www.python.org
Other
63.83k stars 30.56k forks source link

logging module root logger name #41910

Closed 7a034f16-717b-4d44-97a6-6fc13eabbb7f closed 19 years ago

7a034f16-717b-4d44-97a6-6fc13eabbb7f commented 19 years ago
BPO 1190689
Nosy @vsajip

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields: ```python assignee = 'https://github.com/vsajip' closed_at = created_at = labels = ['type-feature', 'library'] title = 'logging module root logger name' updated_at = user = 'https://bugs.python.org/cxdunn' ``` bugs.python.org fields: ```python activity = actor = 'cxdunn' assignee = 'vinay.sajip' closed = True closed_date = None closer = None components = ['Library (Lib)'] creation = creator = 'cxdunn' dependencies = [] files = [] hgrepos = [] issue_num = 1190689 keywords = [] message_count = 10.0 messages = ['54477', '54478', '54479', '54480', '54481', '54482', '54483', '54484', '54485', '54486'] nosy_count = 2.0 nosy_names = ['vinay.sajip', 'cxdunn'] pr_nums = [] priority = 'low' resolution = 'works for me' stage = None status = 'closed' superseder = None type = 'enhancement' url = 'https://bugs.python.org/issue1190689' versions = [] ```

7a034f16-717b-4d44-97a6-6fc13eabbb7f commented 19 years ago

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

7a034f16-717b-4d44-97a6-6fc13eabbb7f commented 19 years ago

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,

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

7a034f16-717b-4d44-97a6-6fc13eabbb7f commented 19 years ago

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:

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:

-cxdunn

7a034f16-717b-4d44-97a6-6fc13eabbb7f commented 19 years ago

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

vsajip commented 19 years ago

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.

vsajip commented 19 years ago

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.

7a034f16-717b-4d44-97a6-6fc13eabbb7f commented 19 years ago

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".

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

7a034f16-717b-4d44-97a6-6fc13eabbb7f commented 19 years ago

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

vsajip commented 19 years ago

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.

7a034f16-717b-4d44-97a6-6fc13eabbb7f commented 19 years ago

Logged In: YES user_id=1267419

You're right. The following works:

>>> import logging, logging.config
>>> logging.root.name = "snafu"
>>> logging.config.fileConfig('test.cfg')
>>> logging.warning("Hello")
[snafu] Hello

Where test.cfg is: [loggers] keys=root

[handlers] keys=hand01

[formatters] keys=form01

[logger_root] level=NOTSET handlers=hand01

[handler_hand01] class=StreamHandler level=NOTSET formatter=form01 args=(sys.stderr,)

[formatter_form01] format=[%(name)s] %(message)s

"If you want to make clearer a distinction between root logger and other loggers ... please submit a documentation patch."

OK....