Custom java.util.logging Handler in tomcat
We have some common logging configuration between all of the webapps on a given system that we are trying to externalize to the level of tomcat, rather than trying to handle at the level of the individual webapp. The webapps that are using java.util.logging
are proving to be somewhat challenging because we have a custom handler, and there doesn't seem to be an obvious way to get the custom handler to play nicely with tomcat's classloaders. This is all in the prototype stage at the moment.
Preliminary: Tomcat 7.0.32, Java 6. Default tomcat 7 install with one REST service deployed and nothing funny in the configuration.
First, roughly following the advice in this answer, I created the custom handler and put the jar in $CATALINA_HOME/lib
and confirmed that said directory was in the right directory and that the common.loader
included this directory:
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
Then I modified the logging.properties
file and added the handler:
handlers = 1catalina.org.apache.juli.FileHandler, 2localhost.org.apache.juli.FileHandler, 3manager.org.apache.juli.FileHandler, 4host-manager.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler, my.custom.Handler
When I run ./startup.sh
, however, I get the following:
[Loaded java.io.PrintWriter from /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/classes.jar]
[Loaded java.util.logging.StreamHandler from /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/classes.jar]
[Loaded java.util.logging.ConsoleHandler from /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/classes.jar]
Handler error
java.lang.ClassNotFoundException: my.custom.Handler
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at org.apache.juli.ClassLoaderLogManager.readConfiguration(ClassLoaderLogManager.java:521)
at org.apache.juli.ClassLoaderLogManager.readConfiguration(ClassLoaderLogManager.java:464)
at org.apache.juli.ClassLoaderLogManager.readConfiguration(ClassLoaderLogManager.java:288)
at java.util.logging.LogManager$2.run(LogManager.java:267) [...]
(This is with JAVA_OPTS=-verbose:class
).
I have seen the relevant classes loaded later, but this does not appear to be consistent and is probably an artifact of the aforementioned REST service (which was using it directly).
I can get everything to work correctly if I add the jar to the CLASSPATH
directly, but this seems to be generally discouraged as opposed to modifying the loaders.
Is there something particular that I am missing in how to cleanly add a custom java.util.logging.Handler
(and, later, a formatter) to the classloader before logging.properties
is read?
Alternatively, if I'm flat barking up the wrong tree, I'd take a better approach to the problem of a shared logging configuration between multiple webapps with java.util.logging
.
For the Tomcat-wide custom logging you need to inject your class into the Tomcat bootstrap ClassLoader. Thus jar with custom Handler and required dependencies have to be put into the startup script CLASSPATH. I'd advice to a add custom script at $CATALINA_BASE/bin/setenv.sh, i.e.
#!/bin/sh
CLASSPATH="$CATALINA_BASE/bin/myhandler.jar"
or you can collect required jars dynamically as script variables are loaded during the Tomcat start-up.