Skip to content

Commit 1f97ae1

Browse files
committed
check for undeclared by referenced appenders
Signed-off-by: ceki <ceki@qos.ch>
1 parent b07355e commit 1f97ae1

File tree

10 files changed

+192
-32
lines changed

10 files changed

+192
-32
lines changed

logback-classic/src/main/java/ch/qos/logback/classic/joran/ModelClassToModelHandlerLinker.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,15 @@ public void link(DefaultProcessor defaultProcessor) {
6363
defaultProcessor.addAnalyser(LoggerModel.class,
6464
() -> new AppenderRefDependencyAnalyser(context));
6565

66+
// an appender may contain appender refs, e.g. AsyncAppender
6667
defaultProcessor.addAnalyser(AppenderModel.class,
6768
() -> new AppenderRefDependencyAnalyser(context));
6869

6970
defaultProcessor.addAnalyser(AppenderModel.class, () -> new FileCollisionAnalyser(context));
7071

72+
73+
defaultProcessor.addAnalyser(AppenderModel.class, () -> new AppenderDeclarationAnalyser(context));
74+
7175
sealModelFilters(defaultProcessor);
7276

7377
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<!DOCTYPE configuration>
3+
4+
<configuration debug="false">
5+
6+
<appender name="A" class="ch.qos.logback.core.read.ListAppender"/>
7+
8+
<logger name="ch.qos.logback.classic.joran" level="DEBUG">
9+
</logger>
10+
11+
<root level="ERROR">
12+
<appender-ref ref="${someUndefinedProp:-A}"/>
13+
</root>
14+
15+
</configuration>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<!--
3+
~ Logback: the reliable, generic, fast and flexible logging framework.
4+
~ Copyright (C) 1999-2026, QOS.ch. All rights reserved.
5+
~
6+
~ This program and the accompanying materials are dual-licensed under
7+
~ either the terms of the Eclipse Public License v1.0 as published by
8+
~ the Eclipse Foundation
9+
~
10+
~ or (per the licensee's choosing)
11+
~
12+
~ under the terms of the GNU Lesser General Public License version 2.1
13+
~ as published by the Free Software Foundation.
14+
-->
15+
16+
<!DOCTYPE configuration>
17+
18+
<configuration debug="false">
19+
20+
<appender name="A" class="ch.qos.logback.core.read.ListAppender"/>
21+
22+
<root level="DEBUG">
23+
<appender-ref ref="NON_EXISTENT_APPENDER"/>
24+
<appender-ref ref="A"/>
25+
26+
</root>
27+
28+
</configuration>

logback-classic/src/test/java/ch/qos/logback/classic/joran/JoranConfiguratorTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,40 @@ public void appenderRefSettingBySystemProperty() throws JoranException {
185185
assertEquals(1, listAppender.list.size());
186186
System.clearProperty(propertyName);
187187
}
188+
@Test
189+
public void appenderRefSettingBySystemPropertyDefault() throws JoranException {
190+
191+
configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "appenderRefByPropertyDefault.xml");
192+
StatusPrinter.print(loggerContext);
193+
final Logger logger = loggerContext.getLogger("ch.qos.logback.classic.joran");
194+
final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("A");
195+
assertNotNull(listAppender);
196+
assertEquals(0, listAppender.list.size());
197+
final String msg = "hello world";
198+
logger.info(msg);
199+
200+
assertEquals(1, listAppender.list.size());
201+
}
202+
203+
204+
@Test
205+
public void refToUndefinedAppender() throws JoranException {
206+
207+
configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "refToUndefinedAppender.xml");
208+
StatusPrinter.print(loggerContext);
209+
final Logger logger = loggerContext.getLogger("ch.qos.logback.classic.joran");
210+
final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("A");
211+
assertNotNull(listAppender);
212+
assertEquals(0, listAppender.list.size());
213+
final String msg = "hello world";
214+
logger.info(msg);
215+
216+
assertEquals(1, listAppender.list.size());
217+
218+
checker.assertContainsMatch(Status.WARN, "Appender named \\[NON_EXISTENT_APPENDER\\] could not be found. Skipping attachment to Logger\\[ROOT\\]");
219+
220+
}
221+
188222

