我想将Python记录器包装在一个自定义类中,以嵌入一些特定于应用程序的功能,并隐藏开发人员的设置细节(设置文件输出,日志记录级别等)。为此,我使用以下API创建了一个类:
__init__(log_level, filename)
debug(msg)
info(msg)
warning(msg)
error(msg)
Logger.debug / info / warning / etc调用通常会在日志中写入进行日志调用的函数和行号。但是,使用我的自定义类,写入日志文件的函数和行号始终相同(对应于自定义类中的debug()/ info()/ warning()/ error()函数)。我希望它保存记录msg的应用程序代码行。这可能吗?
提前致谢。
答案 0 :(得分:4)
如果您愿意重新实现一些标准日志记录模块,则可以生成日志包装器。诀窍是编写自己的findCaller()方法,该方法在解释反向跟踪时知道如何忽略日志包装器源文件。
logwrapper.py中的:
import logging
import os
import sys
from logging import *
# This code is mainly copied from the python logging module, with minor modifications
# _srcfile is used when walking the stack to check when we've got the first
# caller stack frame.
#
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 LogWrapper(object):
def __init__(self, logger):
self.logger = logger
def debug(self, msg, *args, **kwargs):
"""
Log 'msg % args' with severity 'DEBUG'.
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
"""
if self.logger.isEnabledFor(DEBUG):
self._log(DEBUG, msg, args, **kwargs)
def info(self, msg, *args, **kwargs):
"""
Log 'msg % args' with severity 'INFO'.
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
logger.info("Houston, we have a %s", "interesting problem", exc_info=1)
"""
if self.logger.isEnabledFor(INFO):
self._log(INFO, msg, args, **kwargs)
# Add other convenience methods
def log(self, level, msg, *args, **kwargs):
"""
Log 'msg % args' with the integer severity 'level'.
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
logger.log(level, "We have a %s", "mysterious problem", exc_info=1)
"""
if not isinstance(level, int):
if logging.raiseExceptions:
raise TypeError("level must be an integer")
else:
return
if self.logger.isEnabledFor(level):
self._log(level, msg, args, **kwargs)
def _log(self, level, msg, args, exc_info=None, extra=None):
"""
Low-level logging routine which creates a LogRecord and then calls
all the handlers of this logger to handle the record.
"""
# Add wrapping functionality here.
if _srcfile:
#IronPython doesn't track Python frames, so findCaller throws an
#exception on some versions of IronPython. We trap it here so that
#IronPython can use logging.
try:
fn, lno, func = self.findCaller()
except ValueError:
fn, lno, func = "(unknown file)", 0, "(unknown function)"
else:
fn, lno, func = "(unknown file)", 0, "(unknown function)"
if exc_info:
if not isinstance(exc_info, tuple):
exc_info = sys.exc_info()
record = self.logger.makeRecord(
self.logger.name, level, fn, lno, msg, args, exc_info, func, extra)
self.logger.handle(record)
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 = logging.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
使用它的一个例子:
import logging
from logwrapper import LogWrapper
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(filename)s(%(lineno)d): "
"%(message)s")
logger = logging.getLogger()
lw = LogWrapper(logger)
lw.info('Wrapped well, this is interesting')
答案 1 :(得分:1)
是:sys._getframe(NUM)
其中NUM表示您正在寻找的当前项目之外的功能数量。返回的框架对象具有f_lineno
和f_code.co_filename
等属性。
答案 2 :(得分:1)
基于@Will Ware的答案。另一个选择是覆盖findCaller
方法并将自定义类用作默认记录器:
class MyLogger(logging.Logger):
"""
Needs to produce correct line numbers
"""
def findCaller(self):
n_frames_upper = 2
f = logging.currentframe()
for _ in range(2 + n_frames_upper): # <-- correct frame
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 == logging._srcfile:
f = f.f_back
continue
rv = (co.co_filename, f.f_lineno, co.co_name)
break
return rv
logging.setLoggerClass(MyLogger)
logger = logging.getLogger('MyLogger') # init MyLogger
logging.setLoggerClass(logging.Logger) # reset logger class to default
答案 3 :(得分:0)
这是重写findCaller
的又一次尝试。这使您可以基于每个功能自定义额外的堆栈帧深度。
import os
import logging
from contextlib import contextmanager
logging.basicConfig(
format='%(asctime)-15s %(levelname)s %(filename)s:%(lineno)d %(message)s',
level=logging.INFO
)
@contextmanager
def logdelta(n, level=logging.DEBUG):
_frame_stuff = [0, logging.Logger.findCaller]
def findCaller(_):
f = logging.currentframe()
for _ in range(2 + _frame_stuff[0]):
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 == logging._srcfile:
f = f.f_back
continue
rv = (co.co_filename, f.f_lineno, co.co_name)
break
return rv
rootLogger = logging.getLogger()
isEnabled = rootLogger.isEnabledFor(level)
d = _frame_stuff[0]
try:
logging.Logger.findCaller = findCaller
_frame_stuff[0] = d + n
yield isEnabled
except:
raise
finally:
logging.Logger.findCaller = _frame_stuff[1]
_frame_stuff[0] = d
def A(x):
with logdelta(1):
logging.info('A: ' + x) # Don't log with this line number
def B(x):
with logdelta(2):
logging.info('A: ' + x) # or with this line number
def C(x):
B(x) # or this line number
A('hello') # Instead, log with THIS line number
C('hello') # or THIS line number```
答案 4 :(得分:0)
更改行号和文件名的最佳位置(如果确实要更改)在记录器附带的过滤器中。这是概念证明。最后一行将记录一条自定义消息,其中包含文件名和最后一行:
import logging
import inspect
from pathlib import Path
class up_stacked_logger:
def __init__(self, logger, n):
self.logger = logger
calling_frame = inspect.stack()[n+1].frame
trace = inspect.getframeinfo(calling_frame)
class UpStackFilter(logging.Filter):
def filter(self, record):
record.lineno = trace.lineno
record.pathname = trace.filename
record.filename = Path(trace.filename).name
return True
self.f = UpStackFilter()
def __enter__(self):
self.logger.addFilter(self.f)
return self.logger
def __exit__(self, *args, **kwds):
self.logger.removeFilter(self.f)
def my_cool_customized_log_function(logger, msg):
with up_stacked_logger(logger, n=1) as logger:
logger.info(f'--> customize {msg}')
logging.basicConfig(level=logging.DEBUG, format="[%(name)s][%(levelname)s] %(message)s (%(filename)s:%(lineno)d)")
logger = logging.getLogger('some.module.logger')
my_cool_customized_log_function(logger, 'a message')
答案 5 :(得分:0)
尝试stacklevel
,它计算从原始日志记录呼叫到记录器的debug()
,info()
等呼叫的呼叫数。它是logging 3.8中的新功能:
第三个可选关键字参数是堆栈级别,默认为 1.如果大于1,则在计算设置中的行号和函数名称时,将跳过相应的堆栈帧数 为记录事件创建的LogRecord。可用于记录 助手,以便记录函数名称,文件名和行号 不是有关辅助功能/方法的信息,而是 呼叫者。此参数的名称反映了 警告模块。