文档目录

  • logging模块
  • 一、logging模块函数
  • 1.1、**kwargs 参数
  • 1.2、basicConfig()参数
  • 1.3、format格式参数
  • 二、logging日志系统
  • 2.1、Logger 类
  • 2.2、Handler类
  • 2.3、Formater类
  • 2.4、Filter类
  • 2.5、流处理流程
  • 2.6、日志重复记录
  • 三、logging日志配置
  • 3.2、配置文件
  • 四、添加上下文
  • 五、项目引用
  • 5.1、日志封装
  • 5.2、项目引入


logging模块

参考资料:https://docs.python.org/zh-cn/3/howto/logging.html#logging-basic-tutorial

logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等。

  • 日志级别

级别

levelno

场景

DEBUG

10

程序调试详细信息

INFO

20

按照正常预期执行

WARNING

30

告警信息

ERROR

40

程序部分模块运行错误

CRITICAL

50

整体严重错误,无法继续执行

日志等级是从上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL,而日志的信息量是依次减少的;

  • 日志要素
    1)发生事件
    2)发生位置
    3)日志级别
    4)事件内容

命令行方式设置日志级别:

----log=INFO

logging模块的2种日志方式
A:通过 logging提供的模块级别的函数
B:使用Logging日志系统的四大组件

其实,logging所提供的模块级别的日志记录函数也是对logging日志系统相关类的封装。

一、logging模块函数

  • logging.debug(msg, *args, **kwargs)
  • logging.info(msg, *args, **kwargs)
  • logging.warning(msg, *args, **kwargs)
  • logging.error(msg, *args, **kwargs)
  • logging.critical(msg, *args, **kwargs)
  • logging.log(level, *args, **kwargs)
  • logging.basicConfig(**kwargs)
  • 简单实现
# coding=utf-8
"""
@DevTool  : PyCharm
@Author   : xxx
@DateTime : 2022/3/29 9:56
@FileName : test_09.py
"""

import logging

logging.debug("there is debug message!")
logging.info("there is info message!")
logging.warning("there is warning message!")
logging.error("there is error message!")
logging.critical("there is critical message!")

输出结果

D:\SoftWare\Python\python.exe E:/PythonProject/FileOperTest/test_09.py
WARNING:root:there is warning message!
ERROR:root:there is error message!
CRITICAL:root:there is critical message!

Process finished with exit code 0

备注:默认情况下 Python的logging模块只输出日志级别 大于等于 warning的信息;

在代码开始位置新增如下代码:

logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s: %(message)s", datefmt = "%Y-%m-%d %H:%M:%S")

输出结果:

D:\SoftWare\Python\python.exe E:/PythonProject/FileOperTest/test_09.py
2022-04-08 16:01:41 DEBUG: there is debug message!
2022-04-08 16:01:41 INFO: there is info message!
2022-04-08 16:01:41 WARNING: there is warning message!
2022-04-08 16:01:41 ERROR: there is error message!
2022-04-08 16:01:41 CRITICAL: there is critical message!

Process finished with exit code 0

1.1、**kwargs 参数

logging.debug()、logging.info()等方法的定义中,除了msg和args参数外,还有一个**kwargs参数,其支持3个关键字参数: exc_info, stack_info, extra,具体用法如下:

  • exc_info

其值为布尔值,如果该参数的值设置为True,则会将异常异常信息添加到日志消息中。如果没有异常信息则添加None到日志信息中。

  • stack_info

其值也为布尔值,默认值为False。如果该参数的值设置为True,栈信息将会被添加到日志信息中。

  • extra

这是一个字典(dict)参数,它可以用来自定义消息格式中所包含的字段,但是它的key不能与logging模块定义的字段冲突。

具体用法

# coding=utf-8
"""
@DevTool  : PyCharm
@Author   : xxx
@DateTime : 2022/3/29 9:56
@FileName : test_09.py
"""

import logging

dict_extra={"user":"robot","ip":"99.99.99.99"}
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s {%(user)s-%(ip)s}: %(message)s", datefmt = "%Y-%m-%d %H:%M:%S")

logging.info("there is info message!",exc_info=True,stack_info=True,extra=dict_extra)
logging.warning("there is warning message!",exc_info=True,stack_info=False,extra=dict_extra)
logging.error("there is error message!",exc_info=False,stack_info=False,extra=dict_extra)

