001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2025, 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 v1.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 */ 014 015package ch.qos.logback.core.model.processor; 016 017import ch.qos.logback.core.Context; 018import ch.qos.logback.core.FileAppender; 019import ch.qos.logback.core.model.AppenderModel; 020import ch.qos.logback.core.model.ImplicitModel; 021import ch.qos.logback.core.model.Model; 022import ch.qos.logback.core.rolling.RollingFileAppender; 023import ch.qos.logback.core.rolling.helper.FileNamePattern; 024 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Optional; 029import java.util.function.Supplier; 030import java.util.stream.Collectors; 031import java.util.stream.Stream; 032 033@PhaseIndicator(phase = ProcessingPhase.DEPENDENCY_ANALYSIS) 034public class FileCollisionAnalyser extends ModelHandlerBase { 035 036 // Key: appender name, Value: file path 037 final static String FA_FILE_COLLISION_MAP_KEY = "FA_FILE_COLLISION_MAP_KEY"; 038 039 // Key: appender name, Value: FileNamePattern 040 Map<String, FileNamePattern> RFA_FILENAME_COLLISTION_MAP = new HashMap<>(); 041 042 043 public FileCollisionAnalyser(Context context) { 044 super(context); 045 } 046 047 @Override 048 protected Class<AppenderModel> getSupportedModelClass() { 049 return AppenderModel.class; 050 } 051 052 053 @Override 054 public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException { 055 AppenderModel appenderModel = (AppenderModel) model; 056 057 String originalClassName = appenderModel.getClassName(); 058 String className = mic.getImport(originalClassName); 059 060 String appenderName = appenderModel.getName(); 061 062 if (!fileAppenderOrRollingFileAppender(className)) { 063 return; 064 } 065 066 String tagName0 = "file"; 067 checkForCollisions(mic, MapKey.FILE_COLLISION_MAP_KEY, appenderModel, appenderName, tagName0); 068 069 String tagName1 = "fileNamePattern"; 070 checkForCollisions(mic, MapKey.RFA_FILENAME_COLLISION_MAP, appenderModel, appenderName, tagName1); 071 } 072 073 private static boolean fileAppenderOrRollingFileAppender(String className) { 074 return FileAppender.class.getName().equals(className) || RollingFileAppender.class.getName().equals(className); 075 } 076 077 078 boolean tagPredicate(Model model, String tagName) { 079 return (model instanceof ImplicitModel) && tagName.equals(model.getTag()); 080 } 081 082 enum MapKey { 083 FILE_COLLISION_MAP_KEY, RFA_FILENAME_COLLISION_MAP 084 } 085 086 private void checkForCollisions(ModelInterpretationContext mic, MapKey mapKey, AppenderModel appenderModel, String appenderName, final String tagName) { 087 088 089 Stream<Model> streamLevel1 = appenderModel.getSubModels().stream(); 090 Stream<Model> streamLevel2 = appenderModel.getSubModels().stream().flatMap(child -> child.getSubModels().stream()); 091 092 List<Model> matchingModels = Stream.concat(streamLevel1, streamLevel2).filter(m -> tagPredicate(m, tagName)).collect(Collectors.toList()); 093 094 //List<Model> matchingModels = appenderModel.getSubModels().stream().filter(m -> tagPredicate(m, tagName)).collect(Collectors.toList()); 095 096 if(!matchingModels.isEmpty()) { 097 ImplicitModel implicitModel = (ImplicitModel) matchingModels.get(0); 098 String bodyValue = mic.subst(implicitModel.getBodyText()); 099 100 101 Map<String, String> faileCollisionMap = getCollisionMapByKey(mic, mapKey); 102 103 Optional<Map.Entry<String, String>> collision = faileCollisionMap.entrySet() 104 .stream() 105 .filter(entry -> bodyValue.equals(entry.getValue())) 106 .findFirst(); 107 108 if (collision.isPresent()) { 109 addErrorForCollision(tagName, appenderName, collision.get().getKey(), bodyValue); 110 appenderModel.markAsHandled(); 111 appenderModel.deepMarkAsSkipped(); 112 } else { 113 // add to collision map if and only if no collision detected 114 // reasoning: single entry is as effective as multiple entries for collision detection 115 faileCollisionMap.put(appenderName, bodyValue); 116 } 117 } 118 } 119 120 private Map<String, String> getCollisionMapByKey(ModelInterpretationContext mic, MapKey mapKey) { 121 Map<String, String> map = (Map<String, String>) mic.getObjectMap().get(mapKey.name()); 122 if(map == null) { 123 map = new HashMap<>(); 124 mic.getObjectMap().put(mapKey.name(), map); 125 } 126 return map; 127 } 128 129 130 static public final String COLLISION_DETECTED = "Collision detected. Skipping initialization of appender named [%s]"; 131 static public final String COLLISION_MESSAGE = "In appender [%s] option '%s' has the same value '%s' as that set for appender [%s] defined earlier"; 132 private void addErrorForCollision(String optionName, String appenderName, String previousAppenderName, String optionValue) { 133 addError(String.format(COLLISION_DETECTED, appenderName)); 134 addError(String.format(COLLISION_MESSAGE, appenderName, optionName, optionValue, previousAppenderName)); 135 } 136}