1.日志的相关概念

日志是指记录系统或应用程序运行状态、事件和错误信息的文件或数据。在计算机系统中,日志通常用于故障排除、性能分析、安全审计等方面。日志可以记录各种信息,如系统启动和关闭时间、应用程序的运行状态、用户登录和操作记录、网络通信数据等。通过分析日志,可以了解系统或应用程序的运行情况,及时发现问题并进行处理

1.1 日志的作用

  1. 记录系统或应用程序的运行情况,错误信息,警告信息,调试信息
  2. 可以快速定位问题,分析系统或应用程序的运行情况
  3. 分析用户的操作行为,类型喜好,地域分布等信息
  4. 监控系统资源使用情况,统计访问量等

1.2 日志的等级⚓️

日志级别使用场景
DEBUG用于调试阶段,输出详细的调试信息,通常不会在生产环境中使用
INFO用于输出程序运行的一般信息,例如程序启动、停止等
WARNING用于输出警告信息,例如程序运行时出现了一些不严重的问题,但需要引起注意
ERROR用于输出错误信息,例如程序运行时出现了一些严重的问题,需要及时处理
CRITICAL用于输出严重的错误信息,例如程序崩溃、系统崩溃等,需要立即处理

1.3 日志字段信息与日志格式

日志字段信息是指日志中记录的各种信息,例如时间,IP地址,请求方法,请求URL,响应状态码,响应时间等等。而日志格式则是指日志记录的排版方式,即每个字段信息在日志中的位置,格式和分隔符等.

常见的日志格式有以下几种:

  1. Common Log Format (CLF):最常见的日志格式,包含了时间、请求方法、请求URL、协议版本、响应状态码和响应大小等信息
  2. Combined Log Format:在CLF的基础上增加了用户代理、来源地址和Referer等信息
  3. W3C Extended Log Format:比CLF和Combined Log Format更加详细,可以记录更多的信息,例如请求头、响应头、Cookie等
  4. JSON格式:将日志信息以JSON格式进行记录,可以更加灵活地处理和分析日志数据

2.Python的Logging模块简介

2.1 基本概念

Python的Logging模块是Python标准库中的一个模块,用于记录程序运行时的日志信息。它提供了一种灵活的方式来控制日志记录的级别、格式和输出位置等。使用Logging模块可以帮助我们更好地理解程序的运行情况,以及在出现问题时更方便地进行调试和排查。

Logging模块中最基本的组件是Logger对象,它负责记录日志信息。Logger对象可以设置日志级别、输出格式和输出位置等属性。另外,Logging模块还提供了一些Handler对象,用于将日志信息输出到不同的位置,比如控制台、文件、网络等。Formatter对象则用于设置日志输出的格式。

使用Logging模块记录日志信息的基本流程如下:

  1. 创建Logger对象,设置日志级别和输出格式等属性
  2. 创建Handler对象,设置输出位置和输出格式等属性
  3. 将Handler对象添加到Logger对象中
  4. 使用Logger对象记录日志信息
#!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: 1.logging模块的基础使用.py@Time: 2023/06/26 17:25:45@Author: haohe'''import logging# 创建logger对象logger = logging.getLogger('mylogger')logger.setLevel(logging.DEBUG)# 创建FileHandler对象fh = logging.FileHandler('mylog.log')fh.setLevel(logging.DEBUG)# 创建Formatter对象formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')fh.setFormatter(formatter)# 将FileHandler对象添加到Logger对象中logger.addHandler(fh)# 记录日志信息logger.debug('debug message')logger.info('info message')logger.warning('warning message')logger.error('error message')logger.critical('critical message')

在上面的代码样例中,创建了一个名为”mylogger”的logger对象,并设置了日志级别为DEBUG。然后创建一个FileHandler对象,将日志信息输出到名为”mylog.log”的文件中,并设置了输出格式。最后我们将FileHandler对象添加到Logger对象中,并使用Logger对象记录了5条不同级别的日志信息

2.2 使用场景

logging模块默认定义了以下几个日志等级,它允许开发人员自定义其他日志级别,但是这是不被推荐的,尤其是在开发供别人使用的库时,因为这会导致日志级别的混乱

日志等级(level)描述
DEBUG最详细的日志信息,典型应用场景是问题诊断
INFO信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切按照预期
WARNING当某些意外的事情发生时记录的信息(如磁盘可用空间较低),但是此时应用程序正常运行
ERROR由于一个更严重的问题导致某些功能不能正常运行时记录的信息
CAITICAL当发生严重错误,导致应用程序不能继续运行时记录的信息

开发应用程序或部署开发环境时,可以使用DEBUG或INFO级别的日志获取尽可能详细的日志信息来进行开发或部署调试;应用上线或部署生产环境时,应该使用WARNING或ERROR或CRITICAL级别的日志来降低机器的I/O压力和提高获取错误日志信息的效率。日志级别的指定通常都是在应用程序的配置文件中进行指定的。

说明:

​ 上面列表中的日志等级是从上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL,而日志的信息量是依次减少的;
​ 当为某个应用程序指定一个日志级别后,应用程序会记录所有日志级别大于或等于指定日志级别的日志信息,而不是仅仅记录指定级别的日志信息,nginx、php等应用程序以及这里要提高的python的logging模块都是这样的。同样,logging模块也可以指定日志记录器的日志级别,只有级别大于或等于该指定日志级别的日志记录才会被输出,小于该等级的日志记录将会被丢弃

2.3 使用Logging模块记录日志

2.3.1 最简单的日志输出✈️
import logginglogging.debug("This is a debug log.")logging.info("This is a info log.")logging.warning("This is a warning log.")logging.error("This is a error log.")logging.critical("This is a critical log.")--->输出WARNING:root:This is a warning log.ERROR:root:This is a error log.CRITICAL:root:This is a critical log.