189223
@Test
190224
public void statusListener() throws JoranException {
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Logback: the reliable, generic, fast and flexible logging framework.
3+
* Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4+
*
5+
* This program and the accompanying materials are dual-licensed under
6+
* either the terms of the Eclipse Public License v1.0 as published by
7+
* the Eclipse Foundation
8+
*
9+
* or (per the licensee's choosing)
10+
*
11+
* under the terms of the GNU Lesser General Public License version 2.1
12+
* as published by the Free Software Foundation.
13+
*/
14+
15+
package ch.qos.logback.core.model.processor;
16+
17+
import ch.qos.logback.core.Context;
18+
import ch.qos.logback.core.model.AppenderModel;
19+
import ch.qos.logback.core.model.Model;
20+
21+
import java.util.HashSet;
22+
import java.util.Set;
23+
24+
/**
25+
* The AppenderAvailabilityAnalyser class is responsible for analyzing the availability
26+
* of appenders. By available, we mean whether an appender with a given name is declared
27+
* somewhere in the configuration. This availability information is later used by
28+
* AppenderRefModelHandler to attempt to attach only those appenders that were previously
29+
* declared.
30+
*/
31+
@PhaseIndicator(phase = ProcessingPhase.DEPENDENCY_ANALYSIS)
32+
public class AppenderDeclarationAnalyser extends ModelHandlerBase {
33+
34+
static final String DECLARED_APPENDER_NAME_SET_KEY = "DECLARED_APPENDER_NAME_SET";
35+
36+
37+
public AppenderDeclarationAnalyser(Context context) {
38+
super(context);
39+
}
40+
41+
@Override
42+
protected Class<AppenderModel> getSupportedModelClass() {
43+
return AppenderModel.class;
44+
}
45+
46+
47+
@Override
48+
public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
49+
AppenderModel appenderModel = (AppenderModel) model;
50+
51+
String appenderName = appenderModel.getName();
52+
addAppenderDeclaration(mic, appenderName);
53+
}
54+
55+
56+
static public Set<String> getAppenderNameSet(ModelInterpretationContext mic) {
57+
Set<String> set = (Set<String>) mic.getObjectMap().get(DECLARED_APPENDER_NAME_SET_KEY);
58+
if(set == null) {
59+
set = new HashSet<>();
60+
mic.getObjectMap().put(DECLARED_APPENDER_NAME_SET_KEY, set);
61+
}
62+
return set;
63+
}
64+
65+
static public void addAppenderDeclaration(ModelInterpretationContext mic, String appenderName) {
66+
Set<String> set = getAppenderNameSet(mic);
67+
set.add(appenderName);
68+
}
69+
70+
71+
static public boolean isAppenderDeclared(ModelInterpretationContext mic, String appenderName) {
72+
Set<String> set = getAppenderNameSet(mic);
73+
return set.contains(appenderName);
74+
}
75+
76+
}

logback-core/src/main/java/ch/qos/logback/core/model/processor/AppenderRefModelHandler.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package ch.qos.logback.core.model.processor;
22

3-
import java.util.Map;
4-
53
import ch.qos.logback.core.Appender;
64
import ch.qos.logback.core.Context;
75
import ch.qos.logback.core.joran.JoranConstants;
86
import ch.qos.logback.core.model.AppenderRefModel;
97
import ch.qos.logback.core.model.Model;
108
import ch.qos.logback.core.spi.AppenderAttachable;
119

10+
import java.util.Map;
11+
12+
import static ch.qos.logback.core.model.processor.AppenderDeclarationAnalyser.isAppenderDeclared;
13+
1214
public class AppenderRefModelHandler extends ModelHandlerBase {
1315
boolean inError = false;
1416

@@ -50,6 +52,11 @@ void attachReferencedAppenders(ModelInterpretationContext mic, AppenderRefModel
5052
AppenderAttachable<?> appenderAttachable) {
5153
String appenderName = mic.subst(appenderRefModel.getRef());
5254

55+
if(!isAppenderDeclared(mic, appenderName)) {
56+
addWarn("Appender named [" + appenderName + "] could not be found. Skipping attachment to "+appenderAttachable+".");
57+
return;
58+
}
59+
5360
Map<String, Appender> appenderBag = (Map<String, Appender>) mic.getObjectMap().get(JoranConstants.APPENDER_BAG);
5461

5562
Appender appender = appenderBag.get(appenderName);

logback-core/src/main/java/ch/qos/logback/core/model/processor/DefaultProcessor.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,12 @@
1313
*/
1414
package ch.qos.logback.core.model.processor;
1515

16-
import java.lang.reflect.Constructor;
17-
import java.lang.reflect.InvocationTargetException;
1816
import java.util.ArrayList;
1917
import java.util.HashMap;
2018
import java.util.List;
2119
import java.util.function.Supplier;
2220

2321
import ch.qos.logback.core.Context;
24-
import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
2522
import ch.qos.logback.core.model.Model;
2623
import ch.qos.logback.core.model.ModelHandlerFactoryMethod;
2724
import ch.qos.logback.core.model.NamedComponentModel;
@@ -270,7 +267,7 @@ protected int secondPhaseTraverse(Model model, ModelFilter modelFilter) {
270267
}
271268

272269
private boolean dependencyIsADirectSubmodel(Model model) {
273-
List<String> dependecyNames = this.mic.getDependeeNamesForModel(model);
270+
List<String> dependecyNames = this.mic.getDependencyNamesForModel(model);
274271
if (dependecyNames == null || dependecyNames.isEmpty()) {
275272
return false;
276273
}
@@ -289,14 +286,19 @@ private boolean dependencyIsADirectSubmodel(Model model) {
289286

290287
private boolean allDependenciesStarted(Model model) {
291288
// assumes that DependencyDefinitions have been registered
292-
List<String> dependencyNames = mic.getDependeeNamesForModel(model);
289+
List<String> dependencyNames = mic.getDependencyNamesForModel(model);
293290

294291
if (dependencyNames == null || dependencyNames.isEmpty()) {
295292
return true;
296293
}
297294
for (String name : dependencyNames) {
298-
boolean isStarted = mic.isNamedDependeeStarted(name);
299-
if (isStarted == false) {
295+
boolean isRegistered = AppenderDeclarationAnalyser.isAppenderDeclared(mic, name);
296+
if (!isRegistered) {
297+
// non registered dependencies are not taken into account
298+
continue;
299+
}
300+
boolean isStarted = mic.isNamedDependemcyStarted(name);
301+
if (!isStarted) {
300302
return false;
301303
}
302304
}

logback-core/src/main/java/ch/qos/logback/core/model/processor/DependencyDefinition.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,31 @@
1616
import ch.qos.logback.core.model.Model;
1717

1818
/**
19-
* Defines the relation between a dependee (Model) and a dependency (String).
19+
* Defines the relation between a depender (of type Model) and a dependency name (String).
2020
*
21-
* Note that a dependee may have multiple dependencies but
21+
* Note that a depender may have multiple dependencies but
2222
* {@link DependencyDefinition} applies to just one dependency relation.
2323
*
2424
* @author ceki
2525
*
2626
*/
2727
public class DependencyDefinition {
2828

29-
// depender: a component of type Model which depends on a dependee
29+
// OLD terminology: depender: a component of type Model which depends on a dependee
30+
// NEW terminology: dependent: a component of type Model which depends on a dependency
3031
Model depender;
31-
// dependee: the string name of a component depended upon by the depender of type Model
32-
String dependee;
32+
// dependee or dependency: the string name of a component depended upon by the depender of type Model
33+
String dependency;
3334

34-
public DependencyDefinition(Model depender, String dependee) {
35+
public DependencyDefinition(Model depender, String dependency) {
3536
this.depender = depender;
36-
this.dependee = dependee;
37+
this.dependency = dependency;
3738

3839

3940
}
4041

41-
public String getDependee() {
42-
return dependee;
42+
public String getDependency() {
43+
return dependency;
4344
}
4445

4546
public Model getDepender() {
@@ -51,7 +52,7 @@ public Model getDepender() {
5152
public String toString() {
5253
return "DependencyDefinition{" +
5354
"depender=" + depender +
54-
", dependee='" + dependee + '\'' +
55+
", dependency='" + dependency + '\'' +
5556
'}';
5657
}
5758
}

logback-core/src/main/java/ch/qos/logback/core/model/processor/FileCollisionAnalyser.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,6 @@
3333
@PhaseIndicator(phase = ProcessingPhase.DEPENDENCY_ANALYSIS)
3434
public class FileCollisionAnalyser extends ModelHandlerBase {
3535

36-
// Key: appender name, Value: file path
37-
final static String FA_FILE_COLLISION_MAP_KEY = "FA_FILE_COLLISION_MAP_KEY";
38-
39-
// Key: appender name, Value: FileNamePattern
40-
Map<String, FileNamePattern> RFA_FILENAME_COLLISTION_MAP = new HashMap<>();
41-
42-
4336
public FileCollisionAnalyser(Context context) {
4437
super(context);
4538
}

logback-core/src/main/java/ch/qos/logback/core/model/processor/ModelInterpretationContext.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -183,24 +183,24 @@ public List<DependencyDefinition> getDependencyDefinitions() {
183183
return Collections.unmodifiableList(dependencyDefinitionList);
184184
}
185185

186-
public List<String> getDependeeNamesForModel(Model model) {
186+
public List<String> getDependencyNamesForModel(Model model) {
187187
List<String> dependencyList = new ArrayList<>();
188188
for (DependencyDefinition dd : dependencyDefinitionList) {
189189
if (dd.getDepender() == model) {
190-
dependencyList.add(dd.getDependee());
190+
dependencyList.add(dd.getDependency());
191191
}
192192
}
193193
return dependencyList;
194194
}
195195

196-
public boolean hasDependers(String dependeeName) {
196+
public boolean hasDependers(String dependencyName) {
197197

198-
if (dependeeName == null || dependeeName.trim().length() == 0) {
198+
if (dependencyName == null || dependencyName.trim().length() == 0) {
199199
new IllegalArgumentException("Empty dependeeName name not allowed here");
200200
}
201201

202202
for (DependencyDefinition dd : dependencyDefinitionList) {
203-
if (dd.dependee.equals(dependeeName))
203+
if (dd.dependency.equals(dependencyName))
204204
return true;
205205
}
206206

@@ -212,7 +212,7 @@ public void markStartOfNamedDependee(String name) {
212212
startedDependees.add(name);
213213
}
214214

215-
public boolean isNamedDependeeStarted(String name) {
215+
public boolean isNamedDependemcyStarted(String name) {
216216
return startedDependees.contains(name);
217217
}
218218

0 commit comments

Comments
 (0)