我有一个小的python项目,它有以下结构-

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf

我计划使用默认日志记录模块将消息打印到标准输出和日志文件。 要使用日志记录模块,需要进行一些初始化

import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')

logger.info('testing')

目前,在开始记录消息之前,我在每个模块中执行此初始化。是否可以只在一个地方执行一次初始化,以便通过记录整个项目来重用相同的设置?


当前回答

@Yarkee的解决方案似乎更好。我想再加一些

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances.keys():
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class LoggerManager(object):
    __metaclass__ = Singleton

    _loggers = {}

    def __init__(self, *args, **kwargs):
        pass

    @staticmethod
    def getLogger(name=None):
        if not name:
            logging.basicConfig()
            return logging.getLogger()
        elif name not in LoggerManager._loggers.keys():
            logging.basicConfig()
            LoggerManager._loggers[name] = logging.getLogger(str(name))
        return LoggerManager._loggers[name]    


log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)

所以LoggerManager可以插入到整个应用程序中。 希望它有意义和价值。

其他回答

有几个答案。我最终得到了一个类似但不同的解决方案,对我来说有意义,也许对你也有意义。 我的主要目标是能够按级别将日志传递给处理程序(调试级别的日志传递给控制台,警告和以上级别的日志传递给文件):

from flask import Flask
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

# make default logger output everything to the console
logging.basicConfig(level=logging.DEBUG)

rotating_file_handler = RotatingFileHandler(filename="logs.log")
rotating_file_handler.setLevel(logging.INFO)

app.logger.addHandler(rotating_file_handler)

创建了一个名为logger.py的util文件:

import logging

def get_logger(name):
    return logging.getLogger("flask.app." + name)

长颈瓶。App是flask中硬编码的值。应用程序记录器总是从flask开始。App作为它的模块名。

现在,在每个模块中,我能够在以下模式中使用它:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

这将为“app.flask”创建一个新的日志。MODULE_NAME”。

你也可以想出这样的东西!

def get_logger(name=None):
    default = "__app__"
    formatter = logging.Formatter('%(levelname)s: %(asctime)s %(funcName)s(%(lineno)d) -- %(message)s',
                              datefmt='%Y-%m-%d %H:%M:%S')
    log_map = {"__app__": "app.log", "__basic_log__": "file1.log", "__advance_log__": "file2.log"}
    if name:
        logger = logging.getLogger(name)
    else:
        logger = logging.getLogger(default)
    fh = logging.FileHandler(log_map[name])
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.setLevel(logging.DEBUG)
    return logger

现在你可以在同一个模块和整个项目中使用多个记录器,如果上面的定义在一个单独的模块中,并在其他模块中导入日志记录是必需的。

a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")

刚接触python,所以我不知道这是否可取,但它对于不重写样板文件非常有效。

你的项目必须有一个init.py,这样它才能作为一个模块加载

# Put this in your module's __init__.py
import logging.config
import sys

# I used this dictionary test, you would put:
# logging.config.fileConfig('logging.conf')
# The "" entry in loggers is the root logger, tutorials always 
# use "root" but I can't get that to work
logging.config.dictConfig({
    "version": 1,
    "formatters": {
        "default": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s"
        },
    },
    "handlers": {
        "console": {
            "level": 'DEBUG',
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout"
        }
    },
    "loggers": {
        "": {
            "level": "DEBUG",
            "handlers": ["console"]
        }
    }
})

def logger():
    # Get the name from the caller of this function
    return logging.getLogger(sys._getframe(1).f_globals['__name__'])

Sys._getframe(1)的建议来自这里

然后在任何其他文件中使用您的记录器:

from [your module name here] import logger

logger().debug("FOOOOOOOOO!!!")

警告:

你必须将你的文件作为模块运行,否则import [your module]将不起作用: Python -m[你的模块名]。[你的文件名没有。py] 程序入口点的记录器名称将是__main__,但任何使用__name__的解决方案都会有这个问题。