分析输出内容

  1. 为什么没有打印debug和info日志

    `因为logging模块提供的日志记录函数所使用的日志器设置的日志级别是WARNING,因此只有WARNING级别的日志记录以及大于它的ERROR和CRITICAL级别的日志记录被输出了,而小于它的DEBUG和INFO级别的日志记录被丢弃了
  2. 打印出的日志字段意思

    `日志级别:日志器名称:日志内容
  3. 为什么是这样的格式输出

    `logging模块提供的日志记录函数所使用的日志器设置的日志格式默认是BASIC_FORMAT,其指为:"%(levelname)s:%(name)s:%(message)s"`扩展:Logging的其他样式'%(asctime)s - %(levelname)-10s - %(filename)s - %(funcName)s:%(lineno)d - %(message)s'时间 - 日志级别 - 文件名称 - 函数名称:代码位置(行号) - 日志内容
2.3.2 修改默认设置

logging.basicConfig()函数说明

logging.basicConfig(*kwargs)

该函数可接收的关键字参数如下:

参数名称描述
filename指定日志输出目标文件的文件名,指定该参数后日志信息就不会输出到控制台上
filemode指定日志文件的打开模式,默认为’a’.需要注意的是,该选项要在filename被指定时才有效
format指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序
datefmt指定日志记录中日期和时间的格式,该选项要在format中包含时间字段%(asctime)s时才有效
level指定日志器的日志级别
stream指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream。需要说明的是,stream和filename不能同时提供,否则会引发 ValueError异常
style指定format格式字符串的风格,可取值为’%‘、’{‘和’$‘,默认为’%’
handlers创建了多个Handler的可迭代对象,这些handler将会被添加到root logger。需要说明的是:filename、stream和handlers这三个配置项只能有一个存在,不能同时出现2个或3个,否则会引发ValueError异常
2.3.3 格式字符串字段
字段/属性名称使用格式描述
asctime%(asctime)s日志事件发生的事件–人类可读时间
created%(created)f日志事件发生的时间–时间戳,就是当时调用time.time()函数返回的值
relativeCreated%(relativeCreated)d日志事件发生的时间相对于logging模块加载时间的相对毫秒数
msecs%(msecs)d日志事件发生事件的毫秒部分
levelname%(levelname)s该日志记录的文字形式的日志级别(‘DEBUG’, ‘INFO’, ‘WARNING’, ‘ERROR’, ‘CRITICAL’)
levelno%(levelno)s该日志记录的数字形式的日志级别(10, 20, 30, 40, 50)
name%(name)s所使用的日志器名称,默认是’root’,因为默认使用的是 rootLogger
message%(message)s日志记录的文本内容,通过 msg % args计算得到的
pathname%(pathname)s调用日志记录函数的源码文件的全路径
filename%(filename)spathname的文件名部分,包含文件后缀
module%(module)sfilename的名称部分,不包含后缀
lineno%(lineno)d调用日志记录函数的源代码所在的行号
funcName%(funcName)s调用日志记录函数的函数名
process%(process)d进程ID
processName%(processName)s进程名称,Python 3.1新增
thread%(thread)d%(thread)d
threadName%(thread)s线程名称
2.3.4 配置日志输出

1.简单配置日志器的日志级别

import logging# 配置日志器的日志级别为debug,高于debug或等于debug的都会报出logging.basicConfig(level=logging.DEBUG)logging.debug('This is a debug log')logging.info('This is a info log')logging.warning('This is a warning log')logging.error('This is a error log')logging.critical('This is a critical log')

2.在配置日志器日志级别的基础上,配置日志输出目标文件和日志格式

import logging# 定义日志输出格式LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT)logging.debug("This is a debug log.")logging.info("This is a info log.")logging.warning("This is a warning log.")logging.error("This is a error log.")logging.critical("This is a critical log.")

3.在上面的基础上,再来设置日期/时间格式

import logging# 定义日志输出格式LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT,datefmt=DATE_FORMAT)logging.debug("This is a debug log.")logging.info("This is a info log.")logging.warning("This is a warning log.")logging.error("This is a error log.")logging.critical("This is a critical log.")

2.3.5 配置说明
  1. logging.basicConfig()函数是一个一次性的简单配置工具,也就是说只有在第一次调用该函数时会起作用,后续再次调用该函数时完全不会产生任何操作,多次调用的设置并不是累加操作

  2. 日志器(logger)是Python中logging模块的核心组件之一,它负责产生日志记录。在使用logging模块时,我们需要创建一个或多个日志器对象,然后使用这些日志器对象来记录日志信息

    日志器对象可以通过调用logging.getLogger(name)方法来创建,其中name参数是一个字符串,用于标识日志器对象的名称。如果多次调用该方法并传入相同的name参数,将返回同一个日志器对象

    日志器对象可以设置多个处理器(handler),每个处理器可以将日志记录输出到不同的位置,如控制台、文件、网络等。日志器对象还可以设置日志级别(level),只有日志级别高于等于该级别的日志记录才会被处理器处理。

    在使用日志器对象记录日志时,可以调用其不同级别的方法,如logger.debug()、logger.info()、logger.warning()、logger.error()、logger.critical(),分别对应不同的日志级别

  3. 如果要记录的日志中包含变量数据,可使用一个格式字符串作为这个时间的描述信息(loggin.debug,logging.info函数的第一个参数),然后将变量数据作为第二个参数*args的值进行传递

    logging.warning('%s is %d years old.','Tom',10)==>WARNING:root:Tom is 10 years old.
  4. logging.debug()logging.info()等方法的定义中,除了msg和args参数外,还有一个**kwargs参数,支持3个关键字参数:

    • exc_info:其值为布尔值,如果该参数的值设置为True,则会将异常信息添加到日志信息中,如果没有异常信息则将None添加到日志信息中
    • stack_info:其值为布尔值,默认值为False。如果该参数的值为Ture,栈信息将会被添加到日志信息中
    • extra:这是一个字典(dict)参数,可以用来自定义消息格式中所包含的字段,但是它的key不能与logging模块定义的字段冲突
    import logging# 定义日志输出格式LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"logging.basicConfig(format=LOG_FORMAT,datefmt=DATE_FORMAT)logging.warning("Some one delete the log file.",exc_info=True,stack_info=True,extra={'user':'Tom','ip':'192.168.1.1'})

3.Logging模块日志处理流程

3.1 Logging日志模块的四大组件

组件名称对应类名功能描述
日志器Logger提供应用程序可一直使用的接口
处理器Handler指定Logger创建的日志记录输入方式
过滤器Filter提供了更细粒度的控制工具来决定保留那条日志
格式器Formatter决定日志记录的最终输出格式

各种组件之间的关系描述:

日志器(Logger)需要通过处理器(Handler)将日志信息输出到目标位置,如文件,终端或者网络等. 日志器(Logger)可以设置多个处理器(Handler)将同一条日志记录输出到不同的位置。每个处理器(Handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志,每个处理器(Handler)都可以设置自己的格式器(Formatter)实现同一条日志以不同的格式输出到不同的地方

3.2 Logging日志模块相关类及其常用方法介绍⛈

3.2.1 Logger类
`Logger对象是Python中logging模块的核心组件之一,它用于记录应用程序的日志信息。Logger对象可以创建多个实例,每个实例都有一个唯一的名称,用于标识不同的日志记录器。Logger对象可以通过调用其方法来记录不同级别的日志信息,例如debug、info、warning、error和critical等级别的信息。Logger对象还可以设置日志记录的格式、输出位置和级别等属性,以满足不同的日志记录需求。在工作中,Logger对象通常用于记录应用程序的运行状态、错误信息和调试信息等,以便开发人员和运维人员能够及时发现和解决问题

常用的方法如下:

1.设置日志的配置方法

`设置日志器将会处理最低严重级别的日志消息Logger.setLevel()# 关于Logger.setLevel()方法的说明:# 内建等级中,级别最低的是DEBUG,级别最高的是CRITICAL。例如setLevel(logging.INFO),此时函数参数为INFO,那么该logger将只会处理INFO、WARNING、ERROR和CRITICAL级别的日志,而DEBUG级别的消息将会被忽略/丢弃`为该logger对象添加和移除一个handler对象Logger.addHandler() / Logger.removeHandler()`为该logger对象添加和移除一个filter对象Logger.addFilter()/ Logger.removeFilter()

2.创建日志记录的方法​

`创建一个与它们的方法名对应等级的日志记录Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical()`创建一个类似于Logger.error()的日志消息Logger.exception()`需要获取一个明确的日志level参数来创建一个日志记录Logger.log()"说明:"# Logger.exception()与Logger.error()的区别在于:Logger.exception()将会输出退栈追踪信息,另外通常只是在一个exception handler中调用该方法# Logger.log()与Logger.debug(),Logging.info()等方法相比,虽然需要多传一个level参数,显得不是那么方便,但是当需要记录自定义level的日志时还是需要使用Logger.log()方法完成

​那么,怎样得到一个Logger对象呢?一种方式是通过Logger类的实例化方法创建一个Logger类的实例,但是我们通常都是用第二种方式–logging.getLogger()方法。

​logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为’root’ 若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用

3.2.2 Handler类
`Handler对象的作用是(基于日志消息的level)将消息分发到Handler指定的位置(文件,网络,邮件等).Logger对象可以通过addHandler()方法为自己添加0个或多个Handler对象.比如,一个应用程序可能想要实现以下几个日志需求:1)把所有日志都发送到一个日志文件中;2)把所有严重级别大于等于error的日志发送到stdout(标准输出);3)把所有严重级别为critical的日志发送到一个email邮件地址。这种场景就需要3个不同的handlers,每个handler复杂发送一个特定严重级别的日志到一个特定的位置

