本章涉及到的内容为将 log4j 的组件,例如 appender 或者 layout 迁移到 logback-classic。

仅仅调用 log4j 客户端 API 的软件,也就是 org.apache.log4j 包中 Logger 或者 Category 类,可以通过 SLF4J 迁移工具使用 SLF4J 来进行自动迁移。为了将 log4j.property 文件转换为同等的 logback 配置,你可以使用 log4j.properties 转换器

在某种程度上来说,log4j 与 logback-classic 密切相关。核心组件,logger,appender 以及 layout 在两个框架中都存在,并且目的一致。类似的,最重要的内部数据结构,叫做 LoggingEvent,在两个框架中非常相似,但是实现完全不同。最主要的是,在 logback-classic 中,LoggingEvent 实现了 ILoggingEvent 接口。迁移 log4j 组件到 logback-classic 最大的改变在于 LoggingEvent 类相关的实现不同。但是,请放心,这些变化是有限的。如果你尽了最大的努力仍然不能将 log4j 组件迁移到 logback-classic,你可以通过 logback 开发邮件列表来进行提问。logback 的开发者应该可以提供指导。

迁移 log4j 的 layout

假设我们现在要迁移一个简单的,名叫 TrivialLog4jLayout 的 log4j layout,它将日志事件中的消息作为格式化消息返回。代码如下:

  1. package chapters.migrationFromLog4j;
  2. import org.apache.log4j.Layout;
  3. import org.apache.log4j.spi.LoggingEvent;
  4. public class TrivialLog4jLayout extends Layout {
  5. public void activateOptions() {
  6. }
  7. public String format(LoggingEvent loggingEvent) {
  8. return loggingEvent.getRenderedMessage();
  9. }
  10. public boolean ignoresThrowable() {
  11. return true;
  12. }
  13. }

等价的 logback-classic TrivialLogbackLayout 如下:

  1. package chapters.migrationFromLog4j;
  2. import ch.qos.logback.classic.spi.ILoggingEvent;
  3. import ch.qos.logback.core.LayoutBase;
  4. public class TrivialLogbackLayout extends LayoutBase<ILoggingEvent> {
  5. public String doLayout(ILoggingEvent loggingEvent) {
  6. return loggingEvent.getMessage();
  7. }
  8. }

正如你所见,在 logback-classic layout 中,格式化的方法叫做 doLayout,而在 log4j 中叫 format()。因为在 logback-classic 中没有等价的方法,所以 ignoresThrowable() 方法则不需要。logback-classic layout 必须继承 LayoutBase<ILoggingEvent> 类。

activateOptions() 方法的优点值得进一步讨论。在 log4j 中,一个 layout 有它自己的 activateOptions() 方法,通过 log4j 的配置程序,也就是 PropertyConfiguratorDOMConfigurator,会在 layout 所有的选项都设置完之后调用。因此,layout 有机会去检查它的所有的选项是否一致,如果是,那么开始进行初始化。

在 logback-classic 中,layout 必须实现 LifeCycle 接口,该接口包含了一个 start() 方法。这个 start() 方法相当 log4j 中的 activateOptions() 方法。

迁移 log4j 的 appender

迁移 appender 与迁移 layout 相当的类似。下面是有一个名为 TrivialLog4jAppender 的简单 appender,它会在控制台输出由它的 layout 返回的字符串。

  1. package chapters.migrationFromLog4j;
  2. import org.apache.log4j.AppenderSkeleton;
  3. import org.apache.log4j.spi.LoggingEvent;
  4. public class TrivialLog4jAppender extends AppenderSkeleton {
  5. protected void append(LoggingEvent loggingevent) {
  6. String s = this.layout.format(loggingevent);
  7. System.out.println(s);
  8. }
  9. public void close() {
  10. // nothing to do
  11. }
  12. public boolean requiresLayout() {
  13. return true;
  14. }
  15. }

在 logback-classic 中等价的写法为 TrivialLogbackAppender,如下:

  1. package chapters.migrationFromLog4j;
  2. import ch.qos.logback.classic.spi.ILoggingEvent;
  3. import ch.qos.logback.core.AppenderBase;
  4. public class TrivialLogbackAppender extends AppenderBase<ILoggingEvent> {
  5. @Override
  6. public void start() {
  7. if (this.layout == null) {
  8. addError("No layout set for the appender named [" + name + "].");
  9. return;
  10. }
  11. super.start();
  12. }
  13. @Override
  14. protected void append(ILoggingEvent loggingevent) {
  15. // AppenderBase.doAppend 只会在这个 appender 成功启动之后调用这个方法
  16. String s = this.layout.doLayout(loggingevent);
  17. System.out.println(s);
  18. }
  19. }

比较这两个类,你会发现 append() 方法的内容没有改变。requiresLayout 方法在 logback 中没有用到,所以它可以被移除。在 logback 中,stop() 方法与 log4j 中的 close() 方法等价。然而,logback-classic 中的 AppenderBase 包含一个没有实现的 stop 方法,但是在这个简单的 appender 已经足够了。