Java util Logging生成.lck文件及日志类名行号异常问题咨询
问题原因分析
1. .lck文件生成原因
java.util.logging(JUL)的FileHandler为了避免多进程同时写入同一个日志文件引发冲突,会自动为打开的日志文件生成对应的.lck锁文件。正常情况下JVM优雅退出时FileHandler会自动释放锁并删除该文件,若JVM异常崩溃、未主动关闭FileHandler就会导致锁文件残留。
2. 日志行号/类名显示错误原因
JUL的日志记录器默认会抓取直接调用Logger输出方法的代码位置作为日志来源,你的业务代码调用的是LogUtility.info(),最终是在LogUtility类内部调用的logger.info(),所以JUL只会识别到LogUtility的位置,无法向上追溯到实际的业务调用方。
优化解决方案
核心优化点
- 调整初始化顺序,保证日志路径提示信息可以正常写入文件
- 增加JVM关闭钩子,主动关闭
FileHandler避免锁文件残留 - 优化日志输出逻辑,自动抓取实际业务调用方的类名、方法名、行号
- 兼容跨平台路径,避免仅支持Windows系统
- 新增日志目录自动创建逻辑,避免目录不存在时写入报错
优化后代码
import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.FileHandler; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; public class LogUtility { private static final LogUtility INSTANCE = new LogUtility(); private final Logger logger = Logger.getLogger("LogUtility"); private FileHandler fileHandler; private LogUtility() { // 优先用跨平台通用的用户目录获取方式 String userHomePath = System.getProperty("user.home"); String logDirPath; // Windows系统单独适配原有日志路径 if (System.getProperty("os.name").toLowerCase().contains("win")) { logDirPath = userHomePath + File.separator + "AppData" + File.separator + "LocalLow" + File.separator + "Sun" + File.separator + "Java" + File.separator + "Deployment" + File.separator + "log"; } else { // 非Windows系统默认在用户目录下创建.applogs文件夹存日志 logDirPath = userHomePath + File.separator + ".applogs"; } // 自动创建不存在的日志目录 File logDir = new File(logDirPath); if (!logDir.exists()) { logDir.mkdirs(); } try { String dateTimeStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); String logFilePath = logDirPath + File.separator + "app_" + dateTimeStamp + ".log"; // 第二个参数控制是否追加写入,false表示每次启动生成新日志文件,可按需调整 fileHandler = new FileHandler(logFilePath, false); fileHandler.setFormatter(new SimpleFormatter()); logger.addHandler(fileHandler); // 初始化完成后再打印路径,确保日志写入文件 info("Log Path -->" + logFilePath); } catch (SecurityException | IOException e) { e.printStackTrace(); } // 注册JVM关闭钩子,退出时主动释放文件锁 Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (fileHandler != null) { fileHandler.close(); } })); } public static LogUtility getInstance() { return INSTANCE; } public void info(String msg) { // 遍历堆栈获取实际业务调用方信息 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); // stackTrace[0]是getStackTrace方法本身,stackTrace[1]是当前info方法,stackTrace[2]就是业务调用方 StackTraceElement caller = stackTrace[2]; // 用logp方法手动指定调用方信息,覆盖默认的类名识别逻辑 logger.logp(Level.INFO, caller.getClassName(), caller.getMethodName(), "Line " + caller.getLineNumber() + ": " + msg); } }
额外优化建议
- 如果项目允许引入第三方依赖,建议替换为
SLF4J+Logback的日志框架,比原生JUL功能更丰富、配置更灵活,不需要自行处理行号识别、锁文件这类底层问题 - 可以自定义
Formatter修改日志输出格式,调整为更易读的结构化格式,比如固定顺序输出时间、日志级别、类名、方法名、行号、消息内容
内容的提问来源于stack exchange,提问作者Karthikeyan
相关产品推荐
相关产品推荐

