0

0

使用 jpackage 打包后 Log4j2 日志失效的解决方案

DDD

DDD

发布时间:2025-11-06 18:40:01

|

917人浏览过

|

来源于php中文网

原创

使用 jpackage 打包后 Log4j2 日志失效的解决方案

本文旨在解决使用 jpackage 打包 java 应用程序为 windows exe 后,log4j2 日志系统无法正常工作的问题,尤其是在日志路径依赖于应用程序根目录的自定义系统属性时。核心问题在于 log4j2 记录器(logger)的初始化时机与日志配置重载操作之间的冲突。通过延迟记录器的实例化,确保在配置完全加载并重载之后才获取记录器实例,可以有效解决此问题,从而使日志文件按预期写入应用程序的指定目录。

背景与问题描述

在开发 Java 应用程序时,日志功能是不可或缺的一部分。Log4j2 作为一个功能强大的日志框架,常被用于管理应用程序的日志输出。然而,当使用 jpackage 工具将 Java 应用程序打包成 Windows 可执行文件(EXE)后,可能会遇到一个棘手的问题:应用程序在直接运行 JAR 包时日志功能一切正常,但运行生成的 EXE 文件时却无法创建日志文件或日志文件创建位置不正确。

具体表现为,如果日志配置中依赖于一个自定义的系统属性(例如 app.root)来指定日志文件的存放路径,当该属性在 main 方法中设置并尝试重载 Log4j2 配置后,EXE 版本依然无法正确解析路径,导致日志文件被创建在应用程序的当前工作目录,而非预期的应用程序根目录。

本教程将以 Java 17、Log4j2 2.19.0、SLF4J 2.0.4 和 Maven Shade Plugin 3.2.4 为例,深入探讨此问题并提供一个可靠的解决方案。

初始配置与问题重现

为了实现日志文件存放在应用程序根目录,通常会在 log4j.properties 文件中定义一个使用系统属性的路径,例如:

# Root logger option
log4j.rootLogger=INFO, DEBUG, file, stdout

# Direct log messages to a log file
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=${app.root}/application.log
log4j.appender.file.MaxFileSize=200KB
log4j.appender.file.MaxBackupIndex=4
log4j.appender.file.layout.ConversionPattern=%i %-6p [%-30c{1} :%4L]  %m%n
log4j.appender.file.layout=MyPatternLayoutWithQualifiedPath

# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout.ConversionPattern=%i %-6p [%-30c{1} :%4L]  %m%n
log4j.appender.stdout.layout=MyPatternLayoutWithQualifiedPath

在应用程序的 main 方法中,我们会获取应用程序的根目录,并将其设置为 app.root 系统属性,然后重载 Log4j2 配置,以确保新的日志路径生效:

import org.apache.logging.log4j.core.LoggerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.net.URL;

public class Main {

    // 问题所在:静态Logger在main方法执行前初始化
    private static final Logger log = LoggerFactory.getLogger(Main.class); 

    public static void main(String[] args) {
        // 启用 Log4j1 兼容模式
        System.setProperty("log4j1.compatibility", "true");

        // 获取应用程序根目录并设置 app.root 系统属性
        URL mySource = Main.class.getProtectionDomain().getCodeSource().getLocation();
        File applicationRootDirectory = new File(new File(mySource.getPath()).getParent());
        System.setProperty("app.root", applicationRootDirectory.getAbsolutePath().replaceAll("%20", " "));

        // 重载 Log4j2 配置
        LoggerContext.getContext(false).reconfigure();

        // 此处开始应用程序的其余逻辑,log 此时可能已经初始化完毕
        log.info("应用程序启动..."); 
        // ... rest of Main class
    }
}

使用 jpackage 命令打包:

jpackage.exe ^
--name "the name of the app" ^
--app-version %version% ^
--vendor "vendor name" ^
--icon "icon.ico" ^
--license-file license.txt ^
--file-associations file-association.properties ^
--input input_directory ^
--main-jar application.jar ^
--main-class path.to.MainClass ^
--type exe ^
--win-per-user-install ^
--win-dir-chooser ^
--win-menu ^
--win-menu-group menuGroupName ^
--win-shortcut

在上述配置和代码下,直接运行 application.jar 时,日志文件会正确地创建在应用程序根目录。然而,运行 jpackage 生成的 application.exe 时,日志文件却无法在预期位置生成,反而可能出现在当前工作目录。

问题分析:记录器初始化时机

问题的根本原因在于 Log4j2 记录器(Logger)的初始化时机。当我们将 Logger 声明为类的静态字段时:

唱鸭
唱鸭

音乐创作全流程的AI自动作曲工具,集 AI 辅助作词、AI 自动作曲、编曲、混音于一体

下载
private static final Logger log = LoggerFactory.getLogger(Main.class);

这个静态字段会在 Main 类加载时被初始化。类加载通常发生在 main 方法执行之前。这意味着,在 main 方法中设置 app.root 系统属性并调用 LoggerContext.getContext(false).reconfigure() 来重载配置之前,log 实例就已经被创建了。

当 log 实例首次创建时,Log4j2 会尝试加载其配置。如果此时 app.root 系统属性尚未设置,或者其值不正确,Log4j2 会使用默认值或解析失败,从而导致日志路径不正确。即使随后在 main 方法中设置了 app.root 并重载了配置,对于已经初始化过的 log 实例,可能无法完全生效,或者在某些环境下(如 jpackage 生成的 EXE)行为表现不一致。

解决方案:延迟记录器初始化

解决此问题的关键是确保在所有必要的系统属性设置完毕并 Log4j2 配置重载完成后,再获取并初始化 Logger 实例。这意味着应该将 Logger 的初始化从静态字段移到 main 方法内部,在 reconfigure() 调用之后。

