日志是对软件执行时所发生事件的一种追踪方式。软件开发人员对他们的代码添加日志调用,借此来指示某事件的发生。一个事件通过一些包含变量数据的描述信息来描述(比如:每个事件发生时的数据都是不同的)。开发者还会区分事件的重要性,重要性也被称为 等级 或 严重性。
一、logging库日志级别
级别 | 级别数值 | 使用时机 |
DEBUG | 10 | 详细信息,常用于调试。 |
INFO | 20 | 程序正常运行过程中产生的一些信息。 |
WARNING | 30 | 警告用户,虽然程序还在正常运行,但可能发生错误。 |
ERROR | 40 | 由于更严重的问题,程序已经不能执行一些功能了。 |
CRITICAL | 50 | 严重错误,程序已经不能继续运行。 |
默认的日志级别是warning
二、logging初体验
1:简单将日志打印到屏幕
import logging logging.debug("debug") logging.info("info") logging.warning("warning") logging.error("error") logging.critical('critical')
WARNING:root:warning
ERROR:root:error
CRITICAL:root:critical
结果分析:
上面可以看到只有后面三个能打印出来
默认生成的root logger的level是logging.WARNING,低于该级别的就不输出了
格式:WARNING:root:warning
解释:当前日志级别:记录器:详细信息
2:设置日志输出级别
使用basicConfig(level=...)来指定日志输出级别
import logging # 默认的日志输出级别为warning # 使用basicConfig()来指定日志输出级别 logging.basicConfig(level=logging.DEBUG) logging.debug("debug") logging.info("info") logging.warning("warning") logging.error("error") logging.critical('critical')
DEBUG:root:debug
INFO:root:info
WARNING:root:warning
ERROR:root:error
CRITICAL:root:critical
3:简单将日志输出到文件
3.1 使用basicConfig(filename=...)来指定日志输出名称
默认是文件同级目录下,如果指定文件名不存在,则创建并输出。
import logging # 默认的日志输出级别为warning # 使用basicConfig()来指定日志输出级别 logging.basicConfig(filename='test_file.log', level=logging.DEBUG) logging.debug("debug") logging.info("info") logging.warning("warning") logging.error("error") logging.critical('critical')
DEBUG:root:debug
INFO:root:info
WARNING:root:warning
ERROR:root:error
CRITICAL:root:critical
3.2 使用basicConfig(filemode=...)来指定日志输出类型(w:表示覆盖写入。a:表示追加)
默认filemode=“a”,日志信息会依次追加到日志文件中
如果上述文件运行多次,结果如下
import logging # 默认的日志输出级别为warning # 使用basicConfig()来指定日志输出级别 logging.basicConfig(filename='test_file.log', filemode="w", level=logging.DEBUG) logging.debug("debug") logging.info("info") logging.warning("warning") logging.error("error") logging.critical('critical')
DEBUG:root:debug
INFO:root:info
WARNING:root:warning
ERROR:root:error
CRITICAL:root:critical
3.3 使用basicConfig(format=...)来指定日志输出格式
下列的参数可以自己添加,常用参数由:
%(acstime)s 时间
%(filename)s 日志文件名
%(funcName)s 调用日志的函数名
%(levelname)s 日志的级别
%(module)s 调用日志的模块名
%(message)s 日志信息
%(name)s logger的name,不写的话默认是root
import logging # 默认的日志输出级别为warning # 使用basicConfig()来指定日志输出级别 logging.basicConfig(format="%(asctime)s | %(levelname)s | %(filename)s | %(lineno)s | %(message)s", level=logging.DEBUG) # %(asctime)s :文件执行时间 # %(levelname)s:日志信息等级 # %(filename)s:执行文件的名称 # %(lineno)s:日志信息输出文件的具体行号 # %(message)s:详细日志信息 logging.debug("debug") logging.info("info") logging.warning("warning") logging.error("error") logging.critical('critical')
2021-12-05 11:28:44,978 | DEBUG | 日志级别.py | 12 | debug 2021-12-05 11:28:44,978 | INFO | 日志级别.py | 13 | info 2021-12-05 11:28:44,978 | WARNING | 日志级别.py | 14 | warning 2021-12-05 11:28:44,978 | ERROR | 日志级别.py | 15 | error 2021-12-05 11:28:44,978 | CRITICAL | 日志级别.py | 16 | critical
我们输出的时间格式是:2021-12-05 11:28:44,978 ,可以通过datefmt执行相应的时间格式
import logging # 默认的日志输出级别为warning # 使用basicConfig()来指定日志输出级别 logging.basicConfig(format="%(asctime)s | %(levelname)s | %(filename)s | %(lineno)s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S", level=logging.DEBUG) # %(asctime)s :文件执行时间 # %(levelname)s:日志信息等级 # %(filename)s:执行文件的名称 # %(lineno)s:日志信息输出文件的具体行号 # %(message)s:详细日志信息 logging.debug("debug") logging.info("info") logging.warning("warning") logging.error("error") logging.critical('critical')
2021-12-05 11:37:04 | DEBUG | 日志级别.py | 13 | debug 2021-12-05 11:37:04 | INFO | 日志级别.py | 14 | info 2021-12-05 11:37:04 | WARNING | 日志级别.py | 15 | warning 2021-12-05 11:37:04 | ERROR | 日志级别.py | 16 | error 2021-12-05 11:37:04 | CRITICAL | 日志级别.py | 17 | critical
三、logging的高级应用
上面主要讲述了日志的基本使用和概念。但是并不适合在大型项目中使用
1:logging模块化设计
logging框架中主要由四个部分组成:
名称 | 说明 | |
Loggers | 记录器 | 可供程序直接调用的接口 |
Handlers | 处理器 | 决定将日志记录分配至正确的目的地 |
Filters | 过滤器 | 提供更细粒度的日志是否输出的判断 |
Formatters | 格式化器 | 制定最终记录打印的格式布局 |
logging模块工作原理浅析
2:Loggers记录器
用户使用的直接接口, 指定模块使用哪个handlers。以及日志输出的级别。
当logger对象接收到了一条日志消息时会对log level进行比较, 如果log level超过或满足等自己的log level那么消息会交给handler进一步处理,否则会忽略这条消息。
1:提供应用程序的调用接口
logger是单例的
logger = logging.getLogger(__name__)
注意 永远 不要直接实例化记录器,应当通过模块级别的函数
2:决定日志的记录级别
logger.setLevel()
3:将日志内容传递到相关联的handlers中
logger.addHandler()
logger.removeHandler()
import logging # 1:提供应用程序的调用接口 logger = logging.getLogger() print(logger) # <RootLogger root (WARNING)> # 默认记录器是 root 默认级别是 WARNING # 2:决定日志的记录级别 logger_2 = logging.getLogger('app_log') logger_2.setLevel(logging.DEBUG) print(logger_2) # <Logger app_log (DEBUG)> # 设置记录器是 app_log 级别是 DEBUG
3:Handlers处理器
处理器:控制日志输出到哪里,以及输出的方式。被logger引用。它们将日志分发到不同的目的地。可以是文件、标准输出、邮件、或者通过socket、http等协议发送到任何地方。
如同logger, handler也同样有log level 如果log level超过或满足等自己的log level那么消息会进一步处理,否则会忽略这条消息。
1 StreamHandler
标准输出stdout(如显示器)分发器。
sh = logging.StreamHandler(stream=None)
2 FileHandler
将日志保存到磁盘文件的处理器
fh = logging.FileHandler(filename=,mode=,encoding=None,delay=False)
3 代码示例
import logging # 1:提供应用程序的调用接口 logger = logging.getLogger('app_log') # 2:决定日志的记录级别 logger.setLevel(logging.INFO) # 3:StreamHandler 处理器 sh = logging.StreamHandler(stream=None) sh.setLevel(logging.DEBUG) # 指定处理器日志级别 # 4:FileHandler 处理器 fh = logging.FileHandler(filename='file_handler_test', mode='a', encoding=None, delay=False) # 不指定处理器日志级别,将默认使用logger的日志级别 # 5:将处理器添加到 logger.addHandler(sh) logger.debug("debug") logger.info("info") logger.warning("warning") logger.error("error") logger.critical('critical')
info
warning
error
critical
4 其他处理器类型
-
StreamHandler
实例发送消息到流(类似文件对象)。 -
FileHandler
实例将消息发送到硬盘文件。 -
BaseRotatingHandler
是轮换日志文件的处理器的基类。它并不应该直接实例化。而应该使用RotatingFileHandler
或TimedRotatingFileHandler
代替它。 -
RotatingFileHandler
实例将消息发送到硬盘文件,支持最大日志文件大小和日志文件轮换。 -
TimedRotatingFileHandler
实例将消息发送到硬盘文件,以特定的时间间隔轮换日志文件。 -
SocketHandler
实例将消息发送到 TCP/IP 套接字。从 3.4 开始,也支持 Unix 域套接字。 -
DatagramHandler
实例将消息发送到 UDP 套接字。从 3.4 开始,也支持 Unix 域套接字。 -
SMTPHandler
实例将消息发送到指定的电子邮件地址。 -
SysLogHandler
实例将消息发送到 Unix syslog 守护程序,可能在远程计算机上。 -
NTEventLogHandler
实例将消息发送到 Windows NT/2000/XP 事件日志。 -
MemoryHandler
实例将消息发送到内存中的缓冲区,只要满足特定条件,缓冲区就会刷新。 -
HTTPHandler
实例使用GET
或POST
方法将消息发送到 HTTP 服务器。 -
WatchedFileHandler
实例会监视他们要写入日志的文件。如果文件发生更改,则会关闭该文件并使用文件名重新打开。此处理器仅在类 Unix 系统上有用; Windows 不支持依赖的基础机制。 -
QueueHandler
实例将消息发送到队列,例如在queue
或multiprocessing
模块中实现的队列。 -
NullHandler
实例对错误消息不执行任何操作。它们由想要使用日志记录的库开发人员使用,但是想要避免如果库用户没有配置日志记录,则显示 'No handlers could be found for logger XXX' 消息的情况。更多有关信息,请参阅 配置库的日志记录 。
4:Formatters格式化器
控制日志的格式,被handler使用。Formatter对象用来最终设置日志信息的顺序、结构和内容。
其构造方法为:
ft = logging.Formatter.__init__(fmt=None, datefmt=None, style=' %')
# datefmt 默认是 %Y-%m-%d %H:%M:%S
# style 默认百分符 %,这表示%(<dictionary key>)s 格式的字符串
代码示例
import logging # 1:提供应用程序的调用接口 logger = logging.getLogger('app_log') # 2:决定日志的记录级别 logger.setLevel(logging.DEBUG) # 3:StreamHandler 处理器 sh = logging.StreamHandler(stream=None) sh.setLevel(logging.DEBUG) # 指定处理器日志级别 # 4:FileHandler 处理器 fh = logging.FileHandler(filename='file_handler_test.log', mode='a', encoding=None, delay=False) # 不指定处理器日志级别,将默认使用logger的日志级别 # 5:日志格式化 ft = logging.Formatter('%(asctime)s | %(levelname)8s | %(filename)s | %(lineno)s | %(message)s') # 6:给处理器设置格式---将格式添加到处理器上 sh.setFormatter(ft) fh.setFormatter(ft) # 7:给记录器设置处理器---将处理器添加到 logger.addHandler(sh) logger.addHandler(fh) # 8:打印日志的代码 logger.debug("debug") logger.info("info") logger.warning("warning") logger.error("error") logger.critical('critical')
2021-12-05 15:48:52,477 | DEBUG | 日志高级.py | 28 | debug 2021-12-05 15:48:52,478 | INFO | 日志高级.py | 29 | info 2021-12-05 15:48:52,478 | WARNING | 日志高级.py | 30 | warning 2021-12-05 15:48:52,478 | ERROR | 日志高级.py | 31 | error 2021-12-05 15:48:52,478 | CRITICAL | 日志高级.py | 32 | critical
其他格式化类型
属性名称 |
格式 |
描述 |
---|---|---|
args |
此属性不需要用户进行格式化。 |
合并到 |
asctime |
|
表示 |
created |
|
|
exc_info |
此属性不需要用户进行格式化。 |
异常元组(例如 |
filename |
|
|
funcName |
|
函数名包括调用日志记录. |
levelname |
|
消息文本记录级别( |
levelno |
|
消息数字的记录级别 ( |
lineno |
|
发出日志记录调用所在的源行号(如果可用)。 |
message |
|
记入日志的消息,即 |
module |
|
模块 ( |
msecs |
|
|
msg |
此属性不需要用户进行格式化。 |
在原始日志记录调用中传入的格式字符串。 与 |
name |
|
用于记录调用的日志记录器名称。 |
pathname |
|
发出日志记录调用的源文件的完整路径名(如果可用)。 |
process |
|
进程ID(如果可用) |
processName |
|
进程名(如果可用) |
relativeCreated |
|
以毫秒数表示的 LogRecord 被创建的时间,即相对于 logging 模块被加载时间的差值。 |
stack_info |
此属性不需要用户进行格式化。 |
当前线程中从堆栈底部起向上直到包括日志记录调用并引发创建当前记录堆栈帧创建的堆栈帧信息(如果可用)。 |
thread |
|
线程ID(如果可用) |
threadName |
|
线程名(如果可用) |
5:Filters过滤器
过滤器:控制哪些日志可以从logger流向Handler
默认情况下,将处理满足日志级别要求的任何日志消息
其构造方法为:
flt = logging.Filter("XXX")
代码示例
import logging # 1:提供应用程序的调用接口 logger = logging.getLogger('app_log') # 2:决定日志的记录级别 logger.setLevel(logging.DEBUG) # 3:StreamHandler 处理器 sh = logging.StreamHandler(stream=None) sh.setLevel(logging.DEBUG) # 指定处理器日志级别 # 4:FileHandler 处理器 fh = logging.FileHandler(filename='file_handler_test.log', mode='a', encoding=None, delay=False) # 不指定处理器日志级别,将默认使用logger的日志级别 # 5:日志格式化 ft = logging.Formatter('%(asctime)s | %(levelname)8s | %(filename)s | %(lineno)s | %(message)s') # 6:定义过滤器 flt = logging.Filter("cn.") # 7:给处理器设置格式---将格式添加到处理器上 sh.setFormatter(ft) fh.setFormatter(ft) # 8:给记录器设置处理器---将处理器添加到记录器上 logger.addHandler(sh) logger.addHandler(fh) # 9:给记录器设置过滤器---将过滤器器添加到记录器上 logger.addFilter(flt) # 10:打印日志的代码 logger.debug("debug") logger.info("info") logger.warning("warning") logger.error("error") logger.critical('critical')
没有内容
输出结果为空,因为过滤器根据记录器名称过滤,如果想要有值输入可调整过滤器过滤条件,使其与记录器名称保持被包含关系。ps:
flt = logging.Filter("app_log")
四、logging的项目应用
通过编码方式记录日志修改、管理比较繁琐,因此在大型的项目中我们推荐使用配置文件的方式来管理日志。
通过配置文件的方式实现
#./logging.conf #记录器:提供应用程序代码直接使用的接口 #设置记录器名称,root必须存在!!! [loggers] keys=root,applog #处理器,将记录器产生的日志发送至目的地 #设置处理器类型 [handlers] keys=fileHandler,consoleHandler #格式化器,设置日志内容的组成结构和消息字段 #设置格式化器的种类 [formatters] keys=simpleFormatter #设置记录器root的级别与种类 [logger_root] level=DEBUG handlers=consoleHandler #设置记录器applog的级别与种类 [logger_applog] level=DEBUG handlers=fileHandler,consoleHandler #起个对外的名字 qualname=applog #继承关系 propagate=0 #设置 [handler_consoleHandler] class=StreamHandler args=(sys.stdout,) level=DEBUG formatter=simpleFormatter [handler_fileHandler] class=handlers.TimedRotatingFileHandler #在午夜1点(3600s)开启下一个log文件,第四个参数0表示保留历史文件 args=('applog.log','midnight',3600,0) level=DEBUG formatter=simpleFormatter [formatter_simpleFormatter] format=%(asctime)s|%(levelname)8s|%(filename)s[:%(lineno)d]|%(message)s #设置时间输出格式 datefmt=%Y-%m-%d %H:%M:%S
import logging.config logging.config.fileConfig('logging.conf') rootLogger = logging.getLogger('applog') rootLogger.debug("This is root Logger, debug") logger = logging.getLogger('cn.cccb.applog') logger.debug("This is applog, debug") try: int(a) except Exception as e: logger.exception(e)
D:\work\pythonStudent\venv\Scripts\python.exe D:/work/pythonStudent/01_logging/配置文件.py 2021-12-05 17:09:39| DEBUG|配置文件.py[:5]|This is root Logger, debug 2021-12-05 17:09:39| DEBUG|配置文件.py[:8]|This is applog, debug 2021-12-05 17:09:39| ERROR|配置文件.py[:13]|name 'a' is not defined Traceback (most recent call last): File "D:/work/pythonStudent/01_logging/配置文件.py", line 11, in <module> int(a) NameError: name 'a' is not defined Process finished with exit code 0
django中通过setting配置文件以字典方式实现
# logging module setup LOG_LEVEL = 'INFO' LOG_DIR = os.path.join(BASE_DIR, 'logs') if not os.path.exists(LOG_DIR): os.mkdir(LOG_DIR) LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { "format": '%(asctime)s [%(pathname)s: %(lineno)d] [%(module)s:%(funcName)s] ' '[%(levelname)s] - %(message)s' } }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', } }, 'handlers': { 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.TimedRotatingFileHandler', 'filename': os.path.join(LOG_DIR, 'django_all.log'), 'when': 'midnight', 'interval': 0, 'backupCount': 45, 'formatter': 'standard', }, 'error': { 'level': 'ERROR', 'class': 'logging.handlers.TimedRotatingFileHandler', 'filename': os.path.join(LOG_DIR, 'django_error.log'), 'when': 'midnight', 'interval': 0, 'backupCount': 45, 'formatter': 'standard', }, 'console': { 'level': 'WARNING', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'standard' }, 'request_handler': { 'level': 'DEBUG', 'class': 'logging.handlers.TimedRotatingFileHandler', 'filters': ['require_debug_true'], 'filename': os.path.join(LOG_DIR, 'django_request.log'), 'when': 'midnight', 'interval': 0, 'backupCount': 45, 'formatter': 'standard', }, 'db_handler': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'filters': ['require_debug_true'], 'filename': os.path.join(LOG_DIR, 'django_db.log'), 'maxBytes': 1024 * 1024 * 5, # file size 5M 'backupCount': 5, 'formatter': 'standard', }, }, 'loggers': { 'django': { 'handlers': ['default', 'console', 'error'], 'level': LOG_LEVEL, 'propagate': True }, 'django.server': { 'handlers': ['request_handler'], 'level': LOG_LEVEL, 'propagate': False }, 'django.request': { 'handlers': ['request_handler'], 'level': LOG_LEVEL, 'propagate': False }, 'django.db.backends': { 'handlers': ['db_handler'], 'level': LOG_LEVEL, 'propagate': False }, 'api_service': { 'handlers': ['error', 'default', 'console'], 'level': LOG_LEVEL, 'propagate': False } } }
#!/usr/bin/env python # coding: utf-8 """ 这是一个用于测试的日志记录的py文件 """ import os import django import logging from django.conf import settings # Get an instance of a logger logger = logging.getLogger("api_service") profile = os.environ.get('ANNOTATION_STATISTIC_PROFILE', 'development') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'annotation_statistic.annotation_statistic.settings.{}'.format(profile)) django.setup() def log_test(error_level): """ test for log """ if error_level == "ERROR": # Log an error message logger.error('Something went wrong!') elif error_level == "WARNING": # Log an warning message logger.warning('Something went warning!') elif error_level == "INFO": # Log an info message logger.info('Something went info!') def main(): # 【错误】级别,调用【error】处理器,文件记录到 django_error.log 文档中 # 【错误】级别,调用【default】处理器,文件记录到 django_all.log 文档中 # 【错误】级别,同时调用【console】处理器,当 DEBUG = True 打印在控制台,反之不会记录 log_test("ERROR") # 【警告】级别,调用【default】处理器,文件记录到 django_all.log 文档中 # 【警告】级别,调用【console】处理器,当 DEBUG = True 打印在控制台,反之不会记录 log_test("WARNING") # 【Debug】级别,调用【default】处理器,文件记录到 django_all.log 文档中 log_test("DEBUG") # 【Debug】级别,调用【default】处理器,文件记录到 django_all.log 文档中 log_test("INFO") if __name__ == '__main__': main()