Skip to content

Commit 1fdfe14

Browse files
Allow severity-based log message filtering in PluginProcessor (#4063)
1 parent c5396fc commit 1fdfe14

File tree

4 files changed

+180
-19
lines changed

4 files changed

+180
-19
lines changed

log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorPublicSetterTest.java

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import org.junit.jupiter.api.AfterEach;
3838
import org.junit.jupiter.api.BeforeEach;
3939
import org.junit.jupiter.api.Test;
40+
import org.junit.jupiter.params.ParameterizedTest;
41+
import org.junit.jupiter.params.provider.ValueSource;
4042

4143
public class PluginProcessorPublicSetterTest {
4244

@@ -50,6 +52,10 @@ public class PluginProcessorPublicSetterTest {
5052

5153
@BeforeEach
5254
void setup() {
55+
setupWithOptions();
56+
}
57+
58+
private void setupWithOptions(final String... extraOptions) {
5359
// Instantiate the tooling
5460
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
5561
diagnosticCollector = new DiagnosticCollector<>();
@@ -67,13 +73,12 @@ void setup() {
6773
// get compilation units
6874
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(createdFile);
6975

70-
JavaCompiler.CompilationTask task = compiler.getTask(
71-
null,
72-
fileManager,
73-
diagnosticCollector,
74-
Arrays.asList("-proc:only", "-processor", PluginProcessor.class.getName()),
75-
null,
76-
compilationUnits);
76+
final List<String> args =
77+
new java.util.ArrayList<>(Arrays.asList("-proc:only", "-processor", PluginProcessor.class.getName()));
78+
args.addAll(Arrays.asList(extraOptions));
79+
80+
JavaCompiler.CompilationTask task =
81+
compiler.getTask(null, fileManager, diagnosticCollector, args, null, compilationUnits);
7782
task.call();
7883

7984
errorDiagnostics = diagnosticCollector.getDiagnostics().stream()
@@ -92,7 +97,6 @@ void tearDown() {
9297

9398
@Test
9499
void warnWhenPluginBuilderAttributeLacksPublicSetter() {
95-
96100
assertThat(errorDiagnostics).anyMatch(errorMessage -> errorMessage
97101
.getMessage(Locale.ROOT)
98102
.contains("The field `attribute` does not have a public setter"));
@@ -107,4 +111,56 @@ void ignoreWarningWhenSuppressWarningsIsPresent() {
107111
.contains(
108112
"The field `attributeWithoutPublicSetterButWithSuppressAnnotation` does not have a public setter"));
109113
}
114+
115+
@Test
116+
void noteEmittedByDefault() {
117+
final List<Diagnostic<? extends JavaFileObject>> noteDiagnostics = diagnosticCollector.getDiagnostics().stream()
118+
.filter(d -> d.getKind() == Diagnostic.Kind.NOTE)
119+
.collect(Collectors.toList());
120+
assertThat(noteDiagnostics).anyMatch(d -> d.getMessage(Locale.ROOT).contains("writing plugin descriptor"));
121+
}
122+
123+
@Test
124+
void notesSuppressedWhenMinKindIsError() {
125+
setupWithOptions("-A" + PluginProcessor.MIN_ALLOWED_MESSAGE_KIND_OPTION + "=ERROR");
126+
127+
final List<Diagnostic<? extends JavaFileObject>> noteDiagnostics = diagnosticCollector.getDiagnostics().stream()
128+
.filter(d -> d.getKind() == Diagnostic.Kind.NOTE)
129+
.collect(Collectors.toList());
130+
assertThat(noteDiagnostics).noneMatch(d -> d.getMessage(Locale.ROOT).contains("writing plugin descriptor"));
131+
}
132+
133+
@Test
134+
void errorsStillEmittedWhenMinKindIsError() {
135+
setupWithOptions("-A" + PluginProcessor.MIN_ALLOWED_MESSAGE_KIND_OPTION + "=ERROR");
136+
137+
assertThat(errorDiagnostics).anyMatch(d -> d.getMessage(Locale.ROOT)
138+
.contains("The field `attribute` does not have a public setter"));
139+
}
140+
141+
@ParameterizedTest
142+
@ValueSource(strings = {"NOTE", "note"})
143+
void explicitNoteKindBehavesLikeDefault(final String kindValue) {
144+
setupWithOptions("-A" + PluginProcessor.MIN_ALLOWED_MESSAGE_KIND_OPTION + "=" + kindValue);
145+
146+
assertThat(errorDiagnostics).anyMatch(d -> d.getMessage(Locale.ROOT)
147+
.contains("The field `attribute` does not have a public setter"));
148+
149+
final List<Diagnostic<? extends JavaFileObject>> noteDiagnostics = diagnosticCollector.getDiagnostics().stream()
150+
.filter(d -> d.getKind() == Diagnostic.Kind.NOTE)
151+
.collect(Collectors.toList());
152+
assertThat(noteDiagnostics).anyMatch(d -> d.getMessage(Locale.ROOT).contains("writing plugin descriptor"));
153+
}
154+
155+
@Test
156+
void invalidKindValueEmitsWarning() {
157+
setupWithOptions("-A" + PluginProcessor.MIN_ALLOWED_MESSAGE_KIND_OPTION + "=INVALID");
158+
159+
final List<Diagnostic<? extends JavaFileObject>> warningDiagnostics =
160+
diagnosticCollector.getDiagnostics().stream()
161+
.filter(d -> d.getKind() == Diagnostic.Kind.WARNING)
162+
.collect(Collectors.toList());
163+
assertThat(warningDiagnostics)
164+
.anyMatch(d -> d.getMessage(Locale.ROOT).contains("unrecognized value `INVALID`"));
165+
}
110166
}

log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
import java.util.Set;
3636
import javax.annotation.processing.AbstractProcessor;
3737
import javax.annotation.processing.Messager;
38+
import javax.annotation.processing.ProcessingEnvironment;
3839
import javax.annotation.processing.Processor;
3940
import javax.annotation.processing.RoundEnvironment;
4041
import javax.annotation.processing.SupportedAnnotationTypes;
42+
import javax.annotation.processing.SupportedOptions;
4143
import javax.lang.model.SourceVersion;
4244
import javax.lang.model.element.Element;
4345
import javax.lang.model.element.ElementVisitor;
@@ -60,6 +62,7 @@
6062
*/
6163
@ServiceProvider(value = Processor.class, resolution = Resolution.OPTIONAL)
6264
@SupportedAnnotationTypes("org.apache.logging.log4j.core.config.plugins.Plugin")
65+
@SupportedOptions("log4j.plugin.processor.minAllowedMessageKind")
6366
public class PluginProcessor extends AbstractProcessor {
6467

6568
// TODO: this could be made more abstract to allow for compile-time and run-time plugin processing
@@ -68,6 +71,21 @@ public class PluginProcessor extends AbstractProcessor {
6871

6972
private static final String SUPPRESS_WARNING_PUBLIC_SETTER_STRING = "log4j.public.setter";
7073

74+
/**
75+
* Annotation processor option that controls the minimum {@link Diagnostic.Kind} of messages emitted by this
76+
* processor.
77+
* <p>
78+
* Some build environments (e.g. Maven with {@code -Werror}) treat compiler notes or warnings as errors.
79+
* Setting this option to {@code WARNING} or {@code ERROR} suppresses informational notes emitted during
80+
* normal processing.
81+
* </p>
82+
* <p>
83+
* Accepted values (case-insensitive): {@code NOTE}, {@code WARNING}, {@code MANDATORY_WARNING},
84+
* {@code ERROR}, {@code OTHER}. Defaults to {@code NOTE}.
85+
* </p>
86+
*/
87+
static final String MIN_ALLOWED_MESSAGE_KIND_OPTION = "log4j.plugin.processor.minAllowedMessageKind";
88+
7189
/**
7290
* The location of the plugin cache data file. This file is written to by this processor, and read from by
7391
* {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager}.
@@ -77,6 +95,30 @@ public class PluginProcessor extends AbstractProcessor {
7795

7896
private final List<Element> processedElements = new ArrayList<>();
7997
private final PluginCache pluginCache = new PluginCache();
98+
private Diagnostic.Kind minAllowedMessageKind = Diagnostic.Kind.NOTE;
99+
100+
@Override
101+
public void init(final ProcessingEnvironment processingEnv) {
102+
super.init(processingEnv);
103+
final String kindValue = processingEnv.getOptions().get(MIN_ALLOWED_MESSAGE_KIND_OPTION);
104+
if (kindValue != null) {
105+
try {
106+
minAllowedMessageKind = Diagnostic.Kind.valueOf(kindValue.toUpperCase(Locale.ROOT));
107+
} catch (final IllegalArgumentException e) {
108+
processingEnv
109+
.getMessager()
110+
.printMessage(
111+
Diagnostic.Kind.WARNING,
112+
String.format(
113+
"%s: unrecognized value `%s` for option `%s`, using default `%s`. Valid values: %s",
114+
PluginProcessor.class.getName(),
115+
kindValue,
116+
MIN_ALLOWED_MESSAGE_KIND_OPTION,
117+
Diagnostic.Kind.NOTE,
118+
Arrays.toString(Diagnostic.Kind.values())));
119+
}
120+
}
121+
}
80122

81123
@Override
82124
public SourceVersion getSupportedSourceVersion() {
@@ -86,9 +128,28 @@ public SourceVersion getSupportedSourceVersion() {
86128
private static final String PLUGIN_BUILDER_ATTRIBUTE_ANNOTATION =
87129
"org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute";
88130

131+
/**
132+
* Prints a message via the {@link Messager} only if {@code kind} is at least as severe as the configured
133+
* {@link #MIN_ALLOWED_MESSAGE_KIND_OPTION minimum allowed kind}.
134+
*/
135+
private void printMessage(final Diagnostic.Kind kind, final String message) {
136+
if (kind.ordinal() <= minAllowedMessageKind.ordinal()) {
137+
processingEnv.getMessager().printMessage(kind, message);
138+
}
139+
}
140+
141+
/**
142+
* Prints a message via the {@link Messager} only if {@code kind} is at least as severe as the configured
143+
* {@link #MIN_ALLOWED_MESSAGE_KIND_OPTION minimum allowed kind}.
144+
*/
145+
private void printMessage(final Diagnostic.Kind kind, final String message, final Element element) {
146+
if (kind.ordinal() <= minAllowedMessageKind.ordinal()) {
147+
processingEnv.getMessager().printMessage(kind, message, element);
148+
}
149+
}
150+
89151
@Override
90152
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
91-
final Messager messager = processingEnv.getMessager();
92153
final Elements elementUtils = processingEnv.getElementUtils();
93154
// Process the elements for this round
94155
if (!annotations.isEmpty()) {
@@ -105,7 +166,7 @@ public boolean process(final Set<? extends TypeElement> annotations, final Round
105166
// Write the cache file
106167
if (roundEnv.processingOver() && !processedElements.isEmpty()) {
107168
try {
108-
messager.printMessage(
169+
printMessage(
109170
Diagnostic.Kind.NOTE,
110171
String.format(
111172
"%s: writing plugin descriptor for %d Log4j Plugins to `%s`.",
@@ -118,7 +179,7 @@ public boolean process(final Set<? extends TypeElement> annotations, final Round
118179
.append(PLUGIN_CACHE_FILE)
119180
.append("\n");
120181
e.printStackTrace(new PrintWriter(sw));
121-
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, sw.toString());
182+
printMessage(Diagnostic.Kind.ERROR, sw.toString());
122183
}
123184
}
124185
// Do not claim the annotations to allow other annotation processors to run
@@ -180,14 +241,12 @@ private void processBuilderAttribute(final VariableElement element) {
180241
}
181242
}
182243
// If the setter was not found generate a compiler warning.
183-
processingEnv
184-
.getMessager()
185-
.printMessage(
186-
Diagnostic.Kind.ERROR,
187-
String.format(
188-
"The field `%s` does not have a public setter, Note that @SuppressWarnings(\"%s\"), can be used on the field to suppress the compilation error. ",
189-
fieldName, SUPPRESS_WARNING_PUBLIC_SETTER_STRING),
190-
element);
244+
printMessage(
245+
Diagnostic.Kind.ERROR,
246+
String.format(
247+
"The field `%s` does not have a public setter, Note that @SuppressWarnings(\"%s\"), can be used on the field to suppress the compilation error. ",
248+
fieldName, SUPPRESS_WARNING_PUBLIC_SETTER_STRING),
249+
element);
191250
}
192251
}
193252

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<entry xmlns="https://logging.apache.org/xml/ns"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="
5+
https://logging.apache.org/xml/ns
6+
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
7+
type="added">
8+
<issue id="3380" link="https://github.com/apache/logging-log4j2/discussions/3380"/>
9+
<issue id="4063" link="https://github.com/apache/logging-log4j2/pull/4063"/>
10+
<description format="asciidoc">
11+
Add `log4j.plugin.processor.minAllowedMessageKind` annotation processor option to `PluginProcessor` to filter diagnostic messages by severity.
12+
This allows builds that treat compiler notes as errors (e.g. Maven with `-Werror`) to suppress informational notes emitted during normal plugin processing.
13+
</description>
14+
</entry>

src/site/antora/modules/ROOT/pages/manual/plugins.adoc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,38 @@ The `GraalVmProcessor` requires your project's `groupId` and `artifactId` to cor
215215
Provide these values to the processor using the `log4j.graalvm.groupId` and `log4j.graalvm.artifactId` annotation processor options.
216216
====
217217
218+
.Suppressing notes from `PluginProcessor` in strict build environments
219+
[%collapsible]
220+
====
221+
Some build environments treat all compiler notes or warnings as errors (e.g., Maven with `-Werror` or Gradle with `options.compilerArgs << '-Werror'`).
222+
By default, `PluginProcessor` emits a `NOTE`-level diagnostic when it writes the plugin descriptor, which can cause the build to fail in those environments.
223+
To suppress these informational notes, pass the `log4j.plugin.processor.minAllowedMessageKind` annotation processor option with a value of `WARNING` or `ERROR`.
224+
This instructs the processor to only emit diagnostics at or above the specified severity, silencing routine notes while preserving genuine warnings and errors.
225+
226+
Accepted values (case-insensitive): `NOTE` (default), `WARNING`, `MANDATORY_WARNING`, `ERROR`, `OTHER`.
227+
228+
[tabs]
229+
=====
230+
Maven::
231+
+
232+
[source,xml]
233+
----
234+
<compilerArgs>
235+
<arg>-Alog4j.plugin.processor.minAllowedMessageKind=WARNING</arg>
236+
</compilerArgs>
237+
----
238+
239+
Gradle::
240+
+
241+
[source,groovy]
242+
----
243+
compileJava {
244+
options.compilerArgs << '-Alog4j.plugin.processor.minAllowedMessageKind=WARNING'
245+
}
246+
----
247+
=====
248+
====
249+
218250
You need to configure your build tool as follows to use both plugin processors:
219251
220252
[tabs]

0 commit comments

Comments
 (0)