修改后的 main 方法如下:

import org.apache.logging.log4j.core.LoggerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.net.URL;

public class Main {

    // 不再在类级别声明静态Logger
    // private static final Logger log = LoggerFactory.getLogger(Main.class); 

    public static void main(String[] args) {
        // 启用 Log4j1 兼容模式
        System.setProperty("log4j1.compatibility", "true");

        // 获取应用程序根目录并设置 app.root 系统属性
        URL mySource = Main.class.getProtectionDomain().getCodeSource().getLocation();
        File applicationRootDirectory = new File(new File(mySource.getPath()).getParent());
        System.setProperty("app.root", applicationRootDirectory.getAbsolutePath().replaceAll("%20", " "));

        // 重载 Log4j2 配置
        // 确保在获取任何Logger实例之前完成配置
        LoggerContext.getContext(false).reconfigure();

        // 延迟Logger的初始化,确保它在配置完全生效后才被创建
        Logger log = LoggerFactory.getLogger(Main.class); 

        // 此处开始应用程序的其余逻辑
        log.info("应用程序启动成功,日志已配置。"); 
        // ... rest of Main class
    }
}

通过将 Logger log = LoggerFactory.getLogger(Main.class); 这一行移到 LoggerContext.getContext(false).reconfigure(); 之后,我们确保了:

  1. log4j1.compatibility 属性已设置。
  2. app.root 系统属性已设置。
  3. Log4j2 配置上下文已根据新的系统属性进行了重载。
  4. 此时获取的 Logger 实例将基于最新的、已正确解析 app.root 的配置。

注意事项与最佳实践

  • Logger 实例作用域 在 main 方法中声明的 Logger 实例是局部变量。如果应用程序的其他类也需要日志功能,它们应该各自获取自己的 Logger 实例,或者将此 log 实例作为参数传递。通常,在每个类中声明一个 private static final Logger 仍然是推荐的做法,但前提是这些类不会在 main 方法的配置逻辑完成之前被加载和初始化。对于启动类(如 Main),这种延迟初始化是必要的。
  • 系统属性与配置: 任何影响日志配置的系统属性都应在 Log4j2 配置加载或重载之前设置。
  • jpackage 环境: jpackage 生成的 EXE 运行环境与直接运行 JAR 包可能存在细微差异,尤其是在类加载和系统属性解析方面。因此,在 jpackage 环境下出现问题时,检查初始化时序是一个重要的排查方向。
  • 日志框架兼容性: 本文以 Log4j2 为例,但类似的问题可能也会出现在其他日志框架中。核心原则是保持日志配置与记录器初始化之间的正确时序。
  • Shaded JARs: 如果使用了 Maven Shade Plugin,确保 Log4j2 的插件扩展(如 edgwiz log4j-maven-shade-plugin-extensions)配置正确,以避免类加载问题,特别是当 Log4j2 依赖项被重新定位时。

总结

当使用 jpackage 将 Java 应用程序打包为 EXE 后,如果遇到 Log4j2 日志功能异常,特别是日志路径依赖于动态设置的系统属性时,很可能是因为记录器(Logger)的初始化时机早于日志配置的重载。通过将 Logger 实例的获取操作延迟到 main 方法中,在所有必要的系统属性设置和 LoggerContext.getContext(false).reconfigure() 调用之后,可以确保 Logger 实例在正确的配置环境下被初始化。这一简单的调整能够有效解决 EXE 版本中日志无法按预期工作的难题,保证应用程序日志输出的稳定性和正确性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Java Maven专题
Java Maven专题

本专题聚焦 Java 主流构建工具 Maven 的学习与应用,系统讲解项目结构、依赖管理、插件使用、生命周期与多模块项目配置。通过企业管理系统、Web 应用与微服务项目实战,帮助学员全面掌握 Maven 在 Java 项目构建与团队协作中的核心技能。

0

2025.09.15

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

469

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

13

2025.12.06

windows查看端口占用情况
windows查看端口占用情况

Windows端口可以认为是计算机与外界通讯交流的出入口。逻辑意义上的端口一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。怎么查看windows端口占用情况呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

732

2023.07.26

查看端口占用情况windows
查看端口占用情况windows

端口占用是指与端口关联的软件占用端口而使得其他应用程序无法使用这些端口,端口占用问题是计算机系统编程领域的一个常见问题,端口占用的根本原因可能是操作系统的一些错误,服务器也可能会出现端口占用问题。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1128

2023.07.27

windows照片无法显示
windows照片无法显示

当我们尝试打开一张图片时,可能会出现一个错误提示,提示说"Windows照片查看器无法显示此图片,因为计算机上的可用内存不足",本专题为大家提供windows照片无法显示相关的文章,帮助大家解决该问题。

799

2023.08.01

windows查看端口被占用的情况
windows查看端口被占用的情况

windows查看端口被占用的情况的方法:1、使用Windows自带的资源监视器;2、使用命令提示符查看端口信息;3、使用任务管理器查看占用端口的进程。本专题为大家提供windows查看端口被占用的情况的相关的文章、下载、课程内容,供大家免费下载体验。

454

2023.08.02

windows无法访问共享电脑
windows无法访问共享电脑

在现代社会中,共享电脑是办公室和家庭的重要组成部分。然而,有时我们可能会遇到Windows无法访问共享电脑的问题。这个问题可能会导致数据无法共享,影响工作和生活的正常进行。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2354

2023.08.08

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

9

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.9万人学习

C# 教程
C# 教程

共94课时 | 7.7万人学习

Java 教程
Java 教程

共578课时 | 51.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号