结果输出

D:\SoftWare\Python\python.exe E:/PythonProject/FileOperTest/test_09.py
2022-05-02 21:15:53 INFO {robot-99.99.99.99}: there is info message!
NoneType: None
Stack (most recent call last):
  File "E:/PythonProject/FileOperTest/test_09.py", line 14, in <module>
    logging.info("there is info message!",exc_info=True,stack_info=True,extra=dict_extra)
2022-05-02 21:15:53 WARNING {robot-99.99.99.99}: there is warning message!
NoneType: None
2022-05-02 21:15:53 ERROR {robot-99.99.99.99}: there is error message!

Process finished with exit code 0

1.2、basicConfig()参数

参数

描述

filename

指定日志输出到的目标文件的文件名称

filemode

指定日志文件的打开模式,默认为’a’;该选项要在filename指定时才有效;

format

指定日志格式字符串,即指定日志输出时所包含的字段信息以及顺序;

datefmt

指定日期/时间格式,该选项要在format中包含时间字段%(asctime)s时才有效;

level

指定日志器的日志级别;

stream

指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream;stream和filename不能同时指定,否则会引发 ValueError异常;

style

指定format格式字符串的风格,可取值为’%‘、’{‘和’$‘,默认为’%';

handlers

这些handler将会被添加到root logger;注意 filename、stream和handlers这三个配置项只能有一个存在,否则会引发ValueError异常;

1.3、format格式参数

属性

引用格式

描述

asctime

%(asctime)s

记录日志发生时间,可以精确到毫秒

name

%(name)s

日志器名称,默认是’root’,因为默认使用的是 rootLogger

filename

%(filename)s

调用日志输出函数的模块的文件名; pathname的文件名部分,包含文件后缀

funcName

%(funcName)s

调用日志输出函数的函数名

levelname

%(levelname)s

日志的最终等级(被filter修改后的)

message

%(message)s

日志信息, 日志记录的文本内容

lineno

%(lineno)d

当前日志的行号, 调用日志输出函数的语句所在的代码行

levelno

%(levelno)s

该日志记录的数字形式的日志级别(10, 20, 30, 40, 50)

pathname

%(pathname)s

完整路径 ,调用日志输出函数的模块的完整路径名,可能没有

process

%(process)s

当前进程, 进程ID,可能没有

processName

%(processName)s

进程名称,Python 3.1新增

thread

%(thread)s

当前线程, 线程ID,可能没有

threadName

%(thread)s

线程名称

module

%(module)s

调用日志输出函数的模块名, filename的名称部分,不包含后缀即不包含文件后缀的文件名

created

%(created)f

当前时间,用UNIX标准的表示时间的浮点数表示; 日志事件发生的时间–时间戳,就是当时调用time.time()函数返回的值

relativeCreated

%(relativeCreated)d

输出日志信息时的,自Logger创建以 来的毫秒数; 日志事件发生的时间相对于logging模块加载时间的相对毫秒数

msecs

%(msecs)d

日志事件发生事件的毫秒部分。logging.basicConfig()中用了参数datefmt,将会去掉asctime中产生的毫秒部分,可以用这个加上


到此为止,Python中简单的日志记录已经可以实现,不过对于更加复杂的场景日志记录则需要如下的日志系统来完成;

二、logging日志系统

logging日志模块四大组件

组件名称

对应类名

功能描述

日志器

Logger

提供了应用程序可一直使用的接口

处理器

Handler

将logger创建的日志记录发送到合适的目的输出

过滤器

Filter

提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录

格式器

Formatter

决定日志记录的最终输出格式

组件关系

  • 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
  • 不同的处理器(handler)可以将日志输出到不同的位置;
  • 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
  • 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
  • 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。

2.1、Logger 类

logger 对象

logging.getLogger([name])返回一个logger对象,如果没有指定名字,则 name 的值为 “root”;若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。

Logger对象最常用的方法分为两类:配置方法 和 消息发送方法;

常用配置方法:

方法

描述

Logger.setLevel()

设置日志器将会处理的日志消息的最低严重级别

Logger.addHandler() 和 Logger.removeHandler()

为该logger对象添加 和 移除一个handler对象

Logger.addFilter() 和 Logger.removeFilter()

为该logger对象添加 和 移除一个filter对象

日志记录方法:

方法

描述

Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical()

创建一个与它们的方法名对应等级的日志记录

Logger.exception()

创建一个类似于Logger.error()的日志消息

Logger.log()

需要获取一个明确的日志level参数来创建一个日志记录

logger的层级结构和有效等级

  • logger的名称是一个以".“分割的层级结构,每个”.“后面的logger都是”.”前面的logger的children;
  • logger有一个"有效等级(effective level)"的概念:如果一个logger上没有被明确设置一个level,那么该logger就是使用它parent的level;如果它的parent也没有明确设置level则继续向上查找parent的parent的有效level,依次类推;
  • child loggers在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers相关的handlers。因此,我们不必为一个应用程序中所使用的所有loggers定义和配置handlers,只需要为一个顶层的logger配置handlers,然后按照需要创建child loggers就可足够了。我们也可以通过将一个logger的propagate属性设置为False来关闭这种传递机制。

2.2、Handler类

Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加零个或者多个handler对象。

因为Handler是一个基类,它只定义了所有handlers都应该有的接口,所以无法直接实例化和使用Handler实例。

常用Handler

Handler

描述

logging.StreamHandler

将日志消息输出到Stream,如std.out, std.err或任何file-like对象。

logging.FileHandler

将日志消息发送到磁盘文件,默认情况下文件大小会无限增长

logging.handlers.RotatingFileHandler

将日志消息发送到磁盘文件,并支持日志文件按大小切割

logging.hanlders.TimedRotatingFileHandler

将日志消息发送到磁盘文件,并支持日志文件按时间切割

logging.handlers.HTTPHandler

将日志消息以GET或POST的方式发送给一个HTTP服务器

logging.handlers.SMTPHandler

将日志消息发送给一个指定的email地址

logging.NullHandler

该Handler实例会忽略error messages,通常被想使用logging的library开发者使用来避免’No handlers could be found for logger XXX’信息的出现。

handler常用方法

方法

描述

Handler.setLevel()

设置handler将会处理的日志消息的最低严重级别

Handler.setFormatter()

为handler设置一个格式器对象

Handler.addFilter() 和 Handler.removeFilter()

为handler添加 和 删除一个过滤器对象


  • RotatingFileHandler
    类定义
class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False, errors=None)

