者:东东 yasking
来源:https://blog.yasking.org/a/python-logbook.html
Python 本身有logging日志记录模块,之前发现了logbook这个包,介绍说是替代logging,索性整理一下,方便之后使用
>>> from logbook import Logger, StreamHandler
>>> import sys
>>> StreamHandler(sys.stdout).push_application
>>> log=Logger('Logbook')
>>> log.info('Hello, World!')
[2015-10-05 18:55:56.937141] INFO: Logbook: Hello, World!
上边这是文档中给出的例子,它定义了许多的Handler,可以把日志记录到标准输出,文件,E-MAIL,甚至Twitter
使用 StreamHandler
记录的日志会以流输出,这里指定sys.stdout
也就是记录到标准输出,与print
一样
(一)可以使用 with
来在一定作用域内记录日志
# -*- coding: utf-8 -*-
from logbook import Logger, StreamHandler
import logbook
import sys
handler=StreamHandler(sys.stdout)
log=Logger('test')
def main:
log.info('something logging')
if __name__=='__main__':
with handler.applicationbound:
main
(二)也可以指定作用于整个应用
# -*- coding: utf-8 -*-
from logbook import Logger, StreamHandler
import logbook
import sys
handler=StreamHandler(sys.stdout)
handler.push_application
log=Logger('test')
def main:
log.info('something logging')
if __name__=='__main__':
main
使用 FileHandler
可以把日志记录到文件,这也是最常见的方式
# -*- coding: utf-8 -*-
from logbook import Logger, FileHandler
import logbook
import sys
handler=FileHandler('app.log')
handler.push_application
log=Logger('test')
def main:
log.info('something logging')
if __name__=='__main__':
main
日志就写到了 app.log
文件
同时把记录输出到多个地方可以方便查阅和记录,初始化 Handler
的时候设置bubble
参数就可以使得其它Handler
也可以接收到记录
from logbook import Logger, StreamHandler, FileHandler
import logbook
import sys
'''
记录日志到文件和STDOUT
'''
StreamHandler(sys.stdout, level='DEBUG').push_application
FileHandler('app.log', bubble=True, level='INFO').push_application
log=Logger('test')
def main:
log.info('hello world')
if __name__=='__main__':
main
另外,通过 level
可以设置日志级别,级别如下,从下到上级别越来越高,如level
设置为INFO
, 则除了DEBUG
外都会记录,设置不同的级别,搭配各种Handler
可以让日志的记录更加灵活,上边使用的log.info
可以使用不同的记录级别
级别 | 说明 |
---|---|
critical | 严重错误,需要退出程序 |
error | 错误,但在可控范围内 |
warning | 警告 |
notice | 大多数情况下希望看到的记录 |
info | 大多数情况不希望看到的记录 |
debug | 调试程序的时候详细输出的记录 |
和日志文件同样重要的就是 MailHandler
了,当出现了比较严重错误的时候就要发送邮寄进行通知
详细的文档见:
分别使用了 163
和qq
邮箱发送邮件测试,使用的邮箱需要开启SMTP权限,代码如下(163和qq发送参数稍有不同)
163 Mail
# -*- coding: utf-8 -*-
from logbook import Logger, MailHandler
import logbook
import sys
sender='Logger'
recipients=['dongdong@qq.com']
email_user='dongdong@163.com'
email_pass='password'
mail_handler=MailHandler(sender, recipients,
server_addr='smtp.163.com',
starttls=True,
secure=False,
credentials=(email_user, email_pass),
format_string=u'''
Subject: {record.level_name} on My Application
Message type: {record.level_name}
Location: {record.filename}:{record.lineno}
Module: {record.module}
Function: {record.func_name}
Time: {record.time:%Y-%m-%d %H:%M:%S}
Remote IP: {record.extra[ip]}
Request: {record.extra[url]} [{record.extra[method]}]
Message: {record.message}
''',
bubble=True)
log=Logger('test')
def main:
log.info('something logging')
if __name__=='__main__':
with mail_handler.threadbound:
main
QQ Mail
# -*- coding: utf-8 -*-
from logbook import Logger, MailHandler
import logbook
import sys
sender='Logger'
recipients=['dongdong@163.com']
email_user='dongdong@qq.com'
email_pass='password'
mail_handler=MailHandler(sender, recipients,
server_addr='smtp.qq.com',
starttls=False,
secure=True,
credentials=(email_user, email_pass),
format_string=u'''
Subject: {record.level_name} on My Application
Message type: {record.level_name}
Location: {record.filename}:{record.lineno}
Module: {record.module}
Function: {record.func_name}
Time: {record.time:%Y-%m-%d %H:%M:%S}
Remote IP: {record.extra[ip]}
Request: {record.extra[url]} [{record.extra[method]}]
Message: {record.message}
''',
bubble=True)
log=Logger('test')
def main:
log.info('something logging')
if __name__=='__main__':
with mail_handler.threadbound:
main
内容 format_string
中的用大括号的会进行数值替换,Subject
字段上边的``和下边需要空一行,这样解析参数收到的邮件才会正确的显示标题
上边发送邮件的例子,参数里面有一个 record.extra[url]
,这个参数是可以自己指定的,比如编写WSGI的程序,处理URL,每一条记录都希望记录到访问者的IP,可以这样做:
# -*- coding: utf-8 -*-
from logbook import Logger, StreamHandler, Processor
import logbook
import sys
handler=StreamHandler(sys.stdout)
handler.format_string='[{{record.time:%Y-%m-%d %H:%M:%S}}] IP:{record.extra[ip]} {record.level_name}: {record.channel}: {record.message}'
handler.formatter
log=Logger('test')
def inject_ip(record):
record.extra['ip']='127.0.0.1'
with handler.applicationbound:
with Processor(inject_ip).applicationbound:
log.error('something error')
使用自定义参数,需要重新设置 format_string
,才能进行记录,record类可以在这里找到,详细参数见logbook.LogRecord
Output:
[2016-12-13 12:20:46] IP:127.0.0.1 ERROR: test: something error
上边在介绍的自定义日志格式的时候使用的时间是虽然指定了格式但是是 UTC
格式,跟北京时间是差了8个小时的。所以需要设置让它记录本地的时间
在刚才的例子前面加上如下代码即可
logbook.set_datetime_format('local') # <=新加入行
handler=StreamHandler(sys.stdout)
handler.format_string='[{record.time:%Y-%m-%d %H:%M:%S}] IP:{record.extra[ip]} {record.level_name}: {record.channel}: {record.message}'
handler.formatter
更过日期格式化的设置参看:api/utilities - logbook.set_datetime_format(datetime_format)
从最开始的例子来看,可以使用两种方式来记录日志,一种是在最开始使用 push_*
来激活,另一种是使用的时候用with
构造上下文,现在进行一些补充
push | with | pop |
---|---|---|
push_application | applicationbound | pop_application |
push_thread | threadbound | pop_threadbound |
push_greenlet | greenletbound | pop_greenlet |
使用 pop_*
可以取消记录的上下文
application作用于整个应用,thread只针对当前线程
handler=MyHandler
handler.push_application
# all here goes to that handler
handler.pop_application
使用多个Handler的时候,使用 push_
方式启动的上下文不会形成嵌套,但是使用with
启动的上下文会形成嵌套,可以使用nested handler
import os
from logbook import NestedSetup, Handler, FileHandler,
MailHandler, Processor
def inject_information(record):
record.extra['cwd']=os.getcwd
setup=NestedSetup([
# Handler避免stderr接受消息
Handler(),
FileHandler('application.log', level='WARNING'),
MailHandler('servererrors@example.com', ['admin@example.com'],
level='ERROR', bubble=True),
Processor(inject_information)
])
使用的时候就只需要一个 with
来启动
with setup.threadbound:
log.info('something logging')
logbook
是个不错的包,记录日志灵活方便,比自己包装发送邮件方便了不少,整理了一些基本用法,还有不少值得学习的功能,暂时能用到的基本上就这么多,之后用到高级的功能再继续研究补充。
题图:pexels,CC0 授权。
读
作者日常在与其他同学合作时,经常发现不合理的日志配置以及五花八门的日志记录方式,后续作者打算在团队内做一次Java日志的分享,本文是整理出的系列文章第一篇。
序
写这篇文章的初衷,是想在团队内做一次Java日志的分享,因为日常在与其他同学合作时,经常发现不合理的日志配置以及五花八门的日志记录方式。但在准备分享、补充细节的过程中,我又进一步发现目前日志相关的文章,都只是专注于某一个方面,或者讲历史和原理,或者解决包冲突,却都没有把整个Java日志知识串联起来。最终这篇文章超越了之前的定位,越写越丰富,为了让大家看得不累,我的文章将以系列的形式展示。
一、前言
日志发展到今天,被抽象成了三层:接口层、实现层、适配层:
适配层又可以分为绑定(Binding)和桥接(Bridging)两种能力:
如果你觉得上面的描述比较抽象生硬,可以先跳过,等把本篇看完自然就明白了。
接下来我们就以时间顺序,回顾一下Java日志的发展史,这有助于指导我们后续的实践,真正做到知其所以然。
二、历史演进
2.1 标准输出 (<1999)
Java最开始并没有专门记录日志的工具,大家都是用System.out和System.err输出日志。但它们只是简单的信息输出,无法区分错误级别、无法控制输出粒度,也没有什么管理、过滤能力。随着Java工程化的深入,它们的能力就有些捉襟见肘了。
虽然System.out和System.err默认输出到控制台,但它们是有能力将输出保存到文件的:
System.setOut(new PrintStream(new FileOutputStream("log.txt", true)));
System.out.println("这句将输出到 log.txt 文件中");
System.setErr(new PrintStream(new FileOutputStream("error.txt", true)));
System.err.println("这句将输出到 error.txt 文件中");
2.2 Log4j (1999)
在1996年,一家名为SEMPER的欧洲公司决定开发一款用于记录日志的工具。经过多次迭代,最终发展成为Log4j。这款工具的主要作者是一位名叫Ceki Gülcü[2]的俄罗斯程序员,请记住他的名字:Ceki,后面还会多次提到他。
到了1999年,Log4j已经被广泛使用,随着用户规模的增长,用户诉求也开始多样化。于是Ceki在2001年选择将Log4j开源,希望借助社区的力量将Log4j发展壮大。不久之后Apache基金会向Log4j抛出了橄榄枝,自然Ceki也加入Apache继续从事 Log4j的开发,从此Log4j改名Apache Log4j[3]并进入发展的快车道。
Log4j相比于System.out提供了更强大的能力,甚至很多思想到现在仍被广泛接受,比如:
随着Log4j的成功,Apache又孵化了Log4Net[4]、Log4cxx[5]、Log4php[6]产品,开源社区也模仿推出了如Log4c[7]、Log4cpp[8]、Log4perl[9]等众多项目。从中也可以印证Log4j在日志处理领域的江湖影响力。
不过Log4j有比较明显的性能短板,在Logback和Log4j 2推出后逐渐式微,最终Apache在2015年宣布终止开发Log4j并全面迁移至Log4j 2[10](可参考【2.7 Log4j 2 (2012)】)。
2.3 JUL (2002.2)
随着Java工程的发展,Sun也意识到日志记录非常重要,认为这个能力应该由JRE原生支持。所以在1999年Sun提交了JSR 047[11]提案,标题就叫「Logging API Specification」。不过直到2年后的2002年,Java官方的日志系统才随Java 1.4发布。这套系统称做Java Logging API,包路径是java.util.logging,简称JUL。
在某些追溯历史的文章中提到,「Apache曾希望将 Log4j加入到JRE中作为默认日志实现,但傲慢的Sun没有答应,反而很快推出了自己的日志系统」。对于这个说法我并没有找到出处,无法确认其真实性。
不过从实际推出的产品来看,更晚面世的JUL无论是功能还是性能都落后于Log4j,颇有因被寄予厚望而仓促发布的味道,也许那个八卦并非空穴来风,哈哈。虽然在2004年推出的Java 5.0 (1.5) [12]上JUL进步不小,但它在Log4j面前仍无太多亮点,广大开发者并没有迁移的动力,导致JUL始终未成气候。
我们在后文没有推荐JUL的计划,所以这里也不多介绍了(主要是我也不会)。
2.4 JCL (2002.8)
在Log4j和JUL之外,当时市面上还有像Apache Avalon[13](一套服务端开发框架)、 Lumberjack[14](一套跑在JDK 1.2/1.3上的开源日志工具)等日志工具。
对于独立且轻量的项目来说,开发者可以根据喜好使用某个日志方案即可。但更多情况是一套业务系统依赖了大量的三方工具,而众多三方工具会各自使用不同的日志实现,当它们被集成在一起时,必然导致日志记录混乱。
为此Apache在2002年推出了一套接口Jakarta Commons Logging[15],简称 JCL,它的主要作者仍然是Ceki。这套接口主动支持了Log4j、JUL、Apache Avalon、Lumberjack等众多日志工具。开发者如果想打印日志,只需调用JCL的接口即可,至于最终使用的日志实现则由最上层的业务系统决定。我们可以看到,这其实就是典型的接口与实现分离设计。
但因为是先有的实现(Log4j、JUL)后有的接口(JCL),所以JCL配套提供了接口与实现的适配层(没有使用它的最新版,原因会在【1.2.7 Log4j2 (2012)】提到):
简单介绍一下JCL自带的几个适配层/实现层:
当时项目前缀取名Jakarta,是因为它属于Apache与Sun共同推出的Jakarta Project[16]项目(邮件[17])。现在JCL作为Apache Commons[18]的子项目,叫 Apache Commons Logging,与我们常用的Commons Lang[19]、Commons Collections [20]等是师兄弟。但JCL的简写命名被保留了下来,并没有改为ACL。
2.5 Slf4j (2005)
Log4j的作者Ceki看到了很多Log4j和JCL的不足,但又无力推动项目快速迭代,加上对Apache的管理不满,认为自己失去了对Log4j项目的控制权(博客[21]、邮件[22]),于是在2005年选择自立门户,并很快推出了一款新作品Simple Logging Facade for Java[23],简称Slf4j。
Slf4j也是一个接口层,接口设计与JCL非常接近(毕竟有师承关系)。相比JCL有一个重要的区别是日志实现层的绑定方式:JCL是动态绑定,即在运行时执行日志记录时判定合适的日志实现;而Slf4j选择的是静态绑定,应用编译时已经确定日志实现,性能自然更好。这就是常被提到的classloader问题,更详细地讨论可以参考What is the issue with the runtime discovery algorithm of Apache Commons Logging[24]以及Ceki自己写的文章Taxonomy of class loader problems encountered when using Jakarta Commons Logging[25]。
在推出Slf4j的时候,市面上已经有了另一套接口层JCL,为了将选择权交给用户(我猜也为了挖JCL的墙角),Slf4j推出了两个桥接层:
Slf4j通过推出各种适配层,基本满足了用户的所有场景,我们来看一下它的全家桶:
网上介绍Slf4j的文章,经常会引用它官网上的两张图:
感兴趣的同学也可以参考。
这里解释一下slf4j-log4j12这个名字,它表示Slf4j + Log4j 1.2(Log4j的最后一个版本) 的适配层。类似的,slf4j-jdk14表示Slf4j + JDK 1.4(就是 JUL)的适配层。
2.6 Logback (2006)
然而Ceki的目标并不止于Slf4j,面对自己一手创造的Log4j,作为原作者自然是知道它存在哪些问题的。于是在2006年Ceki又推出了一款日志记录实现方案:Logback[26]。无论是易用度、功能、还是性能,Logback 都要优于Log4j,再加上天然支持Slf4j而不需要额外的适配层,自然拥趸者众。目前Logback已经成为Java社区最被广泛接受的日志实现层(Logback自己在2021年的统计是48%的市占率[27])。
相比于Log4j,Logback提供了很多我们现在看起来理所当然的新特性:
Logback主要由三部分组成(网上各种文章在介绍classic和access时都描述的语焉不详,我不得不直接翻官网文档找更明确的解释):
2.7 Log4j 2 (2012)
看着Slf4j + Logback搞的风生水起,Apache自然不会坐视不理,终于在2012年憋出一记大招:Apache Log4j 2[29],它自然也有不少亮点:
Log4j 2主要由两部分组成:
你会发现Log4j 2的设计别具一格,提供JCL和Slf4j之外的第三个接口层(log4j-api,虽然只是自己的接口),它在官网API Separation[33]一节中解释说,这样设计可以允许用户在一个项目中同时使用不同的接口层与实现层。
不过目前大家一般把Log4j 2作为实现层看待,并引入JCL或Slf4j作为接口层。特别是JCL,在时隔近十年后,于2023年底推出了1.3.0 版[34],增加了针对Log4j 2的适配。还记得我们在【1.2.4 JCL (2002.8)】中没有用最新版的JCL做介绍吗,就是因为这个十年之后的版本把那些已经「作古」的日志适配层@Deprecated掉了。
多说一句,其实Logback和Slf4j就像log4j-core和log4j-api的关系一下,目前如果你想用Logback也只能借助Slf4j。但谁让它们生逢其时呢,大家就会分别讨论认为是两个产品。
虽然Log4j 2发布至今已有十年(本文写于2024年),但它仍然无法撼动Logback的江湖地位,我个人总结下来主要有两点:
比如,曾有人建议Spring Boot将日志系统从Logback切换到Log4j2[35],但被Phil Webb[36](Spring Boot核心贡献者)否决。他在回复中给出的原因包括:Spring Boot需要保证向前兼容以方便用户升级,而切换Log4j 2是破坏性的;目前绝大部分用户并未面临日志性能问题,Log4j 2所推崇的性能优势并非框架与用户的核心关切;以及如果用户想在Spring Boot中切换到Log4j 2也很方便(如需切换可参考 官方文档[37])。
2.8 spring-jcl (2017)
因为目前大部分应用都基于Spring/Spring Boot搭建,所以我额外介绍一下spring-jcl [38]这个包,目前Spring Boot用的就是spring-jcl + Logback这套方案。
Spring曾在它的官方Blog《Logging Dependencies in Spring》[39]中提到,如果可以重来,Spring会选择李白Slf4j而不是JCL作为默认日志接口。
现在Spring又想支持Slf4j,又要保证向前兼容以支持JCL,于是从5.0(Spring Boot 2.0)开始提供了spring-jcl这个包。它顶着Spring的名号,代码中包名却与JCL 一致(org.apache.commons.logging),作用自然也与JCL一致,但它额外适配了Slf4j,并将Slf4j放在查找的第一顺位,从而做到了「既要又要」(你可以回到【1.2.4 JCL (2002.8)】节做一下对比)。
如果你是基于Spring Initialize [40]新创建的应用,可以不必管这个包,它已经在背后默默工作了;如果你在项目开发过程中遇到包冲突,或者需要自己选择日志接口和实现,则可以把spring-jcl当作JCL对待,大胆排除即可。
2.9 其他
除了我们上边提到的日志解决方案,还有一些不那么常见的,比如:
因为这些日志框架我们在实际开发中用的很少,此文也不再赘述了(主要是我也不会)。
三、总结
历史介绍完了,但故事并没有结束。两个接口(JCL、Slf4j)四个实现(Log4j、JUL、Logback、Log4j2),再加上无数的适配层,它们之间串联成了一个网,我专门画了一张图:
解释/补充一下这张图:
如果你之前在看「1.1 前言」时觉得过于抽象,那么此时建议你再回头看一下,相信会有更多体会。
从这段历史,我也发现了几个有趣的细节:
参考链接:
[1]https://codedocs.org/what-is/david-wheeler-computer-scientist
[2]https://github.com/ceki
[3]https://logging.apache.org/log4j/1.2/
[4]https://logging.apache.org/log4net/
[5]https://logging.apache.org/log4cxx/
[6]https://logging.apache.org/log4php/
[7]https://log4c.sourceforge.net/
[8]https://log4cpp.sourceforge.net/
[9]https://mschilli.github.io/log4perl/
[10]https://news.apache.org/foundation/entry/apache_logging_services_project_announces
[11]https://jcp.org/en/jsr/detail
[12]https://www.java.com/releases/
[13]https://avalon.apache.org/
[14]https://javalogging.sourceforge.net/
[15]https://commons.apache.org/proper/commons-logging/
[16]https://jakarta.apache.org/
[17]https://lists.apache.org/thread/53otcqljjfnvjs3hv8m4ldzlgz59yk6k
[18]https://commons.apache.org/
[19]https://commons.apache.org/proper/commons-lang/
[20]https://commons.apache.org/proper/commons-collections/
[21]http://ceki.blogspot.com/2010/05/forces-and-vulnerabilites-of-apache.html
[22]https://lists.apache.org/thread/dyzmtholjdlf3h32vvl85so8sbj3v0qz
[23]https://www.slf4j.org/
[24]https://stackoverflow.com/questions/3222895/what-is-the-issue-with-the-runtime-discovery-algorithm-of-apache-commons-logging
[25]https://articles.qos.ch/classloader.html
[26]https://logback.qos.ch/
[27]https://qos.ch/
[28]https://logback.qos.ch/access.html
[29]https://logging.apache.org/log4j/2.x/
[30]https://logging.apache.org/log4j/2.x/manual/extending.html
[31]https://logging.apache.org/log4j/2.x/manual/async.html
[32]https://logging.apache.org/log4j/2.x/performance.html
[33]https://logging.apache.org/log4j/2.x/manual/api-separation.html
[34]https://commons.apache.org/proper/commons-logging/changes-report.html
[35]https://github.com/spring-projects/spring-boot/issues/16864
[36]https://spring.io/team/philwebb
[37]https://docs.spring.io/spring-boot/docs/3.2.x/reference/html/howto.html
[38]https://docs.spring.io/spring-framework/reference/core/spring-jcl.html
[39]https://spring.io/blog/2009/12/04/logging-dependencies-in-spring
[40]https://start.spring.io/
[41]https://google.github.io/flogger/
[42]https://github.com/jboss-logging
[43]https://reload4j.qos.ch/
[44]https://github.com/torvalds
[45]https://moolenaar.net/
作者:尚左
来源-微信公众号:阿里云开发者
出处:https://mp.weixin.qq.com/s/eIiu08fVk194E0BgGL5gow
019年10月24日,也就是“1024程序员节”的时候,腾讯突然发布了Linux系统版本的QQ,官方也宣称“全新回归”。
这是因为在2008年的时候,腾讯曾经发布过一次Linux QQ,但如同浪花一般随即消失得无影无踪,如今随着Linux生态日益完善,QQ回归也是顺应大势。
不过,在第一个2.0.0 Beta版本放出之后,一切又回归沉寂,此后腾讯官方再也没有更新过Linux QQ,直到160天后,就在昨天4月1日愚人节,Linux QQ 2.0.0 Beta2终于出现了。。。
算下来,这也是Linux QQ 12年来第二次更新。
新版变化不大,根据更新日志只是优化了稳定性,并新增多人聊天会话支持(群聊),而且依然是Beta测试状态。
但这至少说明,腾讯这次是打算持续做下去了,只是升级力度和Windows、Android、iOS版本不可同日而语,希望下次更新不要再等到程序员节就好了。
Linux QQ目前支持x64(x86_64/amd64)、ARM64(aarch64)、MIPSs64(mips64el)三种架构,每种架构均支持Debian系、红帽系、Arch Linux系、其它发行版中的一种或几种,未来可能继续扩充,每一次发布均会提供架构和发行版的若干种组合支持的安装包。
安装的时候需要注意,当前版本的Linux QQ依赖于gtk2.0,必须首先确保系统已安装gtk2.0,而卸载尽量使用安装时使用的对应方式,并需要root权限。
Linux QQ官方下载:
https://im.qq.com/linuxqq/download.html
附更新日志:
Linux QQ 2.0.0 Beta2:
- 优化稳定性;
- 增加多人聊天会话支持。
Linux QQ 2.0.0 Beta:
- Linux QQ全新回归;
- 支持x64、ARM64、MIPS64三种架构;
- 优化消息体验,完善消息收发能力;
- 性能优化。
原文来自:http://985.so/mfZ4
Linux命令大全:https://www.linuxcool.com/
*请认真填写需求信息,我们会在24小时内与您取得联系。