views:

535

answers:

12

I maintain an application which acts as a container for multiple individual programs. These programs have their own dedicated logging facility, i.e. everything they log does to a special log file.

Nevertheless, application developers seem to love to throw System.out.println and e.printStackTrace calls all over, making it impossible to maintain a clean console when running the container.

How can I prevent these applications from polluting System.out and System.err?


Implementation notes:

  • the applications use Log4j for logging;
  • the container also uses the console for logging, but it is strictly reserved for lifecycle events and problems, so I still need the console;
  • the applications are loaded using a custom classloader, but no security checks are applied.


Update:

Simply redirecting System.out would not work since it redirects all output, so something like this fails:

    System.setOut(new PrintStream(new OutputStream() {

        @Override
        public void write(int b) {

            throw new Error("Not on my watch you don't");

        }
    }));

    Logger logger = Logger.getLogger(Runner.class);
    logger.info("My log message");

This should succeed.

Update 2:

The applications are loaded and configured using code similar to

App app = new UrlClassLoader(...).loadClass(className)).newInstance();
app.setLogger(loggerForClass(app));

Log4j is loaded from the system class loader.

+5  A: 

Use aversion therapy. A visit from "The Inspectors" is scheduled whenever any code is checked in containing unpleasant constructs.

Nice cubicle you got ere, be shame if anyfing appened to it.
djna
Well ... no thanks :-) Although it was worth a thought.
Robert Munteanu
Wow, this suggestion solved my completely unrelated problem too... thanks! ;-)
Andrzej Doyle
I also have another useful pattern: "Reward", it involves Red Wine, party hats, and a visit from "The Choir". "Halleluia!"
djna
+6  A: 

While Java defines a standard System.out and System.err, these can be overwritten with your own streams. See http://www.devx.com/tips/Tip/5616

Basically you can set up new streams that either pipe to the logging, or simply let data flop off into nothingness. My preference would be the latter, as it would instantly cure developers from relying on System.out and err as anything they write there just disappears.

**Update: I just reread your stipulations in the question and see you still need the console for the container application. This might still work if you write a wrapper around the standard stream so you can check each call and see if it is coming from the parent application (and pass it on) or a child application (and block it)

Mike Clark
+10  A: 

You can use System.setOut() and System.setErr() to redirect stdout and stderr to instances of PrintStream.

thirtyseven
Thanks. Please see my update.
Robert Munteanu
+12  A: 

Assuming that you can control your containers output you can do the following:

import java.io.*;
public class SysOut {
    public static void main(String[] args) throws Exception {
            PrintStream pw = new PrintStream(new FileOutputStream("a.txt"));
            PrintStream realout = System.out;
            System.setOut(pw);
            System.out.println("junk");
            realout.print("useful");
}
}

$ java SysOut 
useful
$ cat a.txt 
junk
disown
Thanks. Please see my update.
Robert Munteanu
+1  A: 

Convert the System.out and System.err streams to special implementations that throw a RuntimeException("Use logging instead of System.out") every time a character is written.

If your container is important, they will get the idea pretty quickly :)

(For extra bonus throw OutOfMemoryException instead ;-))

Thorbjørn Ravn Andersen
Thanks. Please see my update.
Robert Munteanu
In that case please give an example of how to distinguish between output you like, and output you do not like. Are some classes allowed to do this and others not?
Thorbjørn Ravn Andersen
If you have the source for the container then why not refactor it to save and use the original streams, THEN you can replace System.out and System.err?
Thorbjørn Ravn Andersen
+1  A: 

What I have done is redirect the PrintStream for System.out and System.err to commons-logging as INFO and ERROR level logging respectively.

This gets trickier if you want some threads to be able to write to the console or you want logs to go to the console as well but it can be done.

Peter Lawrey
Yes, I'm in the 'trickier' area.
Robert Munteanu
You can make the code reentrant. i.e. set a flag when OutputStream.write is called. if the flag is not set, print to the loggers. If it is set, print as normal as it must have come from the logger.
Peter Lawrey
Note: You have to unset the flag in a finally block. ;)
Peter Lawrey
A: 

We use the log4j trick but log to separate files (stdout.log, stderr.log). It's not useful to have their output mixed in with the parts that actually understand logging...

M1EK
+1  A: 

You can actually get and store System.out/err before replacing them.

OutputStream out=System.getOut();  // I think the names are right
System.setOut(some predefined output stream, null won't work);
out.println("Hey, this still goes to the output");
System.out.println("Oh noes, this does not");

I've used this to intercept all the System.out.println's in the codebase and prefix each line of output with the method name/line number it came from.

Bill K
+1  A: 

Close the System.out and System.err streams.

Javamann
Thanks. Please see my update.
Robert Munteanu
+3  A: 

If you have a headless build mechanism, ant or such like then you could add CheckStyle to the build and configure checkstyle to fail the build if it finds any System.out.println or e.printStackTrace in the code.

If you don't have a headless build I would recommend that you build one as it means you have repeatable, predictable builds.

Michael Wiles
+3  A: 

System.setOut will redirect all output - but the PrintStream you supply can decide how the output is handled. Thus I'm sure you could provide such a stream that would only actually print statements from your application.

The only tricky part really is being able to detect what's a valid call and what's not. A working, but likely very slow way to do this, would be to call Thread.currentThread().getStackTrace() and see what code (or package, at least) is calling you (simply returning if it's not a valid one). I wouldn't recommend this though as the performance hit would be staggering, especially doing this on every byte read.

A better idea might be to set a ThreadLocal flag in all of your valid, container threads. Then you can implement the PrintStream something like the following:

public class ThreadValidity extends ThreadLocal<Boolean>
{
    private static final INSTANCE = new ThreadValidity();

    @Override Boolean initialValue() { return false; }
    public static ThreadValidity getInstance() { return INSTANCE; }
}

class VerifyingPrintStream extends PrintStream
{
    private boolean isValidThread()
    {
        return ThreadValidity.instance().get();
    }

    public void println(String s)
    {
        if (!isValidThread()) return;
        super.println(s);
    }

    public void println(Object o)
    {
        if (!isValidThread()) return;
        super.println(o);
    }

    // etc
}

Alternatively, if you're able to change the printlns in the container code, things get easier. You can hand off all the console writes to a specific worker; and have this worker "steal" System.out (store it in its own field and use it directly for writing output) while setting the actual System.out to a no-op writer.

Andrzej Doyle
A: 

The key here is to configure log4j before redirecting the output streams, e.g.

BasicConfigurator.configure();
System.setOut(...);
System.setErr(...);

System.out.println("I fail");
Logger.getLogger(...).info("I work");
Robert Munteanu