【问题标题】:Logging at Function Level在功能级别记录
【发布时间】:2018-04-26 23:56:34
【问题描述】:

考虑一个python模块包含多个函数的情况。每个函数都有一个id

def f1(id):
    log into file f1/{id}.txt

def f2(id):
    log into file f2/{id}.txt

假设传递给每个函数的 id 始终是唯一的。就像如果将 1 传递给 f1,则无法使用 f1 再次请求 1。与其他功能相同。

我希望记录每个功能而不是模块。这样每个函数都会登录到唯一的文件中,例如 function_name/id.txt

所以函数执行后就不需要打开function_name/id.txt来按函数记录,因为下一个请求会包含不同的id。因此,该文件的文件处理程序应该在函数执行后关闭

如何在 python 中实现每个模块的日志记录,以便每个模块正确捕获所有异常?

我正在尝试这种方法:

   def setup_logger( name, log_file, level=logging.DEBUG ):
        handler = logging.FileHandler(log_file)
        handler.setFormatter(logging.Formatter('[%(asctime)s][%(levelname)s]%(message)s'))
        logger = logging.getLogger(name)
        logger.setLevel(level)
        logger.addHandler(handler)
        return logger

   def f1(id):
        logger = setup_logger('f1_id_logger', f'f1/{id}.txt', level=logging.DEBUG)

    def f2(id):
        logger = setup_logger('f2_id_logger', f'f2/{id}.txt', level=logging.DEBUG)

但我担心的是:

  • 真的有必要创建这么多记录器吗?
  • 记录器能否处理每个函数的异常?
  • 打开的文件是否会在函数完成后或捕获异常时保持打开状态?

【问题讨论】:

  • 到目前为止您尝试了哪些方法,您的方法在哪里遇到了问题?
  • 用方法和关注点更新问题

标签: python python-3.x logging


【解决方案1】:

这是使用装饰器的绝佳案例。

import logging
from os import mkdir
from os.path import exists
from sys import exc_info # for retrieving the exception
from traceback import format_exception # for formatting the exception

def id_logger_setup(level=logging.DEBUG):

    def setup_logger(func):
        if not exists(func.__name__): # makes the directory if it doesn't exist
            mkdir(func.__name__)
        logger = logging.getLogger("{}_id_logger".format(func.__name__))
        logger.setLevel(level)

        def _setup_logger(id, *args, **kwargs):
            handler = logging.FileHandler("{}/{}.txt".format(func.__name__, id)) # a unique handler for each id
            handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s]%(message)s"))
            logger.addHandler(handler)
            try:
                rtn = func(id, logger=logger, *args, **kwargs)
            except Exception: # if the function breaks, catch the exception and log it
                logger.critical("".join(format_exception(*exc_info())))
                rtn = None
            finally:
                logger.removeHandler(handler) # remove ties between the logger and the soon-to-be-closed handler
                handler.close() # closes the file handler
                return rtn

        return _setup_logger
    return setup_logger

@id_logger_setup(level=logging.DEBUG) # set the level
def f1(id, *, logger):
    logger.debug("In f1 with id {}".format(id))

@id_logger_setup(level=logging.DEBUG)
def f2(id, *, logger):
    logger.debug("In f2 with id {}".format(id))

@id_logger_setup(level=logging.DEBUG)
def f3(id, *, logger):
    logger.debug("In f3 with id {}".format(id))
    logger.debug("Something's going wrong soon...")
    int('opps') # raises an error


f1(1234)
f2(5678)
f1(4321)
f2(8765)
f3(345774)

从代码示例中,您可以得到以下信息:

f1 -
   |
   1234.txt
   4321.txt
f2 -
   |
   5678.txt
   8765.txt
f3 -
   |
   345774.txt

在前四个 txt 文件中,您会得到如下内容:

[2018-04-26 18:49:29,209][DEBUG]In f1 with id 1234

