Python 在自定义类中包装记录器功能时显示正确的funcName
这是我用于日志记录的格式化字符串:Python 在自定义类中包装记录器功能时显示正确的funcName,python,logging,Python,Logging,这是我用于日志记录的格式化字符串: '%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s' 但是为了显示日志消息,我有一个包装器做了更多的工作(我设置了不同的日志级别,配置了不同的日志后端,提供了访问自定义级别的方便函数,等等): 使用此设置,每当我记录某些内容时: def myfunc(): log.progress('Hello') 我得到: 013-10-27 08:47:30,130 - PROGRESS
'%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s'
但是为了显示日志消息,我有一个包装器做了更多的工作(我设置了不同的日志级别,配置了不同的日志后端,提供了访问自定义级别的方便函数,等等):
使用此设置,每当我记录某些内容时:
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
我如何告诉记录器为函数名使用正确的上下文?我认为这实际上会比stackframe高出两层
编辑
这是一个显示问题的测试程序:
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?
首先,根据您的代码,很清楚发生这种情况的原因,
levelname
和funcName
属于self.log
,因此当您调用self.log(level,line)
时,levelname
是level
而funcName
是line
您有两个选项IMHO:
inspect
模块获取当前方法并在消息中传递它,您可以解析它并非常轻松地使用它inspect
来获得“父”方法
您可以将下面代码中的数字(3)更改为“播放”方法的层次结构from inspect import stack
class Foo:
def __init__(self):
print stack()[0][3]
f = Foo()
本质上,应该归咎于
记录器
类:
这种方法
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
作为调用方函数名…您可以合并progress
方法和split\u 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)
正如第一个答案中所建议的,对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")
感谢@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
多亏了@glglgl,我才有了高级搜索引擎 请注意
\u logging\u srcfile
和\u此文件
的初始化-灵感来自
当然,您可以将自己的规则放在findCaller()
-这里我只是排除了自定义记录器所在文件中的所有内容,除了test\u logging
函数
重要信息只有将名称传递给getLogger(name)
工厂时,才会检索自定义记录器。如果您只需执行logging.getLogger()
,您将得到一个不是您的记录器的RootLogger
导入系统
导入操作系统
导入日志记录
#从检查导入currentframe
currentframe=lambda:sys.\u getframe(3)
_logging\u srcfile=os.path.normcase(logging.addLevelName.\uuuu代码\uuuu.co\u文件名)
_此\u srcfile=\u文件__
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(处理程序)
logger.debug('将不打印')
logger.your_函数(“测试我”)
类CustomLogger(logging.getLoggerClass()):
def u u init u;(self,name,level=logging.NOTSET):
超级(自定义记录器,自我)。\uuuuu初始化\uuuuuuu(名称,级别)
定义您的_函数(self、msg、*args、**kwargs):
#不管你想在这里做什么。。。
自我记录(12,消息,参数,**kwargs)
def findCaller(自):
"""
找到调用者的堆栈帧,以便我们可以记录源代码
文件名、行号和函数名。
此函数直接来自原始python函数
"""
f=当前帧()
#在某些版本的IronPython上,如果
#IronPython不能与-X:Frames一起运行。
如果f不是无:
f=f.f_后
rv=“(未知文件)”,0“(未知函数)”
而hasattr(f,“f_代码”):
co=f.f_代码
filename=os.path.normcase(co.co_文件名)
##原始状态
#如果文件名==\u日志记录\u文件:
##在这里输入您的自定义条件,例如:
##也跳过此文件,但用于调试的test_日志记录方法除外
如果公司名称!='在[\u logging\u srcfile,\u this\u srcfile]中测试\u logging'和文件名:
f=f.f_后
持续
rv=(co.co_文件名,f.f_行号,co.co_名称)
打破
返回rv
logging.setLoggerClass(CustomLogger)
有人给出了正确的答案。我将做一个总结 ,它通过原始
日志记录包过滤堆栈帧
所以我们做同样的事情,过滤我们自己的记录器包装my\u log\u模块。\u srcfile
。我们将动态替换记录器实例的方法
顺便说一句,请不要创建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
嗯
# 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
# file: app.py
from my_log_module import my_logger
my_logger.debug('I can check right caller now')
# file: my_log_modue.py
import logging
my_logger = logging.getLogger('my_log')
class MyLogger(logging.Logger):
...
my_logger.__class__ = MyLogger
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
def __init__(self):
...
self._logger = logging.getLogger(__name__)
def debug(self, msg, *args, **kwargs):
self._logger.debug(msg, *args, stacklevel=2, **kwargs)