在开发运维中,正确区分程序的标准输出(stdout)、标准错误(stderr)以及通过日志框架(如log4j)产生的日志,对于故障排查和系统监控至关重要。本文从实际编码角度,分别以Java和Python为例,讲解三者的使用、配置及重定向技巧。
## 1. 基础概念
- **stdout(标准输出)**:记录程序正常执行的结果、状态信息,通常用于呈现业务流程。
- **stderr(标准错误)**:专门记录错误信息、异常堆栈,与正常输出分离,便于快速定位问题。
- **log4j日志(以log4j-active.log为例)**:由日志框架管理的详细日志,支持级别(DEBUG/INFO/WARN/ERROR/FATAL)过滤,可输出到文件或控制台,适合长期存档和性能分析。
## 2. Java 中的实现
### 2.1 基础输出:System.out 与 System.err
直接使用`System.out.println`和`System.err.println`即可向对应流写入信息。典型场景:捕获异常后输出错误。
- public class StdOutErrExample {
- public static void main(String[] args) {
- System.out.println("This is a standard output message");
- System.err.println("This is a standard error message");
- try {
- int result = 10 / 0;
- } catch (ArithmeticException e) {
- System.err.println("Caught an exception: " + e.getMessage());
- }
- }
- }
复制代码 运行后,stdout和stderr默认均输出到控制台,但通过Shell重定向(如`java App 2> error.log`)可分别捕获。
### 2.2 使用Log4j(1.2.17)进行结构化日志
首先添加Maven依赖:- <dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>1.2.17</version>
- </dependency>
复制代码 配置日志级别和输出目标时,推荐通过代码动态配置(避免依赖配置文件)。以下示例创建控制台附加器和文件附加器,分别设置不同的级别阈值。
- import org.apache.log4j.*;
- public class AdvancedLog4jExample {
- private static final Logger logger = Logger.getLogger(AdvancedLog4jExample.class);
- public static void main(String[] args) {
- configureLog4j();
- logger.trace("TRACE level message");
- logger.debug("DEBUG level message");
- logger.info("INFO level message");
- logger.warn("WARN level message");
- logger.error("ERROR level message");
- logger.fatal("FATAL level message");
- try {
- throw new RuntimeException("Simulated exception");
- } catch (RuntimeException e) {
- logger.error("Caught exception", e);
- }
- }
- private static void configureLog4j() {
- // 控制台附加器:仅输出INFO及以上级别,格式包含时间、线程、级别、类名和消息
- ConsoleAppender consoleAppender = new ConsoleAppender();
- consoleAppender.setLayout(new PatternLayout("%d [%t] %-5p %c - %m%n"));
- consoleAppender.setThreshold(Level.INFO);
- consoleAppender.activateOptions();
- Logger.getRootLogger().addAppender(consoleAppender);
- // 文件附加器:输出到log4j-active.log,级别阈值DEBUG,追加方式
- try {
- FileAppender fileAppender = new FileAppender(
- new PatternLayout("%d %-5p [%c{1}] %m%n"),
- "log4j-active.log",
- true);
- fileAppender.setThreshold(Level.DEBUG);
- Logger.getRootLogger().addAppender(fileAppender);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
复制代码 运行后,控制台只会看到INFO及以上内容,而文件`log4j-active.log`会包含DEBUG及以上所有日志。
### 2.3 重定向System.out和System.err到文件
有时需要将第三方库的stdout/stderr输出捕获到独立日志文件中,可使用`System.setOut()`和`System.setErr()`。
- import java.io.*;
- public class RedirectIOExample {
- public static void main(String[] args) throws IOException {
- // 重定向标准输出到文件
- PrintStream outFile = new PrintStream(new FileOutputStream("stdout.log"));
- System.setOut(outFile);
- // 重定向标准错误到文件
- PrintStream errFile = new PrintStream(new FileOutputStream("stderr.log"));
- System.setErr(errFile);
- System.out.println("This goes to stdout.log");
- System.err.println("This goes to stderr.log");
- // 恢复原始流(通常在finally块中执行)
- System.setOut(System.out);
- System.setErr(System.err);
- }
- }
复制代码 此外,还可以通过继承PrintStream为输出添加前缀,用于区分日志来源。
## 3. Python 中的实现
Python使用`print()`默认输出到stdout,`sys.stderr.write()`输出到stderr。logging模块可替代基础打印。
- import sys
- import logging
- # 标准输出
- print("This is standard output")
- # 标准错误
- sys.stderr.write("This is an error message\n")
- # 使用logging模块(配置与Java log4j类似)
- logging.basicConfig(
- level=logging.DEBUG,
- format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
- handlers=[
- logging.FileHandler("app.log"),
- logging.StreamHandler(sys.stdout) # 同时输出到控制台
- ]
- )
- logger = logging.getLogger(__name__)
- logger.info("Info message from logging")
- logger.error("Error message with exception", exc_info=True)
复制代码 Python的logging模块支持多个handler(文件、控制台等),且可独立设置级别,功能上与log4j对应。
## 4. 最佳实践要点
- **分级使用**:开发环境开启DEBUG级别,生产环境建议INFO或WARN,避免性能开销。
- **异常必记**:捕获异常时使用`logger.error("描述", exception)`或`logging.exception("描述")`,保留完整堆栈。
- **分离文件**:将stdout、stderr分别重定向到不同文件(如`stdout.log`和`stderr.log`),便于监控系统分别告警。
- **定期轮转**:使用`DailyRollingFileAppender`(Log4j)或`RotatingFileHandler`(Python)防止日志无限增长。
- **避免Println**:生产代码不要使用`System.out.println`或`print`,应全部替换为日志框架,方便动态调整。
## 5. 总结
log4j(Java)与logging(Python)提供了灵活的日志管理:文件输出支持持久化,级别过滤降低噪声。而stdout/stderr作为操作系统级别的标准流,适合短流程脚本或与Shell管道配合。掌握三者的配置与重定向,能显著提升应用程序的可观测性和问题定位效率。 |