常用的方法如下:

1.设置日志的配置方法

`设置handler将会处理的日志消息的最低级别Handler.setLevel()`为handler设置一个格式器对象Handler.setFormatter()`为handler添加和删除一个过滤器对象Handler.addFilter() / Handler.removeFilter()

2.实例化和使用Handler对象方法

Handler名称方法作用
StreamHandlerlogging.StreamHandler日志输出到流,可以是sys.stderr,sys.stdout或者文件
FileHandlerlogging.FileHandler日志输出到文件
BaseRotatingHandlerlogging.handlers.BaseRotatingHandler基本的日志回滚方式
RotatingHandlerlogging.handlers.RotatingHandler日志回滚方式,支持日志文件最大数量和日志文件回滚
TimeRotatingHandlerlogging.handlers.TimeRotatingHandler日志回滚方式,在一定时间区域内回滚日志文件
SocketHandlerlogging.handlers.SocketHandler远程输出日志到TCP/IP sockets
DatagramHandlerlogging.handlers.DatagramHandler远程输出日志到UDP sockets
SMTPHandlerlogging.handlers.SMTPHandler远程输出日志到邮件地址
SysLogHandlerlogging.handlers.SysLogHandler日志输出到syslog
NTEventLogHandlerlogging.handlers.NTEventLogHandler远程输出日志到Windows NT/2000/XP的事件日志
MemoryHandlerlogging.handlers.MemoryHandler日志输出到内存中的指定buffer
HTTPHandlerlogging.handlers.HTTPHandler通过”GET”或者”POST”远程输出到HTTP服务器
NullHandlerlogging.NullHandler()忽略所有的日志消息

3.方法使用示例

  1. logging.FileHandler()方法使用案例

    #!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: 4.logging模块的Handler处理器.py@Time: 2023/06/28 15:40:54@Author: haohe'''import logging# 创建一个日志记录器logger = logging.getLogger('my.log')logger.setLevel(logging.DEBUG) # 设置处理器(Handler)处理的日志消息最低级别# 创建一个文件处理器file_handler = logging.FileHandler('my_log_file.log')file_handler.setLevel(logging.INFO)# 创建一个格式化器formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')# 将格式化器添加到文件处理器file_handler.setFormatter(formatter)# 将文件处理器添加到日志记录器logger.addHandler(file_handler)# 记录日志logger.debug('This is a debug message')logger.info('This is an info message')logger.warning('This is a warning message')logger.error('This is an error message')logger.critical('This is a critical message')"""代码注解:创建了一个名为my_logger的日志记录器,并将其日志级别设置为DEBUG。然后,创建了一个名为my_log_file.log的文件处理器,并将其日志级别设置为INFO接下来,创建了一个格式化器,并将其添加到文件处理器中。最后,将文件处理器添加到日志记录器中。通过调用logger.debug()、logger.info()等方法,可以记录不同级别的日志消息。这些消息将被写入到my_log_file.log文件中,并按照指定的格式进行记录请注意,如果指定的文件不存在,logging.FileHandler会自动创建该文件。如果文件已经存在,新的日志消息将被追加到文件的末尾"""

  1. logging.handlers.HTTPHandler()

    `前提准备:使用tornado编写一个日志接口#!/bin/bash/python3 # -*- encoding: utf-8 -*-# 导入Tornado和logging模块import tornado.ioloopimport tornado.webimport tornadoimport logging# 配置日志记录器logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(levelname)s - %(message)s')# 创建一个Tornado请求处理类class LogHandler(tornado.web.RequestHandler):def get(self):logging.debug("This is a debug message")logging.info("This is an info message")logging.warning("This is a warning message")logging.error("This is an error message")self.write("Log messages have been recorded.")# 在这个处理类中,我们使用logging模块记录了不同级别的日志信息,并通过write方法返回一个响应# 创建Tornado应用并定义路由app = tornado.web.Application([(r"/log",LogHandler),])# 启动Tornado服务器if __name__ == "__main__":app.listen(8888)tornado.ioloop.IOLoop.current().start()# 现在,你可以通过访问http://localhost:8888/log来测试这个日志接口。在浏览器中打开该URL后,你将看到日志信息被记录,并返回了一个响应

