Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 + "=<groupId>",
Expand All @@ -239,8 +240,56 @@ void whenNoGroupIdAndArtifactId_thenWarningIsPrinted(@TempDir(cleanup = CleanupM
.exists();
}

@Test
void noteEmittedByDefaultWithLog4jPrefix(@TempDir Path outputDir) throws Exception {
List<Diagnostic<? extends JavaFileObject>> 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<Diagnostic<? extends JavaFileObject>> 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<String> 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<Diagnostic<? extends JavaFileObject>> 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);
Expand All @@ -267,19 +316,14 @@ private static List<String> generateDescriptor(
if (artifactId != null) {
options.add("-A" + GraalVmProcessor.ARTIFACT_ID + "=" + artifactId);
}
options.addAll(asList(extraOptions));

// Compile the sources
final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
final JavaCompiler.CompilationTask task =
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -117,7 +117,8 @@ void noteEmittedByDefault() {
final List<Diagnostic<? extends JavaFileObject>> 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
Expand Down Expand Up @@ -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`"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, ReachabilityMetadata.Type> reachableTypes = new HashMap<>();
private final List<Element> 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
Expand All @@ -97,7 +115,6 @@ public SourceVersion getSupportedSourceVersion() {

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = processingEnv.getMessager();
for (TypeElement annotation : annotations) {
Annotations.Type annotationType = annotationUtil.classifyAnnotation(annotation);
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
Expand All @@ -115,7 +132,7 @@ public boolean process(Set<? extends TypeElement> 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),
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -192,16 +209,15 @@ 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();

Map<String, String> 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`.",
Expand All @@ -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);
}
}
Expand All @@ -243,7 +259,7 @@ String getReachabilityMetadataPath(
+ " -A%2$s=<groupId>%n"
+ " -A%3$s=<artifactId>%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;
Expand Down Expand Up @@ -273,10 +289,22 @@ private <T extends Element> T safeCast(Element element, Class<T> 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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())));
}
}
}
Expand All @@ -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);
}
}

Expand All @@ -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);
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/changelog/.2.x.x/4225_plugin_processor_messages.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="https://logging.apache.org/xml/ns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
type="changed">
<issue id="4225" link="https://github.com/apache/logging-log4j2/issues/4225"/>
<description format="asciidoc">
Allow `log4j.plugin.processor.minAllowedMessageKind` to filter `GraalVmProcessor` diagnostics and prefix all plugin processor messages with `[Log4j]`.
</description>
</entry>
6 changes: 3 additions & 3 deletions src/site/antora/modules/ROOT/pages/manual/plugins.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down