python日志记录对不同文件上的多个记录器使用相同的处理程序

时间:2021-06-14 18:00:40

标签: python python-logging

我有一个旧项目,我只使用了打印语句,我想开始使用日志记录模块。 这是项目的结构:

  • MAIN.py - 主要可执行文件
  • TASKS.py
  • LOG_SERIAL.py

执行时,我得到用户输入,然后我转到 TASK.py 和 LOG_SERIAL.py(线程)。

  1. 对于 MAIN.py\TASKS.py,我计划使用两个记录器和处理程序(流、文件),以便它们包含相同的数据并更改它们的级别名称

示例: 流\文件

DATE - NAME - SYSTEM - "....."
DATE - NAME - SYSTEM - "....."
DATE - NAME - TASKS - "...."
DATE - NAME - TASKS - "...."
  1. 为 LOG_SERIAL.py 再使用一个记录器,该记录器将记录到不同的文件

这是我目前所做的:

  1. 我在 main.py 上创建了以下方法:
# main.py
import TASKS
def get_logger():
    FORMAT = logging.Formatter('%(asctime)s-%(name)s-[%(levelname)s]-%(message)s')
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(FORMAT)
    file_handler = logging.FileHandler('console.log', mode='w')
    file_handler.setFormatter(FORMAT)
    logging.addLevelName(logging.DEBUG, 'SYSTEM')
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)
    return logger

然后我在 MAIN.py 上使用它:

logger = get_logger()
logger.debug("...")
  1. 我在 TASKS.py 上创建了相同的方法,但更改了文件处理程序、级别名称和值的模式
# tasks.py
def get_logger():
    FORMAT = logging.Formatter('%(asctime)s-%(name)s-[%(levelname)s]-%(message)s')
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(FORMAT)
    file_handler = logging.FileHandler('console.log', mode='a+')
    file_handler.setFormatter(FORMAT)
    logging.addLevelName(logging.INFO, 'TASKS')
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.INFO)
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)
    return logger

然后我打电话:

logger = get_logger()
logger.info("...")

这是我为两个处理程序得到的:

2021-06-14 19:35:42,253-__main__-[SYSTEM]-"..."
2021-06-14 19:35:49,576-clients-[TASKS]-"...."

我相信没有 tasks.py 上的样板代码可以做得更好。

  1. 我的场景推荐的方式是什么?两个记录器在不同的文件上使用相同的处理程序
  2. 如何获取模块名称而不是“__ main __”?

1 个答案:

答案 0 :(得分:0)

