javajunit5archunit

Using archunit to enforce log context


Following the method suggested in this blog post, I am trying to create an archunit rule that will enforce log statements to have contextual information. But the code mentioned in the example is not compiling:

public static ArchRule noInfoLoggingWithoutContext() {
        ArchCondition<JavaClass> loggerWithOutContext =
                callMethodWhere(
                        target(name("info"))
                                .and(target(owner(assignableTo(Logger.class))))
                                .and(target(rawParameterTypes(new DescribedPredicate<>("logger.info without context") {
                                    @Override
                                    public boolean test(List<JavaClass> methodParameters) {
                                        return methodParameters.size() <= 1;
                                    }
                                })))).as("use logger.info without context");
        return noClasses().should(loggerWithOutContext);
 }

The above mentioned code fails with the following error:

Required type: DescribedPredicate <? super AccessTarget>
Provided: DescribedPredicate<HasParameterTypes>

Any suggestions, how can we implement such a rule?

archunit version: 1.0.1


Solution

  • You probably picked the "wrong" static import

    import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.target;
    

    (which returns a DescribedPredicate<JavaAccess<?>>) instead of

    import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target;
    

    (which returns a DescribedPredicate<JavaCall<?>>).

    As callMethodWhere takes a DescribedPredicate<? super JavaMethodCall>, and as JavaMethodCall extends JavaCall, which in turn extends JavaCodeUnitAccess, which in turn extends JavaAccess, the "wrong" target method worked for callMethodWhere, but its predicates could only operate on JavaAccess, which has a name and an owner, but no rawParameterTypes.


    Unrelated to your question: I would simplify the entire rule to

    import com.tngtech.archunit.junit.ArchTest;
    import com.tngtech.archunit.lang.ArchRule;
    import org.slf4j.Logger;
    
    import static com.tngtech.archunit.base.DescribedPredicate.describe;
    import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target;
    import static com.tngtech.archunit.lang.conditions.ArchConditions.callMethodWhere;
    import static com.tngtech.archunit.lang.conditions.ArchPredicates.is;
    import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
    
    public class StackOverflow76250573 {
    
        @ArchTest
        ArchRule noInfoLoggingWithoutContext =
            noClasses().should(callMethodWhere(target(is(describe("logger.info without context",
                target ->
                    target.getOwner().isAssignableTo(Logger.class)
                        && target.getName().equals("info")
                        && target.getRawParameterTypes().size() < 2
            )))));
    }