python日志logging模块总结

it2023-09-01  68

系列文章:

这篇文章最先转载自Python 日志logging模块初探及多线程踩坑(1)但是觉得不够精简,只能改一下喽。

文章目录

1、日志是什么?2、日志怎么用?3、logging 模块的基本架构和对象功能4、打印到控制台5、利用logging.basicConfig()保存log到文件6、利用logging模块组件灵活实现需求7、实现logging中TimedRotatingFileHandler多线程应用

1、日志是什么?

日志可以记录程序运行过程中的一些突发情况,例如程序的debug、info、warning 、error、critial等信息,在必要时可以查看,就像行车记录仪

2、日志怎么用?

这里采用Python的logging模块讲解

先配置 就像把摄像头安装在车上,先配置一下。 logging_conf = { "version": 1, "disable_existing_loggers": False, "formatters": { "simple": { "format": "%(asctime)s %(levelname)s file:%(filename)s|lineno:%(lineno)d|logger:%(name)s - %(message)s" }, "standard": { "format": "%(asctime)s %(levelname)s threadId:%(thread)d|processId:%(process)d:|file:%(filename)s|lineno:%(lineno)d|func:%(funcName)s|module:%(module)s|logger:%(name)s - %(message)s" } }, "filters": { "debug_filter": { "()": "log_filters.DebugFilter" }, "info_filter": { "()": "log_filters.InfoFilter" }, "warning_filter": { "()": "log_filters.WarningFilter" }, "error_filter": { "()": "log_filters.ErrorFilter" }, "critical_filter": { "()": "log_filters.CriticalFilter" }, "no_debug_filter": { "()": "log_filters.NoDebugFilter" } }, "handlers": { "along_debug": { "class": "logging.StreamHandler", "level": "DEBUG", "formatter": "simple", "stream": "ext://sys.stdout", "filters": [ "debug_filter" ] }, "along_info": { "class": "logging.handlers.TimedRotatingFileHandlerMP", #采用handler的类型 "level": "INFO", "formatter": "standard", "filename": "/home/python/log/log_tool/logs/along/info.log", "when": "D", "encoding": "utf8", "filters": [ # 过滤什么信息 "info_filter" ] }, "along_warning": { 省略 }, # 和上面类似"level"和"filters"不同,有自己的 "along_error": { 省略 }, "along_critical": { 省略 }, }, ------------------------------------------------------ "loggers": { # 这里是自定义的loggers,会按照自己定义的方式保存日志到文件,输出自己定义的日志格式,就是真正实现个性化定制的一个logger. "along_logger": { "handlers": [ "along_debug", "along_info", "along_warning", "along_error", "along_critical" ], "level": "DEBUG", "propagate": false }, }, "root": {省略} }

上面的配置主要有三个关键的地方和一个注意点。 三个关键的地方:formatters文件的输出格式、filters筛选出保存的信息、handlers将信息保存到文件有关的配置。 注意: 其中必须包含一个名字叫做root的logger,当在应用程序中,使用无参函数logging.getLogger()时,默认返回root这个logger;自定义logger可以通过 logging.getLogger(“along_logger”) 方式进行调用。

#example.py import logging import logging.config def log_test(): logging.config.fileConfig(logging_conf); # 配置 logger = logging.getLogger("along_logger") # 按照along_logger初始化 logger.debug("debug信息") logger.error('error信息') print("------------强势分割-------------") logger = logging.getLogger() # 按照默认的root初始化 logger.debug("debug信息") logger.error('error信息') if __name__ == "__main__": log_test()

控制台输出如下:

2020-10-20 21:20:41.761 DEBUG file:example.py|lineno:7|logger:along_logger - debug信息 2020-10-20 21:20:41.761 ERROR file:example.py|lineno:8|logger:along_logger - error信息 ------------强势分割------------- error log

没骗你吧,确实是可以配置自己想要的样子,也可以采用默认方式。下面是抄的。。。

3、logging 模块的基本架构和对象功能

Logger:即 Logger Main Class,是我们进行日志记录时创建的对象,我们可以调用它的方法传入日志模板和信息,来生成一条条日志记录,称作 Log Record。 Log Record:就代指生成的一条条日志记录。 Handler:即用来处理日志记录的类,它可以将 Log Record 输出到我们指定的日志位置和存储形式等,如我们可以指定将日志通过 FTP 协议记录到远程的服务器上,Handler 就会帮我们完成这些事情。 Formatter:实际上生成的 Log Record 也是一个个对象,那么我们想要把它们保存成一条条我们想要的日志文本的话,就需要有一个格式化的过程,那么这个过程就由 Formatter 来完成,返回的就是日志字符串,然后传回给 Handler 来处理。 Filter:另外保存日志的时候我们可能不需要全部保存,我们可能只需要保存我们想要的部分就可以了,所以保存前还需要进行一下过滤,留下我们想要的日志,如只保存某个级别的日志,或只保存包含某个关键字的日志等,那么这个过滤过程就交给 Filter 来完成。 Parent Handler:Handler 之间可以存在分层关系,以使得不同 Handler 之间共享相同功能的代码。 以上就是整个 logging 模块的基本架构和对象功能,了解了之后我们详细来了解一下 logging 模块的用法。

4、打印到控制台

import logging logging.debug('debug 信息') logging.warning('只有这个会输出。。。') logging.info('info 信息')

由于默认设置的等级是warning,所有只有warning的信息会输出到控制台。不推荐使用


利用logging.basicConfig()打印信息到控制台

import logging logging.basicConfig(format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s', level=logging.DEBUG) logging.debug('debug 信息') logging.info('info 信息') logging.warning('warning 信息') logging.error('error 信息') logging.critical('critial 信息')

由于在logging.basicConfig()中的level 的值设置为logging.DEBUG, 所有日志级别高于等于debug的log才能打印到控制台。

日志级别: debug < info < warning < error < critical

5、利用logging.basicConfig()保存log到文件

logging.basicConfig(level=logging.DEBUG, filename='new.log', filemode='a',##模式,有w和a,w就是写模式,每次都会重新写日志,覆盖之前的日志 #a是追加模式,默认如果不写的话,就是追加模式 format= '%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s' #日志格式 )

注意的是:一旦在logging.basicConfig()设置filename 和filemode,则只会保存log到文件,不会输出到控制台。!!! 2019/5/29 补充:

但当某些信息较长,会导致日志输出信息不够美观 我们可以对其的固定长度等进行设置:

%%(asctime)s %%(name)s %%(levelname)-8s %%(filename)-25s %%(lineno)-4s %%(message)s

-8 的意思是左对齐,固定长度8 ,默认用空格填充

6、利用logging模块组件灵活实现需求

咳咳,当正题了~~,推荐使用Logging组件: 记录器(Logger )、处理器(Handler)、过滤器(Filter)和格式化器(Formatter )。

简单介绍常用的配置一下:

Logger

Logger.setLevel()指定logger将会处理的最低的安全等级日志信息,。 Logger.addHandler()和Logger.removeHandler()从记录器对象中添加和删除处理程序对象。处理器详见Handlers。 Logger.addFilter()和Logger.removeFilter()从记录器对象添加和删除过滤器对象。

Handler

logging.StreamHandler -> 控制台输出

logging.FileHandler -> 文件输出

logging.handlers.RotatingFileHandler -> 按照大小自动分割日志文件,一旦达到指定的大小重新生成文件

logging.handlers.TimedRotatingFileHandler -> 按照时间自动分割日志文件

Formatters

Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S,下面是Formatter常用的一些信息

%(name)sLogger的名字%(levelno)s数字形式的日志级别%(levelname)s文本形式的日志级别%(pathname)s调用日志输出函数的模块的完整路径名,可能没有%(filename)s调用日志输出函数的模块的文件名%(module)s用日志输出函数的模块名%(funcName)s调用日志输出函数的函数名%(lineno)d调用日志输出函数的语句所在的代码行%(created)f当前时间,用UNIX标准的表示时间的浮 点数表示%(relativeCreated)d输出日志信息时的,自Logger创建以 来的毫秒数%(asctime)s字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒%(thread)d线程ID。可能没有%(threadName)s线程名。可能没有%(process)d进程ID。可能没有%(message)s用户输出的消息

来一个例子,大家就明白了

import logging from logging import handlers filename = os.path.join(dirpath+"log") fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s') log_format = logging.Formatter(fmt) #设置日志格式 sh = logging.StreamHandler() #往屏幕上输出 sh.setFormatter(fmt) #设置屏幕上显示的格式 fh = logging.handlers.TimedRotatingFileHandler(filename=filename , interval=1, when='M', backupCount=2, delay=True) #保存日志到文件 fh.suffix = "%Y-%m-%d_%H-%M" fh.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$") fh.setFormatter(fmt) # log logger = logging.getLogger(__name__) logger.setLevel("DEBUG") #设置日志等级 logger.addHandler(sh) logger.addHandler(fh)

TimedRotatingFileHandler 是以时间分割

filename 是输出日志文件名的前缀,比如log/myapp.log when 是一个字符串的定义如下: “S”: Seconds “M”: Minutes “H”: Hours “D”: Days “W”: Week day (0=Monday) “midnight”: Roll over at midnight

interval 是指等待多少个单位when的时间后,Logger会自动重建文件,当然,这个文件的创建取决于filename+suffix,若这个文件跟之前的文件有重名,则会自动覆盖掉以前的文件,所以有些情况suffix要定义的不能因为when而重复。backupCount 是保留日志个数。默认的0是不会自动删除掉日志。若设2,则在文件的创建过程中库会判断是否有超过这个2,若超过,则会从最先创建的开始删除。

extMatch会根据when的值进行初始化: ‘S’: suffix=”%Y-%m-%d_%H-%M-%S”, extMatch=r”^d{4}-\d{2}-\d{2}\d{2}-\d{2}-\d{2}”; ‘M’:suffix=”%Y-%m-%d%H-%M”,extMatch=r”^\d{4}-\d{2}-\d{2}\d{2}-\d{2}”; ‘H’:suffix=”%Y-%m-%d%H”,extMatch=r”^\d{4}-\d{2}-\d{2}_\d{2}”; ‘D’:suffxi=”%Y-%m-%d”,extMatch=r”^\d{4}-\d{2}-\d{2}”; ‘MIDNIGHT’:”%Y-%m-%d”,extMatch=r”^\d{4}-\d{2}-\d{2}”; ‘W’:”%Y-%m-%d”,extMatch=r”^\d{4}-\d{2}-\d{2}”;

这边如果delay=False,表面直接输入文件,不入缓存。会导致‘win32Error : 文件被占用的错误’.这是由于TimedRotatingFileHandler并不是线程安全的。

而且运行发现,每个进程都保持一个固定的文件句柄,导致在达到条件回滚时,相互之间的rename会相互干扰,还有好多问题

logging中RotatingFileHandler和TimedRotatingFileHandler对于多进程不支持。

7、实现logging中TimedRotatingFileHandler多线程应用

1、修改源码,但在实际开发过程,特别麻烦。

2、引用第三方库ConcurrentLogHandler,但它只支持文件大小分割日志,但按时间分割日志,是比较常见的。

3、继承父类,重写方法。简单易用。下面具体讲解这部分:

网上重写的方法都多多少少有点问题。github上有现成开源的,写得非常完善,经得起实战。但也有缺点(性能较低) https://github.com/kieslee/mlogging

不过要注意的是,作者虽然说它只支持linux,但windows也可以,不过要做一点修改:

下面是源码:继承重写了logging的StreamHandler:

class StreamHandler_MP(StreamHandler): pass class FileHandler_MP(FileHandler, StreamHandler_MP): pass class RotatingFileHandler_MP(RotatingFileHandler, FileHandler_MP): """ Handler for logging to a set of files, which switches from one file to the next when the current file reaches a certain size. Based on logging.RotatingFileHandler, modified for Multiprocess """ _lock_dir = '.lock' if os.path.exists(_lock_dir): pass else: os.mkdir(_lock_dir) # 删了一部分代码 def emit(self, record): # 关键代码,文件写的过程加锁 try: if self.shouldRollover(record): self.doRollover() FileLock = self._lock_dir + '/' + os.path.basename(self.baseFilename) + '.' + record.levelname f = open(FileLock, "w+") fcntl.flock(f.fileno(), fcntl.LOCK_EX) # 加锁 FileHandler_MP.emit(self, record) fcntl.flock(f.fileno(), fcntl.LOCK_UN) # 开锁 f.close() except (KeyboardInterrupt, SystemExit): raise except: self.handleError(record) class TimedRotatingFileHandler_MP(TimedRotatingFileHandler, FileHandler_MP): pass

关键是这部分代码 fcntl.flock(f.fileno(), fcntl.LOCK_EX) 意思是给文件加锁 fcntl.flock(f.fileno(), fcntl.LOCK_UN)意思是给文件解锁

最新回复(0)