`通过POST方式发送给HTTP服务器#!/bin/bash/python3 # -*- encoding: utf-8 -*-import loggingfrom logging import handlers# 创建一个logger对象logger = logging.getLogger('my.log')# 设置logger对象处理的最低日志级别logger.setLevel(logging.DEBUG)# 创建一个HTTPHandler对象http_handler = logging.handlers.HTTPHandler('47.113.146.118:8888','/log',method='POST')# 创建一个格式化对象formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')# 将将格式化器添加到HTTP处理对象中http_handler.setFormatter(formatter)# 将HTTPHandler对象添加到Logger对象中logger.addHandler(http_handler)# 发送日志消息logger.debug('This is a debug message')logger.info('This is an info message')logger.warning('This is a warning message')logger.error('This is an error message')logger.critical('This is a critical message')

解决AttributeError: module 'tornado' has no attribute 'web'报错
问题原因在于:tornado.web.asynchronous在tornado5.1版本中已弃用,并在tornado6.0中已删除,用coroutines代替,而使用pip install tornado 默认装的是最新版
解决办法:

pip uninstall tornado pip install tornado==5.1.1 -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
  1. logging.handlers.SMTPHandler()

    #!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: 7.logging模块的Handler处理器3.py@Time: 2023/06/28 16:35:45@Author: haohe'''import loggingfrom logging.handlers import SMTPHandler# 定义变量mailhost=('smtp.163.com',465)# SMTP服务器地址fromaddr='h1*7****9615@163.com' # 发件人地址toaddrs='eamil.online@qq.com' # 收件人地址,可以是个列表subject='Error Log'# 邮件主题credentials=('h1*7****9615@163.com'','X**********J') # 验证信息主要是为了验证身份,前一个值为发送人的邮箱,后一个值为授权码# timeout=20, # 配置超时时间secure=True # 配置SSL加密# 创建SMTPHandler实例smtp_handler = SMTPHandler(mailhost,fromaddr,toaddrs,subject,credentials,secure)# 设置日志级别smtp_handler.setLevel(logging.ERROR)# 创建Logger对象logger = logging.getLogger('mylog')logger.addHandler(smtp_handler)# 记录日志logger.error('An error occurred')# 关闭Logger对象logger.removeHandler(smtp_handler)
3.2.3 Formater类

Formater对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler类不同的是,应用代码可以直接实例化Formatter类。另外,如果你的应用程序需要一些特殊的处理行为,也可以实现一个Formatter的子类来完成

Formater类的构造方法定义如下:

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

可见,该构造方法接收3个可选参数:

  • fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
  • datefmt:指定日期格式字符串,如果不指定该参数则默认使用”%Y-%m-%d %H:%M:%S”
  • style:Python 3.2新增的参数,可取值为 ‘%’, ‘{‘和 ‘$’,如果不指定该参数则默认使用’%’
3.2.4 Filter类

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

Filter类的构造方法定义如下:

class logging.Filter(name='')filter(record)# 比如一个filter实例化时传递的name参数值为'A.B',那么该filter实例将只允许名称为类似如下规则的loggers产生的日志记录通过过滤:'A.B','A.B,C','A.B.C.D','A.B.D'# 而名称为'A.BB', 'B.A.B'的loggers产生的日志则会被过滤掉。如果name的值为空字符串,则允许所有的日志事件通过过滤

Filter方法用于具体控制传递的record记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非0表示可以通过过滤

说明:

  1. 在某些情况下,也可以在filter(record)方法内部改变该record,比如添加,删除或修改一些属性
  2. 也可以通过filter统计符合某些条件的日志记录
#!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: 8.logging模块的Filter过滤方法.py@Time: 2023/06/28 18:08:25@Author: haohe'''import loggingimport sys# 创建自定义的过滤器类class MyFilter(logging.Filter):def filter(self, record): """ 在这里写过滤逻辑 返回True表示接收该日志,返回False表示拒绝该记录 """ return record.levelno >= logging.WARNING # 只接受警告级别(warning)及以上的记录# 创建Logger对象logger = logging.getLogger('mylog')# 创建并添加过滤器filter = MyFilter()logger.addFilter(filter)# 添加日志处理器stdout = logging.StreamHandler(stream=sys.stdout)# 创建一个格式化对象LOG_FORMAT = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")stdout.setFormatter(LOG_FORMAT)logger.addHandler(stdout)# 发送日志消息logger.debug('This is a debug message')logger.info('This is an info message')logger.warning('This is a warning message')logger.error('This is an error message')logger.critical('This is a critical message')

3.3 Logging日志模块处理流程

  1. 配置日志记录器:创建一个Logger对象,用于记录和管理日志信息
  2. 配置日志处理器:创建一个或多个Handler对象,用于指定日志的输出目标。可以使用不同的Handler对象将日志记录到不同的目标,如控制台、文件、电子邮件等
  3. 配置日志过滤器:创建一个Filter对象,过滤器可以根据日志记录的级别、名称或其他属性来决定是否接受该记录
  4. 配置日志格式化器:创建一个Formatter对象,用于指定日志的格式。可以使用不同的Formatter对象来定义不同的日志格式
  5. 日志经过处理,过滤,格式化后的结果会输出到指定位置

4.Logging模块的配置方式

4.1 使用Python代码实现日志配置

import logging# 创建一个日志器logger并设置其日志级别为DEBUGlogger = logging.getLogger('simple_logger')logger.setLevel(logging.DEBUG)# 创建一个流处理器handler并设置其日志级别为DEBUGhandler = logging.StreamHandler(sys.stdout)handler.setLevel(logging.DEBUG)# 创建一个格式器formatter并将其添加到处理器handlerformatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")handler.setFormatter(formatter)# 为日志器logger添加上面创建的处理器handlerlogger.addHandler(handler)# 日志输出logger.debug('debug message')logger.info('info message')logger.warn('warn message')logger.error('error message')logger.critical('critical message')

4.2 使用配置文件和fileConfig()函数实现日志配置

`Python代码import logging# 读取日志配置文件配置logging.config.fileConfig('logging.conf')# 创建一个日志器logger = logging.getLogger('simpleExample')# 日志输出logger.debug('debug message')logger.info('info message')logger.warn('warn message')logger.error('error message')logger.critical('critical message')`配置文件logging.conf[loggers]keys=root,simpleExample[handlers]keys=FileHandler,consoleHandler[formatters]keys=simpleFormatter[logger_root]level=DEBUGhandlers=FileHandler[logger_simpleExample]level=DEBUGhandlers=consoleHandlerqualname=simpleExamplepropagate=0[handler_consoleHandler]class=StreamHandlerargs=(sys.stdout,)level=DEBUGformatter=simpleFormatter[handler_FileHandler]class=FileHandlerargs=('logging.log','a')level=ERRORformatter=simpleFormatter[formatter_simpleFormatter]format=%(asctime)s - %(name)s - %(levelname)s - %(message)sdatefmt=

