真正有效的拦截需分层设计:优先用UncaughtExceptionHandler兜底线程级异常;主动增强第三方调用的异常可见性;谨慎重定向System.err作辅助;配合日志与监控形成纵深防御。

直接重写 System.out 或 System.err 无法捕获“被第三方组件吞没的隐式异常”——因为这类异常根本没走到输出流。它们通常发生在 catch (Exception e) { } 空块、Future.get() 未调用、CompletableFuture 链中缺失 exceptionally() 等场景,连 printStackTrace() 都没执行,自然不会触发 System.err.println()。
真正有效的拦截,要分层设计,不依赖输出流本身:
优先用 UncaughtExceptionHandler 兜底线程级异常
这是最可靠、最低侵入的方式,能捕获所有未被业务代码显式处理的 Throwable:
- 为自定义线程池设置
ThreadFactory,在创建线程时绑定处理器 - 对
main线程、虚拟线程、ForkJoinWorkerThread 也统一配置 - 处理器中记录带上下文的日志(如线程名、任务ID、堆栈),并可触发告警
主动增强第三方调用的异常可见性
不能等异常被吞,而要在调用点就加固:
- 包装
Runnable/Callable:用safe(r -> {...})模板统一捕获并记录 -
CompletableFuture链中,每个thenApply后追加.exceptionally(e -> { log.error(...); return null; }) -
Future.get()调用必须包裹 try-catch,避免静默丢弃ExecutionException
谨慎使用 System.err 重定向作辅助兜底
仅当确认第三方库确实会调用 e.printStackTrace() 时,才考虑拦截:
- 创建自定义
PrintStream,重写println(Object):若obj instanceof Throwable,则结构化记录并上报 - 对
println(String)做关键词检测(如"Exception"、"Caused by:"),作为二级识别 - 务必缓存原始
System.err并线程安全转发非异常内容,避免破坏正常日志格式
配合日志框架与监控形成纵深防御
单点拦截不可靠,需多层协同:
- 将
UncaughtExceptionHandler和PrintStream捕获的异常,统一桥接到 SLF4J,注入 MDC 上下文(如 traceId、userId) - 在 Spring 中用
@ControllerAdvice拦截已知第三方 SDK 异常(如WeChatApiException),补充业务字段后转为标准响应 - 生产环境开启高频异常忽略告警,对连续出现的空 catch 自动提醒
本质上,这不是“重写输出流”的技术问题,而是“让异常无处可藏”的工程实践。

















