当前位置: 首页 > 后端技术 > Python

Python中更优雅的日志记录方案

时间:2023-03-26 19:02:37 Python

在Python中,一般情况下,我们可能会直接使用内置的logging模块来记录日志,包括我之前的时候。在使用的时候,我们需要配置一些Handlers和Formatters来进行一些处理,比如将日志输出到不同的位置,或者设置不同的输出格式,或者设置日志分块和备份等。但其实我个人觉得logging并没有那么好用。其实主要还是配置比较繁琐。常见的使用先来看看日志的常见解决方案。我通常将输出配置为文件、控制台和Elasticsearch。输出到控制台只是为了直接查看;输出到文件,方便所有历史记录的直接存储和备份;输出到Elasticsearch直接以Elasticsearch为中心存储分析,使用Kibana运行情况分析查看非常方便。所以这里我基本上会写如下日志包:importloggingimportsysfromosimportmakedirsfromos.pathimportdirname,existsfromcmreslogging.handlersimportCMRESHandlerloggers={}LOG_ENABLED=True#是否启用日志LOG_TO_CONSOLE=True#是否输出到LOG_TO_FILE=True#是否输出到文件LOG_TO_ES=True#是否输出到ElasticsearchLOG_PATH='./runtime.log'#日志文件路径LOG_LEVEL='DEBUG'#日志级别LOG_FORMAT='%(levelname)s-%(asctime)s-process:%(process)d-%(filename)s-%(name)s-%(lineno)d-%(module)s-%(message)s'#每种日志输出格式ELASTIC_SEARCH_HOST='eshost'#ElasticsearchHostELASTIC_SEARCH_PORT=9200#ElasticsearchPortELASTIC_SEARCH_INDEX='runtime'#ElasticsearchIndexNameAPP_ENVIRONMENT='dev'#运行环境,比如测试环境或者生产环境defget_logger(name=None):"""getloggerby名称:参数名称:记录器的名称:返回:记录器“”"globalloggersifnotname:name=__name__ifloggers.get(name):returnloggers.get(name)logger=logging.getLogger(name)logger.setLevel(LOG_LEVEL)#如果LOG_ENABLED和LOG_TO_CONSOLE输出到控制台:.setFormatter(formatter)logger.addHandler(stream_handler)#OutputtofileifLOG_ENABLEDandLOG_TO_FILE:#如果路径不存在,创建日志文件夹log_dir=dirname(log_path)ifnotexists(log_dir):makedirs(log_dir)#添加文件处理程序file_handler=logging.FileHandler(log_path,encoding='utf-8')file_handler.setLevel(level=LOG_LEVEL)formatter=logging.Formatter(LOG_FORMAT)file_handler.setFormatter(formatter)logger.addHandler(file_handler)#输出到ElasticsearchifLOG_ENABLEDandLOG_TO_ES:#添加CMRESHandleres_handler=CMRESHandler(hosts=[{'host':ELASTIC_SEARCH_HOST,'port':ELASTIC_SEARCH_PORT}],#可以配置对应的认证权限auth_type=CMRESHandler.AuthType.NO_AUTH,es_index_name=ELASTIC_SEARCH_INDEX,#每月一个Indexindex_name_frequency=CMRESHandler.IndexNameFrequency.MONTHLY,#添加额外的环境标识es_additional_fields={'environment':APP_ENVIRONMENT})es_handler.setLevel(level=LOG_LEVEL)formatter=logging.Formatter(LOG_FORMAT)es_handler.setFormatter(formatter)logger.addHandler(es_handler)#保存到全局记录器loggers[name]=loggerreturn定义后如何使用记录器?只需要使用定义的方法获取一个logger,然后记录相应的内容即可:logger=get_logger()logger.debug('thisisamessage')运行结果如下:DEBUG-2019-10-1122:27:35,923-进程:99490-logger.py-__main__-81-记录器-这是一条消息让我们看看定义的基本实现。首先,这里的一些常量用于定义日志模块的一些基本属性。比如LOG_ENABLED表示是否开启日志功能,LOG_TO_ES表示是否将日志输出到Elasticsearch,还有很多其他的基本日志配置,比如LOG_FORMAT配置了每条日志条目输出的基本格式,还有一些连接所需的信息。这些变量可以在运行时与命令行或环境变量连接,可以轻松实现一些开关和配置的替换。然后定义这样一个接收参数名的get_logger方法。首先,方法获取名称后,会在全局的loggers变量中查找。loggers变量是一个全局字典。如果有声明的logger,可以直接获取并返回,无需重新初始化。如果在loggers中没有找到name对应的logger,就创建一个。创建logger后,可以为其添加各种对应的Handler,如输出到控制台的StreamHandler,输出到文件的FileHandler或RotatingFileHandler,输出到Elasticsearch的CMRESHandler,并分别配置相应的信息。最后将新建的logger保存在全局loggers中并返回即可,这样如果有同名的log??ger可以直接找到loggers直接返回。这里我们依赖一个额外的输出到Elasticsearch的包,叫做CMRESHandler,它支持输出日志到Elasticsearch。如果你想使用它,你可以安装它:pipinstallCMRESHandler,它的GitHub地址是:https://github.com/cmanaha/py...,具体使用可以参考它的官方说明,比如配置认证信息,配置索引分离信息等。嗯,以上是我之前使用的日志记录配置。通过上面的配置,我可以将logging输出到三个位置,达到相应的效果。比如输出到Elasticsearch之后,我可以很方便的用Kibana查看当前的运行状态,ERRORLog的占比等等,我还可以在它的基础上做进一步的统计分析。loguru上面的实现已经是比较可行的配置方案了。不过还是觉得有些Handlers搭配起来比较麻烦,尤其是新建项目的时候懒得写一些配置。即使不使用上面的配置,也使用最基本的日志配置,比如下面的通用配置:importlogginglogging.basicConfig(level=logging.INFO,format='%(asctime)s-%(name)s-%(levelname)s-%(message)s')logger=logging.getLogger(__name__)懒得写了,感觉不是很优雅的实现方式。哪里有需求,哪里就有动力。这不,有人实现了这样一个叫loguru的库,可以让log的配置和使用更加简单方便。让我们来看看它是如何使用的。安装首先,这个库的安装方法很简单,使用基本的pip安装即可,Python3版本的安装如下:pip3installloguru安装完成后,我们就可以在项目中使用这个loguru库了。基本使用那么如何使用这个库呢?我们先用一个例子感受一下:fromloguruimportloggerlogger.debug('thisisadebugmessage')看吧,什么都不用配置,直接导入一个logger,然后调用它的debug方法即可。loguru中只有一个主要对象,就是logger。loguru中只有一个logger,并且已经预先配置了一些基本信息,比如友好的格式、文本颜色信息等等。上面代码运行结果如下:2019-10-1322:46:12.367|调试|__main__::4-这是一条debug消息可以看到默认输出格式就是上面的内容,有时间和级别,模块名,行号和日志信息,不需要手动创建logger,直接使用直接就可以了,输出还是彩色的,看起来比较友好。上面的日志信息是直接输出到控制台的,不会输出到其他地方。如果要输出到其他位置,比如保存为文件,我们只需要用一行代码声明即可。比如同时将结果输出到一个runtime.log文件,可以这样写:fromloguruimportloggerlogger.add('runtime.log')logger.debug('thisisadebug')很简单,我们不需要在声明了一个FileHandler之后,一行add语句就搞定了。运行后会发现,刚才控制台输出的DEBUG信息也出现在该目录下的runtime.log中。以上是一些基本的用途,但这还远远不够。让我们仔细看看它的一些功能模块。由于详细使用是日志,所以最常见的是输出到文件。loguru对输出到文件的配置支持非常强大,比如支持输出到多个文件,按级别输出,文件过大新建,文件过长自动删除等等。下面我们分别看看这些是如何实现的。这里基本上介绍了add方法的使用。因为这个add方法相当于给logger增加了一个Handler,它给我们暴露了很多参数来实现Handler的配置,下面详细介绍一下。先看它的方法定义:defadd(self,sink,*,level=_defaults.LOGURU_LEVEL,format=_defaults.LOGURU_FORMAT,filter=_defaults.LOGURU_FILTER,colorize=_defaults.LOGURU_COLORIZE,serialize=_defaults.LOGURU_SERIALIZE,backtrace_defaults.LOGURU_BACKTRACE,diagnostic=_defaults.LOGURU_DIAGNOSE,enqueue=_defaults.LOGURU_ENQUEUE,catch=_defaults.LOGURU_CATCH,**kwargs):看源码吧,它支持的参数非常多,比如level,format,filter,color等等。sink另外,我们还注意到它有一个非常重要的参数sink。我们看一下官方文档:https://loguru.readthedocs.io...,我们可以了解到通过sink我们可以传入各种不同的数据结构,总结如下:?sink可以传入一个文件对象,例如sys.stderr或open('file.log','w')。?sink可以直接传入一个str字符串或者pathlib.Path对象,其实就是代表文件路径。如果它识别出这种类型,它会自动创建一个对应路径的日志文件,并将日志输出到里面。?sink可以是一个方法,你可以定义你自己的输出实现。?sink可以是一个logging模块的Handler,比如FileHandler、StreamHandler等,也可以是我们上面提到的CMRESHandler,这样就可以实现自定义Handler的配置。?sink也可以是自定义类,具体实现规范可以参考官方文档。所以,刚才我们演示的outputtofile,只是传了一个str字符串路径给它,他给我们创建了一个日志文件,就是这个原理。format,filter,level我们来了解一下它的其他参数,比如format,filter,level等等。事实上,它们的概念和格式与日志记录模块基本相同。例如,这里使用format、filter和level来指定输出格式:logger.add('runtime.log',format="{time}{level}{message}",filter="my_module",level="INFO")删除sink和添加sink,我们也可以删除,相当于刷新写入新的内容。删除时,根据add方法返回的id删除即可,见如下示例:fromloguruimportloggertrace=logger.add('runtime.log')logger.debug('thisisadebugmessage')logger.remove(trace)logger.debug('thisisanotherdebugmessage')看这里,我们先添加一个sink,然后获取它的返回值,赋值为trace。然后输出一个log,然后trace变量传给remove方法,再次输出一个log,看看结果如何。控制台输出如下:2019-10-1323:18:26.469|调试|__main__::4-这是调试信息2019-10-1323:18:26.469|调试|__main__::6-这是另一条调试消息日志文件runtime.log的内容如下:2019-10-1323:18:26.469|调试|__main__::4-这是一条调试信息可以发现调用remove方法后,确实删除了历史日志。这样我们就可以实现日志的刷新和重写操作。旋转配置使用loguru。我们也可以很方便的使用旋转配置。比如我们想每天输出一个日志文件,或者文件太大无法自动分离日志文件,我们可以直接使用add方法的rotation参数来配置。我们看下面的例子:logger.add('runtime_{time}.log',rotation="500MB")通过这个配置,我们可以每隔500MB存储一个文件,如果每个日志文件都更新了太大创建一个日志文件。我们在配置日志名的时候加入了时间占位符,这样生成的时候可以自动替换时间,生成一个文件名包含时间的日志文件。另外,我们还可以使用rotation参数定时创建日志文件,例如:logger.add('runtime_{time}.log',rotation='00:00')这样就可以创建一个新的日志文件每天0:00输出。另外,我们还可以配置日志文件的循环时间,比如每隔一周创建一个日志文件,写法如下:logger.add('runtime_{time}.log',rotation='1week')所以我们可以在一周内创建一个日志文件一个日志文件已经创建。在保留配置的很多情况下,一些非常旧的日志对我们来说是无用的。它们白白占用了一些存储空间,不清除的话会很浪费。retention该参数可以配置日志的最长保留时间。比如我们要设置日志文件最多保留10天,我们可以这样配置:logger.add('runtime.log',retention='10days')这样最近10天'日志将保存在日志文件中。无需担心日志沉积问题。压缩配置loguru还可以配置文件的压缩格式,比如使用zip文件格式保存,例子如下:logger.add('runtime.log',compression='zip')这样可以节省更多的存储空间空间。字符串格式化loguru在输出日志的时候也提供了非常友好的字符串格式化功能,像这样:logger.info('如果你用的是Python{},当然首选{feature}!',3.6,feature='f-strings')所以添加参数非常方便。Traceback记录在很多情况下,如果我们遇到运行错误,如果不小心在打印日志的时候没有配置Traceback的输出,那么很有可能就无法追踪到错误。但是使用loguru之后,我们可以使用它提供的装饰器直接记录Traceback,类似这样的配置:@logger.catchdefmy_function(x,y,z):#Anerror?反正是被抓了!return1/(x+y+z)我们来做个测试。我们在调用的时候三个参数都传入了0,直接导致了除以0的错误。我们看看会发生什么:my_function(0,0,0)运行完成后,可以发现日志中出现了Traceback信息,并且输出了当时的变量值给我们,真是太神奇了!结果如下:>File"run.py",line15,inmy_function(0,0,0)└File"/private/var/py/logurutest/demo5.py",line13,inmy_functionreturn1/(x+y+z)││└0│└0└0ZeroDivisionError:divisionbyzero因此使用loguru实现日志跟踪非常方便,调试效率可能会高十倍?以上就是本次分享的全部内容。想了解更多python知识,请前往公众号:Python编程学习圈,发“J”免费领取,每日干货分享