001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2024, 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.classic.joran; 016 017import ch.qos.logback.classic.Level; 018import ch.qos.logback.classic.Logger; 019import ch.qos.logback.classic.LoggerContext; 020import ch.qos.logback.core.Context; 021import ch.qos.logback.core.joran.spi.JoranException; 022import ch.qos.logback.core.model.util.VariableSubstitutionsHelper; 023import ch.qos.logback.core.spi.ContextAwareBase; 024 025import java.io.File; 026import java.io.FileInputStream; 027import java.io.IOException; 028import java.io.InputStream; 029import java.net.URL; 030import java.net.URLConnection; 031import java.util.*; 032 033import static ch.qos.logback.core.CoreConstants.DOT; 034 035public class PropertyConfigurator extends ContextAwareBase { 036 037 static Comparator<String> LENGTH_COMPARATOR = new Comparator<String>() { 038 @Override 039 public int compare(String o1, String o2) { 040 int len1 = o1 == null ? 0 : o1.length(); 041 int len2 = o2 == null ? 0 : o2.length(); 042 // longer strings first 043 return len2 - len1; 044 } 045 }; 046 047 static final String LOGBACK_PREFIX = "logback"; 048 static final String LOGBACK_ROOT_LOGGER_PREFIX = LOGBACK_PREFIX + DOT + "root"; 049 static final int LOGBACK_ROOT_LOGGER_PREFIX_LENGTH = LOGBACK_ROOT_LOGGER_PREFIX.length(); 050 051 static final String LOGBACK_LOGGER_PREFIX = LOGBACK_PREFIX + DOT + "logger" + DOT; 052 static final int LOGBACK_LOGGER_PREFIX_LENGTH = LOGBACK_LOGGER_PREFIX.length(); 053 054 VariableSubstitutionsHelper variableSubstitutionsHelper; 055 056 LoggerContext getLoggerContext() { 057 return (LoggerContext) getContext(); 058 } 059 060 @Override 061 public void setContext(Context context) { 062 super.setContext(context); 063 } 064 065 public void doConfigure(URL url) throws JoranException { 066 try { 067 URLConnection urlConnection = url.openConnection(); 068 // per http://jira.qos.ch/browse/LOGBACK-117 069 // per http://jira.qos.ch/browse/LOGBACK-163 070 urlConnection.setUseCaches(false); 071 InputStream in = urlConnection.getInputStream(); 072 doConfigure(in); 073 } catch (IOException ioe) { 074 String errMsg = "Could not open URL [" + url + "]."; 075 addError(errMsg, ioe); 076 throw new JoranException(errMsg, ioe); 077 } 078 } 079 080 public void doConfigure(File file) throws JoranException { 081 try(FileInputStream fileInputStream = new FileInputStream(file)) { 082 doConfigure(fileInputStream); 083 } catch (IOException e) { 084 throw new JoranException("Failed to load file "+file, e); 085 } 086 } 087 088 public void doConfigure(String filename) throws JoranException { 089 doConfigure(new File(filename)); 090 } 091 092 public void doConfigure(InputStream inputStream) throws JoranException { 093 Properties props = new Properties(); 094 try { 095 props.load(inputStream); 096 } catch (IOException e) { 097 throw new JoranException("Failed to load from input stream", e); 098 } finally { 099 close(inputStream); 100 } 101 102 doConfigure(props); 103 } 104 105 private void close(InputStream inputStream) throws JoranException { 106 if(inputStream != null) { 107 try { 108 inputStream.close(); 109 } catch (IOException e) { 110 throw new JoranException("failed to close stream", e); 111 } 112 } 113 } 114 115 void doConfigure(Properties properties) { 116 Map<String, String> variablesMap = extractVariablesMap(properties); 117 Map<String, String> instructionMap = extractLogbackInstructionMap(properties); 118 119 this.variableSubstitutionsHelper = new VariableSubstitutionsHelper(context, variablesMap); 120 configureLoggers(instructionMap); 121 configureRootLogger(instructionMap); 122 } 123 124 void configureRootLogger(Map<String, String> instructionMap) { 125 String val = subst(instructionMap.get(LOGBACK_ROOT_LOGGER_PREFIX)); 126 if (val != null) { 127 setLevel(org.slf4j.Logger.ROOT_LOGGER_NAME, val); 128 } 129 } 130 131 void configureLoggers(Map<String, String> instructionMap) { 132 133 for (String key : instructionMap.keySet()) { 134 if (key.startsWith(LOGBACK_LOGGER_PREFIX)) { 135 String loggerName = key.substring(LOGBACK_LOGGER_PREFIX_LENGTH); 136 String value = subst(instructionMap.get(key)); 137 setLevel(loggerName, value); 138 } 139 } 140 } 141 142 private void setLevel(String loggerName, String val) { 143 Logger logger = getLoggerContext().getLogger(loggerName); 144 Level level = Level.toLevel(val); 145 logger.setLevel(level); 146 } 147 148 private Map<String, String> extractVariablesMap(Properties properties) { 149 Map<String, String> variablesMap = new HashMap<>(); 150 for (String key : properties.stringPropertyNames()) { 151 if (key != null && !key.startsWith(LOGBACK_PREFIX)) { 152 variablesMap.put(key, properties.getProperty(key)); 153 } 154 } 155 156 return variablesMap; 157 } 158 159 private Map<String, String> extractLogbackInstructionMap(Properties properties) { 160 Map<String, String> instructionMap = new TreeMap<>(LENGTH_COMPARATOR); 161 for (String key : properties.stringPropertyNames()) { 162 if (key != null && key.startsWith(LOGBACK_PREFIX)) { 163 instructionMap.put(key, properties.getProperty(key)); 164 } 165 } 166 return instructionMap; 167 } 168 169 public String subst(String ref) { 170 171 String substituted = variableSubstitutionsHelper.subst(ref); 172 if (ref != null && !ref.equals(substituted)) { 173 addInfo("value \"" + substituted + "\" substituted for \"" + ref + "\""); 174 } 175 return substituted; 176 } 177 178}