1 /* 2 * Copyright (c) 2025 Shenzhen Kaihong Digital. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package utils; 17 18 import java.io.BufferedReader; 19 import java.io.File; 20 import java.io.FileWriter; 21 import java.io.IOException; 22 import java.nio.charset.StandardCharsets; 23 import java.nio.file.Files; 24 import java.text.SimpleDateFormat; 25 import java.util.ArrayList; 26 import java.util.Collections; 27 import java.util.Date; 28 import java.util.List; 29 30 /** 31 * <h3>类名:该类用于xxx</h3> 32 * description ${description} 33 * 34 * @author ${USER} 35 * date 2025-02-28 36 * @since 2025-02-28 37 * @version 1.0 38 */ 39 public class LogUtils { 40 // 日志级别常量 41 /** 42 * 调试级别 43 */ 44 public static final int DEBUG = 1; 45 46 /** 47 * 信息级别 48 */ 49 public static final int INFO = 2; 50 51 /** 52 * 告警级别 53 */ 54 public static final int WARN = 3; 55 56 /** 57 * 错误级别 58 */ 59 public static final int ERROR = 4; 60 61 // 全局日志级别(默认 INFO) 62 private static int currentLevel = INFO; 63 64 // 其他原有配置(路径、文件大小限制等) 65 private static final String LOG_DIR = "logs"; 66 private static final String LOG_FILE = "app.log"; 67 private static final String DEFAULT_LOG_PATH = "./logs/app.log"; 68 private static final int DEFAULT_LOG_LEVEL = INFO; 69 private static final long MAX_SIZE = 10 * 1024 * 1024; // 10MB 70 private static final int MAX_FILES = 5; 71 private static FileWriter writer; 72 private static String currentLogPath = DEFAULT_LOG_PATH; 73 private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 74 75 static { initLogDir()76 initLogDir(); initWriter()77 initWriter(); 78 } 79 80 // 初始化日志目录 initLogDir()81 private static void initLogDir() { 82 File dir = new File(LOG_DIR); 83 if (!dir.exists()) { 84 boolean res = dir.mkdir(); 85 if (!res) { 86 System.out.println("mkdir " + LOG_DIR + " failed!"); 87 } 88 } 89 } 90 91 // 初始化写入器 initWriter()92 private static void initWriter() { 93 try { 94 File logFile = new File(LOG_DIR, LOG_FILE); 95 writer = new FileWriter(logFile, true); // 追加模式 96 } catch (IOException e) { 97 System.out.println("initWriter error: " + e.getMessage()); 98 } 99 } 100 101 /** 102 * 改名字 103 */ doRename(File src, int i)104 private static synchronized void doRename(File src, int i) { 105 if (src.exists()) { 106 File dest = new File(LOG_DIR, "app." + (i + 1) + ".log"); 107 boolean res = src.renameTo(dest); 108 if (!res) { 109 System.out.println("rollOver " + LOG_DIR + " failed!"); 110 } 111 } 112 } 113 114 /** 115 * 日志滚动 116 */ rollOver()117 private static synchronized void rollOver() { 118 try { 119 writer.close(); 120 for (int i = MAX_FILES - 1; i >= 1; i--) { 121 File src = new File(LOG_DIR, "app." + i + ".log"); 122 doRename(src, i); 123 } 124 boolean res = new File(LOG_DIR, LOG_FILE).renameTo(new File(LOG_DIR, "app.1.log")); 125 if (!res) { 126 System.out.println("rollOver " + LOG_DIR + " failed!"); 127 } 128 initWriter(); // 重新初始化写入器 129 } catch (IOException e) { 130 System.out.println("initWriter error: " + e.getMessage()); 131 } 132 } 133 134 /** 135 * 重新初始化 136 */ reinit()137 public static void reinit() { 138 // 1. 关闭现有日志流:ml-citation{ref="1,3" data="citationList"} 139 if (writer != null) { 140 try { 141 writer.close(); 142 } catch (IOException e) { 143 System.err.println("日志流关闭失败: " + e.getMessage()); 144 } 145 } 146 147 // 2. 恢复初始配置参数:ml-citation{ref="5,8" data="citationList"} 148 currentLogPath = DEFAULT_LOG_PATH; 149 currentLevel = DEFAULT_LOG_LEVEL; 150 151 // 3. 重新初始化日志文件:ml-citation{ref="1,3" data="citationList"} 152 try { 153 File logFile = new File(LOG_DIR, LOG_FILE); 154 writer = new FileWriter(logFile, true); 155 } catch (IOException e) { 156 System.err.println("日志文件初始化失败: " + e.getMessage()); 157 } 158 } 159 160 /** 161 * 清除日志 162 */ clear()163 public static void clear() { 164 if (writer != null) { 165 try { 166 writer.close(); 167 } catch (IOException e) { 168 System.err.println("日志流关闭失败: " + e.getMessage()); 169 } 170 } 171 172 File logFile = new File(LOG_DIR, LOG_FILE); 173 174 // 1. 处理单文件模式:ml-citation{ref="4,5" data="citationList"} 175 if (logFile.isFile()) { 176 if (logFile.delete()) { 177 System.out.println("日志文件已删除: " + currentLogPath); 178 } else { 179 System.err.println("文件删除失败: dir-" + LOG_DIR + " ,file-" + LOG_FILE); 180 } 181 return; 182 } 183 184 // 2. 处理目录模式:ml-citation{ref="3,4" data="citationList"} 185 if (logFile.isDirectory()) { 186 File[] logFiles = logFile.listFiles((dir, name) -> name.endsWith(".log")); 187 if (logFiles == null) { 188 return; 189 } 190 191 for (File file : logFiles) { 192 if (file.delete()) { 193 System.out.println("已删除: " + file.getAbsolutePath()); 194 } else { 195 System.err.println("删除失败: " + file.getAbsolutePath()); 196 } 197 } 198 } 199 } 200 201 202 // 设置日志级别(静态方法) setLevel(int level)203 public static synchronized void setLevel(int level) { 204 currentLevel = level; 205 } 206 207 // 读取日志级别(静态方法) getLevel()208 public static synchronized int getLevel() { 209 return currentLevel; 210 } 211 212 /** 213 * 写调试日志 214 * 215 * @param message 调试日志 216 */ debug(String message)217 public static void debug(String message) { 218 log(DEBUG, message); 219 } 220 221 /** 222 * 写信息日志 223 * 224 * @param message 信息日志 225 */ info(String message)226 public static void info(String message) { 227 log(INFO, message); 228 } 229 230 /** 231 * 写告警日志 232 * 233 * @param message 告警日志 234 */ warn(String message)235 public static void warn(String message) { 236 log(WARN, message); 237 } 238 239 /** 240 * 写错误日志 241 * 242 * @param message 错误日志 243 */ error(String message)244 public static void error(String message) { 245 log(ERROR, message); 246 } 247 248 // 核心日志记录逻辑(含分级判断) 249 /** 250 * 写日志 251 * 252 * @param level 日志等级 253 * @param message 告警日志 254 */ log(int level, String message)255 private static synchronized void log(int level, String message) { 256 if (level < currentLevel) { 257 // 低于当前级别则不记录:ml-citation{ref="3,6" data="citationList"} 258 return; 259 } 260 261 try { 262 File logFile = new File(LOG_DIR, LOG_FILE); 263 if (logFile.length() >= MAX_SIZE) { 264 rollOver(); 265 } 266 267 String timestamp = SDF.format(new Date()); 268 String levelName = getLevelName(level); 269 String logLine = String.format("%s [%s] %s%n", timestamp, levelName, message); 270 writer.write(logLine); 271 writer.flush(); 272 } catch (IOException e) { 273 System.out.println("log write error: " + e.getMessage()); 274 } 275 } 276 277 // 将级别数值转换为名称 278 /** 279 * 获取日志界壁 280 * 281 * @param level 日志等级 282 * @return 日志级别名称 283 */ getLevelName(int level)284 private static String getLevelName(int level) { 285 return switch (level) { 286 case DEBUG -> "DEBUG"; 287 case INFO -> "INFO"; 288 case WARN -> "WARN"; 289 case ERROR -> "ERROR"; 290 default -> "UNKNOWN"; 291 }; 292 } 293 294 /** 295 * 读取日志文件内容(支持分页) 296 * 297 * @param pageNum 页码(从1开始) 298 * @param pageSize 每页行数 299 * @return 按时间倒序排列的日志列表(最近日志在前) 300 */ readLog(int pageNum, int pageSize)301 public static List<String> readLog(int pageNum, int pageSize) { 302 return readLog(currentLogPath, pageNum, pageSize); 303 } 304 305 /** 306 * 指定路径,大小读取日志文件内容 307 * 308 * @param logPath 日志文件路径 309 * @param pageNum 页码 310 * @param pageSize 每页行数 311 * @return 文件内容列表 312 */ readLog(String logPath, int pageNum, int pageSize)313 public static synchronized List<String> readLog(String logPath, int pageNum, int pageSize) { 314 File logFile = new File(logPath); 315 List<String> allLines = new ArrayList<>(); 316 317 try (BufferedReader reader = Files.newBufferedReader(logFile.toPath(), StandardCharsets.UTF_8)) { 318 String line; 319 while ((line = reader.readLine()) != null) { 320 allLines.add(line); 321 } 322 } catch (IOException e) { 323 return Collections.singletonList("日志读取失败: " + e.getMessage()); 324 } 325 326 // 倒序处理(最近日志在前) 327 Collections.reverse(allLines); 328 329 // 分页计算 330 int start = (pageNum - 1) * pageSize; 331 int end = Math.min(start + pageSize, allLines.size()); 332 return start >= allLines.size() ? new ArrayList<>() : allLines.subList(start, end); 333 } 334 335 /** 336 * 全量读取日志文件 337 * 338 * @return 返回日志读取内容 339 */ readLog()340 public static List<String> readLog() { 341 return readLog(currentLogPath, 1, Integer.MAX_VALUE); 342 } 343 } 344