4.2.1 关于fileConfig()函数的说明

该函数实际上是对configparser模块的封装

`函数定义logging.config.fileConfig(fname,defaults=None,disable_existing_loggers=True)`参数说明# fname:表示配置文件的文件名或文件对象# defaults:指定传给ConfigParser的默认值# disable_existing_loggers:这是一个布尔型值,默认值为True表示禁用已经存在的logger,除非它们明确的出现在日志配置中.如果值为False则对已存在的loggers保持启动状态
4.2.2 配置文件格式说明
  1. 配置文件中一定要包含loggershandlersformatters这些片段,它们通过keys这个选项来指定配置文件中已经定义好的loggershandlersformatters

  2. loggershandlersformatters中所指定的日志器,处理器和格式器都需要在下面已单独的片段定义.Section(片段)的命名规则为:

    [loggers]keys=root,simpleExample[handlers]keys=FileHandler,consoleHandler[formatters]keys=simpleFormatter
  3. 定义logger的Section必须指定levelhandlers这两个option

    • level的可取值为DEBUGINFOWARNINGERRORCRITICALNOTEST.其中NOTEST表示所有级别的日志消息都要记录,包括用户自定义级别.
    • handlers的值是以逗号分隔的handler名字列表,这里出现的handler必须出现在[handlers]这个Section中,并且对应的handler必须在配置文件中有对应的section定义
    [logger_root]level=DEBUGhandlers=FileHandler
  4. 对于非root logger来说,除了levelhandler这两个option之外,还需要一些额外的option

    • qualname是必须提供的option,它表示在logger层级中的名字,在应用代码中通过名字得到logger
    • propagate是可选项,默认为1,表示消息会传递给高层次的logger的handler,通常我们需要指定其值为0
    • 对于非root logger的level如果设置为NOTEST,系统会查找高层次的logger来决定此logger的有效level
    [logger_simpleExample]level=DEBUGhandlers=consoleHandlerqualname=simpleExamplepropagate=0
  5. 定义handler的Section中必须指定classargs这两个optionlevelformatter可作为可选参数.

    • class表示用于创建handler的类名
    • args表示传递给class所指定的handler类初始化方法参数,他必须是一个元组(tuple)的形式,即便是只有一个参数也需要是元组的形式
    • level与logger中的level一样
    • formatter指定的是该处理器所使用的格式器,这里指定的格式器名称必须出现在formatters这个section中,且在配置文件中必须要有这个formatter的section定义;如果不指定formatter则该handler将会以消息本身作为日志消息进行记录,而不添加额外的时间,日志器名称等信息
    [handler_FileHandler]class=FileHandlerargs=('logging.log','a')level=ERRORformatter=simpleFormatter
  6. 定义formatter的Section的option都是可选的,其中包括format用于指定格式字符串,默认为消息字符串本身;datefmt用于指定asctime的时间格式,默认为%Y-%m-%d %H:%M:%Sclass用于指定格式器类名,默认为logging.Formatter

