Skip to content

Commit

Permalink
HSEARCH-798 Add a logging categories collector and put collected cate…
Browse files Browse the repository at this point in the history
…gories in the appendix
  • Loading branch information
marko-bekhta committed Nov 18, 2024
1 parent e31be5e commit 6249c01
Show file tree
Hide file tree
Showing 16 changed files with 527 additions and 26 deletions.
2 changes: 1 addition & 1 deletion bom/public/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
</goals>
<configuration>
<rules>
<dependencyManagementIncludesAllPublicArtifactsRule/>
<dependencyManagementIncludesOnlyAllPublicArtifactsRule/>
</rules>
</configuration>
</execution>
Expand Down
4 changes: 4 additions & 0 deletions build/config/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.search.build.report.loggers;

public final class Configuration {

private Configuration() {
}

public static final String MODULE_NAME = "org.hibernate.search.build.loggers.module_name";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.search.build.report.loggers;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

import org.yaml.snakeyaml.Yaml;

@SupportedAnnotationTypes("org.hibernate.search.util.common.logging.CategorizedLogger")
@SupportedOptions({ Configuration.MODULE_NAME })
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class LoggerCategoriesProcessor extends AbstractProcessor {

private Messager messager;
private final Map<String, Set<String>> categories = new TreeMap<>();
private String moduleName;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init( processingEnv );
messager = processingEnv.getMessager();

moduleName = processingEnv.getOptions().get( Configuration.MODULE_NAME );
if ( moduleName == null || moduleName.isBlank() ) {
throw new IllegalArgumentException(
"Module name cannot be null or blank. Specify the %s annotation processor argument to define the module name"
.formatted( Configuration.MODULE_NAME ) );
}
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage( Diagnostic.Kind.NOTE, "Look for logging categories" );

for ( TypeElement annotation : annotations ) {
Set<? extends Element> loggers = roundEnv.getElementsAnnotatedWith( annotation );
for ( Element el : loggers ) {
if ( el.getKind().isInterface() || el.getKind().isClass() ) {
TypeElement logger = (TypeElement) el;
if ( extendsBasicLogger( logger ) || hasLoggingMethods( logger ) ) {
messager.printMessage( Diagnostic.Kind.NOTE, logger.toString() );
for ( AnnotationMirror mirror : logger.getAnnotationMirrors() ) {
if ( processingEnv.getTypeUtils().isSameType( annotation.asType(), mirror.getAnnotationType() ) ) {
String category = getAnnotationValueAsString( mirror, "category" );
String description = getAnnotationValueAsString( mirror, "description" );

Set<String> descriptions = categories.computeIfAbsent( category, k -> new TreeSet<>() );
if ( description != null && !description.isBlank() ) {
descriptions.add( description );
}
else {
messager.printMessage( Diagnostic.Kind.WARNING,
"Logger %s either has some log-message methods or extends a BasicLogger, but does not provide any description on what it is used for."
.formatted( category ) );
}
}
}
}
}
}
}

if ( roundEnv.processingOver() ) {
try {
FileObject report = processingEnv.getFiler().createResource(
StandardLocation.CLASS_OUTPUT, "", "META-INF/hibernate-search/logging-categories.yaml" );
try ( Writer writer = new OutputStreamWriter( report.openOutputStream(), StandardCharsets.UTF_8 ) ) {
writer.write( "# List of logging categories that this module utilizes.\n" );
if ( categories.isEmpty() ) {
writer.write( "# This Hibernate Search module does not use any logging categories." );
}
else {
Yaml yaml = new Yaml();
yaml.dump(
Map.of(
ReportConstants.ROOT,
Map.of(
ReportConstants.MODULE_NAME, moduleName,
ReportConstants.CATEGORIES, toYamlCategories( categories )
)
),
writer
);
}
}
catch (IOException e) {
throw new RuntimeException( e );
}
}
catch (IOException e) {
throw new RuntimeException( e );
}
}


return false;
}

private List<Map<String, Object>> toYamlCategories(Map<String, Set<String>> categories) {
List<Map<String, Object>> values = new ArrayList<>();
for ( var entry : categories.entrySet() ) {
Map<String, Object> value = new HashMap<>();
value.put( ReportConstants.CATEGORY_NAME, entry.getKey() );
value.put( ReportConstants.CATEGORY_DESCRIPTION, new ArrayList<>( entry.getValue() ) );

values.add( value );
}
return values;
}

private boolean hasLoggingMethods(TypeElement logger) {
for ( Element element : processingEnv.getElementUtils().getAllMembers( logger ) ) {
if ( element.getKind() == ElementKind.METHOD ) {
ExecutableElement executable = (ExecutableElement) element;
if ( isVoid( executable ) && hasLoggingAnnotation( executable ) ) {
return true;
}
}
}
return false;
}

private boolean hasLoggingAnnotation(ExecutableElement executable) {
for ( AnnotationMirror am : executable.getAnnotationMirrors() ) {
if ( ( (TypeElement) am.getAnnotationType().asElement() ).getQualifiedName()
.contentEquals( "org.jboss.logging.annotations.LogMessage" ) ) {
return true;
}
}
return false;
}

private boolean isVoid(ExecutableElement executable) {
return executable.getReturnType().getKind() == TypeKind.VOID;
}

private boolean extendsBasicLogger(TypeElement logger) {
List<? extends TypeMirror> interfaces = logger.getInterfaces();
for ( TypeMirror anInterface : interfaces ) {
Element el = processingEnv.getTypeUtils().asElement( anInterface );
if ( ( (TypeElement) el ).getQualifiedName().contentEquals( "org.jboss.logging.BasicLogger" ) ) {
return true;
}
}
return false;
}

private String getAnnotationValueAsString(AnnotationMirror annotationMirror, String name) {
AnnotationValue annotationValue = getAnnotationValue( annotationMirror, name );
if ( annotationValue == null ) {
return "";
}
return annotationValue.getValue().toString();
}

private AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String name) {
var elementValues = annotationMirror.getElementValues();
for ( var entry : elementValues.entrySet() ) {
if ( entry.getKey().getSimpleName().contentEquals( name ) ) {
return entry.getValue();
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.search.build.report.loggers;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.yaml.snakeyaml.Yaml;

public class LoggerCategoriesReporter {

@SuppressWarnings("unchecked")
public static void main(String[] args) throws IOException {
if ( args.length != 1 ) {
throw new IllegalArgumentException( "Has to pass exactly one parameter specifying the report output directory!" );
}
Path doc = Path.of( args[0] ).resolve( "_hibernate-logging-categories.adoc" );
Files.createDirectories( doc.getParent() );

System.out.printf( Locale.ROOT, "Generating the Logging Categories report into %s\n", doc );

Enumeration<URL> reports =
LoggerCategoriesReporter.class.getClassLoader()
.getResources( "META-INF/hibernate-search/logging-categories.yaml" );

if ( reports.hasMoreElements() ) {
System.out.printf( Locale.ROOT, "Found some logging categories yaml reports.\n" );
}
else {
System.out.printf( Locale.ROOT, "Found *NO* logging categories yaml reports.\n" );
}

Map<String, Category> report = new TreeMap<>();

while ( reports.hasMoreElements() ) {
URL reportUrl = reports.nextElement();
System.out.printf( "Processing report file: %s\n", reportUrl );
try ( InputStreamReader reader =
new InputStreamReader( reportUrl.openStream(), StandardCharsets.UTF_8 ) ) {
Yaml yaml = new Yaml();
Map<String, Object> load = yaml.load( reader );

if ( load == null ) {
System.err.printf( "Warning: report %s is empty or invalid\n", reportUrl );
continue;
}

Map<String, Object> root = (Map<String, Object>) load.get( ReportConstants.ROOT );
String moduleName = (String) root.get( ReportConstants.MODULE_NAME );
List<Map<String, Object>> categories =
(List<Map<String, Object>>) root.get( ReportConstants.CATEGORIES );

for ( var category : categories ) {
String name = (String) category.get( ReportConstants.CATEGORY_NAME );
Category c = report.computeIfAbsent( name, Category::new );
c.modules.add( moduleName );
List<String> descr = (List<String>) category.get( ReportConstants.CATEGORY_DESCRIPTION );
if ( descr != null ) {
c.descriptions.addAll( descr );
}
}
}
}

try ( FileOutputStream fos = new FileOutputStream( doc.toFile() );
Writer writer = new OutputStreamWriter( fos, StandardCharsets.UTF_8 ) ) {
for ( Category category : report.values() ) {
writer.write( "[[logging-category-%s]]`%s`::\n".formatted( category.name.replace( '.', '-' ), category.name ) );
if ( !category.descriptions.isEmpty() ) {
writer.write( "Description:::\n" );
for ( String description : category.descriptions ) {
writer.write( "* %s\n".formatted( description ) );
}
}
writer.write( "Used in modules:::\n" );
for ( String module : category.modules ) {
writer.write( "* `%s`\n".formatted( module ) );
}
}
}
}

private static class Category {
String name;
Set<String> descriptions = new TreeSet<>();
Set<String> modules = new TreeSet<>();

public Category(String name) {
this.name = name;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.search.build.report.loggers;

class ReportConstants {

static final String ROOT = "org.hibernate.search.report";
static final String MODULE_NAME = "module";
static final String CATEGORIES = "categories";
static final String CATEGORY_NAME = "name";
static final String CATEGORY_DESCRIPTION = "description";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.hibernate.search.build.report.loggers.LoggerCategoriesProcessor,isolating
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.hibernate.search.build.report.loggers.LoggerCategoriesProcessor
Loading

0 comments on commit 6249c01

Please sign in to comment.