1
2
3
4
5
6
7
8
9
10
11
12
13
14 package ch.qos.logback.classic.model.processor;
15
16 import java.util.LinkedHashMap;
17 import java.util.LinkedHashSet;
18 import java.util.Map;
19 import java.util.Set;
20 import java.util.regex.Pattern;
21
22 import ch.qos.logback.classic.AsyncAppender;
23 import ch.qos.logback.classic.net.SMTPAppender;
24 import ch.qos.logback.classic.net.SocketAppender;
25 import ch.qos.logback.core.Context;
26 import ch.qos.logback.core.model.AppenderModel;
27 import ch.qos.logback.core.model.ImplicitModel;
28 import ch.qos.logback.core.model.Model;
29 import ch.qos.logback.core.model.processor.ModelHandlerBase;
30 import ch.qos.logback.core.model.processor.ModelHandlerException;
31 import ch.qos.logback.core.model.processor.ModelInterpretationContext;
32 import ch.qos.logback.core.model.processor.PhaseIndicator;
33 import ch.qos.logback.core.model.processor.ProcessingPhase;
34 import ch.qos.logback.core.util.OptionHelper;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 @PhaseIndicator(phase = ProcessingPhase.DEPENDENCY_ANALYSIS)
63 public class CallerContradictionAnalyser extends ModelHandlerBase {
64
65 static final String APPENDER_TO_CALLER_INSTRUCTION_MAP_KEY = "APPENDER_TO_CALLER_INSTRUCTION_MAP_KEY";
66
67
68
69
70
71 public static final String SKIP_CALLER_CONTRADICTION_ANALYSIS_PROPERTY =
72 "logback.skipCallerContradictionAnalysis";
73
74
75
76
77 static final String INCLUDE_CALLER_DATA_NAME_SUFFIX = ".includeCallerData";
78
79
80
81
82 static final String LAYOUT_NAME_SUFFIX = ".layout";
83
84
85
86
87
88
89
90
91 static final Pattern CALLER_PATTERN = Pattern.compile(
92 "%([CMLFl]|caller|class|method|line|file)(?![a-zA-Z])");
93
94 public CallerContradictionAnalyser(Context context) {
95 super(context);
96 }
97
98 @Override
99 protected Class<AppenderModel> getSupportedModelClass() {
100 return AppenderModel.class;
101 }
102
103 @Override
104 public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
105 if (isSkipCallerContradictionAnalysis(mic)) {
106 return;
107 }
108
109 AppenderModel appenderModel = (AppenderModel) model;
110
111 Map<String, CallerInstructionLogic.Instruction> appenderNameToCallerInstructionMap
112 = getAppenderNameToCallerInstructionMap(mic);
113
114 String originalClassName = appenderModel.getClassName();
115 String className = mic.getImport(originalClassName);
116 String appenderName = mic.subst(appenderModel.getName());
117
118 if (SMTPAppender.class.getName().equals(className)) {
119 recordSmtpAppenderInstructions(mic, appenderModel, appenderName,
120 appenderNameToCallerInstructionMap);
121 return;
122 }
123
124 if (isCallerDataPreprocessingAppender(className)) {
125 if (isIncludeCallerDataTrue(mic, appenderModel)) {
126 appenderNameToCallerInstructionMap.put(appenderName,
127 CallerInstructionLogic.Instruction.PREPROCESS_WANT);
128 } else {
129 appenderNameToCallerInstructionMap.put(appenderName,
130 CallerInstructionLogic.Instruction.DO_NOT_WANT);
131 }
132 }
133
134 if (hasCallerDataConverters(appenderModel)) {
135 appenderNameToCallerInstructionMap.put(appenderName, CallerInstructionLogic.Instruction.DIRECT_WANT);
136 }
137 }
138
139
140
141
142
143
144 static boolean isSkipCallerContradictionAnalysis(ModelInterpretationContext mic) {
145 String value = OptionHelper.propertyLookup(SKIP_CALLER_CONTRADICTION_ANALYSIS_PROPERTY, mic,
146 mic.getContext());
147 return OptionHelper.toBoolean(value, false);
148 }
149
150
151
152
153
154
155
156
157
158 private void recordSmtpAppenderInstructions(ModelInterpretationContext mic, AppenderModel appenderModel,
159 String appenderName, Map<String, CallerInstructionLogic.Instruction> map) {
160 String includeCallerDataKey = appenderName + INCLUDE_CALLER_DATA_NAME_SUFFIX;
161 if (isIncludeCallerDataTrue(mic, appenderModel)) {
162 map.put(includeCallerDataKey, CallerInstructionLogic.Instruction.PREPROCESS_WANT);
163 } else {
164 map.put(includeCallerDataKey, CallerInstructionLogic.Instruction.DO_NOT_WANT);
165 }
166
167
168 Model layoutModel = findLayoutSubModel(appenderModel);
169 if (layoutModel != null && hasCallerDataConvertersIn(layoutModel)) {
170 map.put(appenderName + LAYOUT_NAME_SUFFIX, CallerInstructionLogic.Instruction.DIRECT_WANT);
171 }
172 }
173
174 private Model findLayoutSubModel(AppenderModel appenderModel) {
175 for (Model child : appenderModel.getSubModels()) {
176 if ("layout".equalsIgnoreCase(child.getTag())) {
177 return child;
178 }
179 }
180 return null;
181 }
182
183
184
185
186
187
188 private boolean isCallerDataPreprocessingAppender(String className) {
189 return AsyncAppender.class.getName().equals(className)
190 || SocketAppender.class.getName().equals(className);
191 }
192
193
194
195
196
197
198
199
200
201
202 private boolean isIncludeCallerDataTrue(ModelInterpretationContext mic,
203 AppenderModel appenderModel) {
204 for (Model child : appenderModel.getSubModels()) {
205 if (child instanceof ImplicitModel
206 && "includeCallerData".equalsIgnoreCase(child.getTag())) {
207 String value = mic.subst(((ImplicitModel) child).getBodyText());
208 return "true".equalsIgnoreCase(value);
209 }
210 }
211 return false;
212 }
213
214 private boolean hasCallerDataConverters(AppenderModel appenderModel) {
215 return hasCallerDataConvertersIn(appenderModel);
216 }
217
218 private boolean hasCallerDataConvertersIn(Model model) {
219 return collectPatternBodyTexts(model).stream()
220 .anyMatch(p -> CALLER_PATTERN.matcher(p).find());
221 }
222
223 private Set<String> collectPatternBodyTexts(Model model) {
224 Set<String> patterns = new LinkedHashSet<>();
225 collectPatternBodyTextsRecursive(model, patterns);
226 return patterns;
227 }
228
229 private void collectPatternBodyTextsRecursive(Model model, Set<String> out) {
230 if (model instanceof ImplicitModel && "pattern".equalsIgnoreCase(model.getTag())) {
231 String body = model.getBodyText();
232 if (body != null) {
233 out.add(body);
234 }
235 }
236 for (Model child : model.getSubModels()) {
237 collectPatternBodyTextsRecursive(child, out);
238 }
239 }
240
241 @SuppressWarnings("unchecked")
242 static Map<String, CallerInstructionLogic.Instruction> getAppenderNameToCallerInstructionMap(ModelInterpretationContext mic) {
243 Map<String, CallerInstructionLogic.Instruction> map =
244 (Map<String, CallerInstructionLogic.Instruction>) mic.getObjectMap().get(APPENDER_TO_CALLER_INSTRUCTION_MAP_KEY);
245 if (map == null) {
246 map = new LinkedHashMap<>();
247 mic.getObjectMap().put(APPENDER_TO_CALLER_INSTRUCTION_MAP_KEY, map);
248 }
249 return map;
250 }
251 }