Skip to content

Commit

Permalink
Provide a New Logging Rule checking logger consistent usage
Browse files Browse the repository at this point in the history
Closes gh-52
  • Loading branch information
mnhock authored and mnhock committed Jun 21, 2024
1 parent f45b700 commit df14804
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 63 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ void shouldFulfillConstraints() {
.methodsShouldNotMatch("foo")
.fieldsShouldNotMatch("bar")
.fieldsShouldMatch("com.awesome.Foo", "foo")
.constantsShouldFollowConvention()
.constantsShouldFollowConventions()
.interfacesShouldNotHavePrefixI()))
.logging(logging -> logging
.loggersShouldFollowConventions(Logger.class, "logger", EnumSet.of(PRIVATE, FINAL)))
.test(test -> test
.junit5(junit5 -> junit5
.classesShouldNotBeAnnotatedWithDisabled()
.methodsShouldNotBeAnnotatedWithDisabled()))
.methodsShouldNotBeAnnotatedWithDisabled()))
.spring(spring -> spring
.noAutowiredFields()
.boot(boot -> boot
Expand Down
68 changes: 59 additions & 9 deletions docs/USERGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,32 @@ To use Taikai, include it as a dependency in your Maven `pom.xml`:

Ensure to configure `${taikai.version}` to the latest stable version compatible with your project's ArchUnit version.

## 3. Rules Overview
## 3. Usage

### 3.1 Setting the Namespace

The `namespace` setting specifies the base package of your project. Taikai will analyze all classes within this namespace. The default mode is `WITHOUT_TESTS`, which excludes test classes from the import check.

```java
Taikai.builder()
.namespace("com.company.yourproject")
.build()
.check();
```

### 3.2 Enforcing Rules on Empty Sets

The `failOnEmpty` setting determines whether the build should fail if no classes match a given rule. This is useful to ensure that your rules are applied consistently and to avoid false positives. The default is `false`.

```java
Taikai.builder()
.namespace("com.company.yourproject")
.failOnEmpty(true)
.build()
.check();
```

## 4. Rules Overview

Taikai's architecture rules cover a wide range of categories to enforce best practices and maintain consistency.

Expand Down Expand Up @@ -48,9 +73,17 @@ The default mode is `WITHOUT_TESTS`, which excludes test classes from the import
| Naming | `fieldsShouldNotMatch` | Fields should not match specific naming patterns |
| Naming | `fieldsShouldMatch` | Fields should match specific naming patterns for specific classes |
| Naming | `fieldsAnnotatedWithShouldMatch` | Fields annotated with should match specific naming patterns |
| Naming | `constantsShouldFollowConvention` | Constants should follow naming conventions, except `serialVersionUID` |
| Naming | `constantsShouldFollowConventions` | Constants should follow naming conventions, except `serialVersionUID` |
| Naming | `interfacesShouldNotHavePrefixI` | Interfaces should not have the prefix `I` |

### Logging Rules

The default mode is `WITHOUT_TESTS`, which checks only test classes.

| Category | Method Name | Rule Description |
|----------|-------------------|----------------------------------------------------------------------------------------------------|
| General | `loggersShouldFollowConventions` | Ensure that the specified logger follow a specific naming pattern and have the required modifiers |

### Test Rules

The default mode is `ONLY_TESTS`, which checks only test classes.
Expand Down Expand Up @@ -90,7 +123,7 @@ The default mode is `WITHOUT_TESTS`, which excludes test classes from the import
| Services | `shouldBeAnnotatedWithService` | Services should be annotated with `@Service` |
| Services | `shouldNotDependOnControllers` | Services annotated with `@Service.` should not depend on controllers annotated with `@Controller` or `@RestController` |

## 4. Java Rules
## 5. Java Rules

Java configuration involves defining constraints related to Java language features, coding standards, and architectural patterns.

Expand Down Expand Up @@ -178,7 +211,7 @@ Taikai.builder()
.fieldsShouldMatch("com.awesome.Foo", "foo")
.fieldsShouldMatch(Foo.class, "foo")
.fieldsAnnotatedWithShouldMatch(Annotation.class, "coolField")
.constantsShouldFollowConvention()
.constantsShouldFollowConventions()
.interfacesShouldNotHavePrefixI())))
.build()
.check();
Expand Down Expand Up @@ -206,7 +239,7 @@ Taikai.builder()
.check();
```

- **No Usage of System.out or System.err**: Enforce disallowing the use of `System.out` and `System.err` for logging, encouraging the use of proper logging frameworks instead.
- **No Usage of `System.out` or `System.err`**: Enforce disallowing the use of `System.out` and `System.err` for logging, encouraging the use of proper logging frameworks instead.

```java
Taikai.builder()
Expand All @@ -228,7 +261,22 @@ Taikai.builder()
.check();
```

## 5. Test Rules
## 6. Logging Rules

Logging configuration involves specifying constraints related to logging frameworks and practices.

- **Ensure Logger Field Conforms to Standards**: Ensure that classes use a logger field of the specified type, with the correct name and modifiers.

```java
Taikai.builder()
.namespace("com.company.yourproject")
.logging(logging -> logging
.loggersShouldFollowConventions(org.slf4j.Logger.class, "logger", EnumSet.of(PRIVATE, FINAL)))
.build()
.check();
```

## 7. Test Rules

Test configuration involves specifying constraints related to testing frameworks and practices.

Expand Down Expand Up @@ -317,7 +365,7 @@ Taikai.builder()
.check();
```

