在多个模块中使用日志记录

我有一个小的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')

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

288810 次浏览

我总是这样做。

使用一个python文件将我的日志配置为名为“log_conf.py”的单例模式

#-*-coding:utf-8-*-


import logging.config


def singleton(cls):
instances = {}
def get_instance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return get_instance()


@singleton
class Logger():
def __init__(self):
logging.config.fileConfig('logging.conf')
self.logr = logging.getLogger('root')

在另一个模块中,只需导入配置。

from log_conf import Logger


Logger.logr.info("Hello World")

这是一种简单有效的单例日志模式。

实际上,每个记录器都是父包记录器的子记录器(例如,package.subpackage.module继承了package.subpackage)的配置,所以你所需要做的只是配置根记录器。这可以通过logging.config.fileConfig(你自己的记录器配置)或logging.basicConfig(设置根记录器)来实现。在你的入口模块(__main__.py或任何你想运行的,例如main_script.py__init__.py工作以及)

使用basicConfig:

# package/__main__.py
import logging
import sys


logging.basicConfig(stream=sys.stdout, level=logging.INFO)

使用fileConfig:

# package/__main__.py
import logging
import logging.config


logging.config.fileConfig('logging.conf')

然后创建每个记录器使用:

# package/submodule.py
# or
# package/subpackage/submodule.py
import logging
log = logging.getLogger(__name__)


log.info("Hello logging!")

更多信息见高级测井教程

最佳实践是,在每个模块中定义一个记录器,如下所示:

import logging
logger = logging.getLogger(__name__)

在模块的顶部附近,然后在模块中的其他代码中执行例如。

logger.debug('My message with %s', 'variable data')

如果你需要在一个模块中细分日志活动,使用例如。

loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')

并登录到loggerAloggerB

在你的主程序中,执行以下操作:

def main():
"your program code"


if __name__ == '__main__':
import logging.config
logging.config.fileConfig('/path/to/logging.conf')
main()

def main():
import logging.config
logging.config.fileConfig('/path/to/logging.conf')
# your program code


if __name__ == '__main__':
main()

参见在这里获取多个模块的日志记录,参见在这里获取将被其他代码用作库模块的代码的日志记录配置。

更新:当调用fileConfig()时,如果你使用的是Python 2.6或更高版本,你可能想指定disable_existing_loggers=False(有关更多信息,请参阅的文档)。为了向后兼容,默认值是True,这将导致所有现有的记录器被fileConfig()禁用,除非它们或它们的祖先在配置中显式命名。当值设置为False时,现有的记录器将被保留。如果使用Python 2.7/Python 3.2或更高版本,你可能希望考虑dictConfig() API,它比fileConfig()更好,因为它对配置提供了更多的控制。

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

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")

@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可以被整个应用程序插入。

再加入另一种溶液。

在我的模块初始化.py中,我有如下内容:

# mymodule/__init__.py
import logging


def get_module_logger(mod_name):
logger = logging.getLogger(mod_name)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
return logger

然后在每个模块我需要一个记录器,我做:

# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)

当日志丢失时,您可以根据它们来自的模块来区分它们的来源。

这些答案中有几个建议在模块的顶部

import logging
logger = logging.getLogger(__name__)

这是我的理解,这被认为是非常糟糕的做法。原因是文件配置默认情况下将禁用所有现有的记录器。如。

#my_module
import logging


logger = logging.getLogger(__name__)


def foo():
logger.info('Hi, foo')


class Bar(object):
def bar(self):
logger.info('Hi, bar')

在主模块中:

#main
import logging


# load my module - this now configures the logger
import my_module


# This will now disable the logger in my module by default, [see the docs][1]
logging.config.fileConfig('logging.ini')


my_module.foo()
bar = my_module.Bar()
bar.bar()

现在logging.ini中指定的日志将为空,因为现有的记录器已被fileconfig调用禁用。

虽然这当然是可能的(disable_existing_Loggers=False),但实际上你库的许多客户端不会知道这个行为,也不会接收到你的日志。通过始终调用logging使您的客户更容易。在本地getlog。提示:我从林毅夫网站中学到了这个行为。

因此,好的做法是总是调用日志记录。在本地getlog。如。

#my_module
import logging


logger = logging.getLogger(__name__)


def foo():
logging.getLogger(__name__).info('Hi, foo')


class Bar(object):
def bar(self):
logging.getLogger(__name__).info('Hi, bar')

同样,如果你在main中使用fileconfig,设置disable_existing_loggers=False,以防你的库设计人员使用模块级记录器实例。

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

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”。

最好的做法是单独创建一个模块,该模块只有一个方法,我们的任务是为调用方法提供一个记录器处理程序。将该文件保存为m_logger.py

import logger, logging


def getlogger():
# logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
#ch = logging.StreamHandler()
ch = logging.FileHandler(r'log.txt')
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
return logger

现在,只要需要记录器处理程序,就调用getlogger()方法。

from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')

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

你的项目必须有一个初始化.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 [your module name].[your filename without .py]
    • 李< / ul > < / >
    • 程序入口点的记录器名称将是__main__,但任何使用__name__的解决方案都会有这个问题。

在多个模块中使用一个日志库实例的简单方法是以下解决方案:

base_logger.py

import logging


logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)

其他文件

from base_logger import logger


if __name__ == '__main__':
logger.info("This is an info message")

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

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

我知道我的一些代码可能不是最佳实践,但我仍在学习。我把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

需求

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

  • 主模块是__abc0,由__main__.py调用
  • 我希望能够在开发/测试时分别调用模块mod1.pymod2.py
  • 在这一点上,我不想使用basicConfig()FileConfig(),而是将其保留在日志食谱

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

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

我的错误

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

第一个问题是,当从daemon.py调用时,记录器的命名空间被设置为my_log_test.common.my_loggermod1.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()

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