Peter Otten wrote:
Jean-Michel Pichavant wrote:

Hellmut Weber wrote:
Am 11.03.2010 12:14, schrieb Peter Otten:
Hellmut Weber wrote:

Logging works very well giving the filename and line number of the
point
where it is called. As long as I use the loggers directly.
BUT when I have to wrap the logger call in some other function, I
always
get file name and line number of the call of the logger inside the
wrapping function.

Is there a possibility to get this information in this situation too?
The official way is probably to write a custom Logger class that
overrides
the findCaller() method.

Below is a hack that monkey-patches the logging._srcfile attribute to
ignore
user-specified modules in the call stack:

$ cat wrapper.py
import logging
import os
import sys

logger = logging.getLogger()

class SrcFile(object):
     def __init__(self, exclude_files):
         self.files = set(exclude_files)
     def __eq__(self, other):
         return other in self.files

def fixname(filename):
     if filename.lower().endswith((".pyc", ".pyo")):
         filename = filename[:-4] + ".py"
     return os.path.normcase(filename)

if "--monkey" in sys.argv:
     print "patching"
     logging._srcfile = SrcFile([logging._srcfile, fixname(__file__)])

def warn(*args, **kw):
     logger.warn(*args, **kw)

$ cat main.py
import logging
logging.basicConfig(format="%(filename)s<%(lineno)s>: %(message)s")
import wrapper
wrapper.warn("foo")
wrapper.warn("bar")
wrapper.warn("baz")

$ python main.py
wrapper.py<23>: foo
wrapper.py<23>: bar
wrapper.py<23>: baz

$ python main.py --monkey
patching
main.py<4>: foo
main.py<5>: bar
main.py<6>: baz

$ python -V
Python 2.6.4

Peter
Hi Peter,
your hack is exactly what I was looking for.
It permits to configure my logging messages as I want, e.g. using
different colors for different classes of messages.

I do not yet understand all details WHY it is working but suppose some
study of the logging module will help me to understand.

Thank you very much

_scrFile is a private attribute of the logging module. Don't change it.

As you said yourself, 'The official way is probably to write a custom
Logger class that overrides
the findCaller() method'

OK, I tried the this approach, too:

import logging
import os
import sys

from logging import currentframe

def fixname(filename):
    if filename.lower().endswith((".pyc", ".pyo")):
        filename = filename[:-4] + ".py"
    return os.path.normcase(filename)

class MyLogger(logging.Logger):
    exclude_files = set([logging._srcfile, fixname(__file__)])

    def findCaller(self):
        """
        Find the stack frame of the caller so that we can note the source
        file name, line number and function name.
        """
        f = currentframe()
        #On some versions of IronPython, currentframe() returns None if
        #IronPython isn't run with -X:Frames.
        if f is not None:
            f = f.f_back
        rv = "(unknown file)", 0, "(unknown function)"
        while hasattr(f, "f_code"):
            co = f.f_code
            filename = os.path.normcase(co.co_filename)
            if filename in self.exclude_files:
                f = f.f_back
                continue
            rv = (filename, f.f_lineno, co.co_name)
            break
        return rv

if "--custom-logger" in sys.argv:
    print "setting custom logger"
    logging.setLoggerClass(MyLogger)
    logging.root = MyLogger("root", logging.WARNING)

logger = logging.getLogger()
def warn(*args, **kw):
    logger.warn(*args, **kw)

I had to duplicate the original findCaller() method with only one line changed. This means I have now some code duplication and I still have to monitor future changes in the logging source.

In this case the "official way" seems to be more intrusive than the "hack".

Peter
You are still accessing the private attribute of the  module logging.
My previous remark was misleading, in fact there's nothing you can do about it. _srcfile is not meant to be used elsewhere than in the logging module itself.

However, I don't wanna sound like I'm rejecting this solution, 1st the OP is satisified with it and since this solution is working, it is still more helpful than anyone noticing that you've accessed a private attribute (some will successfully argue that python allows to do so).

At the very begining of this thread I've provided a complete different approach, instead of using the builtin 'filename' and 'lineno' field of the logging, use custom fileds with the extra parameter.

It has also some drawbacks but could be a possible alternative.

Cheers,

JM

in test.py:

import logging
import inspect

_logger = logging.getLogger(__name__)

class Foo:
  def __init__(self):
      self._logger = _logger

  def info(self, msg):
# this is the method you don't want to appear in the logs, instead the lineno and filename of this method caller
      previousFrame = inspect.currentframe().f_back
self._logger.info(msg, extra={'custom_lineno':previousFrame.f_lineno, 'custom_filename': previousFrame.f_code.co_filename})

if __name__ == '__main__':
  _logger.handlers=[]
  _logger.addHandler(logging.StreamHandler())
_logger.handlers[-1].setFormatter(logging.Formatter('file %(custom_filename)s line %(custom_lineno)d : %(message)s'))
  _logger.setLevel(logging.DEBUG)
  foo = Foo()
  foo.info('a foo info')

In [3]: run test.py
file test.py line 20 : a foo info

--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to