这是我用于记录的格式化字符串:
'%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s'
但为了显示日志消息,我有一个包装器做了更多(我设置了不同的日志级别,配置不同的日志后端,提供访问自定义级别的便利功能等):
class MyLogger(logging.Logger):
def split_line(self, level, message):
....
self.log.(level, line)
def progress(self, message):
self.split_line(PROGRESS, message)
使用此设置,每当我记录某些内容时:
def myfunc():
log.progress('Hello')
我明白了:
013-10-27 08:47:30,130 - PROGRESS - split_line - Hello
这不是我想要的,即:
013-10-27 08:47:30,130 - PROGRESS - myfunc - Hello
如何告诉记录器使用正确的上下文作为函数名?我认为这在堆栈框架上实际上会高出两个级别。
这是一个显示问题的测试程序:
import sys
import logging
PROGRESS = 1000
class MyLogger(logging.Logger):
PROGRESS = PROGRESS
LOG_FORMATTER = '%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s'
DEF_LOGGING_LEVEL = logging.WARNING
def __init__(self, log_name, level=None):
logging.Logger.__init__(self, log_name)
self.formatter = logging.Formatter(self.LOG_FORMATTER)
self.initLogger(level)
def initLogger(self, level=None):
self.setLevel(level or self.DEF_LOGGING_LEVEL)
self.propagate = False
def add_handler(self, log_file, use_syslog):
if use_syslog : hdlr = logging.handlers.SysLogHandler(address='/dev/log')
elif log_file : hdlr = logging.FileHandler(log_file)
else : hdlr = logging.StreamHandler(sys.stderr)
hdlr.setFormatter(self.formatter)
self.addHandler(hdlr)
return hdlr
def addHandlers(self, log_file=None, progress_file=None, use_syslog=False):
self.logger_hdlr = self.add_handler(log_file, use_syslog)
if progress_file:
self.progress_hdlr = self.add_handler(progress_file, use_syslog)
self.progress_hdlr.setLevel(self.PROGRESS)
else:
self.progress_hdlr = None
def split_line(self, level, txt, *args):
txt = txt % (args)
for line in txt.split('\n'):
self.log(level, line)
def progress(self, txt, *args):
self.split_line(self.PROGRESS, txt, *args)
logging.setLoggerClass(MyLogger)
logging.addLevelName(PROGRESS, 'PROGRESS')
logger = logging.getLogger(__name__)
logger.addHandlers()
name = 'John'
logger.progress('Hello %s\nHow are you doing?', name)
产地:
2013-10-27 09:47:39,577 - PROGRESS - split_line - Hello John
2013-10-27 09:47:39,577 - PROGRESS - split_line - How are you doing?
答案 0 :(得分:2)
基本上,责任的代码在于Logger
类:
此方法
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 == _srcfile:
f = f.f_back
continue
rv = (co.co_filename, f.f_lineno, co.co_name)
break
return rv
返回不属于当前模块的调用者链中的第一个函数。
您可以继承Logger
并通过添加稍微复杂的逻辑来覆盖此方法。跳过另一级别的呼叫深度或添加其他条件。
在你非常特殊的情况下,可能更简单的是不要进行自动分割和
logger.progress('Hello %s', name)
logger.progress('How are you doing?')
或做
def splitter(txt, *args)
txt = txt % (args)
for line in txt.split('\n'):
yield line
for line in splitter('Hello %s\nHow are you doing?', name):
logger.progress(line)
并有一个
def progress(self, txt, *args):
self.log(self.PROGRESS, txt, *args)
可能会给你带来很多麻烦。
编辑2:不,这无济于事。它现在会显示progress
作为您的来电者功能名称......
答案 1 :(得分:2)
感谢@cygnusb和已经提供有用指针的其他人。我选择使用Python 3.4 Logger.findCaller方法作为我的起点。以下解决方案已使用Python 2.7.9和3.4.2进行了测试。此代码旨在放置在自己的模块中。它只用循环的一次迭代产生正确的答案。
import io
import sys
def _DummyFn(*args, **kwargs):
"""Placeholder function.
Raises:
NotImplementedError
"""
_, _ = args, kwargs
raise NotImplementedError()
# _srcfile is used when walking the stack to check when we've got the first
# caller stack frame, by skipping frames whose filename is that of this
# module's source. It therefore should contain the filename of this module's
# source file.
_srcfile = os.path.normcase(_DummyFn.__code__.co_filename)
if hasattr(sys, '_getframe'):
def currentframe():
return sys._getframe(3)
else: # pragma: no cover
def currentframe():
"""Return the frame object for the caller's stack frame."""
try:
raise Exception
except Exception:
return sys.exc_info()[2].tb_frame.f_back
class WrappedLogger(logging.Logger):
"""Report context of the caller of the function that issues a logging call.
That is, if
A() -> B() -> logging.info()
Then references to "%(funcName)s", for example, will use A's context
rather than B's context.
Usage:
logging.setLoggerClass(WrappedLogger)
wrapped_logging = logging.getLogger("wrapped_logging")
"""
def findCaller(self, stack_info=False):
"""Return the context of the caller's parent.
Find the stack frame of the caller so that we can note the source
file name, line number and function name.
This is based on the standard python 3.4 Logger.findCaller method.
"""
sinfo = None
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
if sys.version_info.major == 2:
rv = "(unknown file)", 0, "(unknown function)"
else:
rv = "(unknown file)", 0, "(unknown function)", sinfo
while hasattr(f, "f_code"):
co = f.f_code
filename = os.path.normcase(co.co_filename)
if filename == _srcfile or filename == logging._srcfile:
f = f.f_back
continue
# We want the frame of the caller of the wrapped logging function.
# So jump back one more frame.
f = f.f_back
co = f.f_code
if sys.version_info.major == 2:
rv = "(unknown file)", 0, "(unknown function)"
else:
rv = "(unknown file)", 0, "(unknown function)", sinfo
while hasattr(f, "f_code"):
co = f.f_code
filename = os.path.normcase(co.co_filename)
if filename == _srcfile or filename == logging._srcfile:
f = f.f_back
continue
# We want the frame of the caller of the wrapped logging function.
# So jump back one more frame.
f = f.f_back
co = f.f_code
if sys.version_info.major == 2:
rv = co.co_filename, f.f_lineno, co.co_name
else:
if stack_info:
sio = io.StringIO()
sio.write('Stack (most recent call last):\n')
traceback.print_stack(f, file=sio)
sinfo = sio.getvalue()
if sinfo[-1] == '\n':
sinfo = sinfo[:-1]
sio.close()
rv = co.co_filename, f.f_lineno, co.co_name, sinfo
break
return rv
答案 2 :(得分:1)
首先根据您的代码,明确了为什么会发生这种情况,levelname
和funcName
"属于"至self.log
,当您致电self.log(level, line)
时,levelname
为level
而funcName
为line
。
你有2个选项恕我直言:
要使用inspect
模块获取当前方法并将其传递到邮件中,您可以非常轻松地解析它并使用它。
更好的方法是在split_line中使用inspect
来获取"父亲"方法
您可以将以下代码中的数字(3)更改为" play"与方法的层次结构。
使用inspect获取当前方法的示例
from inspect import stack
class Foo:
def __init__(self):
print stack()[0][3]
f = Foo()
答案 3 :(得分:1)
您可以合并progress
方法和split_line
方法:
def progress(self, txt, *args, **kwargs):
if self.isEnabledFor(self.PROGRESS):
txt = txt % (args)
for line in txt.split('\n'):
self._log(self.PROGRESS, line, [], **kwargs)
答案 4 :(得分:1)
正如第一个答案中所建议的那样,继承Logger类并使用logging.setLoggerClass应该可以解决问题。您将需要一个修改过的findCaller函数,即处理您的包装函数调用。
将以下内容放入模块中,因为findCaller类方法正在从文件中搜索第一个调用,该文件不是当前的源文件名。
import inspect
import logging
import os
if hasattr(sys, 'frozen'): #support for py2exe
_srcfile = "logging%s__init__%s" % (os.sep, __file__[-4:])
elif __file__[-4:].lower() in ['.pyc', '.pyo']:
_srcfile = __file__[:-4] + '.py'
else:
_srcfile = __file__
_srcfile = os.path.normcase(_srcfile)
class WrappedLogger(logging.Logger):
def __init__(self,name):
logging.Logger.__init__(self, name)
def findCaller(self):
"""
Find the stack frame of the caller so that we can note the source
file name, line number and function name.
"""
# get all outer frames starting from the current frame
outer = inspect.getouterframes(inspect.currentframe())
# reverse the order, to search from out inward
outer.reverse()
rv = "(unknown file)", 0, "(unknown function)"
pos = 0
# go through all frames
for i in range(0,len(outer)):
# stop if we find the current source filename
if outer[i][1] == _srcfile:
# the caller is the previous one
pos=i-1
break
# get the frame (stored in first tuple entry)
f = outer[pos][0]
co = f.f_code
rv = (co.co_filename, f.f_lineno, co.co_name)
return rv
# Usage:
logging.setLoggerClass(WrappedLogger)
log = logging.getLogger("something")
答案 5 :(得分:1)
有人给出了正确的答案。我将做一个总结。
logging.Logger.findCaller(),它会在原始logging
包中按logging._srcfile过滤堆栈帧。
所以我们做同样的事情,过滤我们自己的记录器包装器my_log_module._srcfile
。我们动态替换记录器实例的方法logging.Logger.findCaller()。
logging.Logger
的子类,logging
包没有设计OOP时findCaller,pitty ...是吗?
# file: my_log_module.py, Python-2.7, define your logging wrapper here
import sys
import os
import logging
my_logger = logging.getLogger('my_log')
if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3)
# done filching
#
# _srcfile is used when walking the stack to check when we've got the first
# caller stack frame.
#
_srcfile = os.path.normcase(currentframe.__code__.co_filename)
def findCallerPatch(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 == _srcfile:
f = f.f_back
continue
rv = (co.co_filename, f.f_lineno, co.co_name)
break
return rv
# DO patch
my_logger.findCaller = findCallerPatch
好的,一切都准备好了。您现在可以在其他模块中使用您的记录器,添加您的记录消息格式:lineno,路径,方法名称,blablabla
# file: app.py
from my_log_module import my_logger
my_logger.debug('I can check right caller now')
或者您可以使用优雅的方式,但不要使用全局logging.setLoggerClass
# file: my_log_modue.py
import logging
my_logger = logging.getLogger('my_log')
class MyLogger(logging.Logger):
...
my_logger.__class__ = MyLogger
答案 6 :(得分:0)
感谢@glglgl,我可以提出广告先进的findCaller
请注意_logging_srcfile
和_this_srcfile
的初始化 - 受python logging source code启发
当然你可以把自己的规则放在findCaller()
中 - 这里我只是排除自定义记录器所在文件中的所有内容,除了test_logging
函数。
重要只有在将名称传递给getLogger(name)
工厂时才会检索自定义记录器。如果您只是logging.getLogger()
,您将获得不是您的记录器的RootLogger。
import sys
import os
import logging
# from inspect import currentframe
currentframe = lambda: sys._getframe(3)
_logging_srcfile = os.path.normcase(logging.addLevelName.__code__.co_filename)
_this_srcfile = __file__
def test_logging():
logger = logging.getLogger('test')
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(logging.Formatter('%(funcName)s: %(message)s'))
handler.setLevel(11)
logger.addHandler(handler)
logger.debug('Will not print')
logger.your_function('Test Me')
class CustomLogger(logging.getLoggerClass()):
def __init__(self, name, level=logging.NOTSET):
super(CustomLogger, self).__init__(name, level)
def your_function(self, msg, *args, **kwargs):
# whatever you want to do here...
self._log(12, msg, args, **kwargs)
def findCaller(self):
"""
Find the stack frame of the caller so that we can note the source
file name, line number and function name.
This function comes straight from the original python one
"""
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)
## original condition
# if filename == _logging_srcfile:
## PUT HERE YOUR CUSTOM CONDITION, eg:
## skip also this file, except the test_logging method which is used for debug
if co.co_name != 'test_logging' and filename in [_logging_srcfile, _this_srcfile]:
f = f.f_back
continue
rv = (co.co_filename, f.f_lineno, co.co_name)
break
return rv
logging.setLoggerClass(CustomLogger)
答案 7 :(得分:0)
此问题在Python 3.8中已解决,并添加了堆栈级参数。但是,我采用了findCaller from cpython的当前实现来制作Python 3.7兼容版本。
来自以上答案的组合:
import sys,os
#Get both logger's and this file's path so the wrapped logger can tell when its looking at the code stack outside of this file.
_loggingfile = os.path.normcase(logging.__file__)
if hasattr(sys, 'frozen'): #support for py2exe
_srcfile = "logging%s__init__%s" % (os.sep, __file__[-4:])
elif __file__[-4:].lower() in ['.pyc', '.pyo']:
_srcfile = __file__[:-4] + '.py'
else:
_srcfile = __file__
_srcfile = os.path.normcase(_srcfile)
_wrongCallerFiles = set([_loggingfile, _srcfile])
#Subclass the original logger and overwrite findCaller
class WrappedLogger(logging.Logger):
def __init__(self, name):
logging.Logger.__init__(self, name)
#Modified slightly from cpython's implementation https://github.com/python/cpython/blob/master/Lib/logging/__init__.py#L1374
def findCaller(self, stack_info=False, stacklevel=1):
"""
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
orig_f = f
while f and stacklevel > 1:
f = f.f_back
stacklevel -= 1
if not f:
f = orig_f
rv = "(unknown file)", 0, "(unknown function)", None
while hasattr(f, "f_code"):
co = f.f_code
filename = os.path.normcase(co.co_filename)
if filename in _wrongCallerFiles:
f = f.f_back
continue
sinfo = None
if stack_info:
sio = io.StringIO()
sio.write('Stack (most recent call last):\n')
traceback.print_stack(f, file=sio)
sinfo = sio.getvalue()
if sinfo[-1] == '\n':
sinfo = sinfo[:-1]
sio.close()
rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
break
return rv
答案 8 :(得分:0)
Python 3.8.6, Logging _log 函数有一个关键参数“stacklevel”,所以我解决了这样的问题:
包装类 MyLogger:
def __init__(self):
...
self._logger = logging.getLogger(__name__)
def debug(self, msg, *args, **kwargs):
self._logger.debug(msg, *args, stacklevel=2, **kwargs)
此代码在调试消息中正确显示了函数名称、代码行号等