javatomcat

Tomcat embed with JULI cannot separate webapp's servlet context logging from Catalina container logging


I'm using tomcat's JULI logger and the properties file to launch a Tomcat embed server, in which there is a single webapp named myapp on localhost. (working directory: /Users/me/TomcatEmbedTest)

public class Main {
    public static void main(String[] args) {
        Tomcat tomcat = new Tomcat();
        tomcat.setBaseDir(new File("resource").getAbsolutePath());
        tomcat.setPort(8080);
        tomcat.getConnector();
        tomcat.addWebapp("/myapp", new File("/Users/me/TomcatEmbedTest/resource/webapps/myapp").getAbsolutePath());
        try {
            tomcat.start();
        } catch (LifecycleException e) {
            throw new RuntimeException(e);
        }
    }
}

In order to use JULI, I used the following arguments:

-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager 
-Djava.util.logging.config.file=/Users/me/TomcatEmbedTest/resource/logging.properties

In the properties file I designate myapp's servlet context log to be logged using 3myapp.org.apache.juli.AsyncFileHandler, which logs to file with prefix myapp:

3myapp.org.apache.juli.AsyncFileHandler.prefix = myapp.

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/myapp].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/myapp].handlers = 3myapp.org.apache.juli.AsyncFileHandler

For readability, the full properties file is appended at the bottom of the question.

The test servlet only executes the following line:

req.getServletContext().log("========my web app's log=====");

For readability, the full servlet class is appended at the bottom of the question.

As I've configured in the properties file, when the application launches, it creates 3 log files in the log directory inside my tomcat-embed application.

.
├── catalina.2025-08-17.log
├── localhost.2025-08-17.log
└── myapp.2025-08-17.log

However, the localhost log and myapp log is just empty. And the ServletContext#log log in my servlet goes to catalina log.

When I deploy the same webapp inside a stand-alone tomcat, and use the same properties file, ServletContext#log log goes to separate log files declared in logging.properties file (in this case it is myapp.2025-08-17.log.

I wonder if this is a bug for tomcat-embed, or tomcat-embed inherently does not support separate logs for webapps' servlet context log?

My single test-purpose servlet:

@WebServlet("/test")
public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        req.getServletContext().log("========my web app's log=====");
        resp.getWriter().write("Hello, this is /test servlet!");
    }
}

my properties file:

handlers = java.util.logging.ConsoleHandler, 1catalina.org.apache.juli.AsyncFileHandler, 2localhost.org.apache.juli.AsyncFileHandler, 3myapp.org.apache.juli.AsyncFileHandler

.handlers = 1catalina.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler

1catalina.org.apache.juli.AsyncFileHandler.level = FINE
1catalina.org.apache.juli.AsyncFileHandler.directory = logs
1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina.
1catalina.org.apache.juli.AsyncFileHandler.maxDays = 90
1catalina.org.apache.juli.AsyncFileHandler.encoding = UTF-8

2localhost.org.apache.juli.AsyncFileHandler.level = FINE
2localhost.org.apache.juli.AsyncFileHandler.directory = logs
2localhost.org.apache.juli.AsyncFileHandler.prefix = localhost.
2localhost.org.apache.juli.AsyncFileHandler.maxDays = 90
2localhost.org.apache.juli.AsyncFileHandler.encoding = UTF-8

3myapp.org.apache.juli.AsyncFileHandler.level = FINE
3myapp.org.apache.juli.AsyncFileHandler.directory = logs
3myapp.org.apache.juli.AsyncFileHandler.prefix = myapp.
3myapp.org.apache.juli.AsyncFileHandler.maxDays = 90
3myapp.org.apache.juli.AsyncFileHandler.encoding = UTF-8

java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter
java.util.logging.ConsoleHandler.encoding = UTF-8

org.apache.catalina.core.ContainerBase.[Catalina].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].handlers = 1catalina.org.apache.juli.AsyncFileHandler

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/myapp].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/myapp].handlers = 3myapp.org.apache.juli.AsyncFileHandler

My dependencies:

.
├── tomcat-annotations-api-9.0.108-sources.jar
├── tomcat-annotations-api-9.0.108.jar
├── tomcat-embed-core-9.0.108-sources.jar
├── tomcat-embed-core-9.0.108.jar
├── tomcat-embed-logging-juli-9.0.0.M6-sources.jar
└── tomcat-embed-logging-juli-9.0.0.M6.jar


Solution

  • Since there is scarce documentation or other resources online, I debugged tomcat-embed source code myself, and I found the problem arises from inconsistent logger name creation between tomcat standalone and tomcat-embed.

    The logger names in tomcat's logging.properties like org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/myapp].level = INFO are composed in ContainerBase#getLogName in tomcat standalone, and Tomcat#getLoggerName in tomcat-embed.

    In tomcat-embed, the correct logger name in the properties file should be org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/myapp] instead of org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/myapp]

    This is because, as the following code shows, host.getParent() is StandardEngine[Tomcat] , whose name is Tomcat instead of Catalina.

    // Tomcat.java, getLoggerName
    private String getLoggerName(Host host, String contextName) {
        if (host == null) {
            host = getHost();
        }
        StringBuilder loggerName = new StringBuilder();
        loggerName.append(ContainerBase.class.getName());
        loggerName.append(".[");
        // Engine name
        loggerName.append(host.getParent().getName());
        loggerName.append("].[");
        // Host name
        loggerName.append(host.getName());
        loggerName.append("].[");
        // Context name
        if (contextName == null || contextName.isEmpty()) {
            loggerName.append('/');
        } else if (contextName.startsWith("##")) {
            loggerName.append('/');
            loggerName.append(contextName);
        }
        loggerName.append(']');
    
        return loggerName.toString();
    }
    

    Whereas in Tomcat standalone, the standard engine's name is Catalina .

    By changing logger names in the logging.properties to cater to tomcat-embed (changing original org.apache.catalina.core.ContainerBase.[Catalina].[localhost]. to org.apache.catalina.core.ContainerBase.[Tomcat].[localhost]. , the problem resolves and logging functions correctly.

    So I believe this should be clarified in tomcat-embed documentation, or this is a bug that needs correction.