ulrich
ulrich

Reputation: 1593

Logging System.out.println to log file for single application

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

Answers (1)

Piotr P. Karwasz
Piotr P. Karwasz

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:

System.out to JULI redirection

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

Direct System.out to Log4j redirection

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

Related Questions