不能只对某段代码生效,因为System.setOut()是JVM级静态操作,会全局覆盖所有线程的System.out输出,不区分业务逻辑、日志级别,且存在竞态与恢复风险。

直接用 System.setOut() 和 System.setErr() 配合 PrintStream 就能实现日志文件重定向,但必须注意:这会全局覆盖所有 System.out.println() 和未捕获异常的堆栈输出,不是“只重定向日志”,而是“劫持整个标准输出流”。
为什么不能只对某段代码生效?
System.setOut() 是 JVM 级别的静态操作,一旦设置,后续所有线程中调用 System.out 的地方都会写入该 PrintStream。它不区分业务逻辑、不识别日志级别、也不支持按需开关。
- 如果你在 main 开头调用了
System.setOut(new PrintStream("app.log")),那么连第三方库里的System.out调用也会进文件 - 如果程序是多线程的,且某个线程里又调用了
System.setOut(),会导致竞态 —— 输出可能错乱或被覆盖 - 无法同时保留控制台输出和文件输出(除非你自己封装一个双写
PrintStream)
如何安全地创建带自动刷新的 PrintStream 文件输出
默认 PrintStream 构造器不会自动 flush,容易造成日志丢失(尤其程序异常退出时)。必须显式启用 auto-flush,并确保使用带 charset 的构造函数避免中文乱码。
- 推荐写法:
PrintStream logStream = new PrintStream(new FileOutputStream("app.log", true), true, "UTF-8"); -
true第二个参数表示 auto-flush(遇到println、printf或换行符就刷缓冲区) -
"UTF-8"显式指定编码,否则 Windows 下默认用GBK,Linux/macOS 可能用UTF-8,跨平台必加 -
new FileOutputStream("app.log", true)的true表示追加模式,避免每次启动清空历史
重定向后怎么恢复控制台输出?
保存原始 PrintStream 引用是唯一可靠方式。JVM 启动时的 System.out 是 ConsoleOutputStream(底层封装了 FileDescriptor.out),不能靠重新 new 出来还原。
- 正确做法:
PrintStream originalOut = System.out;<br>System.setOut(new PrintStream(new FileOutputStream("app.log", true), true, "UTF-8"));<br>// ... 运行一段时间<br>System.setOut(originalOut); // 恢复 - 别写
System.setOut(new PrintStream(System.out))—— 这会创建新流指向已关闭/不可写的状态,大概率抛IOException - 如果需要“既写文件又写控制台”,得自己写个继承
PrintStream的类,重写write()方法分发到两个目标流
真正要用于生产环境的日志,应该用 log4j2、slf4j + logback 这类框架 —— 它们支持异步、滚动、分级、多目的地。用 PrintStream 重定向只是临时调试或极简脚本的权宜之计,而且一旦 setOut 后忘了恢复,后续测试或集成时可能发现控制台突然不打印了,排查起来反而更费时间。


