## 6. Spring Rules
## 8. Spring Rules

Spring configuration involves defining constraints specific to Spring Framework usage.

Expand Down Expand Up @@ -403,7 +451,7 @@ Taikai.builder()
.check();
```

## 7. Customization
## 9. Customization

### Custom Configuration for Import Rules

Expand Down Expand Up @@ -439,7 +487,7 @@ Taikai.builder()
```
By using the `addRule()` method and providing a custom ArchUnit rule, you can extend Taikai's capabilities to enforce additional architectural constraints that are not covered by the predefined rules. This flexibility allows you to adapt Taikai to suit the unique architectural needs of your Java project.

## 8. Examples
## 10. Examples

Below are some examples demonstrating the usage of Taikai to define and enforce architectural rules in Java projects, including Spring-specific configurations:

Expand Down Expand Up @@ -478,6 +526,8 @@ class ArchitectureTest {
.namesShouldMatch("regex")
.shouldNotDependOnOtherControllers()
.shouldBePackagePrivate()))
.logging(logging -> logging
.loggersShouldFollowConventions(Logger.class, "logger", EnumSet.of(PRIVATE, FINAL)))
.build()
.check();
}
Expand Down
9 changes: 7 additions & 2 deletions src/main/java/com/enofex/taikai/Taikai.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.enofex.taikai.configures.Configurers;
import com.enofex.taikai.configures.Customizer;
import com.enofex.taikai.java.JavaConfigurer;
import com.enofex.taikai.logging.LoggingConfigurer;
import com.enofex.taikai.spring.SpringConfigurer;
import com.enofex.taikai.test.TestConfigurer;
import com.tngtech.archunit.ArchConfiguration;
Expand Down Expand Up @@ -100,14 +101,18 @@ public Builder java(Customizer<JavaConfigurer> customizer) {
return configure(customizer, JavaConfigurer::new);
}

