删除对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