使用 Jacoco 从 Sonar 中的条件覆盖范围中排除 groovy slf4j 日志记录

Posted

技术标签:

【中文标题】使用 Jacoco 从 Sonar 中的条件覆盖范围中排除 groovy slf4j 日志记录【英文标题】:Exclude groovy slf4j logging from condition coverage in Sonar with Jacoco 【发布时间】:2016-04-08 11:23:56 【问题描述】:

我们正在使用带有 Jacoco maven 插件 0.7.4 的 SonarQube 5.1,并且我们所有的 slf4j 日志记录语句(例如 log.debug('Something happened'))显示只有 2 个分支中的 1 个被覆盖。我知道这是因为 slf4j 内部做了一个if debug,这很好,但我们不希望这会影响我们的数字。我们对测试 slf4j 不感兴趣,我们宁愿不为不同的日志记录级别多次运行每个测试。

那么,我们如何告诉 Sonar 和/或 Jacoco 将这些行排除在覆盖范围之外?它们都具有可配置的文件排除项,但据我所知,这些仅用于将您自己的类排除在覆盖范围之外(使用目标目录),而不是导入的库。无论如何,我尝试将groovy.util.logging.*' 添加到排除列表中,但它没有做任何事情。

logger.isDebugEnabled() is killing my code coverage. I'm planning to exclude it while running cobertura 类似并建议对于 Cobertura 应使用“忽略”属性而不是“排除”。我在设置或文档中看不到 Jacoco 或 Sonar 的类似内容。

编辑: 在运行 Jacoco 覆盖后附上来自 Eclipse 的示例图像(Sonar 在其 GUI 中显示相同的内容)。这是来自我们的一个类的实际代码。

编辑 2: 我们正在使用 Slf4j 注释。这里的文档: http://docs.groovy-lang.org/next/html/gapi/groovy/util/logging/Slf4j.html

此本地转换使用 LogBack 日志记录为您的程序添加日志记录功能。对名为 log 的未绑定变量的每个方法调用都将映射到对记录器的调用。为此,将在类中插入一个日志字段。如果该字段已存在,则使用此转换将导致编译错误。方法名称将用于确定在记录器上调用什么。

log.name(exp)

映射到

if (log.isNameLoggable() 
        log.name(exp)
     

这里的 name 是 info、debug、warning、error 等的占位符。如果表达式 exp 是常量或仅是变量访问,则不会转换方法调用。但这仍然会导致对注入的记录器的调用。

希望这能澄清发生了什么。我们的日志语句变成了 2 个分支 if,以避免为未启用的日志级别构建昂贵的字符串(据我所知,这是一种常见的做法)。但这意味着,为了保证覆盖所有这些分支,我们必须针对每个日志记录级别重复运行每个测试。

【问题讨论】:

您能否发布一个小代码示例,该示例仅显示两个条件之一?准确地说:这是 JaCoCo 方面的问题,SonarQube 仅显示您的覆盖工具的结果。 @benzonico 谢谢,添加了截图。 这在很多方面听起来很奇怪:您介意精确log 对象的类型并最终反编译您的类的.class 文件并共享生成的相关字节码指令吗? @benzonico 请参阅 EDIT2。希望这可以澄清事情。 嗯,事实上,你自己回答得差不多了。您遇到的主要问题是日志行在编译时会在字节码中生成一个分支。当 JaCoCo 检测字节码时,您最终只覆盖了这些字节码指令分支的一侧。在 sonarqube 方面没有太多工作要做,最终问题可能会报告给 JaCoCo,因为它与 java 中的 try-with-resources 问题相同。 【参考方案1】:

我没有找到排除它的通用解决方案,但如果您的代码库允许您这样做,您可以将日志记录语句包装在一个方法中,并在其名称中使用包含“Generated”的注释.

一个简单的例子:

package org.example.logging

import groovy.transform.Generated
import groovy.util.logging.Slf4j

@Slf4j
class Greeter 

    void greet(name) 
        logDebug("called greet for $name")
        println "Hello, $name!"
    

    @Generated
    private logDebug(message) 
        log.debug message
    


不幸的是javax.annotation.Generated不适合,因为它只有SOURCE的保留,因此我在这里(ab)使用了groovy.transform.Generated,但可以轻松地为此创建自己的注释。

我在这里找到了解决方案:How would I add an annotation to exclude a method from a jacoco code coverage report?


更新:在 Groovy 中,您可以使用 trait 最优雅地解决它:

package org.example.logging

import groovy.transform.Generated
import groovy.util.logging.Slf4j

@Slf4j
trait LoggingTrait 

    @Generated
    void logDebug(String message) 
        log.debug message
    

...然后...

package org.example.logging

import groovy.util.logging.Slf4j

@Slf4j
class Greeter implements LoggingTrait 

    void greet(name) 
        logDebug "called greet for $name"
        println "Hello, $name!"
    


不幸的是,log 属性被解释为Greeter 的属性,而不是LoggingTrait 的属性,因此您必须将@Slf4j 附加到特征和实现特征的类。 尽管如此,这样做会给您预期的记录器 - 实现类之一:

14:25:09.932 [main] DEBUG org.example.logging.Greeter - called greet for world

【讨论】:

@Egor 我不得不承认你提到的插件版本(0.7.4)没有这个功能。如果我没记错的话,您将不得不为此使用 0.8.2 或更高版本。 (但你的问题是从 2016 年开始的,所以你现在可能已经在使用更新的版本了) 顺便说一句,请注意您的日志记录代码可能产生的副作用。在我的工作中,我记得一个案例,代码在测试阶段运行良好,但在生产中意外停止工作。这很难找到!理论上,必须测试多个日志级别,但实际上没有人这样做。一种策略是在接近生产的单元测试中设置日志级别。

以上是关于使用 Jacoco 从 Sonar 中的条件覆盖范围中排除 groovy slf4j 日志记录的主要内容,如果未能解决你的问题,请参考以下文章

Sonar 和 Jacoco:由于没有类文件,因此无法对项目覆盖率进行 JaCoCo 分析

ant+sonar+jacoco代码质量代码覆盖率扫描

Kotlin Setup 的 Sonar Jacoco 不生成代码覆盖率

jacoco+maven+sonar+springboot 单元测试代码覆盖率统计

解决Sonar扫描Lombok注解的代码没有覆盖率

SonarQube + Maven + JaCoCo + GitLab CI:Sonar 在升级到 SonarQube 7.9.2 后开始显示 0% 的代码覆盖率