public Builder spring(Customizer<SpringConfigurer> customizer) {
return configure(customizer, SpringConfigurer::new);
public Builder logging(Customizer<LoggingConfigurer> customizer) {
return configure(customizer, LoggingConfigurer::new);
}

public Builder test(Customizer<TestConfigurer> customizer) {
return configure(customizer, TestConfigurer::new);
}

public Builder spring(Customizer<SpringConfigurer> customizer) {
return configure(customizer, SpringConfigurer::new);
}

private <T extends Configurer> Builder configure(Customizer<T> customizer,
Function<ConfigurerContext, T> supplier) {
Objects.requireNonNull(customizer);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/enofex/taikai/java/ConstantNaming.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ final class ConstantNaming {
private ConstantNaming() {
}

static ArchCondition<JavaField> shouldFollowConstantNamingConvention() {
static ArchCondition<JavaField> shouldFollowConstantNamingConventions() {
return new ArchCondition<>("follow constant naming convention") {
@Override
public void check(JavaField field, ConditionEvents events) {
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/com/enofex/taikai/java/NamingConfigurer.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.enofex.taikai.java;

import static com.enofex.taikai.java.ConstantNaming.shouldFollowConstantNamingConvention;
import static com.enofex.taikai.java.ConstantNaming.shouldFollowConstantNamingConventions;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
Expand Down Expand Up @@ -185,14 +185,14 @@ public void check(JavaClass javaClass, ConditionEvents events) {
};
}

public NamingConfigurer constantsShouldFollowConvention() {
return constantsShouldFollowConvention(null);
public NamingConfigurer constantsShouldFollowConventions() {
return constantsShouldFollowConventions(null);
}

public NamingConfigurer constantsShouldFollowConvention(Configuration configuration) {
public NamingConfigurer constantsShouldFollowConventions(Configuration configuration) {
return addRule(TaikaiRule.of(fields()
.that().areFinal().and().areStatic()
.should(shouldFollowConstantNamingConvention())
.as("Constants should follow constant naming convention"), configuration));
.should(shouldFollowConstantNamingConventions())
.as("Constants should follow constant naming conventions"), configuration));
}
}
44 changes: 44 additions & 0 deletions src/main/java/com/enofex/taikai/logging/LoggerConventions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.enofex.taikai.logging;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaField;
import com.tngtech.archunit.core.domain.JavaModifier;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import java.util.Set;

final class LoggerConventions {

private LoggerConventions() {
}

static ArchCondition<JavaClass> followLoggerConventions(String typeName, String regex,
Set<JavaModifier> requiredModifiers) {
return new ArchCondition<>(
"have a logger field of type %s with name pattern %s and modifiers %s".formatted(
typeName, regex, requiredModifiers)) {
@Override
public void check(JavaClass javaClass, ConditionEvents events) {
for (JavaField field : javaClass.getAllFields()) {
if (field.getRawType().isAssignableTo(typeName)) {
if (!field.getName().matches(regex)) {
events.add(SimpleConditionEvent.violated(field,
"Field '%s' in class %s does not match the naming pattern '%s'".formatted(
field.getName(),
javaClass.getName(), regex)));
}

if (!field.getModifiers().containsAll(requiredModifiers)) {
events.add(SimpleConditionEvent.violated(field,
"Field '%s' in class %s does not have the required modifiers %s".formatted(
field.getName(),
javaClass.getName(),
requiredModifiers)));
}
}
}
}
};
}
}
42 changes: 42 additions & 0 deletions src/main/java/com/enofex/taikai/logging/LoggingConfigurer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.enofex.taikai.logging;

import static com.enofex.taikai.logging.LoggerConventions.followLoggerConventions;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

import com.enofex.taikai.TaikaiRule;
import com.enofex.taikai.TaikaiRule.Configuration;
import com.enofex.taikai.configures.AbstractConfigurer;
import com.enofex.taikai.configures.ConfigurerContext;
import com.tngtech.archunit.core.domain.JavaModifier;
import java.util.Set;

public final class LoggingConfigurer extends AbstractConfigurer {

public LoggingConfigurer(ConfigurerContext configurerContext) {
super(configurerContext);
}

public LoggingConfigurer loggersShouldFollowConventions(String typeName, String regex,
Set<JavaModifier> requiredModifiers) {
return loggersShouldFollowConventions(typeName, regex, requiredModifiers, null);
}

public LoggingConfigurer loggersShouldFollowConventions(String typeName, String regex,
Set<JavaModifier> requiredModifiers, Configuration configuration) {
return addRule(TaikaiRule.of(classes().should(
followLoggerConventions(typeName, regex, requiredModifiers)),
configuration));
}

public LoggingConfigurer loggersShouldFollowConventions(Class<?> clazz, String regex,
Set<JavaModifier> requiredModifiers) {
return loggersShouldFollowConventions(clazz, regex, requiredModifiers, null);
}

public LoggingConfigurer loggersShouldFollowConventions(Class<?> clazz, String regex,
Set<JavaModifier> requiredModifiers, Configuration configuration) {
return addRule(TaikaiRule.of(classes().should(
followLoggerConventions(clazz.getName(), regex, requiredModifiers)),
configuration));
}
}
2 changes: 0 additions & 2 deletions src/main/java/com/enofex/taikai/test/JUnit5Configurer.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_PARAMETRIZED_TEST;
import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_TEST;
import static com.enofex.taikai.test.JUnit5DescribedPredicates.annotatedWithTestOrParameterizedTest;
import static com.tngtech.archunit.base.DescribedPredicate.not;
import static com.tngtech.archunit.lang.conditions.ArchConditions.beInterfaces;

import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
Expand Down
18 changes: 9 additions & 9 deletions src/test/java/com/enofex/taikai/ArchitectureTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ class ArchitectureTest {
void shouldFulfilConstrains() {
Taikai.builder()
.namespace("com.enofex.taikai")
.test(test -> test
.junit5(junit5 -> junit5
.classesShouldNotBeAnnotatedWithDisabled()
.classesShouldBePackagePrivate(".*Test")
.methodsShouldNotBeAnnotatedWithDisabled()
.methodsShouldMatch("should.*")
.methodsShouldBePackagePrivate()
.methodsShouldNotDeclareExceptions()))
.java(java -> java
.noUsageOfDeprecatedAPIs()
.noUsageOfSystemOutOrErr()
Expand All @@ -39,7 +31,15 @@ void shouldFulfilConstrains() {
.naming(naming -> naming
.classesShouldNotMatch(".*Impl")
.interfacesShouldNotHavePrefixI()
.constantsShouldFollowConvention()))
.constantsShouldFollowConventions()))
.test(test -> test
.junit5(junit5 -> junit5
.classesShouldNotBeAnnotatedWithDisabled()
.classesShouldBePackagePrivate(".*Test")
.methodsShouldNotBeAnnotatedWithDisabled()
.methodsShouldMatch("should.*")
.methodsShouldBePackagePrivate()
.methodsShouldNotDeclareExceptions()))
.build()
.check();
}
Expand Down
Loading

0 comments on commit df14804

Please sign in to comment.