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 v2.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 package ch.qos.logback.classic.model.processor;
15
16 import ch.qos.logback.core.status.InfoStatus;
17 import ch.qos.logback.core.status.Status;
18 import ch.qos.logback.core.status.WarnStatus;
19
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.Map;
23
24 import static ch.qos.logback.core.CoreConstants.CODES_URL;
25
26 /**
27 * Detects contradictory caller-data extraction instructions across appenders.
28 *
29 * <p>During configuration analysis, each appender is associated with an
30 * {@link Instruction} describing whether it wants caller data extracted and
31 * how. This class checks a map of appender name to instruction for
32 * combinations that cannot work together at runtime and returns the
33 * corresponding {@link Status} messages.</p>
34 *
35 * <p>Compatibility rules:</p>
36 * <ul>
37 * <li>{@link Instruction#DIRECT_WANT} may appear alone.</li>
38 * <li>{@link Instruction#PREPROCESS_WANT} may coexist with
39 * {@link Instruction#DIRECT_WANT}.</li>
40 * <li>{@link Instruction#DO_NOT_WANT} must not coexist with
41 * {@link Instruction#PREPROCESS_WANT}.</li>
42 * <li>{@link Instruction#DO_NOT_WANT} must not coexist with
43 * {@link Instruction#DIRECT_WANT}.</li>
44 * <li>{@link Instruction#PREPROCESS_WANT} alone is not a valid
45 * configuration.</li>
46 * </ul>
47 *
48 * @since 1.6.2
49 * @see CallerContradictionAnalyser
50 * @see CallerContradictionWarnAnalyser
51 */
52 public class CallerInstructionLogic {
53
54 static final String CALLER_CONTRADICTION_ANCHOR = "#callerContradiction";
55 static final String CALLER_CONTRADICTION_URL = CODES_URL + CALLER_CONTRADICTION_ANCHOR;
56 static final String WARNING_MSG_TEMPLATE = "appenders named %s instruct against caller extraction info while other appenders named %s instruct in favor of caller extraction";
57 static final String LONE_PREPROCESS_WANT_MSG_TEMPLATE = "appenders named %s instruct preprocessing of caller extraction info but no appender instructs in favor of caller extraction";
58 static final String NO_CONTRADICTIONS_MSG = "No contradictions in caller extraction instruction were detected";
59
60 /**
61 * How an appender relates to caller-data extraction.
62 */
63 enum Instruction {
64 /**
65 * Caller data should be extracted during preprocessing (for example by
66 * an {@code AsyncAppender}, {@code SocketAppender} or
67 * {@code SMTPAppender} with {@code includeCallerData} set to
68 * {@code true}) so that nested appenders, the remote peer or the
69 * SMTP layout can use it.
70 */
71 PREPROCESS_WANT,
72
73 /**
74 * Caller data should not be extracted (for example an
75 * {@code AsyncAppender}, {@code SocketAppender} or
76 * {@code SMTPAppender} with {@code includeCallerData} false or
77 * absent, the default).
78 */
79 DO_NOT_WANT,
80
81 /**
82 * The appender itself requires caller data, typically because its
83 * layout pattern uses a caller-data converter such as {@code %C},
84 * {@code %M}, {@code %L}, {@code %F}, {@code %l}, or
85 * {@code %caller}.
86 */
87 DIRECT_WANT,
88 }
89
90 /**
91 * Checks the given appender instructions for contradictions.
92 *
93 * <p>The map maps appender names to the caller-inclusion instruction
94 * gathered during analysis of the configuration model. Contradictions
95 * are reported as {@link WarnStatus} entries; if none are found, a
96 * single {@link InfoStatus} is returned. When one or more contradiction
97 * warnings are produced, an additional warning pointing to
98 * {@link #CALLER_CONTRADICTION_URL} is appended.</p>
99 *
100 * @param appenderNameToInstructionMap map of appender name to its
101 * {@link Instruction}; must not be {@code null}
102 * @return a non-empty list of status objects describing the outcome of
103 * the contradiction check
104 */
105 public List<Status> contradiction(Map<String, Instruction> appenderNameToInstructionMap) {
106 List<String> preprocessWantList = new ArrayList<>();
107 List<String> doNotWantList = new ArrayList<>();
108 List<String> directWantList = new ArrayList<>();
109
110 for (Map.Entry<String, Instruction> e : appenderNameToInstructionMap.entrySet()) {
111 switch (e.getValue()) {
112 case PREPROCESS_WANT:
113 preprocessWantList.add(e.getKey());
114 break;
115 case DO_NOT_WANT:
116 doNotWantList.add(e.getKey());
117 break;
118 case DIRECT_WANT:
119 directWantList.add(e.getKey());
120 break;
121 }
122 }
123
124 List<Status> result = new ArrayList<>();
125
126 // DIRECT_WANT elements can exist alone
127 // one or more PREPROCESS_WANT elements can coexist one or more DIRECT_WANT elements
128 // DO_NOT_WANT cannot be allowed to coexist with PREPROCESS_WANT;
129 // DO_NOT_WANT cannot be allowed to coexist with DIRECT_WANT;
130 // PREPROCESS_WANT alone is not allowed.
131 // DO_NOT_WANT and PREPROCESS_WANT are contradictory
132
133 if (!doNotWantList.isEmpty() && !preprocessWantList.isEmpty()) {
134 String msg = String.format(
135 WARNING_MSG_TEMPLATE,
136 String.join(", ", doNotWantList),
137 String.join(", ", preprocessWantList));
138
139 result.add(new WarnStatus(msg, this));
140 }
141
142 if (!doNotWantList.isEmpty() && !directWantList.isEmpty()) {
143 String msg = String.format(
144 WARNING_MSG_TEMPLATE,
145 String.join(", ", doNotWantList),
146 String.join(", ", directWantList));
147
148 result.add(new WarnStatus(msg, this));
149 }
150
151 // PREPROCESS_WANT alone (without DIRECT_WANT) is not allowed
152 if (!preprocessWantList.isEmpty() && directWantList.isEmpty() && doNotWantList.isEmpty()) {
153 String msg = String.format(
154 LONE_PREPROCESS_WANT_MSG_TEMPLATE,
155 String.join(", ", preprocessWantList));
156 result.add(new WarnStatus(msg, this));
157 }
158
159 if (result.isEmpty()) {
160 result.add(new InfoStatus(NO_CONTRADICTIONS_MSG, this));
161 } else {
162 result.add(new WarnStatus("See "+CALLER_CONTRADICTION_URL+" for details", this));
163 }
164
165 return result;
166 }
167
168
169
170 }