整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Java日志通关 - 前世今生

Java日志通关 - 前世今生


作者日常在与其他同学合作时,经常发现不合理的日志配置以及五花八门的日志记录方式,后续作者打算在团队内做一次Java日志的分享,本文是整理出的系列文章第一篇。

写这篇文章的初衷,是想在团队内做一次Java日志的分享,因为日常在与其他同学合作时,经常发现不合理的日志配置以及五花八门的日志记录方式。但在准备分享、补充细节的过程中,我又进一步发现目前日志相关的文章,都只是专注于某一个方面,或者讲历史和原理,或者解决包冲突,却都没有把整个Java日志知识串联起来。最终这篇文章超越了之前的定位,越写越丰富,为了让大家看得不累,我的文章将以系列的形式展示。


一、前言


日志发展到今天,被抽象成了三层:接口层、实现层、适配层:

  • 接口层:或者叫日志门面(facade),就是interface,只定义接口,等着别人实现。
  • 实现层:真正干活的、能够把日志内容记录下来的工具。但请注意它不是上边接口实现,因为它不感知也不直接实现接口,仅仅是独立的实现。
  • 适配层:一般称为Adapter,它才是上边接口的implements。因为接口层和适配层并非都出自一家之手,它们之间无法直接匹配。而鲁迅曾经说过:「计算机科学领域的任何问题都可以通过增加一个中间层来解决」(All problems in computer science can be solved by another level of indirection. -- David Wheeler[1]),所以就有了适配层。

适配层又可以分为绑定(Binding)和桥接(Bridging)两种能力:

  • 绑定(Binding):将接口层绑定到某个实现层(实现一个接口层,并调用实现层的方法)
  • 桥接(Bridging):将接口层桥接到另一个接口层(实现一个接口层,并调用另一个接口层的接口),主要作用是方便用户低成本的在各接口层和适配层之间迁移

如果你觉得上面的描述比较抽象生硬,可以先跳过,等把本篇看完自然就明白了。
接下来我们就以时间顺序,回顾一下Java日志的发展史,这有助于指导我们后续的实践,真正做到知其所以然。

二、历史演进



2.1 标准输出 (<1999)

Java最开始并没有专门记录日志的工具,大家都是用System.outSystem.err输出日志。但它们只是简单的信息输出,无法区分错误级别、无法控制输出粒度,也没有什么管理、过滤能力。随着Java工程化的深入,它们的能力就有些捉襟见肘了。

虽然System.outSystem.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提供了更强大的能力,甚至很多思想到现在仍被广泛接受,比如:

  • 日志可以输出到控制台、文件、数据库,甚至远程服务器和电子邮件(被称做 Appender);
  • 日志输出格式(被称做 Layout)允许定制,比如错误日志和普通日志使用不同的展现形式;
  • 日志被分为5个级别(被称作Level),从低到高依次是debug, info, warn, error, fatal,输出前会校验配置的允许级别,小于此级别的日志将被忽略。除此之外还有all, off两个特殊级别,表示完全放开和完全关闭日志输出;
  • 可以在工程中随时指定不同的记录器(被称做Logger),可以为之配置独立的记录位置、日志级别;
  • 支持通过properties或者xml文件进行配置;

随着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自带的几个适配层/实现层:

  • AvalonLogger/LogKitLogger:用于绑定Apache Avalon的适配层,因为Avalon 不同时期的日志包名不同,适配层也对应有两个
  • Jdk13LumberjackLogger:用于绑定Lumberjack的适配层
  • Jdk14Logger:用于绑定JUL(因为JUL从JDK 1.4开始提供)的适配层
  • Log4JLogger:用于绑定Log4j的适配层
  • NoOpLog:JCL自带的日志实现,但它是空实现,不做任何事情
  • SimpleLog: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推出了两个桥接层:

  • jcl-over-slf4j:作用是让已经在使用JCL的用户方便的迁移到Slf4j 上来,你以为调的是JCL接口,背后却又转到了Slf4j接口。我说这是在挖JCL的墙角不过分吧?
  • slf4j-jcl:让在使用Slf4j的用户方便的迁移到JCL上,自己的墙角也挖,主打的就是一个公平公正公开。

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提供了很多我们现在看起来理所当然的新特性:

  • 支持日志文件切割滚动记录、支持异步写入
  • 针对历史日志,既支持按时间或按硬盘占用自动清理,也支持自动压缩以节省硬盘空间
  • 支持分支语法,通过<if>, <then>, <else>可以按条件配置不同的日志输出逻辑,比如判断仅在开发环境输出更详细的日志信息
  • 大量的日志过滤器,甚至可以做到通过登录用户Session识别每一位用户并输出独立的日志文件
  • 异常堆栈支持打印jar包信息,让我们不但知道调用出自哪个文件哪一行,还可以知道这个文件来自哪个jar包