在 f3/345774.txt 中,你会得到:

[2018-04-26 18:49:29,213][DEBUG]In f3 with id 345774
[2018-04-26 18:49:29,213][DEBUG]Something's going wrong soon...
[2018-04-26 18:49:29,216][CRITICAL]Traceback (most recent call last):
  File "/path.py", line 20, in _setup_logger
    rtn = func(id, logger=logger, *args, **kwargs)
  File "/path.py", line 43, in f3
    int('opps')
ValueError: invalid literal for int() with base 10: 'opps'

以下是您问题的答案:

  1. 真的有必要创建这么多记录器吗?

使用装饰器,您只需创建一个记录器。所以不,每个功能一个记录器就足够了。由于您的记录器采用这种格式“{func-name}_id_logger”,这意味着每个不同的函数都必须有一个唯一的记录器。

  1. 记录器能否处理每个函数的异常?

是的,记录器将捕获任何属于 Exception 子类的异常。尽管无论如何都会捕获您的异常,但您仍然应该尝试在函数中捕获 + 处理异常。

  1. 打开的文件是否会在函数完成后或捕获异常时保持打开状态?

不,会适当关闭。

【讨论】:

    【解决方案2】:

    您不必为每个案例分别设置记录器。您应该设置一次,这样您就有两个记录器,每个记录器都输出到不同的文件。然后在两个函数中使用两个不同的记录器。

    例如,您可以这样配置记录器*:

    import logging.config
    
    logging.config.dictConfig({
        'version': 1,
        'formatters': {
            'simple_formatter': {
                'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
            }
        },
        'handlers': {
            'first_handler': {
                'class' : 'logging.FileHandler',
                'formatter': 'simple_formatter',
                'filename': 'C:\\Temp\\log1.txt'
            },
            'second_handler': {
                'class' : 'logging.FileHandler',
                'formatter': 'simple_formatter',
                'filename': 'C:\\Temp\\log2.txt'
            }
        },
        'loggers': {
            'first_logger': {
                'handlers': ['first_handler']
            },
            'second_logger': {
                'handlers': ['second_handler']
            }
        }
    })
    

    然后,只需在需要它们的地方使用一个或另一个记录器:

    def f1():
        logger = logging.getLogger('first_logger')
        logger.warning('Hello from f1')
    
    def f2():
        logger = logging.getLogger('second_logger')
        logger.warning('Hello from f2')
    

    *有不同的方式来配置记录器,其他选项见https://docs.python.org/3.6/library/logging.config.html

    【讨论】:

    • 有 id 传递给函数。所以日志记录应该是 f1/id.txt 而不是 f1.txt。 id 是动态的,所以我们不能硬编码 dictconfig
    • @AnkitVallecha 在这种情况下,您完全误用/误解了日志记录。日志记录旨在跟踪您的代码在做什么,因此它的组织方式与代码相同(每个应用程序、模块或函数一个文件),而不是按数据。
    • @AnkitVallecha 如果您只想写入文件而不是记录,请执行with open('f1/{}.txt'.format(id)) as f: f.write(something)
    • 让我给你一个实际的例子来说明我在做什么。假设这些 id 是用户。这些函数是休息调用。我想跟踪每个用户每次休息呼叫遇到的错误。所以我需要单独的文件。因为在单个文件中也有其他用户数据。
    • @AnkitVallecha 我仍然不会按用户分隔文件。日志记录是为了找出应用程序出了什么问题,即很少使用。您可以稍后按用户 ID 过滤文件。对于不仅仅是记录,我会使用不同的解决方案,例如一个数据库。无论如何,您可能仍然可以完成您想要的操作,例如,如果您实现自己的处理程序类,该处理程序类使用来自某些线程本地存储或其他东西的有关当前登录用户的信息,但这似乎太过分了你的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-03
    • 1970-01-01
    • 2011-05-06
    相关资源
    最近更新 更多