当前被写入的文件总是 app.log。
如果 maxBytes 或 backupCount 两者之一的值为零,日志文件不会发生轮换;
当 backupCount 为 5 而基本文件名为 app.log 时,你将得到 app.log、app.log.1、app.log.2 直至 app.log.5。

  • TimedRotatingFileHandler
    类定义
class logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None, errors=None)

日志配置

[handler_timedRotatingFileHandler]
class=logging.handlers.TimedRotatingFileHandler
level=INFO
formatter=commonFormatter
args=("logTrack.log","D",1,180,"utf-8")

说明:此处因为报错TimedRotatingFileHandler 没有定义,所以使用全路径来描述类名称;

2.3、Formater类

Formater对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是,应用代码可以直接实例化Formatter类。

类构造方法:

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

一般直接用 logging.Formatter(fmt, datefmt)

对象获取

2.4、Filter类

Filter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤。

2.5、流处理流程

1、创建一个根 logger对象;
2、设置根 logger对象的日志等级;
3、创建需要的Handler(FileHandler要有路径);
4、设置下每个Handler对象的日志等级;
5、创建下日志的fmt格式和 datefmt格式;
6、设置每个 Handler对象的 formatter 的格式
7、将每个 Handler对象添加到logger对象中;
8、打印输出logger.debug\logger.info\logger.warning\logger.error\logger.critical日志信息;

代码实现

功能描述:按照不同的日志级别,将日志信息分别记录到不同的日志文件;

# coding=utf-8
"""
@DevTool  : PyCharm
@Author   : xxx
@DateTime : 2022/5/2 0:34
@FileName : test_14.py
"""
import logging

logger = logging.getLogger("Tracker")
logger.setLevel(logging.DEBUG)

allHandler = logging.FileHandler("all_log.txt",mode="a+",encoding="utf-8")
allHandler.setLevel(logging.DEBUG)

warnHandler = logging.FileHandler("warn_log.txt",mode="a+",encoding="utf-8")
warnHandler.setLevel(logging.WARNING)

