打印流实战:日志输出
你写过这样的代码吗?
java
// ❌ 初级写法:System.out.println 满天飞
System.out.println("用户登录");
System.out.println("用户名: " + username);
System.out.println("登录成功");
// ... 1000 行后 ...
System.out.println("出错了");这种写法的问题是:没有分级、没有格式、没有文件输出、一行代码改全局。
本节用 PrintWriter 实现一个实用的日志系统。
简单日志工具
java
public class SimpleLogger {
private static PrintWriter out = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("app.log", true), StandardCharsets.UTF_8)),
true); // autoFlush
public static void info(String msg) {
out.println("[" + timestamp() + "] [INFO] " + msg);
}
public static void error(String msg) {
out.println("[" + timestamp() + "] [ERROR] " + msg);
}
public static void error(String msg, Throwable t) {
out.println("[" + timestamp() + "] [ERROR] " + msg);
t.printStackTrace(out); // 异常栈输出到日志文件
}
private static String timestamp() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}
// 使用
SimpleLogger.info("程序启动");
SimpleLogger.error("出错了", new RuntimeException("测试异常"));日志分级
java
public enum LogLevel { DEBUG, INFO, WARN, ERROR }
public class Logger {
private static LogLevel currentLevel = LogLevel.INFO;
private static PrintWriter writer;
static {
try {
writer = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("app.log", true), StandardCharsets.UTF_8)),
true);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void setLevel(LogLevel level) {
currentLevel = level;
}
private static void log(LogLevel level, String msg) {
if (level.ordinal() < currentLevel.ordinal()) return;
String line = String.format("[%s] [%5s] %s",
timestamp(), level.name(), msg);
writer.println(line);
}
public static void debug(String msg) { log(LogLevel.DEBUG, msg); }
public static void info(String msg) { log(LogLevel.INFO, msg); }
public static void warn(String msg) { log(LogLevel.WARN, msg); }
public static void error(String msg) { log(LogLevel.ERROR, msg); }
private static String timestamp() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
}
}日志文件滚动
java
public class RollingLogger {
private PrintWriter writer;
private String basePath;
private long maxFileSize = 10 * 1024 * 1024; // 10MB
private int fileCount = 0;
public RollingLogger(String basePath) {
this.basePath = basePath;
openNewFile();
}
public synchronized void log(String level, String msg) {
if (writer == null) return;
// 检查文件大小
try {
FileChannel ch = ((FileOutputStream)writer).getChannel();
if (ch.size() >= maxFileSize) {
rotate();
}
} catch (IOException e) {
// 忽略
}
writer.printf("[%s] [%5s] %s%n",
timestamp(), level, msg);
}
private void rotate() throws IOException {
writer.close();
fileCount++;
openNewFile();
}
private void openNewFile() {
try {
String path = basePath + "." + fileCount + ".log";
writer = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path), StandardCharsets.UTF_8)),
true);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String timestamp() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
}格式化日志输出
java
public class FormattedLogger {
private PrintWriter writer;
public FormattedLogger(String path) throws IOException {
writer = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path), StandardCharsets.UTF_8)),
true);
}
// 表格形式日志
public void logTable(String... columns) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < columns.length; i++) {
sb.append(String.format("%-20s", columns[i]));
if (i < columns.length - 1) sb.append(" | ");
}
writer.println(sb);
}
// 分割线
public void logSeparator() {
writer.println("=".repeat(80));
}
}
// 使用
FormattedLogger logger = new FormattedLogger("table.log");
logger.logSeparator();
logger.logTable("ID", "姓名", "分数", "评级");
logger.logSeparator();
logger.logTable("001", "张三", "98", "A");
logger.logTable("002", "李四", "85", "B");
logger.logSeparator();记住这些教训:
不要用 System.out.println 做日志。 要分级、要格式化、要写文件。 生产环境用 SLF4J + Logback,这只是学习阶段的练习。
