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
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.