4.2.3 对于propagate属性说明
  1. 修改logging.conf中simpleExample这个handler定义中的propagate属性值改为1,或者删除这个option

    `Python代码:import loggingimport logging.configfrom logging import handlers# 读取日志配置文件配置logging.config.fileConfig('logging.conf')# 创建一个日志器logger = logging.getLogger('simpleExample')# 日志输出logger.debug('debug message')logger.info('info message')logger.warn('warn message')logger.error('error message')logger.critical('critical message')`logging.conf……[logger_simpleExample]level=DEBUGhandlers=consoleHandlerqualname=simpleExamplepropagate=1……

除了在控制台中有输出信息,在logging.log文件中也有内容输出

  1. 用一个没有在配置文件中定义的logger名称来获取logger

    import logging# 获取日志配置文件内容logging.config.fileConfig('logging.conf')# 用一个没有在配置文件中定义的logger名称来创建一个日志器loggerlogger = logging.getLogger('simpleExample1')# 日志输出logger.debug('debug message')logger.info('info message')logger.warn('warn message')logger.error('error message')logger.critical('critical message')

运行程序后,我们会发现控制台没有任何输出,而logging.log文件中又多了两行输出

这是因为,当一个日志器没有被设置任何处理器是,系统会去查找该日志器的上层日志器上所设置的日志处理器来处理日志记录。simpleExample1在配置文件中没有被定义,因此logging.getLogger(simpleExample1)这行代码这是获取了一个logger实例,并没有给它设置任何处理器,但是它的上级日志器–root logger在配置文件中有定义且设置了一个FileHandler处理器,simpleExample1处理器最终通过这个FileHandler处理器将日志记录输出到logging.log文件中了

4.3 使用字典配置信息和dictConfig()函数实现日志配置

Python 3.2中引入的一种新的配置日志记录的方法——用字典来保存logging配置信息.这相对于上面所讲的基于配置文件来保存logging配置信息的方式来说,功能更加强大,也更加灵活,因为我们可把很多的数据转换成字典。比如,我们可以使用JSON格式的配置文件、YAML格式的配置文件,然后将它们填充到一个配置字典中;或者,我们也可以用Python代码构建这个配置字典,或者通过socket接收pickled序列化后的配置信息。总之,你可以使用你的应用程序可以操作的任何方法来构建这个配置字典。

这个例子中,我们将使用YAML格式来完成与上面同样的日志配置

# 安装PyYAML模块pip install PyYAML -i http://mirrors.cloud.aliyuncs.com/pypi/simple/

Python代码:

#!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: 11.Logging模块使用Yaml文件.py@Time: 2023/07/01 16:20:19@Author: haohe'''import loggingimport logging.configimport yamlwith open('logging.yaml','r') as f:# 使用Yaml模块加载配置文件内容到字典中# yaml.load(f,Loader=yaml.FullLoader)方法是使用PyYAML库加载YAML文件的一种常见方式,使用FullLoader类作为加载器,以确保安全加载YAML数据dict_conf = yaml.load(f,Loader=yaml.FullLoader)# 使用修改后的配置字典来配置日志系统logging.config.dictConfig(dict_conf)# 获取名为'simpleExample'的日志记录器logger = logging.getLogger('simpleExample')# 日志输出logger.debug('debug message')logger.info('info message')logger.warning('warn message')logger.error('error message')logger.critical('critical message')

Yaml代码

version: 1formatters:simple:format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'handlers:console:class: logging.StreamHandlerlevel: DEBUGformatter: simplestream: ext://sys.stdoutconsole_err:class: logging.StreamHandlerlevel: ERRORformatter: simplestream: ext://sys.stderrloggers:simpleExample:level: DEBUGhandlers: [console]propagate: noroot:level: DEBUGhandlers: [console_err]

4.3.1 关于dictConfig()函数的说明

该函数实际上是对configparser模块的封装

函数定义:

logging.config.dictConfig(config)

该函数可以从一个字典对象中获取日志配置信息,config参数就是这个字典对象

4.3.2 配置字典说明

Key名称描述
version必选项,其值是一个整数值,表示配置格式的版本
formatters可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的格式器名称,value为格式器的配置信息组成的dict,如format和datefmt
filters可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的处理器名称,value为处理器的配置信息组成的dcit,如class,level,formatter和filters,其中class为必选项,其它为可选项;其他配置信息将会传递给class所指定的处理器类的构造函数
loggers可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的日志器名称,value为日志器的配置信息组成的dcit,如level、handlers、filters 和 propagate(yes|no),这些都是可选项
root可选项,这是root logger的配置信息,其值也是一个字典对象。除非在定义其它logger时明确指定propagate值为no,否则root logger定义的handlers都会被作用到其它logger上
incremental可选项,默认值为False。该选项的意义在于,如果这里定义的对象已经存在,那么这里对这些对象的定义是否应用到已存在的对象上。值为False表示,已存在的对象将会被重新定义
disable_existing_loggers可选项,默认值为True。该选项用于指定是否禁用已存在的日志器loggers,如果incremental的值为True则该选项将会被忽略

handlers定义示例:

handlers:console:class : logging.StreamHandlerformatter: brieflevel : INFOfilters: [allow_foo]stream: ext://sys.stdoutfile:class : logging.handlers.RotatingFileHandlerformatter: precisefilename: logconfig.logmaxBytes: 1024backupCount: 3

