View Javadoc
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 }