嵌入式 Tomcat 使用 log4j 进行日志记录

Embedded Tomcat using log4j for logging

我正在使用嵌入式 Tomcat 8.5.4,即

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>8.5.4</version>
</dependency>

实施工作完美(Tomcat 工作得很好),唯一困扰我的是嵌入式 Tomcat 登录 System.out。在我的应用程序内部,我使用 log4j 进行日志记录,因此这会导致以下日志记录混合(而不是将 Tomcat 记录到任何文件):

...
2017-07-30 17:57:54 DEBUG EmbeddedTomcat:136 - Binding servlet 'sample' to path '/sample/*'.
Jul 30, 2017 5:57:54 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-15000"]
Jul 30, 2017 5:57:54 PM org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
INFO: Using a shared selector for servlet write/read
Jul 30, 2017 5:57:54 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service Tomcat
Jul 30, 2017 5:57:54 PM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet Engine: Apache Tomcat/8.5.4
Jul 30, 2017 5:57:54 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler [http-nio-15000]
2017-07-30 17:57:54 INFO  EmbeddedTomcat:80 - Successfully started Tomcat on port 15000 (base: null, url: http://localhost:15000).
...

在此片段中,我的应用程序使用 log4j 和 log4j 的配置(写入文件和 System.out 记录了第一行和最后一行(在 ... 之后和之前) ).尽管如此,中间部分(Tomcat 的日志记录)由嵌入式 Tomcat 处理,我不知道如何让 Tomcat 使用可用的 log4j(及其配置)。

我尝试添加以下依赖项(8.5.4 版本在 Maven 存储库或 Maven Central 上不可用),但没有成功。

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-logging-log4j</artifactId>
    <version>8.5.2</version>
</dependency>

有谁知道如何让嵌入式 Tomcat 使用 log4j(版本 1,我没有使用 log4j2)进行登录?

我查看了 at/tried 以下 Whosebug 答案:

我花了一些时间,但在我获得 8.5.4 实现的源代码后,我意识到 juli 日志实现被添加到 core jar 中。

版本 <= 8.5.2

所以我回去开始使用 8.5.2 版本并使用 tomcat-embed-logging-log4j-8.5.2.jar,以及 tomcat-embed-core-8.5.2.jar。首先要注意的是,对于大多数在线文档,不要添加 tomcat-embed-logging-juli-8.5.2.jar 很重要。话虽如此,8.5.2 版本可以与 log4j 开箱即用,没有什么可做的了。

版本 > 8.5.2

当使用较新版本的嵌入式 Tomcat,即 8.5.4. 甚至最新版本 8.5.19 时,LogFactory 已包含在 jar 中。因此,当在 class 路径上添加较旧的 tomcat-embed-logging-log4j-8.5.2.jar 时,现在有两个 LogFactory 实现可用。第一个由 core 提供并加载 DirectJDKLog(我将此称为 Core-LogFactory),第二个通过 log4j 提供(称为 Log4j-LogFactory).因此,当 LogFactory 从 class 路径加载时, Core-LogFactory 被选中(因为它在同一个 jar 中,因此 "closer" (不需要太深入class路径加载顺序))。通常,在 class 路径上使用相同的 class(在同一个包中)是不好的做法。这只会导致混乱,你 mostly 永远不知道实际使用了哪个 class (是的,我知道有方法和规则,但长话短说,这不好) .因此,我决定不再使用 tomcat-embed-logging-log4j-8.5.2.jar,而是遵循 ServiceLoader 方法,该方法实际上在较新版本 (https://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/juli/logging/LogFactory.java) 的 Core-LogFactory 中实现。

private LogFactory() {
    // Look via a ServiceLoader for a Log implementation that has a
    // constructor taking the String name.
    ServiceLoader<Log> logLoader = ServiceLoader.load(Log.class);
    Constructor<? extends Log> m=null;
    for (Log log: logLoader) {
        Class<? extends Log> c=log.getClass();
        try {
            m=c.getConstructor(String.class);
            break;
        }
        catch (NoSuchMethodException | SecurityException e) {
            throw new Error(e);
        }
    }
    discoveredLogConstructor=m;
}

为此,我在 META-INF/services 文件夹中添加了文件 org.apache.juli.logging.Log(在我的 jar/sources 中)并添加了我的 "own" Log实现,即net.meisen.tomcat.logging.Log4jLog,如下:

package net.meisen.tomcat.logging;

import org.apache.juli.logging.Log;
import org.apache.log4j.Logger;

public class Log4jLog implements Log {
    private final Logger logger;

    // this constructor is important, otherwise the ServiceLoader cannot start
    public Log4jLog() {
        logger = Logger.getLogger(Log4jLog.class);
    }

    // this constructor is needed by the LogFactory implementation
    public Log4jLog(final String name) {
        logger = Logger.getLogger(name);
    }

    // now we have to implement the `Log` interface
    @Override
    public boolean isFatalEnabled() {
        return true;
    }

    // ... more isLevelEnabled()

    @Override
    public boolean isTraceEnabled() {
        return logger.isTraceEnabled();
    }

    // ... and also all the fatal(...) - trace(...) methods

    @Override
    public void fatal(final Object msg) {
        logger.fatal(msg);
    }

    @Override
    public void fatal(final Object msg, final Throwable throwable) {
        logger.fatal(msg, throwable);
    }
}

等等,这是最终结果:

2017-07-31 19:27:04 TRACE EmbeddedTomcat:48 - Initializing Tomcat on port 15000 (base: null)...
2017-07-31 19:27:33 INFO  Http11NioProtocol:69 - Initializing ProtocolHandler ["http-nio-15000"]
2017-07-31 19:27:33 INFO  NioSelectorPool:69 - Using a shared selector for servlet write/read
2017-07-31 19:27:33 INFO  StandardService:69 - Starting service [Tomcat]
2017-07-31 19:27:33 INFO  StandardEngine:69 - Starting Servlet Engine: Apache Tomcat/8.5.19
2017-07-31 19:27:34 WARN  SessionIdGeneratorBase:79 - Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [170] milliseconds.
2017-07-31 19:27:34 INFO  Http11NioProtocol:69 - Starting ProtocolHandler ["http-nio-15000"]
2017-07-31 19:27:34 INFO  EmbeddedTomcat:80 - Successfully started Tomcat on port 15000 (base: null, url: http://localhost:15000).

附录:

这里有一些链接帮助我弄清楚 ServiceLoader 的东西,以及为什么我很快决定不在我项目的不同 jar 中的同一个包中使用相同的 class: