删除对java中System.out的访问

删除对java中System.out的访问,java,logging,log4j,Java,Logging,Log4j,我维护一个应用程序,它充当多个单独程序的容器。这些程序有自己的专用日志记录功能,也就是说,它们将所做的一切记录到一个特殊的日志文件中 然而,应用程序开发人员似乎喜欢到处抛出System.out.println和e.printStackTrace调用,这使得在运行容器时无法保持干净的控制台 如何防止这些应用程序污染System.out和System.err 实施说明: 应用程序使用Log4j进行日志记录 容器还使用控制台进行日志记录,但它严格保留用于生命周期事件和问题,因此我仍然需要控制台 应

我维护一个应用程序,它充当多个单独程序的容器。这些程序有自己的专用日志记录功能,也就是说,它们将所做的一切记录到一个特殊的日志文件中

然而,应用程序开发人员似乎喜欢到处抛出
System.out.println
e.printStackTrace
调用,这使得在运行容器时无法保持干净的控制台

如何防止这些应用程序污染
System.out
System.err


实施说明:

  • 应用程序使用Log4j进行日志记录
  • 容器还使用控制台进行日志记录,但它严格保留用于生命周期事件和问题,因此我仍然需要控制台
  • 应用程序是使用自定义类加载器加载的,但不应用任何安全检查

更新:

简单地重定向
System.out
将不起作用,因为它重定向所有输出,因此类似的操作失败:

    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");
这应该会成功

更新2:

应用程序的加载和配置代码与

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

Log4j从系统类加载器加载。

使用厌恶疗法。每当签入任何包含不愉快结构的代码时,都会安排“检查者”的访问

Nice cubicle you got ere, be shame if anyfing appened to it.

虽然Java定义了一个标准的System.out和System.err,但可以用您自己的流覆盖它们。看

基本上,您可以设置新的流,或者通过管道传输到日志记录,或者简单地让数据变成虚无。我更倾向于后者,因为它可以立即解决开发人员依赖System.out和出错的问题,因为他们在那里编写的任何东西都会消失

**更新:
我只是重新阅读了您在问题中的规定,发现您仍然需要容器应用程序的控制台。如果您围绕标准流编写一个包装器,这样您就可以检查每个调用,看看它是来自父应用程序(并将其传递给)还是来自子应用程序(并阻止它)

您可以使用
System.setOut()
System.setErr()
要将
stdout
stderr
重定向到
PrintStream

的实例,假设您可以控制容器输出,则可以执行以下操作:

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
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
}

将System.out和System.err流转换为特殊实现,这些实现在每次写入字符时抛出RuntimeException(“使用日志记录而不是System.out”)

如果你的容器很重要,他们会很快想到:)


(对于额外的额外奖励,请改为抛出MemoryException;-)

我所做的是将System.out和System.err的打印流重定向到commons日志,分别作为信息和错误级别日志记录


如果您希望某些线程能够写入控制台,或者希望日志也可以写入控制台,但这是可以做到的,那么这就变得更加棘手了。

我们使用log4j技巧,但日志要分开文件(stdout.log、stderr.log)。将它们的输出与真正理解日志的部分混合在一起是没有用的…

在替换它们之前,您可以实际获取并存储System.out/err

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");

我已经用它截取了代码库中的所有System.out.println,并在每一行输出前加上它来自的方法名/行号。

关闭System.out和System.err流。

如果您有无头构建机制,ant或类似的工具,然后您可以将CheckStyle添加到构建中,并将CheckStyle配置为在代码中找到任何System.out.println或e.printStackTrace时使构建失败


如果您没有无头构建,我建议您构建一个,因为这意味着您有可重复、可预测的构建。

系统。设置将重定向所有输出,但您提供的打印流可以决定如何处理输出。因此,我确信您可以提供这样一个流,它实际上只打印应用程序中的语句

唯一棘手的部分是能够检测什么是有效呼叫,什么不是。一种可行但可能非常缓慢的方法是调用
Thread.currentThread().getStackTrace()
,查看调用您的是什么代码(或至少是包)(如果不是有效代码,只需返回)。不过我不建议这样做,因为性能的影响将是惊人的,尤其是在每次读取字节时都这样做

更好的办法可能是在所有有效的容器线程中设置ThreadLocal标志。然后您可以实现PrintStream,如下所示:

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
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
}
公共类ThreadValidity扩展ThreadLocal
{
private static final INSTANCE=new ThreadValidity();
@重写布尔初始值(){return false;}
公共静态ThreadValidity getInstance(){return INSTANCE;}
}
类VerifyingPrintStream扩展了PrintStream
{
私有布尔值isValidThread()
{
返回ThreadValidity.instance().get();
}
公共void println(字符串s)
{
如果(!isValidThread())返回;
super.println(s);;
}
公共void println(对象o)
{
如果(!isValidThread())返回;
super.println(o);
}
//等
}

或者,如果您能够更改容器代码中的
println
s,事情就会变得简单。您可以将所有控制台写入操作移交给特定的工作者;在将实际System.out设置为无操作写入器时,让该工作者“窃取”System.out(将其存储在自己的字段中并直接用于写入输出)。

这里的关键是在重定向输出流之前配置log4j,例如

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

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

这并不是一个真正的答案,但是:作为一名开发人员,我也倾向于这样做,因为检查控制台比挖掘日志文件更容易。因此,当我签入时,我首先执行查找/替换:搜索
System.out
(替换为
log.debug()
),搜索
S