我想添加我的解决方案(这是基于日志记录烹饪书和本线程中的其他文章和建议)。然而,我花了很长一段时间才弄明白,为什么它没有立即像我预期的那样工作。所以我创建了一个小测试项目来了解日志是如何工作的。

既然我已经弄明白了,我想分享我的解决方案,也许它可以帮助到别人。

我知道我的一些代码可能不是最佳实践,但我仍在学习。我把print()函数留在那里,因为我使用了它们,而日志记录没有像预期的那样工作。这些在我的其他应用程序中被删除了。同时,我也欢迎任何关于代码或结构的反馈。

my_log_test项目结构(从我工作的另一个项目克隆/简化)

my_log_test
├── __init__.py
├── __main__.py
├── daemon.py
├── common
│   ├── my_logger.py
├── pkg1
│   ├── __init__.py
│   └── mod1.py
└── pkg2
    ├── __init__.py
    └── mod2.py

需求

在我使用的组合中,有一些不同的或我没有看到明确提到的东西:

主要模块是daemon。由__main__.py调用 我希望能够在开发/测试时分别调用模块mod1.py和mod2.py 在这一点上,我不想使用basicConfig()或FileConfig(),而是像在日志记录烹饪书中那样使用它

所以基本上,这意味着,我需要在daemon.py(总是)和mod1.py和mod2.py模块中初始化根日志记录器(仅当直接调用它们时)。

为了使几个模块中的初始化更容易,我创建了my_logger.py,这在烹饪书中有描述。

我的错误

在此之前,我在该模块中的错误是使用logger = logging.getLogger(__name__)(模块记录器)初始化记录器,而不是使用logger = logging.getLogger()(获取根记录器)。

第一个问题是,当从daemon.py调用时,记录器的名称空间被设置为my_log_test.common.my_logger。mod1.py中的模块记录器具有“不匹配”的命名空间my_log_test.pkg1。因此,mod1不能连接到另一个记录器,我将看不到来自mod1的日志输出。

第二个“问题”是,我的主程序在daemon.py中,而不是__main__.py中。但对我来说毕竟不是一个真正的问题,但它增加了名称空间的混乱。

工作方案

这是来自烹饪书,但在一个单独的模块。我还添加了一个logger_cleanup函数,我可以从守护进程中调用它来删除超过x天的日志。

## my_logger.py
from datetime import datetime
import time
import os

## Init logging start 
import logging
import logging.handlers

def logger_init():
    print("print in my_logger.logger_init()")
    print("print my_logger.py __name__: " +__name__)
    path = "log/"
    filename = "my_log_test.log"

    ## get logger
    #logger = logging.getLogger(__name__) ## this was my mistake, to init a module logger here
    logger = logging.getLogger() ## root logger
    logger.setLevel(logging.INFO)

    # File handler
    logfilename = datetime.now().strftime("%Y%m%d_%H%M%S") + f"_{filename}"
    file = logging.handlers.TimedRotatingFileHandler(f"{path}{logfilename}", when="midnight", interval=1)
    #fileformat = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
    fileformat = logging.Formatter("%(asctime)s [%(levelname)s]: %(name)s: %(message)s")
    file.setLevel(logging.INFO)
    file.setFormatter(fileformat)

    # Stream handler
    stream = logging.StreamHandler()
    #streamformat = logging.Formatter("%(asctime)s [%(levelname)s:%(module)s] %(message)s")
    streamformat = logging.Formatter("%(asctime)s [%(levelname)s]: %(name)s: %(message)s")
    stream.setLevel(logging.INFO)
    stream.setFormatter(streamformat)

    # Adding all handlers to the logs
    logger.addHandler(file)
    logger.addHandler(stream)