我认为你混淆了很多东西,所以这里是一个回顾:

  • 您的 FORMAT 指定要包含在日志输出中的记录器名称 (%(name)s),在这里不是最佳选择,您可以使用其他名称 (参见 doc):

    • %(filename)s 会给你 main.py
    • %(module)s 会给你 main
  • __name__ 是模块名,不是文件名(参见 doc,搜索 __file__

    • 当您导入一个模块时,例如通过 import os.path,那么 os.path 将是此模块中 __name__ 的值
    • 但是对于第一个模块的特殊情况,它没有明确导入(你只是把它交给 Python 运行),那么它的 __name__ 将是 __main__(参见 doc )
  • __file__ 是文件的绝对路径,例如 C:/PycharmProjects/stack_overflow/q67975119/main.py 在我的情况下

  • 您将日志级别别名为“SYSTEM”,以便将其写入输出中。不建议将日志级别用于日志(严重性)级别以外的其他事项。

  • 您的函数 get_logger 执行配置然后返回一个实例。但是日志配置是全局,所以第二次调用它会产生讨厌的副作用(特别是addHandler),所以不建议混合实际的设置强大> 获取实例的日志基础架构。

这里是您问题的解决方案:

  • 要将 SYSTEM 显示为记录器的名称,您可以:
    • 或者把它作为一个常量放在你的 FORMAT 中:'%(asctime)s-SYSTEM-[%(levelname)s]-%(message)s'
    • 或将您的记录器命名为 SYSTEM,因此将您创建的记录器替换为 logger = logging.getLogger("SYSTEM")
  • 要表明日志行与系统有关,请将其用作记录器名称(参见上文),或使用 logger adapter
  • 将您的 get_logger 函数中的日志设置提取到另一个函数中,该函数仅在程序启动时调用一次(最好在 if __name__ == "__main__": 块中)。

我的建议是不要使用 __file____name__ 作为您的记录器名称,只需将它们命名为 "main""tasks"。如果您不打算重命名源文件,这是一种更简单、更灵活的解决方案。

提示:将您的 FileHandler 模式更改为 w+a,这样每次新的程序运行时,日志都会附加到上次运行的日志中,而不是覆盖(丢失) 他们。

这是一个完整的例子:

# main.py
import logging

from q67975119 import serial, tasks


def setup_main_logging():
    FORMAT = logging.Formatter('%(asctime)s - %(name)s - [%(levelname)s] - %(message)s')
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(FORMAT)
    file_handler = logging.FileHandler('console.log', mode='w')
    file_handler.setFormatter(FORMAT)
    system_logger.setLevel(logging.DEBUG)
    system_logger.addHandler(console_handler)
    system_logger.addHandler(file_handler)


system_logger = logging.getLogger("SYSTEM")


if __name__ == '__main__':
    setup_main_logging()  # before using the logger for the first time !
    tasks.setup_tasks_logging()  # before using the logger for the first time !
    system_logger.debug(f"__file__={__file__!r}")
    system_logger.debug(f"__name__={__name__!r}")
    tasks.do_task()
    serial.do_serial()
# serial.py
def do_serial():
    import main  # to prevent circular imports
    system_logger = main.system_logger
    system_logger.info(f"__file__={__file__!r}")
    system_logger.info(f"__name__={__name__!r}")
# tasks.py
import logging


def setup_tasks_logging():
    FORMAT = logging.Formatter('%(asctime)s - %(name)s - [%(levelname)s] - %(message)s')
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(FORMAT)
    file_handler = logging.FileHandler('console.log', mode='a+')
    file_handler.setFormatter(FORMAT)
    tasks_logger.setLevel(logging.INFO)
    tasks_logger.addHandler(console_handler)
    tasks_logger.addHandler(file_handler)


tasks_logger = logging.getLogger("TASKS")


def do_task():
    tasks_logger.info(f"__file__={__file__!r}")
    tasks_logger.info(f"__name__={__name__!r}")

运行时,我在标准输出和文件中得到同样的结果:

2021-06-17 11:50:19,661 - SYSTEM - [DEBUG] - __file__='C:/Users/PycharmProjects/stack_overflow/q67975119/main.py'
2021-06-17 11:50:19,661 - SYSTEM - [DEBUG] - __name__='__main__'
2021-06-17 11:50:19,661 - TASKS - [INFO] - __file__='C:/Users/PycharmProjects/stack_overflow/q67975119/tasks.py'
2021-06-17 11:50:19,661 - TASKS - [INFO] - __name__='q67975119.tasks'
2021-06-17 11:50:19,663 - SYSTEM - [INFO] - __file__='C:/Users/PycharmProjects/stack_overflow/q67975119/serial.py'
2021-06-17 11:50:19,663 - SYSTEM - [INFO] - __name__='q67975119.serial'

如果您想减少样板文件,您可以使用 logging.config,它允许您将配置放在文本文件或 Python 字典中,并按原样提供给日志库。

使用文件的示例:

# main.py
import logging.config

from q67975119 import serial, tasks


system_logger = logging.getLogger("SYSTEM")


if __name__ == '__main__':
    logging.config.fileConfig("logging.config")
    system_logger.debug(f"__file__={__file__!r}")
    system_logger.debug(f"__name__={__name__!r}")
    tasks.do_task()
    serial.do_serial()

logging.config 文件:

[loggers]
keys=root,system,tasks

[handlers]
keys=console,file

[formatters]
keys=formatter

[logger_root]
level=NOTSET
handlers=console,file

[logger_system]
level=DEBUG
handlers=console,file
propagate=0
qualname=SYSTEM

[logger_tasks]
level=INFO
handlers=console,file
propagate=0
qualname=TASKS

[handler_console]
class=StreamHandler
level=NOTSET
formatter=formatter
args=(sys.stdout,)

[handler_file]
class=FileHandler
level=NOTSET
formatter=formatter
args=('console.log', 'w+')

[formatter_formatter]
format=%(asctime)s - %(name)s - [%(levelname)s] - %(message)s
datefmt=
class=logging.Formatter

它产生与通过代码指定所有内容完全相同的输出(标准输出和文件)。