errorHandler = logging.FileHandler("error_log.txt",mode="a+",encoding="utf-8")
errorHandler.setLevel(logging.ERROR)

criticalHandler = logging.FileHandler("critical_log.txt",mode="a+",encoding="utf-8")
criticalHandler.setLevel(logging.CRITICAL)

formatter = logging.Formatter(fmt="%(asctime)s {%(name)s}-[%(levelname)s] %(message)s",datefmt = "%Y-%m-%d %H:%M:%S")

# set formatter
allHandler.setFormatter(formatter)
warnHandler.setFormatter(formatter)
errorHandler.setFormatter(formatter)
criticalHandler.setFormatter(formatter)

# add handlers
logger.addHandler(allHandler)
logger.addHandler(warnHandler)
logger.addHandler(errorHandler)
logger.addHandler(criticalHandler)


# write log messages
logger.debug("there is debug message!")
logger.info("there is info message!")
logger.warning('{} is {} years old.'.format('Tom', 10))
logger.error("there is error message!")
logger.critical("there is critical message!")

all_log.txt

2022-05-02 00:59:13 {Tracker}-[DEBUG] there is debug message!
2022-05-02 00:59:13 {Tracker}-[INFO] there is info message!
2022-05-02 00:59:13 {Tracker}-[WARNING] Tom is 10 years old.
2022-05-02 00:59:13 {Tracker}-[ERROR] there is error message!
2022-05-02 00:59:13 {Tracker}-[CRITICAL] there is critical message!

warn_log.txt

2022-05-02 00:59:13 {Tracker}-[WARNING] Tom is 10 years old.
2022-05-02 00:59:13 {Tracker}-[ERROR] there is error message!
2022-05-02 00:59:13 {Tracker}-[CRITICAL] there is critical message!

error_log.txt

2022-05-02 00:59:13 {Tracker}-[ERROR] there is error message!
2022-05-02 00:59:13 {Tracker}-[CRITICAL] there is critical message!

critical_log.txt

2022-05-02 00:59:13 {Tracker}-[CRITICAL] there is critical message!

2.6、日志重复记录

当重复调用一个 同名的 logger 时,将重复 添加相同 Handler,导致日志的重复记录;
解决方式,即在添加之前 判断 logger 的 handlers 是否为空;

实现代码

# coding=utf-8
"""
@DevTool  : PyCharm
@Author   : xxx
@DateTime : 2022/5/2 0:34
@FileName : test_14.py
"""
import logging


def getConfigedLogger():

    logger = logging.getLogger("Tracker")
    logger.setLevel(logging.DEBUG)

    warnHandler = logging.FileHandler("warn_log.txt", mode="a+", encoding="utf-8")
    warnHandler.setLevel(logging.WARNING)

    errorHandler = logging.FileHandler("error_log.txt", mode="a+", encoding="utf-8")
    errorHandler.setLevel(logging.ERROR)

    formatter = logging.Formatter(fmt="%(asctime)s {%(name)s}-[%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")

    # set formatter
    warnHandler.setFormatter(formatter)
    errorHandler.setFormatter(formatter)

    if not logger.handlers:
        logger.addHandler(warnHandler)
        logger.addHandler(errorHandler)

    return logger



realLogger = getConfigedLogger()

# write log messages
realLogger.debug("there is debug message!")
realLogger.info("there is info message!")
realLogger.warning('{} is {} years old.'.format('Tom', 10))
realLogger.error("there is error message!")
realLogger.critical("there is critical message!")

三、logging日志配置

Python日志常用的3种配置方式如下:

1、使用Python代码显式的创建loggers, handlers和formatters并分别调用它们的配置函数;
2、创建一个日志配置文件,然后使用fileConfig()函数来读取该文件的内容;
3、创建一个包含配置信息的dict,然后把它传递个dictConfig()函数;

3.2、配置文件

通过配置文件实现日志记录,实现了配置和代码的分离,同时开发者可以轻松修改日志记录属性;

  • 配置文件
[loggers]
keys=root,tracker

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=commonFormatter

[logger_root]
level=INFO
handlers=consoleHandler

[logger_tracker]
level=INFO
handlers=consoleHandler,fileHandler
qualname=tracker
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=commonFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=INFO
formatter=commonFormatter
args=("logTrack.log","a+","utf-8")

