diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java index 4605fcb70b1..f46150f4a35 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java @@ -222,6 +222,7 @@ void whenNoGroupIdAndArtifactId_thenWarningIsPrinted(@TempDir(cleanup = CleanupM assertThat(diagnostics).hasSize(1); // The warning message should contain the information about the missing groupId and artifactId arguments assertThat(diagnostics.get(0)) + .startsWith("[Log4j] ") .contains( "recommended", "-A" + GraalVmProcessor.GROUP_ID + "=", @@ -239,8 +240,56 @@ void whenNoGroupIdAndArtifactId_thenWarningIsPrinted(@TempDir(cleanup = CleanupM .exists(); } + @Test + void noteEmittedByDefaultWithLog4jPrefix(@TempDir Path outputDir) throws Exception { + List> diagnostics = + generateDiagnostics(sourceDir, GROUP_ID, ARTIFACT_ID, outputDir); + + assertThat(diagnostics) + .anyMatch(diagnostic -> diagnostic.getKind() == Diagnostic.Kind.NOTE + && diagnostic + .getMessage(Locale.ROOT) + .startsWith("[Log4j] GraalVmProcessor: writing GraalVM metadata")); + } + + @Test + void notesSuppressedWithoutAffectingMetadataGeneration(@TempDir Path outputDir) throws Exception { + List> diagnostics = generateDiagnostics( + sourceDir, + GROUP_ID, + ARTIFACT_ID, + outputDir, + "-A" + PluginProcessor.MIN_ALLOWED_MESSAGE_KIND_OPTION + "=warning"); + + assertThat(diagnostics) + .noneMatch(diagnostic -> diagnostic.getKind() == Diagnostic.Kind.NOTE + && diagnostic.getMessage(Locale.ROOT).contains("writing GraalVM metadata")); + assertThat(outputDir.resolve("META-INF/native-image/log4j-generated/groupId/artifactId/reflect-config.json")) + .exists(); + } + private static List generateDescriptor( - Path sourceDir, @Nullable String groupId, @Nullable String artifactId, Path outputDir) throws Exception { + Path sourceDir, + @Nullable String groupId, + @Nullable String artifactId, + Path outputDir, + String... extraOptions) + throws Exception { + return generateDiagnostics(sourceDir, groupId, artifactId, outputDir, extraOptions).stream() + .filter(d -> d.getKind() != Diagnostic.Kind.NOTE) + .map(d -> d.getMessage(Locale.ROOT)) + // This message appears when the test runs on JDK 8 + .filter(m -> !"unknown enum constant java.lang.annotation.ElementType.MODULE".equals(m)) + .collect(Collectors.toList()); + } + + private static List> generateDiagnostics( + Path sourceDir, + @Nullable String groupId, + @Nullable String artifactId, + Path outputDir, + String... extraOptions) + throws Exception { // Instantiate the tooling final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.ROOT, UTF_8); @@ -267,6 +316,7 @@ private static List generateDescriptor( if (artifactId != null) { options.add("-A" + GraalVmProcessor.ARTIFACT_ID + "=" + artifactId); } + options.addAll(asList(extraOptions)); // Compile the sources final DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); @@ -274,12 +324,6 @@ private static List generateDescriptor( compiler.getTask(null, fileManager, diagnosticCollector, options, null, sources); task.call(); - // Verify successful compilation - return diagnosticCollector.getDiagnostics().stream() - .filter(d -> d.getKind() != Diagnostic.Kind.NOTE) - .map(d -> d.getMessage(Locale.ROOT)) - // This message appears when the test runs on JDK 8 - .filter(m -> !"unknown enum constant java.lang.annotation.ElementType.MODULE".equals(m)) - .collect(Collectors.toList()); + return diagnosticCollector.getDiagnostics(); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorPublicSetterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorPublicSetterTest.java index 2b6a03d1fea..3772cced4dc 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorPublicSetterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorPublicSetterTest.java @@ -99,7 +99,7 @@ void tearDown() { void warnWhenPluginBuilderAttributeLacksPublicSetter() { assertThat(errorDiagnostics).anyMatch(errorMessage -> errorMessage .getMessage(Locale.ROOT) - .contains("The field `attribute` does not have a public setter")); + .startsWith("[Log4j] The field `attribute` does not have a public setter")); } @Test @@ -117,7 +117,8 @@ void noteEmittedByDefault() { final List> noteDiagnostics = diagnosticCollector.getDiagnostics().stream() .filter(d -> d.getKind() == Diagnostic.Kind.NOTE) .collect(Collectors.toList()); - assertThat(noteDiagnostics).anyMatch(d -> d.getMessage(Locale.ROOT).contains("writing plugin descriptor")); + assertThat(noteDiagnostics).anyMatch(d -> d.getMessage(Locale.ROOT) + .startsWith("[Log4j] PluginProcessor: writing plugin descriptor")); } @Test @@ -161,6 +162,9 @@ void invalidKindValueEmitsWarning() { .filter(d -> d.getKind() == Diagnostic.Kind.WARNING) .collect(Collectors.toList()); assertThat(warningDiagnostics) - .anyMatch(d -> d.getMessage(Locale.ROOT).contains("unrecognized value `INVALID`")); + .anyMatch(d -> d.getMessage(Locale.ROOT) + .startsWith( + "[Log4j] org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor:") + && d.getMessage(Locale.ROOT).contains("unrecognized value `INVALID`")); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java index 4603ec5ee91..05528f96a92 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java @@ -25,10 +25,10 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; @@ -71,23 +71,41 @@ "org.apache.logging.log4j.core.config.plugins.PluginValue", "org.apache.logging.log4j.core.config.plugins.PluginVisitorStrategy" }) -@SupportedOptions({"log4j.graalvm.groupId", "log4j.graalvm.artifactId"}) +@SupportedOptions({"log4j.graalvm.groupId", "log4j.graalvm.artifactId", "log4j.plugin.processor.minAllowedMessageKind"}) public class GraalVmProcessor extends AbstractProcessor { static final String GROUP_ID = "log4j.graalvm.groupId"; static final String ARTIFACT_ID = "log4j.graalvm.artifactId"; private static final String LOCATION_PREFIX = "META-INF/native-image/log4j-generated/"; private static final String LOCATION_SUFFIX = "/reflect-config.json"; + private static final String MESSAGE_PREFIX = "[Log4j] "; private static final String PROCESSOR_NAME = GraalVmProcessor.class.getSimpleName(); private final Map reachableTypes = new HashMap<>(); private final List processedElements = new ArrayList<>(); private Annotations annotationUtil; + private Diagnostic.Kind minAllowedMessageKind = Diagnostic.Kind.NOTE; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.annotationUtil = new Annotations(processingEnv.getElementUtils()); + final String kindValue = processingEnv.getOptions().get(PluginProcessor.MIN_ALLOWED_MESSAGE_KIND_OPTION); + if (kindValue != null) { + try { + minAllowedMessageKind = Diagnostic.Kind.valueOf(kindValue.toUpperCase(Locale.ROOT)); + } catch (final IllegalArgumentException e) { + printMessage( + Diagnostic.Kind.WARNING, + String.format( + "%s: unrecognized value `%s` for option `%s`, using default `%s`. Valid values: %s", + GraalVmProcessor.class.getName(), + kindValue, + PluginProcessor.MIN_ALLOWED_MESSAGE_KIND_OPTION, + Diagnostic.Kind.NOTE, + Arrays.toString(Diagnostic.Kind.values()))); + } + } } @Override @@ -97,7 +115,6 @@ public SourceVersion getSupportedSourceVersion() { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - Messager messager = processingEnv.getMessager(); for (TypeElement annotation : annotations) { Annotations.Type annotationType = annotationUtil.classifyAnnotation(annotation); for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) { @@ -115,7 +132,7 @@ public boolean process(Set annotations, RoundEnvironment processFactory(element); break; case UNKNOWN: - messager.printMessage( + printMessage( Diagnostic.Kind.WARNING, String.format( "The annotation type `%s` is not handled by %s", annotation, PROCESSOR_NAME), @@ -174,7 +191,7 @@ private void processParameter(Element element) { break; default: String msg = String.format("Invalid Log4j parameter element `%s`.", element); - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, element); + printMessage(Diagnostic.Kind.ERROR, msg, element); throw new IllegalStateException(msg); } } @@ -192,7 +209,7 @@ private void writeReachabilityMetadata() { } catch (IOException e) { String message = String.format( "%s: an error occurred while generating reachability metadata: %s", PROCESSOR_NAME, e.getMessage()); - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message); + printMessage(Diagnostic.Kind.ERROR, message); return; } byte[] data = arrayOutputStream.toByteArray(); @@ -200,8 +217,7 @@ private void writeReachabilityMetadata() { Map options = processingEnv.getOptions(); String reachabilityMetadataPath = getReachabilityMetadataPath( options.get(GROUP_ID), options.get(ARTIFACT_ID), Integer.toHexString(Arrays.hashCode(data))); - Messager messager = processingEnv.getMessager(); - messager.printMessage( + printMessage( Diagnostic.Kind.NOTE, String.format( "%s: writing GraalVM metadata for %d Java classes to `%s`.", @@ -218,7 +234,7 @@ private void writeReachabilityMetadata() { } catch (IOException e) { String message = String.format( "%s: unable to write reachability metadata to file `%s`", PROCESSOR_NAME, reachabilityMetadataPath); - messager.printMessage(Diagnostic.Kind.ERROR, message); + printMessage(Diagnostic.Kind.ERROR, message); throw new IllegalArgumentException(message, e); } } @@ -243,7 +259,7 @@ String getReachabilityMetadataPath( + " -A%2$s=%n" + " -A%3$s=%n", PROCESSOR_NAME, GROUP_ID, ARTIFACT_ID); - processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message); + printMessage(Diagnostic.Kind.WARNING, message); return LOCATION_PREFIX + fallbackFolderName + LOCATION_SUFFIX; } return LOCATION_PREFIX + groupId + '/' + artifactId + LOCATION_SUFFIX; @@ -273,10 +289,22 @@ private T safeCast(Element element, Class type) { String msg = String.format( "Unexpected type of element `%s`: expecting `%s` but found `%s`", element, type.getName(), element.getClass().getName()); - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, element); + printMessage(Diagnostic.Kind.ERROR, msg, element); throw new IllegalStateException(msg); } + private void printMessage(final Diagnostic.Kind kind, final String message) { + if (kind.ordinal() <= minAllowedMessageKind.ordinal()) { + processingEnv.getMessager().printMessage(kind, MESSAGE_PREFIX + message); + } + } + + private void printMessage(final Diagnostic.Kind kind, final String message, final Element element) { + if (kind.ordinal() <= minAllowedMessageKind.ordinal()) { + processingEnv.getMessager().printMessage(kind, MESSAGE_PREFIX + message, element); + } + } + /** * Returns the fully qualified name of a type. * diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java index 94557a7ddb7..4d8da8dd8c6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java @@ -68,6 +68,7 @@ public class PluginProcessor extends AbstractProcessor { // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing private static final Element[] EMPTY_ELEMENT_ARRAY = {}; + private static final String MESSAGE_PREFIX = "[Log4j] "; private static final String SUPPRESS_WARNING_PUBLIC_SETTER_STRING = "log4j.public.setter"; @@ -105,17 +106,15 @@ public void init(final ProcessingEnvironment processingEnv) { try { minAllowedMessageKind = Diagnostic.Kind.valueOf(kindValue.toUpperCase(Locale.ROOT)); } catch (final IllegalArgumentException e) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.WARNING, - String.format( - "%s: unrecognized value `%s` for option `%s`, using default `%s`. Valid values: %s", - PluginProcessor.class.getName(), - kindValue, - MIN_ALLOWED_MESSAGE_KIND_OPTION, - Diagnostic.Kind.NOTE, - Arrays.toString(Diagnostic.Kind.values()))); + printMessage( + Diagnostic.Kind.WARNING, + String.format( + "%s: unrecognized value `%s` for option `%s`, using default `%s`. Valid values: %s", + PluginProcessor.class.getName(), + kindValue, + MIN_ALLOWED_MESSAGE_KIND_OPTION, + Diagnostic.Kind.NOTE, + Arrays.toString(Diagnostic.Kind.values()))); } } } @@ -134,7 +133,7 @@ public SourceVersion getSupportedSourceVersion() { */ private void printMessage(final Diagnostic.Kind kind, final String message) { if (kind.ordinal() <= minAllowedMessageKind.ordinal()) { - processingEnv.getMessager().printMessage(kind, message); + processingEnv.getMessager().printMessage(kind, MESSAGE_PREFIX + message); } } @@ -144,7 +143,7 @@ private void printMessage(final Diagnostic.Kind kind, final String message) { */ private void printMessage(final Diagnostic.Kind kind, final String message, final Element element) { if (kind.ordinal() <= minAllowedMessageKind.ordinal()) { - processingEnv.getMessager().printMessage(kind, message, element); + processingEnv.getMessager().printMessage(kind, MESSAGE_PREFIX + message, element); } } diff --git a/src/changelog/.2.x.x/4225_plugin_processor_messages.xml b/src/changelog/.2.x.x/4225_plugin_processor_messages.xml new file mode 100644 index 00000000000..222dabe4deb --- /dev/null +++ b/src/changelog/.2.x.x/4225_plugin_processor_messages.xml @@ -0,0 +1,12 @@ + + + + + Allow `log4j.plugin.processor.minAllowedMessageKind` to filter `GraalVmProcessor` diagnostics and prefix all plugin processor messages with `[Log4j]`. + + diff --git a/src/site/antora/modules/ROOT/pages/manual/plugins.adoc b/src/site/antora/modules/ROOT/pages/manual/plugins.adoc index f8d6b5ff4b9..7fbc837f2d7 100644 --- a/src/site/antora/modules/ROOT/pages/manual/plugins.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/plugins.adoc @@ -215,13 +215,13 @@ The `GraalVmProcessor` requires your project's `groupId` and `artifactId` to cor Provide these values to the processor using the `log4j.graalvm.groupId` and `log4j.graalvm.artifactId` annotation processor options. ==== -.Suppressing notes from `PluginProcessor` in strict build environments +.Suppressing annotation processor notes in strict build environments [%collapsible] ==== Some build environments treat all compiler notes or warnings as errors (e.g., Maven with `-Werror` or Gradle with `options.compilerArgs << '-Werror'`). -By default, `PluginProcessor` emits a `NOTE`-level diagnostic when it writes the plugin descriptor, which can cause the build to fail in those environments. +By default, `PluginProcessor` and `GraalVmProcessor` emit `NOTE`-level diagnostics when they write their descriptors, which can cause the build to fail in those environments. To suppress these informational notes, pass the `log4j.plugin.processor.minAllowedMessageKind` annotation processor option with a value of `WARNING` or `ERROR`. -This instructs the processor to only emit diagnostics at or above the specified severity, silencing routine notes while preserving genuine warnings and errors. +This instructs both processors to only emit diagnostics at or above the specified severity, silencing routine notes while preserving genuine warnings and errors. Accepted values (case-insensitive): `NOTE` (default), `WARNING`, `MANDATORY_WARNING`, `ERROR`, `OTHER`.