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}