5.Logging模块日志输出添加上下文信息

5.1 extra参数引入上下文信息

#!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: 13.Logging模块extra参数引入上下文信息.py@Time: 2023/07/01 18:17:46@Author: haohe'''import loggingimport sysfmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s")h_console = logging.StreamHandler(sys.stdout)h_console.setFormatter(fmt)logger = logging.getLogger("myPro")logger.setLevel(logging.DEBUG)logger.addHandler(h_console)extra_dict = {"ip": "113.208.78.29", "username": "Petter"}logger.debug("User Login!", extra=extra_dict)extra_dict = {"ip": "223.190.65.139", "username": "Jerry"}logger.info("User Access!", extra=extra_dict)

5.2 使用LoggerAdapters引入上下问信息

使用LoggerAdapter类来传递上下文信息到日志事件的信息中是一个非常简单的方式,可以把它看做第一种实现方式的优化版–因为它为extra提供了一个默认值。这个类设计的类似于Logger,因此我们可以像使用Logger类的实例那样来调用debug(), info(), warning(),error(), exception(), critical()log()方法。

当创建一个LoggerAdapter的实例时,我们需要传递一个Logger实例和一个包含上下文信息的类字典对象给该类的实例构建方法。当调用LoggerAdapter实例的一个日志记录方法时,该方法会在对日志日志消息和字典对象进行处理后,调用构建该实例时传递给该实例的logger对象的同名的日志记录方法

#!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: 14.Logging模块中的LoggerAdapters.py@Time: 2023/07/01 18:22:23@Author: haohe'''import loggingimport sys# 初始化一个要传递给LoggerAdapter构造方法的logger实例fmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s")h_console = logging.StreamHandler(sys.stdout)h_console.setFormatter(fmt)init_logger = logging.getLogger("myPro")init_logger.setLevel(logging.DEBUG)init_logger.addHandler(h_console)# 初始化一个要传递给LoggerAdapter构造方法的上下文字典对象extra_dict = {"ip": "IP", "username": "USERNAME"}# 获取一个LoggerAdapter类的实例logger = logging.LoggerAdapter(init_logger, extra_dict)# 应用中的日志记录方法调用logger.info("User Login!")logger.info("User Login!", extra={"ip": "113.208.78.29", "username": "Petter"})logger.extra = {"ip": "113.208.78.29", "username": "Petter"}logger.info("User Login!")logger.info("User Login!")

根据上面的程序输出结果,我们会发现一个问题:传递给LoggerAdapter类构造方法的extra参数值不能被LoggerAdapter实例的日志记录函数(如上面调用的info()方法)中的extra参数覆盖,只能通过修改LoggerAdapter实例的extra属性来修改默认值(如上面使用的logger.extra=xxx),但是这也就意味着默认值被修改了。

解决这个问题的思路应该是:实现一个LoggerAdapter的子类,重写process()方法。其中对于kwargs参数的操作应该是先判断其本身是否包含extra关键字,如果包含则不使用默认值进行替换;如果kwargs参数中不包含extra关键字则取默认值。来看具体实现:

import loggingimport sysclass MyLoggerAdapter(logging.LoggerAdapter):def process(self, msg, kwargs):if 'extra' not in kwargs:kwargs["extra"] = self.extrareturn msg, kwargsif __name__ == '__main__':# 初始化一个要传递给LoggerAdapter构造方法的logger实例fmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s")h_console = logging.StreamHandler(sys.stdout)h_console.setFormatter(fmt)init_logger = logging.getLogger("myPro")init_logger.setLevel(logging.DEBUG)init_logger.addHandler(h_console)# 初始化一个要传递给LoggerAdapter构造方法的上下文字典对象extra_dict = {"ip": "IP", "username": "USERNAME"}# 获取一个自定义LoggerAdapter类的实例logger = MyLoggerAdapter(init_logger, extra_dict)# 应用中的日志记录方法调用logger.info("User Login!")logger.info("User Login!", extra={"ip": "113.208.78.29", "username": "Petter"})logger.info("User Login!")logger.info("User Login!")

输出结果:

# 使用extra默认值:{"ip": "IP", "username": "USERNAME"}2017-05-22 17:35:38,499 - myPro - IP - USERNAME - User Login!# info(msg, extra)方法中传递的extra方法已覆盖默认值2017-05-22 17:35:38,499 - myPro - 113.208.78.29 - Petter - User Login!# extra默认值保持不变2017-05-22 17:35:38,499 - myPro - IP - USERNAME - User Login!2017-05-22 17:35:38,499 - myPro - IP - USERNAME - User Login!

5.3 使用Filters引入上下文信息

#!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: 15.Logging模块使用Filters引入上下文信息.py@Time: 2023/07/01 18:25:03@Author: haohe'''import loggingfrom random import choiceclass ContextFilter(logging.Filter):ip = 'IP'username = 'USER'def filter(self, record):record.ip = self.iprecord.username = self.usernamereturn Trueif __name__ == '__main__':levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)users = ['Tom', 'Jerry', 'Peter']ips = ['113.108.98.34', '219.238.78.91', '43.123.99.68']logging.basicConfig(level=logging.DEBUG,format='%(asctime)-15s %(name)-5s %(levelname)-8s %(ip)-15s %(username)-8s %(message)s')logger = logging.getLogger('myLogger')filter = ContextFilter()logger.addFilter(filter)logger.debug('A debug message')logger.info('An info message with %s', 'some parameters')for x in range(5):lvl = choice(levels)lvlname = logging.getLevelName(lvl)filter.ip = choice(ips)filter.username = choice(users)logger.log(lvl, 'A message at %s level with %d %s' , lvlname, 2, 'parameters')

6.Logging模块日志处理案例

6.1 案例1

