Reputation: 1593
We have multiple applications on a tomcat which are using System.out.println statements logging to catalina.out. There is a single application which creates a lot of log statements so I would like to log those application outpout to a separate logfile instead.
I have created a log4j.xml setup which logs only WARN level to catalina.out. But the RollingFileAppender does not yet work for the System.out statements and I am not sure, what to change.
I would like to not touch the other applications. Is that possible somehow?
<Configuration status="INFO">
<Appenders>
<Console name="stdout" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36}: %msg%n" />
</Console>
<RollingFile
name="logFile"
fileName="../logs/app/log.log"
filePattern="../logs/app/log.%d{yyyy-MM-dd}.log.gz"
ignoreExceptions="false">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<DefaultRolloverStrategy max="31" />
</RollingFile>
<Async name="logFile_async">
<AppenderRef ref="logFile"/>
</Async>
</Appenders>
<Loggers>
<Root level="WARN">
<AppenderRef ref="stdout" />
</Root>
<Logger name="my.company.logger" level="DEBUG">
<AppenderRef ref="logFile_async"/>
</Logger>
</Loggers>
</Configuration>
Upvotes: 1
Views: 2150
Reputation: 16045
As already stated in the comments, printing to System.out
is not a proper way to log and it is difficult to redirect the data sent there to a proper logging framework.
You have two choices:
You can set the swallowOutput
attribute of a context to true (cf. documentation), which will redirect the standard output and error of the application to the JULI logging system.
The log4j2-appserver.jar
has a JULI implementation, that sends all logs to Log4j2 (see documentation).
You can define PrintStream
to Logger
adapter like this:
public class LogPrintStream extends PrintStream {
private static class LogOnFlush extends ByteArrayOutputStream {
// WeakHashMap so that we don't keep a reference to the classloaders.
protected final Map<ClassLoader, Logger> classLoaderLoggers = new WeakHashMap<>();
public LogOnFlush() {
// NOP
}
@Override
public void flush() {
final String message = toString();
if (message != null && message.length() > 0) {
Logger logger = getLogger();
logger.debug(message);
}
reset();
}
public Logger getLogger() {
final ClassLoader cl = Thread.currentThread().getContextClassLoader();
Logger logger = classLoaderLoggers.get(cl);
if (logger == null) {
final String name;
if (cl instanceof WebappClassLoaderBase) {
final WebappClassLoaderBase webCl = (WebappClassLoaderBase) cl;
name = String.format("%s.[%s].[%s]", LogPrintStream.class.getName(), webCl.getHostName(), webCl.getContextName());
} else {
name = String.format("%s.[%08x]", LogPrintStream.class.getName(), cl.hashCode());
}
logger = LogManager.getLogger(name);
classLoaderLoggers.put(cl, logger);
}
return logger;
}
}
public LogPrintStream() {
super(new LogOnFlush(), true);
}
}
and use it to replace System.out
through System#setOut
at a very early stage of the server's startup. You can, e.g. use a LifecycleListener
like this:
public class SystemOutReplaceListener implements LifecycleListener {
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (Lifecycle.AFTER_INIT_EVENT.equals(event.getType())) {
System.setOut(new LogPrintStream());
}
}
}
Warning: in this example the output to System.out
is sent to Log4j2. Care must be taken, so that Log4j2 will not forward its logs to System.out
.
Upvotes: 1