Skip to content

Commit 230553f

Browse files
committed
Improve extensibility of DefaultConfigurationBuilder/BuiltConfiguration (apache#3441)
1 parent a4dfb37 commit 230553f

File tree

8 files changed

+1364
-327
lines changed

8 files changed

+1364
-327
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.core.config.builder;
18+
19+
import static org.assertj.core.api.Assertions.assertThatCollection;
20+
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
22+
import static org.junit.jupiter.api.Assertions.assertNotNull;
23+
24+
import java.lang.reflect.Constructor;
25+
import java.util.Arrays;
26+
import java.util.Objects;
27+
import java.util.concurrent.TimeUnit;
28+
import java.util.function.Function;
29+
import java.util.stream.Collectors;
30+
import org.apache.logging.log4j.Level;
31+
import org.apache.logging.log4j.core.Appender;
32+
import org.apache.logging.log4j.core.Filter;
33+
import org.apache.logging.log4j.core.LoggerContext;
34+
import org.apache.logging.log4j.core.appender.ConsoleAppender;
35+
import org.apache.logging.log4j.core.appender.mom.kafka.KafkaAppender;
36+
import org.apache.logging.log4j.core.config.ConfigurationSource;
37+
import org.apache.logging.log4j.core.config.CustomLevelConfig;
38+
import org.apache.logging.log4j.core.config.Property;
39+
import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
40+
import org.apache.logging.log4j.core.config.builder.api.Component;
41+
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
42+
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
43+
import org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder;
44+
import org.apache.logging.log4j.core.filter.ThresholdFilter;
45+
import org.junit.jupiter.api.Test;
46+
47+
class CustomBuiltConfigurationTest {
48+
49+
private static final String CONFIG_NAME = "FooBar Configuration";
50+
51+
private static final FooBar FOOBAR = new FooBar("wingding");
52+
53+
/** Test that the build configuration contains the intended attributes. */
54+
@Test
55+
void testCustomBuiltConfiguration_Attributes() {
56+
57+
final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
58+
final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
59+
final FooBarConfiguration config = builder.build(false);
60+
61+
try {
62+
63+
// build the configuration and set it in the context to start the configuration
64+
loggerContext.setConfiguration(config);
65+
66+
assertNotNull(config);
67+
assertEquals(CONFIG_NAME, config.getName());
68+
assertEquals(10, config.getMonitorInterval());
69+
assertEquals(5000, config.getShutdownTimeoutMillis());
70+
assertThatCollection(config.getPluginPackages()).containsExactlyInAnyOrder("foo", "bar");
71+
72+
} finally {
73+
74+
loggerContext.stop();
75+
}
76+
}
77+
78+
/** Test that the custom constructor of the custom configuration was called and the test object is accessible. */
79+
@Test
80+
void testCustomBuiltConfiguration_CustomObject() {
81+
82+
final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
83+
final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
84+
final FooBarConfiguration config = builder.build(false);
85+
86+
try {
87+
88+
// build the configuration and set it in the context to start the configuration
89+
loggerContext.setConfiguration(config);
90+
91+
assertNotNull(config);
92+
93+
final FooBar fb = config.getFooBar();
94+
assertNotNull(fb);
95+
assertEquals(FOOBAR, fb);
96+
assertEquals(FOOBAR.getValue(), fb.getValue());
97+
98+
} finally {
99+
100+
loggerContext.stop();
101+
}
102+
}
103+
104+
/** Test that the build configuration contains the intended appenders. */
105+
@Test
106+
void testCustomBuiltConfiguration_Appenders() {
107+
108+
final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
109+
final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
110+
final FooBarConfiguration config = builder.build(false);
111+
112+
try {
113+
114+
// build the configuration and set it in the context to start the configuration
115+
loggerContext.setConfiguration(config);
116+
117+
assertNotNull(config);
118+
assertThatCollection(config.getAppenders().keySet()).containsExactlyInAnyOrder("Stdout", "Kafka");
119+
120+
} finally {
121+
122+
loggerContext.stop();
123+
}
124+
}
125+
126+
/** Test that the build configuration contains the custom levels. */
127+
@Test
128+
void testCustomBuiltConfiguration_CustomLevels() {
129+
130+
final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
131+
final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
132+
final FooBarConfiguration config = builder.build(false);
133+
134+
try {
135+
136+
// build the configuration and set it in the context to start the configuration
137+
loggerContext.setConfiguration(config);
138+
139+
assertNotNull(config);
140+
assertThatCollection(config.getCustomLevels().stream()
141+
.map(CustomLevelConfig::getLevelName)
142+
.collect(Collectors.toList()))
143+
.containsExactlyInAnyOrder("Panic");
144+
145+
} finally {
146+
147+
loggerContext.stop();
148+
}
149+
}
150+
151+
/** Test that the build configuration contains the intended filter. */
152+
@Test
153+
void testCustomBuiltConfiguration_Filter() {
154+
155+
final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
156+
final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
157+
final FooBarConfiguration config = builder.build(false);
158+
159+
try {
160+
161+
// build the configuration and set it in the context to start the configuration
162+
loggerContext.setConfiguration(config);
163+
164+
assertNotNull(config);
165+
Filter filter = config.getFilter();
166+
assertNotNull(filter);
167+
assertInstanceOf(ThresholdFilter.class, filter);
168+
169+
} finally {
170+
171+
loggerContext.stop();
172+
}
173+
}
174+
175+
/** Test that the build configuration contains the intended loggers. */
176+
@Test
177+
void testCustomBuiltConfiguration_Loggers() {
178+
179+
final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
180+
final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
181+
final FooBarConfiguration config = builder.build(false);
182+
183+
try {
184+
185+
// build the configuration and set it in the context to start the configuration
186+
loggerContext.setConfiguration(config);
187+
188+
assertNotNull(config);
189+
assertThatCollection(config.getLoggers().keySet())
190+
.containsExactlyInAnyOrder("", "org.apache.logging.log4j", "org.apache.logging.log4j.core");
191+
192+
} finally {
193+
194+
loggerContext.stop();
195+
}
196+
}
197+
198+
/** Test that the build configuration correctly registers and resolves properties. */
199+
@Test
200+
void testCustomBuiltConfiguration_PropertyResolution() {
201+
202+
final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
203+
final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
204+
final FooBarConfiguration config = builder.build(false);
205+
206+
try {
207+
208+
// build the configuration and set it in the context to start the configuration
209+
loggerContext.setConfiguration(config);
210+
211+
assertNotNull(config);
212+
assertEquals("Wing", config.getConfigurationStrSubstitutor().replace("${P1}"));
213+
assertEquals("WingDing", config.getConfigurationStrSubstitutor().replace("${P2}"));
214+
215+
Appender kafkaAppender = config.getAppender("Kafka");
216+
assertNotNull(kafkaAppender);
217+
assertInstanceOf(KafkaAppender.class, kafkaAppender);
218+
final Property p2Property = Arrays.stream(((KafkaAppender) kafkaAppender).getPropertyArray())
219+
.collect(Collectors.toMap(Property::getName, Function.identity()))
220+
.get("P2");
221+
assertNotNull(p2Property);
222+
assertEquals("WingDing", p2Property.getValue());
223+
224+
} finally {
225+
226+
loggerContext.stop();
227+
}
228+
}
229+
230+
/**
231+
* Creates, preconfigures and returns a test builder instance.
232+
* @param loggerContext the logger context to use
233+
* @return the created configuration builder
234+
*/
235+
private FoobarConfigurationBuilder createTestBuilder(final LoggerContext loggerContext) {
236+
final FoobarConfigurationBuilder builder = new FoobarConfigurationBuilder();
237+
builder.setLoggerContext(loggerContext);
238+
addTestFixtures(builder);
239+
return builder;
240+
}
241+
242+
/**
243+
* Populates the given configuration-builder.
244+
* @param builder the builder
245+
*/
246+
private void addTestFixtures(final ConfigurationBuilder<? extends BuiltConfiguration> builder) {
247+
builder.setConfigurationName(CONFIG_NAME);
248+
builder.setStatusLevel(Level.ERROR);
249+
builder.setMonitorInterval(10);
250+
builder.setShutdownTimeout(5000, TimeUnit.MILLISECONDS);
251+
builder.add(builder.newScriptFile("target/test-classes/scripts/filter.groovy")
252+
.addIsWatched(true));
253+
builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL)
254+
.addAttribute("level", Level.DEBUG));
255+
256+
final AppenderComponentBuilder appenderBuilder =
257+
builder.newAppender("Stdout", "CONSOLE").addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
258+
appenderBuilder.add(
259+
builder.newLayout("PatternLayout").addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable"));
260+
appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
261+
.addAttribute("marker", "FLOW"));
262+
builder.add(appenderBuilder);
263+
264+
final AppenderComponentBuilder appenderBuilder2 =
265+
builder.newAppender("Kafka", "Kafka").addAttribute("topic", "my-topic");
266+
appenderBuilder2.addComponent(builder.newProperty("bootstrap.servers", "localhost:9092"));
267+
appenderBuilder2.addComponent(builder.newProperty("P2", "${P2}"));
268+
appenderBuilder2.add(builder.newLayout("GelfLayout")
269+
.addAttribute("host", "my-host")
270+
.addComponent(builder.newKeyValuePair("extraField", "extraValue")));
271+
builder.add(appenderBuilder2);
272+
273+
builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG, true)
274+
.add(builder.newAppenderRef("Stdout"))
275+
.addAttribute("additivity", false));
276+
builder.add(builder.newLogger("org.apache.logging.log4j.core").add(builder.newAppenderRef("Stdout")));
277+
builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout")));
278+
279+
builder.addProperty("P1", "Wing");
280+
builder.addProperty("P2", "${P1}Ding");
281+
builder.add(builder.newCustomLevel("Panic", 17));
282+
builder.setPackages("foo,bar");
283+
}
284+
285+
//
286+
// Test implementations
287+
//
288+
289+
/** A custom {@link DefaultConfigurationBuilder} implementation that generates a {@link FooBarConfiguration}. */
290+
public static class FoobarConfigurationBuilder extends DefaultConfigurationBuilder<FooBarConfiguration> {
291+
292+
public FoobarConfigurationBuilder() {
293+
super(FooBarConfiguration.class);
294+
}
295+
296+
/** {@inheritDoc} */
297+
@Override
298+
protected FooBarConfiguration createNewConfigurationInstance(Class<FooBarConfiguration> configurationClass) {
299+
Objects.requireNonNull(configurationClass, "The 'configurationClass' argument must not be null.");
300+
try {
301+
final Constructor<FooBarConfiguration> constructor = FooBarConfiguration.class.getConstructor(
302+
LoggerContext.class, ConfigurationSource.class, Component.class, FooBar.class);
303+
return constructor.newInstance(
304+
getLoggerContext().orElse(null),
305+
getConfigurationSource().orElse(null),
306+
getRootComponent(),
307+
FOOBAR);
308+
} catch (final Exception ex) {
309+
throw new IllegalStateException(
310+
"Configuration class '" + configurationClass.getName() + "' cannot be instantiated.", ex);
311+
}
312+
}
313+
}
314+
315+
/**
316+
* A custom {@link BuiltConfiguration} implementation with a custom constructor that takes
317+
* an additional {@code FooBar} argument.
318+
*/
319+
public static class FooBarConfiguration extends BuiltConfiguration {
320+
321+
private int monitorInterval;
322+
323+
private final FooBar fooBar;
324+
325+
public FooBarConfiguration(
326+
LoggerContext loggerContext, ConfigurationSource source, Component rootComponent, FooBar fooBar) {
327+
super(loggerContext, source, rootComponent);
328+
this.fooBar = Objects.requireNonNull(fooBar, "fooBar");
329+
}
330+
331+
public FooBar getFooBar() {
332+
return fooBar;
333+
}
334+
335+
public int getMonitorInterval() {
336+
return this.monitorInterval;
337+
}
338+
339+
/** {@inheritDoc} */
340+
@Override
341+
public void setMonitorInterval(int seconds) {
342+
super.setMonitorInterval(seconds);
343+
this.monitorInterval = seconds;
344+
}
345+
}
346+
347+
/** Test object used by custom configuration-builder and configuration. */
348+
public static class FooBar {
349+
350+
private final String value;
351+
352+
public FooBar(final String value) {
353+
this.value = value;
354+
}
355+
356+
public String getValue() {
357+
return this.value;
358+
}
359+
}
360+
}

0 commit comments

Comments
 (0)