65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Maven 实现自动化日志记录

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2015 年 12 月 13 日

Apache

5分钟阅读

viewsIcon

12810

downloadIcon

39

一个 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 Problem

Plugin 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")。

下载 TestProject.zip

有什么问题吗?

如果您有任何问题、建议或想给我反馈 - 请随时提出!

© . All rights reserved.