def logger_cleanup(path, days_to_keep):
    lclogger = logging.getLogger(__name__)
    logpath = f"{path}"
    now = time.time()
    for filename in os.listdir(logpath):
        filestamp = os.stat(os.path.join(logpath, filename)).st_mtime
        filecompare = now - days_to_keep * 86400
        if  filestamp < filecompare:
            lclogger.info("Delete old log " + filename)
            try:
                os.remove(os.path.join(logpath, filename))
            except Exception as e:
                lclogger.exception(e)
                continue

要运行daemon .py(通过__main__.py),请使用python3 -m my_log_test

## __main__.py
from  my_log_test import daemon

if __name__ == '__main__':
    print("print in __main__.py")
    daemon.run()

要(直接)运行daemon .py,请使用python3 -m my_log_test.daemon

## daemon.py
from datetime import datetime
import time
import logging
import my_log_test.pkg1.mod1 as mod1
import my_log_test.pkg2.mod2 as mod2

## init ROOT logger from my_logger.logger_init()
from my_log_test.common.my_logger import logger_init
logger_init() ## init root logger
logger = logging.getLogger(__name__) ## module logger

def run():
    print("print in daemon.run()")
    print("print daemon.py __name__: " +__name__)
    logger.info("Start daemon")
    loop_count = 1
    while True:
        logger.info(f"loop_count: {loop_count}")
        logger.info("do stuff from pkg1")
        mod1.do1()
        logger.info("finished stuff from pkg1")

        logger.info("do stuff from pkg2")
        mod2.do2()
        logger.info("finished stuff from pkg2")

        logger.info("Waiting a bit...")
        time.sleep(30)


if __name__ == '__main__':
    try:
        print("print in daemon.py if __name__ == '__main__'")
        logger.info("running daemon.py as main")
        run()
    except KeyboardInterrupt as e:
        logger.info("Program aborted by user")
    except Exception as e:
        logger.info(e)

要运行mod1.py(直接),请使用python3 -m my_log_test.pkg1.mod1

## mod1.py
import logging
# mod1_logger = logging.getLogger(__name__)
mod1_logger = logging.getLogger("my_log_test.daemon.pkg1.mod1") ## for testing, namespace set manually

def do1():
    print("print in mod1.do1()")
    print("print mod1.py __name__: " +__name__)
    mod1_logger.info("Doing someting in pkg1.do1()")

if __name__ == '__main__':
    ## Also enable this pkg to be run directly while in development with
    ## python3 -m my_log_test.pkg1.mod1

    ## init root logger
    from my_log_test.common.my_logger import logger_init
    logger_init() ## init root logger

    print("print in mod1.py if __name__ == '__main__'")
    mod1_logger.info("Running mod1.py as main")
    do1()

要运行mod2.py(直接),请使用python3 -m my_log_test.pkg2.mod2

## mod2.py
import logging
logger = logging.getLogger(__name__)

def do2():
    print("print in pkg2.do2()")
    print("print mod2.py __name__: " +__name__) # setting namespace through __name__
    logger.info("Doing someting in pkg2.do2()")

if __name__ == '__main__':
    ## Also enable this pkg to be run directly while in development with
    ## python3 -m my_log_test.pkg2.mod2

    ## init root logger
    from my_log_test.common.my_logger import logger_init
    logger_init() ## init root logger

    print("print in mod2.py if __name__ == '__main__'")
    logger.info("Running mod2.py as main")
    do2()

如果有帮助我很高兴。也很高兴收到反馈!

@Yarkee的解决方案似乎更好。我想再加一些

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances.keys():
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class LoggerManager(object):
    __metaclass__ = Singleton

    _loggers = {}

    def __init__(self, *args, **kwargs):
        pass

    @staticmethod
    def getLogger(name=None):
        if not name:
            logging.basicConfig()
            return logging.getLogger()
        elif name not in LoggerManager._loggers.keys():
            logging.basicConfig()
            LoggerManager._loggers[name] = logging.getLogger(str(name))
        return LoggerManager._loggers[name]    


log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)

所以LoggerManager可以插入到整个应用程序中。 希望它有意义和价值。