10

We are using SonarQube 5.1 with Jacoco maven plugin 0.7.4, and all of our slf4j logging statements such as log.debug('Something happened') show that only 1 of 2 branches are covered. I understand that this is because slf4j internally does an if debug, and that's great, but we don't want this to throw off our numbers. We aren't interested in testing slf4j and we'd rather not run every test multiple times for different logging levels.

So, how can we tell Sonar and/or Jacoco to exclude these lines from coverage? Both of them have configurable file exclusions, but from what I can tell those are only for excluding your own classes from coverage (using the target dir), not the imported libraries. I tried adding groovy.util.logging.*' to the exclusion list anyway but it didn't do anything.

logger.isDebugEnabled() is killing my code coverage. I'm planning to exclude it while running cobertura is similar and suggested that for Cobertura the 'ignore' property should be used instead of 'exclude'. I don't see anything like that for Jacoco or Sonar in settings or documentation.

EDIT: Example image from Eclipse attached, after running Jacoco coverage (Sonar shows the same thing in their GUI). This is actual code from one of our classes. Jacoco branch coverage on slf4j logging

EDIT 2: We are using the Slf4j annotation. Docs here: http://docs.groovy-lang.org/next/html/gapi/groovy/util/logging/Slf4j.html

This local transform adds a logging ability to your program using LogBack logging. Every method call on a unbound variable named log will be mapped to a call to the logger. For this a log field will be inserted in the class. If the field already exists the usage of this transform will cause a compilation error. The method name will be used to determine what to call on the logger.

log.name(exp)

is mapped to

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

Here name is a place holder for info, debug, warning, error, etc. If the expression exp is a constant or only a variable access the method call will not be transformed. But this will still cause a call on the injected logger.

Hopefully this clarifies what's going on. Our log statements become 2 branch ifs to avoid expensive string building for log levels that aren't enabled (a common practice, as far as I know). But that means that to guarantee coverage of all these branches, we have to run every test repeatedly for every logging level.

Community
  • 1
  • 1
Egor
  • 1,622
  • 12
  • 26
  • Could you post a small code sample which is displaying one of two condition covered only ? Just to be precise : this is a problem on JaCoCo side, SonarQube is only displaying the result of your coverage tool. – benzonico Jan 04 '16 at 19:37
  • @benzonico Thanks, added a screenshot. – Egor Jan 04 '16 at 20:50
  • This sounds quite odd in many ways : would you mind precising the type of the `log` object and eventually decompile the .class file of your class and share the relevant bytecode instructions generated ? – benzonico Jan 05 '16 at 08:52
  • @benzonico Please see EDIT2. Hopefully this clarifies things. – Egor Jan 05 '16 at 16:51
  • Well in fact you pretty much answered to yourself. Main problem you have is that the line of log generates a branch in bytecode when you compile it. As JaCoCo instruments bytecode you end up by only covering one side of branch of those bytecode instructions. Not a lot to be done on sonarqube side, eventually the problem might be reported to JaCoCo as it is the same kind of problem that with try-with-resources in java. – benzonico Jan 06 '16 at 12:27
  • See https://github.com/jacoco/jacoco/issues/15 for a more detail story about this. – benzonico Jan 06 '16 at 12:34
  • @benzonico Well yeah, I understand why it's happening, but my question is how can I work around it. Some coverage tools have options to ignore specific method calls, for example, so it's not impossible in principle. Perhaps there are other ways too. I'm just not sure if it's possible specifically with Jacoco, hence the question. – Egor Jan 18 '16 at 21:22
  • It's still present in 2020. – Jakub Pogorzelski Jan 21 '20 at 11:49
  • Did anyone find workaround for this? – San Jan 22 '21 at 16:18

1 Answers1

0

I did not find a general solution for excluding it, but if your codebase allows you to do so, you could wrap your logging statements in a method with an annotation containing "Generated" in its name.

A simple example:

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
    }
}

Unfortunately javax.annotation.Generated is not suitable, because it has only a retention of SOURCE, therefor I (ab)used groovy.transform.Generated here, but can easily create your own annotation for that purpose.

I found that solution here: How would I add an annotation to exclude a method from a jacoco code coverage report?


UPDATE: In Groovy you can solve it most elegantly with a 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
    }
}

...and then...

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}!"
    }

}

Unfortunately the property log is interpreted as property of Greeter, not of LoggingTrait, so you must attach @Slf4j to the trait and the class implementing the trait. Nevertheless doing so gives you the expected logger - the one of the implementing class:

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

  • @Egor I have to admit that the plugin version you mention (0.7.4) does not have this feature. If I am not mistaken, you would have to use 0.8.2 or newer for that. (But your question is from 2016, so probably you already use a newer version now) – nineninesevenfour Jun 06 '21 at 12:06
  • 1
    BTW be aware of possible side effects of your logging code. At my job I remember a case where code worked perfectly fine in a testing stage and unexpectedly stopped working in production. This was extremely hard to find! In theory one would have to test multiple log levels, but no one does it in practice. One strategy could be to set log levels in unit test close to production. – nineninesevenfour Jun 06 '21 at 22:04