Wrapper 导致自定义 python 记录器中的 funcName 属性发生变化
Posted
技术标签:
【中文标题】Wrapper 导致自定义 python 记录器中的 funcName 属性发生变化【英文标题】:Wrapper causes change in the funcName attribute in custom python logger 【发布时间】:2017-10-25 03:56:42 【问题描述】:我在自定义 python 记录器中创建了一个包装器,以在日志格式化程序中添加其他属性。
logFormat = logging.Formatter('[%(levelname)s],[%(asctime)-15s], %(API_SERVER)s, %(funcName)s,%(lineno)d, %(message)s')
附加属性 -> API_SERVER
日志将包装器方法显示为 funcName 而不是实际的 funcName。
[DEBUG],[2017-05-23 17:52:13,865], jupiter-api-server, loggingMethodsWrapper,91, Response returned from DolphinD is 200, returning status success, to caller.
包装代码-->
def loggingMethodsWrapper(self, logLevelMethod, *kargs):
# Calling method logging methods dynamcially
# Add the parameters in the extra if you want to add
# more columns in logging format
getattr(self.log, logLevelMethod)(
*kargs, extra='API_SERVER': self.API_SERVER)
【问题讨论】:
【参考方案1】:我有同样的问题,我有许多不同的功能,写了漂亮干净的日志包装器。在那种情况下,我当然需要反馈,它是什么功能。我复制了造成这种痛苦的source from github,只删除了几行
# enable overwritting log record attributes
# I know, dirty cheating
def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
func=None, extra=None, sinfo=None):
"""
A factory method which can be overridden in subclasses to create
specialized LogRecords.
"""
rv = logging._logRecordFactory(name, level, fn, lno, msg, args, exc_info, func,
sinfo)
if extra is not None:
for key in extra:
rv.__dict__[key] = extra[key]
return rv
logging.Logger.makeRecord = makeRecord
比在 Logger 类中更改该方法的引用 所以现在我可以设置 LogRecord 的属性,即使它们已经被使用过,比如 funcName
所以包装看起来像这样
def log_string_convertors(convertor: callable):
@wraps(convertor)
def wrapper(string: str):
result: str = convertor(string)
logger.debug("%s->%s", string, result,
extra="input": string, "output": result, "funcName": convertor.__name__)
return result
return wrapper
我将 LogRecords 保存到 json 以便以后能够用程序分析它们,所以我添加了一些额外的属性,例如输入和输出
【讨论】:
【参考方案2】:您可以使用inspect
包来获取上一个函数的名称。这是一个显示“真实”调用者的日志记录功能的单例包装器示例:
import logging
import inspect
class Singleton(type):
_instances =
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class MyLogger(metaclass=Singleton):
logger = None
def __init__(self):
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(threadName)s - %(message)s",
handlers=[
logging.StreamHandler()
])
self.logger = logging.getLogger(__name__ + '.logger')
@staticmethod
def __get_call_info():
stack = inspect.stack()
# stack[1] gives previous function (info in our case)
# stack[2] gives before previous function and so on
fn = stack[2][1]
ln = stack[2][2]
func = stack[2][3]
return fn, func, ln
def info(self, message, *args):
message = " - at line : ".format(*self.__get_call_info(), message)
self.logger.info(message, *args)
【讨论】:
【参考方案3】:日志.py
class Log(Singleton):
log = None
caller = None
def __init__(self):
### Configure log
# TODO: The config has to come from Dictconfig YAML file
logFolder = CONSTANTS['LOG']['DIR']
self.API_SERVER = API_SERVER
caller = inspect.stack()[1][3]
print caller
self.log = logging.getLogger(__name__)
# If handlers are already set donot do it again
if not len(self.log.handlers):
logFormat = logging.Formatter('[%(levelname)s],[%(asctime)-15s], %(API_SERVER)s, %(caller)s,%(lineno)d, %(message)s')
def loggingMethodsWrapper(self, logLevelMethod, *kargs):
# Calling method logging methods dynamically
# Add the parameters in the extra if you want to add
# more columns in logging format
getattr(self.log, logLevelMethod)(
*kargs, extra='API_SERVER': self.API_SERVER, 'caller': self.caller)
示例 API 文件 -->
from flask import Flask, Response, request
from base import base
from jupiter_api.lib.jupFlaskClassy import *
from jupiter_api.business.accounts import accounts as bzAccounts
from jupiter_api.business.role_decorators import permGate, login_required
from flask.ext.cors import CORS
from jupiter_api.utils.log import Log
Log = Log()
class accounts(base):
supported_methods = "OPTIONS, GET, POST, DELETE"
## GET request to pull all user accounts
@route('/<userId>/<companyId>/accounts/<accountId>', methods=['GET'])
@permGate('list_accounts')
@login_required
def get_details(self, userId, companyId, accountId):
#import pdb; pdb.set_trace()
Log.debug("In get accounts for user")
# Validate arguments
if not userId or not companyId:
return self.invalidData()
# Print arguments
Log.debug("Getting account details for user %s ", userId)
注意 - 我有 100 多个类似于调用 Log() 的 Accounts API 的 API。
【讨论】:
【参考方案4】:发生了错误的函数名称,因为 loggingMethodsWrapper
是实际调用日志的函数。
你想要的似乎是调用 loggingMethodsWrapper 的函数。解决方法是更改您的格式化程序,使其不包含%(funcName)s
,并将其作为传递给%(message)s
的参数的一部分或作为额外参数。
选项 1:
这样做,一个肮脏的解决办法是让你的包装器走上堆栈并抓住下一个函数。
在 python2 中这是这样的:
caller = inspect.stack()[1][3]
在 python3 上,这将是:
caller = inspect.stack()[1].filename
所以:
logFormat = logging.Formatter('[%(levelname)s],[%(asctime)-15s], %(API_SERVER)s, %(CALLER)s,%(lineno)d, %(message)s')
...
def loggingMethodsWrapper(self, logLevelMethod, *kargs):
caller = # python-specific method of getting caller
getattr(self.log, logLevelMethod)(
*kargs, extra='CALLER' : caller, 'API_SERVER': self.API_SERVER)
选项 2:
如果您可以更改调用者,则简单修复 -- 将函数名称传递给您的记录器包装器
loggingMethodsWrapper(self, func_name, logLevelMethod, *kargs):
....
然后当你调用它时
def foo():
loggingMethodsWrapper(foo.__name__, log_level, ...)
【讨论】:
loggingMethodWrapper() 是我的自定义 log.py 文件的一部分。我有 100 个调用 Log() 的 API 文件,但我不能在所有文件中调用 foo()。您能否建议一个不需要更改所有文件的替代方案? @xgord 当我调用 API(比如 Accounts)时,会调用 log()。日志文件中预期的 funcName 应该是 get_accounts,但它显示 loggingMethodsWrapper。 @TanuSachdeva 我用另一种方式更新了它。不过,如果您用一个如何调用log
以及如何设置log
的示例更新您的问题,这可能会有所帮助
也只是为了澄清第一条评论。我并不是说将函数 foo
添加到您的库中。这只是一个调用日志包装器的函数示例以及调用必须如何更改。
我已经尝试过选项 1,inspect.stack[1][3] 返回“None”。以上是关于Wrapper 导致自定义 python 记录器中的 funcName 属性发生变化的主要内容,如果未能解决你的问题,请参考以下文章
mybatis plus wrapper.orderBy 自定义排序 自定义sql 多字段拼接
mybatis plus wrapper.orderBy 自定义排序 自定义sql 多字段拼接
mybatis plus wrapper.orderBy 自定义排序 自定义sql 多字段拼接