001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v2.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.model.processor;
015
016import ch.qos.logback.core.status.InfoStatus;
017import ch.qos.logback.core.status.Status;
018import ch.qos.logback.core.status.WarnStatus;
019
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Map;
023
024import static ch.qos.logback.core.CoreConstants.CODES_URL;
025
026/**
027 * Detects contradictory caller-data extraction instructions across appenders.
028 *
029 * <p>During configuration analysis, each appender is associated with an
030 * {@link Instruction} describing whether it wants caller data extracted and
031 * how. This class checks a map of appender name to instruction for
032 * combinations that cannot work together at runtime and returns the
033 * corresponding {@link Status} messages.</p>
034 *
035 * <p>Compatibility rules:</p>
036 * <ul>
037 *   <li>{@link Instruction#DIRECT_WANT} may appear alone.</li>
038 *   <li>{@link Instruction#PREPROCESS_WANT} may coexist with
039 *       {@link Instruction#DIRECT_WANT}.</li>
040 *   <li>{@link Instruction#DO_NOT_WANT} must not coexist with
041 *       {@link Instruction#PREPROCESS_WANT}.</li>
042 *   <li>{@link Instruction#DO_NOT_WANT} must not coexist with
043 *       {@link Instruction#DIRECT_WANT}.</li>
044 *   <li>{@link Instruction#PREPROCESS_WANT} alone is not a valid
045 *       configuration.</li>
046 * </ul>
047 *
048 * @since 1.6.2
049 * @see CallerContradictionAnalyser
050 * @see CallerContradictionWarnAnalyser
051 */
052public class CallerInstructionLogic {
053
054    static final String CALLER_CONTRADICTION_ANCHOR = "#callerContradiction";
055    static final String CALLER_CONTRADICTION_URL = CODES_URL + CALLER_CONTRADICTION_ANCHOR;
056    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";
057    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";
058    static final String NO_CONTRADICTIONS_MSG = "No contradictions in caller extraction instruction were detected";
059
060    /**
061     * How an appender relates to caller-data extraction.
062     */
063    enum Instruction {
064        /**
065         * Caller data should be extracted during preprocessing (for example by
066         * an {@code AsyncAppender}, {@code SocketAppender} or
067         * {@code SMTPAppender} with {@code includeCallerData} set to
068         * {@code true}) so that nested appenders, the remote peer or the
069         * SMTP layout can use it.
070         */
071        PREPROCESS_WANT,
072
073        /**
074         * Caller data should not be extracted (for example an
075         * {@code AsyncAppender}, {@code SocketAppender} or
076         * {@code SMTPAppender} with {@code includeCallerData} false or
077         * absent, the default).
078         */
079        DO_NOT_WANT,
080
081        /**
082         * The appender itself requires caller data, typically because its
083         * layout pattern uses a caller-data converter such as {@code %C},
084         * {@code %M}, {@code %L}, {@code %F}, {@code %l}, or
085         * {@code %caller}.
086         */
087        DIRECT_WANT,
088    }
089
090    /**
091     * Checks the given appender instructions for contradictions.
092     *
093     * <p>The map maps appender names to the caller-inclusion instruction
094     * gathered during analysis of the configuration model. Contradictions
095     * are reported as {@link WarnStatus} entries; if none are found, a
096     * single {@link InfoStatus} is returned. When one or more contradiction
097     * warnings are produced, an additional warning pointing to
098     * {@link #CALLER_CONTRADICTION_URL} is appended.</p>
099     *
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}