1.需求

  1. 要求将所有级别的日志都写入磁盘
  2. all.log文件中记录的日志信息,日志格式为: 日期和事件 – 日志级别 – 日志信息
  3. error.log文件中单独记录error及以上的日志信息,日志格式为: 日期和事件 – 日志级别 – 文件名[:行号] – 日志信息
  4. 要求all.log在每天凌晨进行日志切割

2.分析

  • 要记录所有级别的日志,因此日志器的有效level需要设置为最低级别–DEBUG;
  • 日志需要被发送到两个不同的目的地,因此需要为日志器设置两个handler;另外,两个目的地都是磁盘文件,因此这两个handler都是与FileHandler相关的;
  • all.log要求按照时间进行日志切割,因此他需要用logging.handlers.TimedRotatingFileHandler; 而error.log没有要求日志切割,因此可以使用logging.FileHandler
  • 两个日志文件的格式不同,因此需要对这两个handler分别设置格式器;

3.代码实现

#!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: 9.Logging模块的使用案例.py@Time: 2023/06/28 18:26:55@Author: haohe'''"""日志需求:1要求将所有级别的所有日志都写入磁盘文件中2all.log文件中记录所有的日志信息,日志格式为:日期和时间 - 日志级别 - 日志信息3error.log文件中单独记录error及以上级别的日志信息,日志格式为:日期和时间 - 日志级别 - 文件名[:行号] - 日志信息4要求all.log在每天凌晨进行日志切割"""import loggingfrom logging import handlersimport datetime# 创建日志记录器logger = logging.getLogger('mylog')# 设置all.log文件的日志最低级别为DEBUGlogger.setLevel(logging.DEBUG)# 创建一个格式化器formatter1 = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')formatter2 = logging.Formatter('$(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s')# 创建一个日志切割器All_Handler =logging.handlers.TimedRotatingFileHandler(filename='all.log', when='midnight', interval=1, backupCount=7, atTime=datetime.time(0, 0, 0, 0))# filename:日志文件名称,when:指定文件的切割时间间隔,可选值为S(秒),M(分钟),H(小时),D(天),W0-W6(周一至周日),midnight(每天凌晨)# backupCount:指定保留的日志文件数量# interval:指定切割时间间隔的数量All_Handler.setLevel(logging.DEBUG)All_Handler.setFormatter(formatter1)# 创建一个文件查看器Error_Handler = logging.FileHandler('error.log')Error_Handler.setLevel(logging.ERROR)Error_Handler.setFormatter(formatter2)logger.addHandler(All_Handler)logger.addHandler(Error_Handler)# 发送日志消息logger.debug('This is a debug message')logger.info('This is an info message')logger.warning('This is a warning message')logger.error('This is an error message')logger.critical('This is a critical message')
6.2 案例2

1.需求

Python执行过程中输出的东西会很多,而终端中显示的信息很快就会被覆盖,这时候,将这些信息输入到日志文件中.如果在执行Python代码时,直接调用一个写好的日志方法来实现这个功能

2.需求分析

  1. 需要先构建一个写好的日志脚本
  2. 将脚本和代码放在同一目录下,代码中导入方法

3.代码实现

loguti.py

#!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: loguti.py@Time: 2023/07/01 17:14:33@Author: haohe'''import logging.configimport osconfig = {'version':1,# 定义格式器'formatters':{'simple':{'format':'%(asctime)s - %(name)s - %(levelname)s -%(message)s',},},# 定义处理器'handlers':{# 日志输出流'console':{ 'class':'logging.StreamHandler', 'level':'WARNING', 'formatter':'simple'},# 日志文件切割,默认保存10天日志'file':{ 'class':'logging.handlers.TimedRotatingFileHandler', 'filename':'loging.log', 'level':'INFO', 'when':'D', 'backupConut':10, 'formatter':'simple'},},# 定义日志器'loggers':{'consoleLogger':{'handlers':['console'],'level':'INFO',},'fileLogger':{'handlers':['console','file'],'level':'INFO',}}}def getFileLogger(log_file,hours=None,days=None,size=None):log_dir = os.path.dirname(log_file) # 获取目录名称if not os.path.exists(log_dir):os.makedirs(log_dir) # 创建目录if log_file:file_info = { 'class':'logging.handlers.TimedRotatingFileHandler', 'filename':log_file, 'level':'INFO', 'when':'D', 'backupCount':10, 'formatter':'simple'}if hours:file_info["when"] = "H"file_info["backupCount"] = hoursif days:file_info["when"] = "D"file_info["backupCount"] = daysif size:file_info['class'] = 'logging.handlers.RotatingFileHandler'file_info['maxBytes'] = 1024*1024*sizeconfig['handlers']['file'] = file_infologging.config.dictConfig(config=config)return logging.getLogger('fileLogger')def getConsoleLogger():logging.config.dictConfig(config=config)return logging.getLogger('fileLogger')if __name__ == '__main__':logger = getFileLogger('test.log',days=1)logger.info('test')

test.py

#!/bin/bash/python3 # -*- encoding: utf-8 -*-'''@File: 12.Logging模块调用写好的日出处理脚本.py@Time: 2023/07/01 17:43:51@Author: haohe'''from logutil import getFileLogger# 配置日志logger = getFileLogger('/root/Python_Study/Devops/Logging/logging.log',days=1)# 日志输出logger.debug('debug message')logger.info('info message')logger.warning('warn message')logger.error('error message')logger.critical('critical message')

疑问:为什么终端只输出了三条记录,但是文件中却写入了四条记录

答疑:

'handlers':{# 日志输出流'console':{ 'class':'logging.StreamHandler', 'level':'WARNING', 'formatter':'simple'},# 日志文件切割,默认保存10天日志'file':{ 'class':'logging.handlers.TimedRotatingFileHandler', 'filename':'loging.log', 'level':'INFO', 'when':'D', 'backupConut':10, 'formatter':'simple'}

在终端上只输出WARNING级别及其以上,而将INFO级别及其以上写入文本中