I am having some issues getting the right "planetary alignment" of
file locations & configuration settings to get logging to work how
I'd ideally like it. I think I'm having some undesireable
interactions with static members and classloaders and such. This
post is a bit of a novel, sorry about that, but I like to give all
the details instead of having everyone incorrectly assume what is
going on or worse yet being one of those annoying "plz hlp me,
kthxbai" types that expects some kind of miracle.
First off, here is some relevant system configuration information:
RHEL5
Java 1.6
Tomcat 7.0.30
I have configured Tomcat to have 4 Host entries, so 4 webapp/contexts
total. It's a clustered setup as well, but for this task that is not
relevant, only that there are multiple contexts.
And now some back story to make things more clear:
I have a code library in a JAR and a pile (read: 5 digit quantity) of
JSP files which represent a 10+ year evolution of code touched by
many different developers. The JSPs are full of scriptlets with Java
code (I know that's less than ideal, hence the cleanup efforts) and
both the JSP & Java code in the JAR are littered with
System.out.println() & Exception.printStackTrace() calls for various
logging and/or debugging purposes. I have been working to modernize
this to use a proper logging framework, and have standardized on
log4j as it is what has been used in several other applications I
have developed -- some of which are now utility libraries shared with
and used by the web applications.
Previously (Tomcat 5.0.x) we had all internal logging going to the
catalina.out, and each webapp's logging going to a file local to the
filesystem heirarchy used by said webapp. The webapps themselves use
the same backend codebase and it must always be in sync, so to avoid
complications we always deployed the JAR to the common lib
directory. Despite being in the common area, the System.out output
was still being captured by the swallowOutput=true setting, and thus
the "shared" logging statements were still going to the respective
webapp logfile that made calls to those libraries.
When first upgrading Tomcat to 7.x we ran into some issues with
getting the logs to the right places because of the new JULI stuff
xand whatnot, and eventually got it to work by configuring log4j at
the webapp level, and still using the swallowOutput=true to capture
the System stuff. This was working great, and all System.out content
was sent to the log4j appender local to the webapp. This was still
an ugly way of doing logging though, so time to get rid of the bad practices.
So now to make use of the logging framework:
I first decided to tackle the JAR, went the shared code library and
re-wrote every call (a lot!) to System.out.println() and
Exception.printStackTrace() to use a proper log4j method indicating a
logging level and passing exceptions when appropriate. However, when
I deployed the new JAR file my results were not exactly as expected/desired.
Some of the early logging was ending up in the catalina.out, along
with some log4j configuration warnings. I converted the relevant
class to use JULI instead as it was an extention of the DeltaManager
from Tomcat, and thus created prior to the context itself, and used
only by Tomcat. The only problem here is that it referenced some
utility classes that were shared, and thus those still produced the
log4j startup errors. So then I converted tomcat to use log4j for
internal logging as well, which cleared up those errors, and made for
a clean console.
I set up a quick testing JSP which made System.out calls, log4j
calls, and a call to a shared Java class which did a log4j call. I
put this testing page on two different webapps and could hit each one
and determine if the logging destinations were correct.
I went on to test more logging and found that calls to log4j within
the utility libraries (called from JSPs) were either not showing up
anywhere at all, or all going to the webapp logs of the first loaded
webapp, regardless of which one actually sourced them. This, I
realized, was due to only having the log4j/etc. JAR files at the
common classloader level.
So I then ended up with a copy of log4j (and the JULI adapter stuff
needed) in the common lib, as well as in each WEB-INF/lib. The
System.out calls (the ones remaining, not yet converted) in the JSP
files are still being routed to their respective local/per-webapp
logs, so that's good. Calls to log4j directly from the JSP files
also lead to the local log files, and I was able to get the URI as
part of the logformat, so that's good. Calls to log4j from the
shared Java classes, no so much.
FWIW, I am using typical static logger declarations such as this in
my Java classes:
private static final Logger log = Logger.getLogger(LogTest.class);
So that brings us to the current issue:
No matter which webapp makes a call to the shared library, the log4j
calls within are routed into the main tomcat log (same one Tomcat's
internal logging goes to now). This is bad for 3 reasons -- 1) I
can't distinguish which webapp triggered it, 2) the messages are not
inline with any logging from the calling JSP, 3) the internal logging
is node specific whereas the webapp logs are shared among the cluster nodes.
After doing much research on this, it seems to once again come down
to which classloader is being used. So the next thing I tried was to
put my shared JAR into each WEB-INF/lib as well. At first this seems
to solve everything as all output from my testing JSP (System, log4j,
shared Java doing log4j) ends up in the local log file for each
webapp, all in proper sequence and everything. Well, System.out
seems to be buffered until the request ends, but that's something I
can live with until it's gone.
Then I try to actually use the webapp, and the first page I hit blows up.
java.lang.ClassCastException:
my.custom.StandardSessionFacade cannot be cast to
my.custom.StandardSessionFacade
What? It can't be casted to its own type? Well once again the
classloader discrepancy causes a problem. See, the sessions are
created by the Manager (common classloader) level and I'm trying to
reference it at the local (webapp classloader) level. I believe that
since the local vs. common classes themselves are different
class-instances (classloader instances that is, not "instance of a
class" aka an object) it causes some sort of sandboxing violation,
thus the CCE.
So maybe the solution there is to split out the small number of
classes loaded at the common level (namely those that extend the
tomcat Manager/Session/etc.) and then deploy the rest of my shared
library at a per-webapp level. The main problem I have with this is
that those common level classes also use some of the shared library
code. And some of those shared items I specifically do not want a
per-classloader instance of (sort of like JVM-scoped singletons and
pure utility classes with all static members/methods), but do still
want their runtime logging to be per-webapp.
There are other benefits to only having this shared library at the
common level such as to ensure/enforce correct & consistent versions
of libraries across webapps, reduce deployment times/complexity, and
to an extent improve performance and/or reduce memory usage.
So in summary, I'm just trying to get shared libraries to have their
log4j-based logging written to the webapp-specific log files of the
calling webapp. Is this possible? If so, what's the trick?
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org
For additional commands, e-mail: users-h...@tomcat.apache.org