|
Python日志记录:6大日志记录库的比较文章目录Python日志记录:6大日志记录库的比较前言一些日志框架建议1.logging-内置的标准日志模块默认日志记录器自定义日志记录器生成结构化日志2.Loguru-最流行的Python第三方日志框架默认日志记录器自定义日志记录器3.Structlog4.Eliot5.Logbook6.Picologging最后的想法前言日志记录框架是一种工具,可帮助您标准化应用程序中的日志记录过程。虽然某些编程语言提供内置日志记录模块作为其标准库的一部分,但大多数日志记录框架都是第三方库,例如logging(Python)、Log4j(Java)、Zerolog(Go)或Winston(Node.js)。有时,组织会选择开发自定义日志记录解决方案,但这通常仅限于具有高度专业化需求的大型公司。虽然Python在其标准库中提供了强大且功能丰富的日志记录解决方案,但第三方日志记录生态系统也提供了一系列引人注目的替代方案。根据您的需求,这些外部库可能更适合您的日志记录需求。因此,本文将介绍Python用于跟踪应用程序和库行为的六大日志解决方案。我们将首先讨论标准logging模块,然后研究Python社区创建的其他五个loggingframeworks。一些日志框架建议Pino(Node.js)Zerolog,Zap,orSlog(Go)Monolog(PHP)SLF4JwithLog4J2orLogback(Java)Loguru(Python)SemanticLogger(Ruby)1.logging-内置的标准日志模块默认日志记录器与大多数编程语言不同,Python在其标准库中包含了一个功能齐全的日志框架。该日志记录解决方案有效地满足了库和应用程序开发人员的需求,并包含了以下严重性级别:DEBUG、INFO、WARNING、ERROR和CRITICAL。有了默认日志记录器,无需任何前期设置,您就可以立即开始记录日志。importlogginglogging.debug("Adebugmessage")logging.info("Aninfomessage")logging.warning("Awarningmessage")logging.error("Anerrormessage")logging.critical("Acriticalmessage")1234567'运行运行此default(或root)记录器在该WARNING级别运行,这意味着只有严重性等于或超过的记录调用WARNING才会产生输出:WARNING:root:AwarningmessageERROR:root:AnerrormessageCRITICAL:root:Acriticalmessage123自定义日志记录器这种配置可确保只显示潜在的重要信息,减少日志输出中的噪音。不过,您也可以根据需要自定义日志级别并微调日志记录行为。使用logging模块的推荐方法是通过getLogger()方法创建自定义日志记录器:importlogginglogger=logging.getLogger(__name__)123'运行运行一旦有了自定义记录器,您就可以通过logging模块提供的Handler(处理程序)、Formatter(格式化器)和Filter(过滤器)类来自定义其输出。Handlers决定输出目的地,并可根据日志级别进行定制。一个日志记录器还可添加多个Handlers,以便同时向不同目的地发送日志信息。Formatters决定了日志记录器产生的记录的格式。然而,目前还没有JSON、Logfmt等预定义格式。您必须结合可用的日志记录属性来构建自己的格式。root日志记录器的默认格式为%(levelname)s:%(name)s:%(message)s。然而,自定义日志记录器默认为只有%(message)s。Filters由handler和loggerobjects使用,用于过滤日志记录。与日志级别相比,Filters能更好地控制哪些日志记录应被处理或忽略。在日志被发送到最终目的地之前,它们还能以某种方式增强或修改记录。例如,您可以创建一个自定义过滤器,以删除日志中的敏感数据。下面是一个使用自定义日志记录器将日志记录到控制台和文件的示例:importsysimportlogginglogger=logging.getLogger("example")logger.setLevel(logging.DEBUG)#CreatehandlersforloggingtothestandardoutputandafilestdoutHandler=logging.StreamHandler(stream=sys.stdout)errHandler=logging.FileHandler("error.log")#SettheloglevelsonthehandlersstdoutHandler.setLevel(logging.DEBUG)errHandler.setLevel(logging.ERROR)#CreatealogformatusingLogRecordattributesfmt=logging.Formatter("%(name)s:%(asctime)s|%(levelname)s|%(filename)s:%(lineno)s|%(process)d>>>%(message)s")#SetthelogformatoneachhandlerstdoutHandler.setFormatter(fmt)errHandler.setFormatter(fmt)#AddeachhandlertotheLoggerobjectlogger.addHandler(stdoutHandler)logger.addHandler(errHandler)logger.info("Serverstartedlisteningonport8080")logger.warning("Diskspaceondrive'/var/log'isrunninglow.Considerfreeingupspace")try:raiseException("Failedtoconnecttodatabase:'my_db'")exceptExceptionase:#exc_info=TrueensuresthataTracebackisincludedlogger.error(e,exc_info=True)12345678910111213141516171819202122232425262728293031323334353637'运行运行执行上述程序时,控制台会如期打印出以下日志信息:example:2023-07-2314:42:18,599|INFO|main.py:30|187901>>>Serverstartedlisteningonport8080example:2023-07-2314:14:47,578|WARNING|main.py:28|143936>>>Diskspaceondrive'/var/log'isrunninglow.Considerfreeingupspaceexample:2023-07-2314:14:47,578|ERROR|main.py:34|143936>>>Failedtoconnecttodatabase:'my_db'Traceback(mostrecentcalllast):File"/home/ayo/dev/betterstack/demo/python-logging/main.py",line32,inraiseException("Failedtoconnecttodatabase:'my_db'")Exception:Failedtoconnecttodatabase:'my_db'12345678910同时还创建了error.log文件,该文件应仅包含ERROR日志,因为errHandler的最小级别已设置为ERROR:example:2023-07-2314:14:47,578|ERROR|main.py:34|143936>>>Failedtoconnecttodatabase:'my_db'Traceback(mostrecentcalllast):File"/home/ayo/dev/betterstack/demo/python-logging/main.py",line32,inraiseException("Failedtoconnecttodatabase:'my_db'")Exception:Failedtoconnecttodatabase:'my_db'12345生成结构化日志在撰写本文时,除非执行一些附加代码,否则logging模块无法生成结构化日志。值得庆幸的是,有一种更简单、更好的方法可以获得结构化输出:python-json-logger库。$pipinstallpython-json-logger1安装后,您可以按以下方式使用它:importsysimportloggingfrompythonjsonloggerimportjsonlogger...#ThedesiredLogRecordattributesmustbeincludedhere,andtheycanbe#renamedifnecessaryfmt=jsonlogger.JsonFormatter("%(name)s%(asctime)s%(levelname)s%(filename)s%(lineno)s%(process)d%(message)s",rename_fields={"levelname":"severity","asctime":"timestamp"},)#SetthelogformatoneachhandlerstdoutHandler.setFormatter(fmt)errHandler.setFormatter(fmt)...123456789101112131415161718如果用上面突出显示的几行修改前面的示例,执行时将观察到以下输出:{"name":"example","filename":"main.py","lineno":31,"process":179775,"message":"Serverstartedlisteningonport8080","severity":"INFO","timestamp":"2023-07-2314:39:03,265"}{"name":"example","filename":"main.py","lineno":32,"process":179775,"message":"Diskspaceondrive'/var/log'isrunninglow.Considerfreeingupspace","severity":"WARNING","timestamp":"2023-07-2314:39:03,265"}{"name":"example","filename":"main.py","lineno":38,"process":179775,"message":"Failedtoconnecttodatabase:'my_db'","exc_info":"Traceback(mostrecentcalllast):\nFile\"/home/ayo/dev/betterstack/demo/python-logging/main.py\",line36,in\nraiseException(\"Failedtoconnecttodatabase:'my_db'\")\nException:Failedtoconnecttodatabase:'my_db'","severity":"ERROR","timestamp":"2023-07-2314:39:03,265"}123还可以通过level方法上的extra属性在logpoint添加上下文数据,如下所示:logger.info("Serverstartedlisteningonport8080",extra={"python_version":3.10,"os":"linux","host":"fedora38"},)1234{"name":"example","filename":"main.py","lineno":31,"process":195301,"message":"Serverstartedlisteningonport8080","python_version":3.1,"os":"linux","host":"fedora38","severity":"INFO","timestamp":"2023-07-2314:45:42,472"}1正如您所看到的,built-in的loggingmodule能够满足各种日志记录需求,并且具有可扩展性。不过,它的初始配置和自定义可能比较麻烦,因为在开始有效记录之前,你必须创建和配置loggers、handlers和formatters。请参阅我们的Python日志指南和官方文档,进一步了解日志模块的功能和最佳实践。2.Loguru-最流行的Python第三方日志框架Loguru是最流行的Python第三方日志框架,在撰写本文时已在GitHub上获得超过15k颗星。它旨在通过预配置日志记录器来简化日志记录过程,并通过其add()方法使自定义日志记录器变得非常容易。默认日志记录器使用Loguru启动日志记录非常简单,只需安装软件包并导入,然后调用其级别方法即可,如下所示:$pipinstallloguru1fromloguruimportloggerlogger.trace("Executingprogram")logger.debug("Processingdata...")logger.info("Serverstartedsuccessfully.")logger.success("Dataprocessingcompletedsuccessfully.")logger.warning("Invalidconfigurationdetected.")logger.error("Failedtoconnecttothedatabase.")logger.critical("Unexpectedsystemerroroccurred.Shuttingdown.")123456789默认配置将半结构化和彩色化的输出记录到标准错误。它还默认将DEBUG作为最低级别,这也解释了为什么TRACE输出不会被记录。自定义日志记录器通过add()函数,可以轻松定制Loguru的内部工作机制,该函数可处理从日志格式化到日志目的地设置等一切操作。例如,您可以将日志记录到标准输出,将默认级别更改为INFO,并使用下面的配置将日志格式化为JSON:fromloguruimportloggerimportsyslogger.remove(0)#removethedefaulthandlerconfigurationlogger.add(sys.stdout,level="INFO",serialize=True)...1234567{"text":"2023-07-1715:26:21.597|INFO|__main__::9-Serverstartedsuccessfully.\n","record":{"elapsed":{"repr":"0:00:00.006401","seconds":0.006401},"exception":null,"extra":{},"file":{"name":"main.py","path":"/home/ayo/dev/betterstack/demo/python-logging/main.py"},"function":"","level":{"icon":"ℹ️","name":"INFO","no":20},"line":9,"message":"Serverstartedsuccessfully.","module":"main","name":"__main__","process":{"id":3852028,"name":"MainProcess"},"thread":{"id":140653618894656,"name":"MainThread"},"time":{"repr":"2023-07-1715:26:21.597156+02:00","timestamp":1689600381.597156}}}1Loguru生成的默认JSON输出可能相当冗长,但使用类似这样的自定义函数可以轻松地将日志信息序列化:fromloguruimportloggerimportsysimportjsondefserialize(record):subset={"timestamp":record["time"].timestamp(),"message":record["message"],"level":record["level"].name,"file":record["file"].name,"context":record["extra"],}returnjson.dumps(subset)defpatching(record):record["extra"]["serialized"]=serialize(record)logger.remove(0)logger=logger.patch(patching)logger.add(sys.stderr,format="{extra[serialized]}")logger.bind(user_id="USR-1243",doc_id="DOC-2348").debug("Processingdocument")1234567891011121314151617181920212223{"timestamp":1689601339.628792,"message":"Processingdocument","level":"DEBUG","file":"main.py","context":{"user_id":"USR-1243","doc_id":"DOC-2348"}}1Loguru也完全支持上下文日志记录。您已经看到上面的bind()方法,它允许在logpoint添加上下文数据。您还可以使用bind()方法来创建子记录器来记录共享相同上下文的记录:child=logger.bind(user_id="USR-1243",doc_id="DOC-2348")child.debug("Processingdocument")child.warning("Invalidconfigurationdetected.Fallingbacktodefaults")child.success("Documentprocessedsuccessfully")1234请注意,user_id和doc_id字段出现在所有三条记录中:{"timestamp":1689601518.884659,"message":"Processingdocument","level":"DEBUG","file":"main.py","context":{"user_id":"USR-1243","doc_id":"DOC-2348"}}{"timestamp":1689601518.884706,"message":"Invalidconfigurationdetected.Fallingbacktodefaults","level":"WARNING","file":"main.py","context":{"user_id":"USR-1243","doc_id":"DOC-2348"}}{"timestamp":1689601518.884729,"message":"Documentprocessedsuccessfully","level":"SUCCESS","file":"main.py","context":{"user_id":"USR-1243","doc_id":"DOC-2348"}}12345另一方面,它的contextualize()方法可以轻松地为特定范围或上下文中的所有日志记录添加上下文字段。例如,下面的代码段演示了将唯一的请求ID属性添加到因该请求而创建的所有日志中:fromloguruimportloggerimportuuiddeflogging_middleware(get_response):defmiddleware(request):request_id=str(uuid.uuid4())withlogger.contextualize(request_id=request_id):response=get_response(request)response["X-Request-ID"]=request_idreturnresponsereturnmiddleware12345678910111213Loguru还支持优秀日志框架所应具备的所有功能,如通过自动旋转和压缩将日志记录到文件、自定义日志级别、异常处理、同时记录到多个目的地等。它还为来自标准logging模块的用户提供了迁移指南。请参阅Loguru官方文档和我们专用Loguru指南,了解有关使用Loguru为Python应用程序创建production-ready日志设置的更多信息。3.StructlogStructlog是一个日志库,专门用于生成JSON或Logfmt格式的结构化输出。它支持为开发环境提供彩色、美观的控制台输出,也允许完全自定义日志格式,以满足不同需求。你可以使用下面的命令安装Structlog软件包:$pipinstallstructlog1Structlog最简单的用法是调用get_logger()方法,然后在生成的logger上使用任何level方法:importstructloglogger=structlog.get_logger()logger.debug("Databasequeryexecutedin0.025seconds")logger.info("Processingfile'data.csv'completed.1000recordswereimported",file="data.csv",elapsed_ms=300,num_records=1000,)logger.warning("Unabletoloadconfigurationfile'config.ini'.Usingdefaultsettingsinstead",file="config.ini",)try:1/0exceptZeroDivisionErrorase:logger.exception("Divisionbyzeroerroroccurredduringcalculation.Checktheinputvalues",exc_info=e,)logger.critical("Applicationcrashedduetoanunhandledexception")123456789101112131415161718192021222324Structlog日志记录器的默认配置对开发环境非常友好。输出是彩色的,任何包含的上下文数据都以key=value对的形式出现。此外,tracebacks的格式和organized都很整齐,因此更容易发现问题的原因。Structlog的独特之处在于,它不会按levels去过滤记录。这就是为什么上面所有的levels都被写入控制台的原因。不过,通过configure()方法配置默认级别也很简单,如下所示:importstructlogimportloggingstructlog.configure(wrapper_class=structlog.make_filtering_bound_logger(logging.INFO))1234Structlog与标准logging模块中的日志级别兼容,因此可以使用上述logging.INFO常量。你也可以直接使用与级别相关的数字:structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(20))1get_logger()函数返回的日志记录器称为绑定日志记录器,因为您可以将上下文值与之绑定。一旦绑定了key/valuepairs,它们将包含在日志记录器生成的每个后续日志条目中。importstructlogimportplatformlogger=structlog.get_logger()logger=logger.bind(python_version=platform.python_version(),os="linux")...123456782023-07-2317:20:10[debug]Databasequeryexecutedin0.025secondsos=linuxpython_version=3.11.42023-07-2317:20:10[info]Processingfile'data.csv'completed.1000recordswereimportedelapsed_ms=300file=data.csvnum_records=1000os=linuxpython_version=3.11.4123绑定日志记录器还包括一系列处理器函数,可在日志记录通过日志记录管道时对日志记录进行转换和丰富。例如,您可以使用以下配置以JSON格式记录日志:importstructlogimportplatformstructlog.configure(processors=[structlog.processors.TimeStamper(fmt="iso"),structlog.processors.add_log_level,structlog.processors.JSONRenderer(),])...123456789101112每个处理器都按照声明顺序执行,因此首先调用TimeStamper()为每个条目添加ISO-8601格式的时间戳,然后通过add_log_level添加严重级别,最后调用JSONRenderer()将整个记录序列化为JSON格式。对程序进行高亮显示的修改后,您将看到以下输出:{"python_version":"3.11.4","os":"linux","event":"Databasequeryexecutedin0.025seconds","timestamp":"2023-07-23T15:32:21.590688Z","level":"debug"}{"python_version":"3.11.4","os":"linux","file":"data.csv","elapsed_ms":300,"num_records":1000,"event":"Processingfile'data.csv'completed.1000recordswereimported","timestamp":"2023-07-23T15:32:21.590720Z","level":"info"}123Structlog能做的另一件很酷的事情是自动格式化tracebacks,使其也以JSON格式序列化。你只需要像这样使用dict_tracebacks处理器:structlog.configure(processors=[structlog.processors.TimeStamper(fmt="iso"),structlog.processors.add_log_level,structlog.processors.dict_tracebacks,structlog.processors.JSONRenderer(),])12345678每当记录异常情况时,你会发现记录中的异常情况信息格式非常丰富,便于在日志管理服务中进行分析。{"python_version":"3.11.4","os":"linux","event":"Divisionbyzeroerroroccurredduringcalculation.Checktheinputvalues","timestamp":"2023-07-23T16:07:50.127241Z","level":"error","exception":[{"exc_type":"ZeroDivisionError","exc_value":"divisionbyzero","syntax_error":null,"is_cause":false,"frames":[{"filename":"/home/ayo/dev/betterstack/demo/python-logging/main.py","lineno":32,"name":"","line":"","locals":{"__name__":"__main__","__doc__":"None","__package__":"None","__loader__":"","__spec__":"None","__annotations__":"{}","__builtins__":"","__file__":"/home/ayo/dev/betterstack/demo/python-logging/main.py","__cached__":"None","structlog":"\"","logging":"","logger":"\"1Eliot的内容远不止于此,因此请务必查看其文档以了解更多信息。5.LogbookLogbook自称是Python标准库logging模块的酷炫替代品,其目的是让日志记录变得有趣。你可以使用以下命令将其安装到你的项目中:$pipinstalllogbook1开始使用Logbook也非常简单:importsysimportlogbooklogger=logbook.Logger(__name__)handler=logbook.StreamHandler(sys.stdout,level="INFO")handler.push_application()logger.info("Successfullyconnectedtothedatabase'my_db'onhost'ubuntu'")logger.warning("DetectedsuspiciousactivityfromIPaddress:111.222.333.444")1234567891011[2023-07-2421:41:50.932575]INFO:__main__:Successfullyconnectedtothedatabase'my_db'onhost'ubuntu'[2023-07-2421:41:50.932623]WARNING:__main__etectedsuspiciousactivityfromIPaddress:111.222.333.444123如上图所示,logbook.Logger方法用于创建一个新的loggerchannel。该loggerprovides提供了对info()和warning()等级别方法的访问,用于写入日志信息。支持logging模块中的所有日志levels,并增加了介于INFO和WARNING之间的NOTICE级别。Logbook还使用Handler概念来确定日志的目的地和格式。StreamHandler类可将日志发送到任何输出流(本例中为标准output),其他处理程序可将日志发送到文件、Syslog、Redis、Slack等。不过,与标准logging模块不同的是,我们不鼓励你直接在日志记录器上注册handlers。相反,你应该分别通过push_application()、push_thread()和push_greenlet()方法将处理程序绑定到process、thread或greenletstack中。相应的pop_application()、pop_thread()和pop_greenlet()方法用于取消处理程序的注册:handler=MyHandler()handler.push_application()#everythingloggedhereheregoestothathandlerhandler.pop_application()1234您还可以在with-block的持续时间内绑定一个handler。这样可以确保在块内创建的日志只发送给指定的handler:withhandler.applicationbound():logger.info(...)withhandler.threadbound():logger.info(...)withhandler.greenletbound():logger.info(...)12345678日志格式化也是通过handlers完成的。为此,每个处理程序都有一个format_string属性,它接受LogRecord类的属性:importsysimportlogbooklogger=logbook.Logger(__name__)handler=logbook.StreamHandler(sys.stdout,level="INFO")handler.format_string="{record.channel}|{record.level_name}|{record.message}"handler.push_application()logger.info("Successfullyconnectedtothedatabase'my_db'onhost'ubuntu'")logger.warning("DetectedsuspiciousactivityfromIPaddress:111.222.333.444")123456789101112__main__|INFO|Successfullyconnectedtothedatabase'my_db'onhost'ubuntu'__main__|WARNING|DetectedsuspiciousactivityfromIPaddress:111.222.333.44412遗憾的是,Logbook的任何内置处理程序都不支持结构化日志记录。您必须通过自定义处理程序自行实现。有关详细信息,请参阅Logbook文档。6.PicologgingMirosoft的Picologging库是Python日志生态系统的一个相对较新的补充。如其GitHubReadme所述,它被定位为标准日志模块的高性能直接替代品,速度可显著提高4-10倍。要将其集成到你的项目中,你可以用以下命令安装它:$pipinstallpicologging1Picologging与Python中的logging模块共享相同的熟悉的API,并且使用相同的日志记录属性进行格式化:importsysimportpicologgingaslogginglogger=logging.getLogger(__name__)logger.setLevel(logging.INFO)stdout_handler=logging.StreamHandler(sys.stdout)fmt=logging.Formatter("%(name)s:%(asctime)s|%(levelname)s|%(process)d>>>%(message)s")stdout_handler.setFormatter(fmt)logger.addHandler(stdout_handler)logger.info("Successfullyconnectedtothedatabase'%s'onhost'%s'","my_db","ubuntu20.04")logger.warning("DetectedsuspiciousactivityfromIPaddress:%s","111.222.333.444")12345678910111213141516171819__main__:2023-07-2405:46:38,-2046715687|INFO|795975>>>Successfullyconnectedtothedatabase'my_db'onhost'ubuntu20.04'__main__:2023-07-2405:46:38,-2046715687|WARNING|795975>>>DetectedsuspiciousactivityfromIPaddress:111.222.333.44412Picologging的文档强调,它目前还处于早期开发阶段,因此应暂缓在生产中使用。不过,根据这些benchmarks,Picologging在提高标准日志模块的性能方面已经显示出了一定的前景。有关其功能和限制的更多信息,请参阅文档。最后的想法在Python中使用日志记录时,我们的主要推荐是使用Loguru,因为它具有令人印象深刻的功能和用户友好的API。不过,熟悉built-inlogging模块也很重要,因为它仍然是一个功能强大、使用广泛的解决方案。Structlog是另一个值得考虑的强大选项,Eliot也可能是一个不错的选择,只要它缺乏日志级别并不是您的用例的主要问题。另一方面,Picologging目前还处于早期开发阶段,而Logbook缺乏对结构化日志记录的原生支持,因此不太适合在生产环境中进行日志记录。
|
|