使用 Maven 实现自动化日志记录
一个 Maven 插件,允许您的代码自我写入——在本例中:注入日志调用
引言
您是否需要为一个中型项目实现日志记录部分?为此,您可能需要通读代码,几乎查看每一行。查找捕获的异常(空 catch 块等)或在日志条目中包含实际参数和字段值非常耗时。如果发生更改,您将不得不从头开始。您的代码会增长,可能会因为类似这样的行而变得有些难看
LoggerFactory.getLogger(AttractionDTO.class).debug(String.format("The x coordinate of the attraction '%s' was set to %f", getName(), x));
背景
在我的例子中,这个中型项目是一个 JSF Web 应用程序。起初,我尝试使用修改过的 ClassLoader 来自动化日志记录过程的一部分,该 ClassLoader 在运行时注入所需的调用(使用 Javassist)。这种方法有几个缺点,例如
- 复杂的 ClassLoader 结构
- 类加载时的性能下降
- 注入的日志调用在单元测试执行期间不可用
- 代码注入期间的异常处理困难(静默失败或销毁容器)
因此,我决定在构建过程中进行必要的代码注入。“Java 注解处理 API”不可用,因为它只允许添加源。您无法编辑现有的源。我还放弃了使用 Lombok,因为它使用了内部的、非标准的和特定于编译器的技术。
由于我们使用 Maven 来构建我们的项目,我决定编写一个插件,它将在我们的项目中调用一些“后处理器类”。这些后处理器类现在能够使用 Javassist 修改已编译的类文件。
结果
我将结果发布为一个开源项目,因为我认为这在其他场景中可能很有用。执行这些后处理器类的 maven 插件可以在以下位置找到
https://github.com/RandomCodeOrg/PPPlugin
以及一些默认的处理器实现,在
https://github.com/RandomCodeOrg/PPDefaults
使用插件
创建一个新的 maven 项目
如果您已经有一个 maven 项目或熟悉 maven,则可以跳过此部分。我将以 Eclipse 为例,但您可以找到很多关于使用其他 IDE 完成此过程的教程。
1. 创建一个新的 maven 项目
选择 Maven > "Maven Project" 并单击 "Next"
2. 通过单击 "Next" 确认以下步骤,直到您到达以下步骤
输入您选择的 "Group Id" 和 "Artifact Id",然后单击 "Finish" 完成向导。您可以在 这里 找到关于 group- 和 artifact id 的解释。
向导将创建一个空的 maven 项目,其中包含 "pom.xml" 和一个名为给定 group- 和 artifact id 的包中的主类 "App"。
修改您的 maven 项目
1. 每次构建时运行 PPPlugin
打开 "pom.xml" 并单击名为 "pom.xml" 的选项卡。
您将看到一个包含项目所有设置的 xml 文档。
2. 配置 PPPlugin
为了执行 PPPlugin,您需要将其包含在 pom.xml 的
<plugin>
<groupId>com.github.randomcodeorg.ppplugin</groupId>
<artifactId>ppplugin</artifactId>
<version>0.1.0</version>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>postprocess</goal>
</goals>
</execution>
</executions>
</plugin>
(2a. 让 Eclipse 保持安静)
如果您的 Eclipse 安装抱怨
Eclipe Maven ProblemPlugin execution not covered by lifecycle configuration: com.github.randomcodeorg[...]
可以在
<pluginManagement> <plugins> <plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId> com.github.randomcodeorg.ppplugin </groupId> <artifactId> ppplugin </artifactId> <versionRange> [0.0.0,) </versionRange> <goals> <goal>postprocess</goal> </goals> </pluginExecutionFilter> <action> <execute></execute> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin> </plugins> </pluginManagement>
以解决此问题。保存更改后,错误应消失。
3. 包含默认后处理器
将以下代码段复制到 pom.xml 的
<dependency> <groupId>com.github.randomcodeorg.ppplugin</groupId> <artifactId>ppdefaults</artifactId> <version>0.0.1</version> </dependency> <!-- The following two dependencies are required because the processor uses SLF4J's logger by default --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.13</version> </dependency>
请注意,最后两个依赖项是必需的,因为预定义的处理器使用 SLF4J 的 logger。请参阅本文的“修改”部分来更改此行为(例如,使用不同的日志框架)。
启用一些默认处理器
该插件仅执行属于您源代码的处理器。这将防止构建过程执行第三方代码。但是,您可以通过在项目中创建一个继承类来启用此类“外部”处理器的执行。
创建以下两个类来实现此目的
CaughtExceptionProcessor
import com.github.randomcodeorg.ppplugin.ppdefaults.logging.InsertCaughtExceptionLogProcessor; public class CaughtExceptionProcessor extends InsertCaughtExceptionLogProcessor { public CaughtExceptionProcessor(){ } }
MethodCallProcessor
import com.github.randomcodeorg.ppplugin.ppdefaults.logging.InsertMethodCallLogProcessor; public class MethodCallProcessor extends InsertMethodCallLogProcessor{ public MethodCallProcessor(){ } }
现在您已准备就绪!该插件将向每个 catch 块以及使用 @LogThis 注解的每个方法注入日志命令。
修改
注解
@LogThis 可用于注解方法或类。该处理器将在用此注解注解的每个方法中注入日志命令。对类的注解将应用于其中声明的所有方法。请注意,方法级注解将覆盖类级注解。该注解包含以下属性
value
定义结果日志条目的日志级别(默认为 'DEBUG')logFields
定义是否应在日志条目中包含实例字段的值(默认为 'true')ignoreStaticFinal
定义是否应在日志条目中包含静态 final 字段(默认为 'true')
@Stealth 可用于将参数、字段或方法从日志中排除。
使用不同的日志框架
如果您想使用另一个日志框架,您应该查看 AbstractLoggingProcessor。您可以覆盖此类中定义的方法来更改日志程序的创建和访问方式。以下示例将向您展示如何使用 java.util.logging.Logger
import com.github.randomcodeorg.ppplugin.ppdefaults.logging.InsertMethodCallLogProcessor; import com.github.randomcodeorg.ppplugin.ppdefaults.logging.LogLevel; import javassist.CtClass; public class MyCustomMethodCallProcessor extends InsertMethodCallLogProcessor { public MyCustomMethodCallProcessor() { } @Override protected String getLoggerType() { return "java.util.logging.Logger"; } @Override protected String getLoggerInitialization(CtClass cl) { return String.format("java.util.logging.Logger.getLogger(\"%s\");", cl.getName()); } @Override protected String getLogMethodName(LogLevel level) { switch (level) { case VERBOSE: return "finest"; case DEBUG: return "fine"; case INFORMATION: return "info"; case WARNING: case ERROR: return "warning"; default: return "info"; } } @Override protected String getLoggerFieldPrefix() { return "sysLogger_"; } }
测试
您可以从下面的链接下载示例。运行 maven 构建(右键单击项目 > "Run as" > "Maven Build" > Goals: "clean install" > "Run")以执行插件。完成后,您可以像运行“普通”Java 应用程序一样简单地运行它(右键单击项目 > "Run as" > "Java Application")。
有什么问题吗?
如果您有任何问题、建议或想给我反馈 - 请随时提出!