[formatter_commonFormatter]
format=%(asctime)s -[%(name)s-%(levelname)s]: %(message)s
datefmt=%Y-%m-%d %H:%M:%S

当使用配置文件进行日志的记录时,有些关键规则:

0)在配置文件中,首先包含了三大主要模块,loggers、handlers、formatters。对于三个主要模块其包含的内容都是通过keys进行指定,然后通过logger_key/handler_key/formatter_key对里面的key进行具体的设置。

1)配置logger信息,必须包含一个名字叫做root的logger,当使用无参函数logging.getLogger()时,默认返回root这个logger;其他自定义logger可以通过 logging.getLogger(“loggername”) 方式进行调用;

2)对loggers中声明的logger进行逐个配置,且必须指定level和handlers这两个选项;对于非roothandler,还需要添加一些额外的option,其中qualname表示它在logger层级中的名字;
handlers可以指定多个,中间用逗号隔开,比如handlers=fileHandler,consoleHandler,同时制定使用控制台和文件输出日志;

3)在handler中,必须指定class和args这两个option,常用的class包括 StreamHandler、FileHandler、RotaRotatingFileHandler;
args表示传递给class所指定的handler类初始化方法参数,它必须是一个元组(tuple)的形式,即便只有一个参数值也需要是一个元组的形式;里面指定输出路径,比如输出的文件名称等。
level与logger中的level一样,而formatter指定的是该处理器所使用的格式器,这里指定的格式器名称必须出现在formatters这个section中,且在配置文件中必须要有这个formatter的section定义;如果不指定formatter则该handler将会以消息本身作为日志消息进行记录,而不添加额外的时间、日志器名称等信息;

import logging
import logging.config

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

# create logger
logger = logging.getLogger('Tracker')

四、添加上下文

除了传递给日志记录函数的参数外,有时候需要日志输出中包含一些额外的上下文信息,如客户端的IP地址和用户信息等,实现方式如下:

  • 通过向日志记录函数传递一个extra参数引入上下文信息
  • 使用LoggerAdapters引入上下文信息
  • 使用Filters引入上下文信息

五、项目引用

5.1、日志封装

5.2、项目引入

  • 项目结构
    项目根目录tree /f 显示目录结构
E:.
│  appprog.py
│
├─comTool
│      comFun.py
│
├─conf
│      logging.conf
│
├─logs
│      logTrack.log
│
└─__pycache__

logging.conf 日志配置文件内容:

[loggers]
keys=root,tracker

# 和上面部分一样
...

[handler_fileHandler]
class=FileHandler
level=INFO
formatter=commonFormatter
args=("./logs/logTrack.log","a+","utf-8")


[formatter_commonFormatter]
format=[%(asctime)s]-[%(name)s-%(levelname)s]-->[%(filename)s]: %(message)s
datefmt=%Y-%m-%d %H:%M:%S

主程序 appprog.py 内容如下:

# -*- coding= utf-8 -*-
"""
@DevTool  : PyCharm
@Author   : gzh
@DateTime : 2023/6/15 13:41
@FileName : appprog.py
"""
import logging
import logging.config
from comTool import comFun

if __name__ == "__main__":
    print("the main app is running!")
    logging.config.fileConfig('./conf/logging.conf')
    logger = logging.getLogger("tracker")

    logger.info("the main app is running!")
    comFun.recordTest()

公共程序 comFun.py 内容如下:

# -*- coding= utf-8 -*-
"""
@DevTool  : PyCharm
@Author   : gzh
@DateTime : 2023/6/15 14:12
@FileName : comFun.py
"""
import logging


def recordTest():
    print("current step is in function recordTest!")
    logger = logging.getLogger("tracker")
    logger.info("current step is in function recordTest!")

在公共程序中,logger 可以直接使用在主程序配置的;

控制台输出内容:

the main app is running!
[2023-06-15 14:48:58]-[tracker-INFO]-->[appprog.py]: the main app is running!
current step is in function recordTest!
[2023-06-15 14:48:58]-[tracker-INFO]-->[comFun.py]: current step is in function recordTest!

Process finished with exit code 0

日志输出内容:

[2023-06-15 14:40:43]-[tracker-INFO]-->[appprog.py]: the main app is running!
[2023-06-15 14:40:43]-[tracker-INFO]-->[comTool.py]: current step is in function recordTest!

================================ over ========================================