Logback主要由三部分组成(网上各种文章在介绍classic和access时都描述的语焉不详,我不得不直接翻官网文档找更明确的解释):

  • logback-core:记录/输出日志的核心实现
  • logback-classic:适配层,完整实现了Slf4j接口
  • logback-access[28]:用于将Logback集成到Servlet容器(Tomcat、Jetty)中,让这些容器的HTTP访问日志也可以经由强大的Logback输出


2.7 Log4j 2 (2012)


看着Slf4j + Logback搞的风生水起,Apache自然不会坐视不理,终于在2012年憋出一记大招:Apache Log4j 2[29],它自然也有不少亮点:

  • 插件化结构[30],用户可以自己开发插件,实现Appender、Logger、Filter完成扩展
  • 基于LMAX Disruptor的异步化输出[31],在多线程场景下相比Logback有10倍左右的性能提升,Apache官方也把这部分作为主要卖点加以宣传,详细可以看Log4j 2 Performance[32]。

Log4j 2主要由两部分组成:

  • log4j-core:核心实现,功能类似于logback-core
  • log4j-api:接口层,功能类似于Slf4j,里面只包含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的江湖地位,我个人总结下来主要有两点:

  • Log4j 2虽然顶着Log4j的名号,但却是一套完全重写的日志系统,无法只通过修改Log4j版本号完成升级,历史用户升级意愿低
  • Log4j 2比Logback晚面世6年,却没有提供足够亮眼及差异化的能力(前边介绍的两个亮点对普通用户并没有足够吸引力),而Slf4j+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 其他


除了我们上边提到的日志解决方案,还有一些不那么常见的,比如:

  • Flogger[41]:由Google在2018年推出的日志接口层。首字母F的含义是Fluent,这也正是它的最大特点:链式调用(或者叫流式API,Slf4j 2.0也支持Fluent API 了,我们会在后续系列文章中介绍)
  • JBoss Logging[42]:由RedHat在约2010年推出,包含完整的接口层、实现层、适配层
  • slf4j-reload4j[43]:Ceki基于Log4j 1.2.7 fork出的版本,旨在解决Log4j的安全问题,如果你的项目还在使用Log4j且不想迁移,建议平替为此版本。(但也不是所有安全问题都能解决,具体可以参考上边的链接)

因为这些日志框架我们在实际开发中用的很少,此文也不再赘述了(主要是我也不会)。

三、总结


历史介绍完了,但故事并没有结束。两个接口(JCL、Slf4j)四个实现(Log4j、JUL、Logback、Log4j2),再加上无数的适配层,它们之间串联成了一个网,我专门画了一张图:

解释/补充一下这张图:

  1. 相同颜色的模块拥有相同的groupId,可以参考图例中给出的具体值。
  2. JCL的适配层是直接在它自己的包中提供的,详情我们在前边已经介绍过,可以回【1.2.4 JCL (2002.8)】查看。
  3. 要想使用Logback,就一定绕不开Slf4j(引用它的适配层也算);同样的,要想使用 Log4j 2,那它的log4j-api也绕不开。

如果你之前在看「1.1 前言」时觉得过于抽象,那么此时建议你再回头看一下,相信会有更多体会。

从这段历史,我也发现了几个有趣的细节:

  • 在Log4j 2面世前后的很长一段时间,Slf4j及Logback因为没有竞争对手而更新缓慢。英雄没有对手只能慢慢垂暮,只有棋逢对手才能笑傲江湖。
  • 技术人的善良与倔强:面世晚的产品都针对前辈产品提供支持;面世早的产品都不搭理它的「后辈」。
  • 计算机科学领域的任何问题都可以通过增加一个中间层来解决,如果不行就两个(桥接层干的事儿)。
  • Ceki一人肩挑Java日志半壁江山25年(还在增长ing),真神人也。(当然在代码界有很多这样的神人,比如Linus Torvalds[44]维护Linux至今已有33年,虽然后期主要作为产品经理参与,再比如已故的Bram Moolenaar[45]老爷子持续维护 Vim 32年之久)。

参考链接:

[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

置Postfix

Postfix有数百种配置选项。 我概述了/etc/postfix/main.cf文件中的一些常见问题。

用于出站邮件的域名

将此选项设置为出站邮件所需的域名。 默认情况下,此选项使用出站电子邮件的主机名。 该选项还指定附加到非限定收件人地址的域。

#default value is:
#myorigin=$myhostname
#myorigin=$mydomain
#we are going to send outbound mail as originating from example.com
mydomain=example.com
myorigin=$mydomain

要接收电子邮件的域名

此选项指定Postfix接收电子邮件的域名。 默认情况下,Postfix仅接收主机名的电子邮件。 对于域邮件服务器,请更改该值以包含域名。

#default value
mydestination=$myhostname, localhost.$mydomain, localhost
#we are going to change it so that we can receive email for example.com
mydestination=$myhostname, localhost.$mydomain, localhost, $mydomain

允许中继的网络

默认情况下,Postfix允许Postfix服务器的本地子网上的客户端将其用作中继 - 换句话说,即$ mynetworks配置参数中定义的那些网络。 更改此项以包括组织内应允许使用此Postfix服务器发送电子邮件的所有网络。

#default value
#mynetworks_style=class
#mynetworks_style=subnet
#mynetworks_style=host
#mynetworks=168.100.189.0/28, 127.0.0.0/8
#mynetworks=$config_directory/mynetworks
#mynetworks=hash:/etc/postfix/network_table
#change the default values to be your network, assuming your network is 10.0.0.0/8
mynetworks=10.0.0.0/8
#another way of accomplishing the above is:
mynetworks_style=class
#if you want to forward e-mail only from the Postfix host:
mynetworks_style=host
#if you want only the Postfix server subnet to forward e-mail via the Postfix server:
mynetworks_style=subnet

将邮件中继到目的地

当邮件来自授权网络之外的客户端时,Postfix仅将电子邮件转发给授权域。 您可以使用relay_domains参数指定哪些域可以是未经身份验证的发件人的收件人域。

#default value is:
#relay_domains=$mydestination
#if you do not want to forward e-mail from strangers, then change it as follows (recommended for outgoing mail servers, not for incoming):
relay_domains=#if you want to forward e-mail from strangers to your domain:
relay_domains=$mydomain

运输方式

Postfix使用收件人的邮件交换器(MX)记录直接发送邮件。 您可能不需要此功能,因为转发到过滤出站邮件的外部邮件托管提供商可能更好。

#default value is:
#relayhost=$mydomain
#relayhost=[gateway.my.domain]
#relayhost=[mailserver.isp.tld]
#relayhost=uucphost
#relayhost=[an.ip.add.ress]
#change the value to be
relayhost=external.mail.provider.ip.address

报告麻烦

您可能想要定义的另一个值是在出现任何问题时向谁发送电子邮件。 postmaster电子邮件地址在/ etc / aliases中指定,而不是在/etc/postfix/main.cf中指定。

#default value is:
$ grep -i postmaster /etc/aliases
mailer-daemon: postmaster
postmaster: root
#change to an e-mail address in your org that is an alias for the team responsible for Postfix
postmaster: email-admins@example.com

使用NAT

如果Postfix服务器使用NAT(换句话说,它在私有IP空间中),并且它正在接收公共IP地址的电子邮件地址,则还需要指定它。

#default value is:
#proxy_interfaces=#proxy_interfaces=1.2.3.4
#change it to your external IP address to which e-mail is sent
proxy_interfaces=your.public.email.server.ip.address

记录

Postfix日志记录通过/etc/rsyslog.conf中的Syslog完成。 默认情况下,通常会将所有邮件日志发送到/ var / log / maillog,这是存储邮件日志的好地方。

#default value is:
mail.* -/var/log/maillog

电子邮件协议

设置Postfix后,下一个要解决的问题是如何让客户端访问电子邮件。 在客户端访问方面,一些流行的协议包括

  1. IMAP
  2. POP(也称为POP3)

通常,避免直接传递到组织中的目标服务器,以保持简单; 相反,应用程序服务器从邮箱服务器中提取电子邮件。 应用程序服务器可以使用一个或多个协议,例如IMAP或POP。 您也可以让最终用户使用这些协议来访问其邮箱,而不是应用程序服务器。 IMAP在RFC 3501(http://tools.ietf.org/html/rfc3501)中定义,并且具有比POP更多的功能。 POP在RFC 1939(https://www.ietf.org/rfc/rfc1939.txt)中定义,并且已经存在了很长时间。

如果您有需要提取电子邮件的应用程序服务器,可以提供帮助的潜在设计如图4-12所示。 邮件传递代理(MDA)和邮件提交代理(MSA)都可以是Postfix服务器。 MDA是将邮件发送到邮箱的代理; MSA接受来自邮件用户代理的电子邮件。

For the MSA, there are numerous options, including

  • Dovecot (http://www.dovecot.org/)
  • Courier (http://www.courier-mta.org/imap/)
  • Cyrus (https://www.cyrusimap.org/)

这些MSA选项中的每一个都是开源和免费的。 它们都支持数千个邮箱,可以帮助您轻松管理电子邮件环境。

获得Postfix的帮助

Postfix支持有很多选项。 在线文档非常好,与任何开源免费软件一样,用户社区是您阅读文档后获得帮助的最佳选择。 一些在线帮助选项包括:

  • Mailing lists (http://www.postfix.org/lists.html)
  • IRC channel (http://irc.lc/freenode/postfix/irctc@@@)
  • Online documentation (http://www.postfix.org/documentation.html)

企业中的版本控制

版本控制在企业基础结构中有许多用途。 传统方法是使用修订控制来进行源代码管理。 但是,修订控制也可以在基础设施管理中发挥重要作用。 例如,如果在环境中使用BIND,则BIND配置可以存储在Git中。 Postfix和其他软件(如OpenVPN,iptables和Hadoop)等软件的配置也可以存储在版本控制中。 Puppet和Salt等配置管理工具也可以轻松地与Git集成。

大量的开源软件源代码存储在Git中。 在企业中使用Git的一个优点是与Internet社区的协作变得更加容易。 一些使用Git的主要开源项目包括:

  • Linux kernel
  • OpenStack
  • OpenVZ
  • GNOME
  • Yum

A more complete list is found at https://git.wiki.kernel.org/index.php/GitProjects.

ython的list是一种非常灵活的数据结构,它允许存储任意类型的有序集合,包括数字、字符串、甚至是其他的list,其强大的特性为Python编程提供了很大的便利。在这个文档中,我们将介绍Python list的基础知识,涵盖了list的定义、创建、访问、操作等方面的内容。

定义与创建

在Python中,可以使用方括号[]来创建一个空的list,例如:

Copy codea=[]

也可以使用方括号[]初始化一个包含元素的list,例如:

Copy codeb=[1, 2, 3, 4, 5]
c=['apple', 'banana', 'orange']
d=[1, 'hello', 3.14]

list的元素可以是任何类型,而且list中的元素可以是不同的类型,这是它非常灵活的地方。

访问元素

Python的list可以使用索引来访问其中的元素,索引从0开始,例如:

Copy codea=[1, 2, 3, 4, 5]
print(a[0])  # 1
print(a[2])  # 3

list还可以使用负数索引来访问其中的元素,例如:

Copy codeprint(a[-1])  # 5
print(a[-3])  # 3

这样,我们可以更加方便地访问list中的元素。

操作list

  1. 添加元素

向list中添加元素有两种方式:使用append()方法添加,或使用+运算符连接两个list。例如:

Copy codea=['apple', 'banana', 'orange']
a.append('watermelon')
print(a)  # ['apple', 'banana', 'orange', 'watermelon']

b=['pear', 'grape']
c=a + b
print(c)  # ['apple', 'banana', 'orange', 'watermelon', 'pear', 'grape']
  1. 删除元素

可以使用del语句或remove()方法从list中删除元素:

Copy codea=['apple', 'banana', 'orange']
del a[0]
print(a)  # ['banana', 'orange']

a.remove('banana')
print(a)  # ['orange']
  1. 切片

我们可以使用切片来访问list的一个子集合。切片使用[start:stop:step]的格式,例如:

Copy codea=[1, 2, 3, 4, 5]
print(a[1:3])  # [2, 3]
print(a[:3])  # [1, 2, 3]
print(a[2:])  # [3, 4, 5]
print(a[::2])  # [1, 3, 5]
  1. 排序

Python提供了两种方法对list进行排序:sort()方法和sorted()函数。sort()方法会改变原list,而sorted()函数返回一个新的已排序的list。例如:

Copy codea=[3, 1, 4, 2, 5]
a.sort()
print(a)  # [1, 2, 3, 4, 5]

b=[3, 1, 4, 2, 5]
c=sorted(b)
print(b)  # [3, 1, 4, 2, 5]
print(c)  # [1, 2, 3, 4, 5]

学习资源

以下是一些有用的学习资源,帮助你学习Python list:

  1. Python官方文档:https://docs.python.org/3/tutorial/datastructures.html#more-on-lists,这是Python官方文档中关于list的章节,详细介绍了list的操作和用法。
  2. W3Schools Python List教程:https://www.w3schools.com/python/python_lists.asp,这是W3Schools提供的Python list教程,简单易懂地介绍了list的基础知识和操作。
  3. Python for Data Science Handbook:Jake VanderPlas著,这是一本深入探讨Python数据科学的书籍,其中包括了从基础知识到高级技巧的Python list的操作与应用。

总结

Python的list是一种非常灵活的数据结构,它可以存储任何类型的元素,包括数字、字符串、甚至是其他的list。Python list提供了丰富的操作方法,包括访问元素、添加元素、删除元素、切片、排序等。我们希望通过这篇文档提供了Python list的基础知识和一些有用的学习资源,帮助你更好地掌握Python编程中的list。