001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.log4j.config; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.net.URI; 022import java.util.Arrays; 023import java.util.HashMap; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.Properties; 027 028import org.apache.logging.log4j.Level; 029import org.apache.logging.log4j.core.appender.ConsoleAppender; 030import org.apache.logging.log4j.core.appender.ConsoleAppender.Target; 031import org.apache.logging.log4j.core.config.Configuration; 032import org.apache.logging.log4j.core.config.ConfigurationFactory; 033import org.apache.logging.log4j.core.config.ConfigurationSource; 034import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; 035import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; 036import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder; 037import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; 038import org.apache.logging.log4j.status.StatusLogger; 039 040/** 041 * Experimental ConfigurationFactory for Log4j 1.2 properties files. 042 * <p> 043 * Currently supports: 044 * </p> 045 * <ul> 046 * <li>log4j.debug</li> 047 * <li>log4j.rootLogger</li> 048 * <li>log4j.logger</li> 049 * <li>log4j.appender</li> 050 * <li>org.apache.log4j.ConsoleAppender</li> 051 * <li>org.apache.log4j.PatternLayout</li> 052 * <ul> 053 * <li>Follow</li> 054 * <li>Target</li> 055 * <li>layout = org.apache.log4j.PatternLayout</li> 056 * <li>layout = org.apache.log4j.SimpleLayout</li> 057 * <li>layout = org.apache.log4j.TTCCLayout (partial)</li> 058 * <li>layout = org.apache.log4j.HtmlLayout (partial)</li> 059 * <li>layout = org.apache.log4j.XmlLayout (partial)</li> 060 * <li>layout.ConversionPattern</li> 061 * </ul> 062 * </ul> 063 */ 064// TODO 065// @Plugin(name = "Log4j1ConfigurationFactory", category = ConfigurationFactory.CATEGORY) 066// 067// Best Value? 068// @Order(50) 069public class Log4j1ConfigurationFactory extends ConfigurationFactory { 070 071 private Map<String, String> buildClassToPropertyPrefixMap(final Properties properties, 072 final String[] sortedAppenderNames) { 073 final String prefix = "log4j.appender."; 074 final int preLength = prefix.length(); 075 final Map<String, String> map = new HashMap<>(sortedAppenderNames.length); 076 for (final Entry<Object, Object> entry : properties.entrySet()) { 077 final Object keyObj = entry.getKey(); 078 if (keyObj != null) { 079 final String key = keyObj.toString(); 080 if (key.startsWith(prefix)) { 081 if (key.indexOf('.', preLength) < 0) { 082 final String name = key.substring(preLength); 083 if (Arrays.binarySearch(sortedAppenderNames, name) >= 0) { 084 final Object value = entry.getValue(); 085 if (value != null) { 086 map.put(name, value.toString()); 087 } 088 } 089 } 090 } 091 } 092 } 093 return map; 094 } 095 096 private void buildConsoleAppender(final Properties properties, final String name, 097 final ConfigurationBuilder<BuiltConfiguration> builder) { 098 final AppenderComponentBuilder appenderBuilder = builder.newAppender(name, "CONSOLE"); 099 buildConsoleAppenderTarget(properties, name, builder, appenderBuilder); 100 buildAppenderLayout(properties, name, builder, appenderBuilder); 101 buildConsoleAppenderFollow(properties, name, builder, appenderBuilder); 102 builder.add(appenderBuilder); 103 } 104 105 private void buildAppenderLayout(final Properties properties, final String name, 106 final ConfigurationBuilder<BuiltConfiguration> builder, final AppenderComponentBuilder appenderBuilder) { 107 final String layoutValue = getLog4jAppenderValue(properties, name, "layout", null); 108 if (layoutValue != null) { 109 final String cpValue = getLog4jAppenderValue(properties, name, "layout.ConversionPattern", null); 110 switch (layoutValue) { 111 case "org.apache.log4j.PatternLayout": { 112 appenderBuilder.add(newPatternLayout(builder, cpValue)); 113 break; 114 } 115 case "org.apache.log4j.EnhancedPatternLayout": { 116 appenderBuilder.add(newPatternLayout(builder, cpValue)); 117 break; 118 } 119 case "org.apache.log4j.SimpleLayout": { 120 appenderBuilder.add(newPatternLayout(builder, "%level - %m%n")); 121 break; 122 } 123 case "org.apache.log4j.TTCCLayout": { 124 // TODO We do not have a %d for the time since the start of the app? 125 appenderBuilder.add(newPatternLayout(builder, "%relative [%threadName] %level %logger - %m%n")); 126 break; 127 } 128 case "org.apache.log4j.HTMLLayout": { 129 appenderBuilder.add(builder.newLayout("HtmlLayout")); 130 break; 131 } 132 case "org.apache.log4j.XMLLayout": { 133 appenderBuilder.add(builder.newLayout("XmlLayout")); 134 break; 135 } 136 default: 137 reportWarning("Unsupported value for console appender layout: " + layoutValue); 138 } 139 } 140 } 141 142 private LayoutComponentBuilder newPatternLayout(final ConfigurationBuilder<BuiltConfiguration> builder, 143 final String pattern) { 144 final LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout"); 145 if (pattern != null) { 146 layoutBuilder.addAttribute("pattern", pattern); 147 } 148 return layoutBuilder; 149 } 150 151 private void buildConsoleAppenderTarget(final Properties properties, final String name, 152 final ConfigurationBuilder<BuiltConfiguration> builder, final AppenderComponentBuilder appenderBuilder) { 153 final String value = getLog4jAppenderValue(properties, name, "Target", "System.out"); 154 if (value != null) { 155 final Target target; 156 switch (value) { 157 case "System.out": 158 target = ConsoleAppender.Target.SYSTEM_OUT; 159 break; 160 case "System.err": 161 target = ConsoleAppender.Target.SYSTEM_ERR; 162 break; 163 default: 164 reportWarning("Unknow value for console Target: " + value); 165 target = null; 166 } 167 if (target != null) { 168 appenderBuilder.addAttribute("target", target); 169 } 170 } 171 } 172 173 private void buildConsoleAppenderFollow(final Properties properties, final String name, 174 final ConfigurationBuilder<BuiltConfiguration> builder, final AppenderComponentBuilder appenderBuilder) { 175 final String value = getLog4jAppenderValue(properties, name, "Follow", "false"); 176 if (value != null) { 177 appenderBuilder.addAttribute("follow", Boolean.valueOf(value).booleanValue()); 178 } 179 } 180 181 Configuration createConfiguration(final String configName, final URI configLocation, 182 final ConfigurationBuilder<BuiltConfiguration> builder) throws IOException { 183 builder.setConfigurationName(configName); 184 final Properties properties = load(configLocation); 185 if (properties == null) { 186 return null; 187 } 188 // DEBUG 189 final String debugValue = getLog4jValue(properties, "debug"); 190 if (Boolean.valueOf(debugValue)) { 191 builder.setStatusLevel(Level.DEBUG); 192 } 193 // Root 194 final String[] sortedAppenderNamesC = buildRootLogger(builder, getRootCategoryValue(properties)); 195 final String[] sortedAppenderNamesL = buildRootLogger(builder, getRootLoggerValue(properties)); 196 final String[] sortedAppenderNames = sortedAppenderNamesL.length > 0 ? sortedAppenderNamesL 197 : sortedAppenderNamesC; 198 // Appenders 199 final Map<String, String> classNameToProperty = buildClassToPropertyPrefixMap(properties, sortedAppenderNames); 200 for (final Entry<String, String> entry : classNameToProperty.entrySet()) { 201 final String appenderName = entry.getKey(); 202 switch (entry.getValue()) { 203 case "org.apache.log4j.ConsoleAppender": 204 buildConsoleAppender(properties, appenderName, builder); 205 break; 206 default: 207 reportWarning("Ignoring appender " + appenderName 208 + "; consider porting your configuration file to the current Log4j format."); 209 } 210 } 211 // Loggers 212 buildLoggers(properties, "log4j.category.", builder); 213 buildLoggers(properties, "log4j.logger.", builder); 214 return builder.build(); 215 } 216 217 private String[] buildRootLogger(final ConfigurationBuilder<BuiltConfiguration> builder, 218 final String rootLoggerValue) { 219 if (rootLoggerValue == null) { 220 return new String[0]; 221 } 222 final String[] rootLoggerParts = rootLoggerValue.split("\\s*,\\s*"); 223 final Level rootLoggerLevel = rootLoggerParts.length > 0 ? Level.valueOf(rootLoggerParts[0]) : Level.ERROR; 224 builder.add(builder.newRootLogger(rootLoggerLevel)); 225 final String[] sortedAppenderNames = Arrays.copyOfRange(rootLoggerParts, 1, rootLoggerParts.length); 226 Arrays.sort(sortedAppenderNames); 227 return sortedAppenderNames; 228 } 229 230 private void buildLoggers(final Properties properties, final String prefix, 231 final ConfigurationBuilder<BuiltConfiguration> builder) { 232 final int preLength = prefix.length(); 233 for (final Entry<Object, Object> entry : properties.entrySet()) { 234 final Object keyObj = entry.getKey(); 235 if (keyObj != null) { 236 final String key = keyObj.toString(); 237 if (key.startsWith(prefix)) { 238 final String name = key.substring(preLength); 239 final Object value = entry.getValue(); 240 if (value != null) { 241 builder.add(builder.newLogger(name, Level.valueOf(value.toString()))); 242 } 243 } 244 } 245 } 246 247 } 248 249 @Override 250 public Configuration getConfiguration(final ConfigurationSource source) { 251 return getConfiguration(source.toString(), null); 252 } 253 254 @Override 255 public Configuration getConfiguration(final String name, final URI configLocation) { 256 try { 257 return createConfiguration(name, configLocation, newConfigurationBuilder()); 258 } catch (final IOException e) { 259 StatusLogger.getLogger().error(e); 260 return null; 261 } 262 } 263 264 private String getLog4jAppenderValue(final Properties properties, final String appenderName, 265 final String attributeName, final String defaultValue) { 266 return properties.getProperty("log4j.appender." + appenderName + "." + attributeName, defaultValue); 267 } 268 269 private String getLog4jValue(final Properties properties, final String key) { 270 return properties.getProperty("log4j." + key); 271 } 272 273 private String getRootCategoryValue(final Properties properties) { 274 return getLog4jValue(properties, "rootCategory"); 275 } 276 277 private String getRootLoggerValue(final Properties properties) { 278 return getLog4jValue(properties, "rootLogger"); 279 } 280 281 @Override 282 protected String[] getSupportedTypes() { 283 return new String[] { "*.properties", ".xml" }; 284 } 285 286 private Properties load(final URI uri) throws IOException { 287 final Properties properties = toProperties(uri); 288 final String rootCategoryValue = getRootCategoryValue(properties); 289 final String rootLoggerValue = getRootLoggerValue(properties); 290 if (rootCategoryValue == null && rootLoggerValue == null) { 291 // This is not a Log4j 1 properties file. 292 return null; 293 } 294 return properties; 295 } 296 297 private void reportWarning(final String msg) { 298 StatusLogger.getLogger().warn("Log4j version 1 to 2 configuration bridge: " + msg); 299 300 } 301 302 private Properties toProperties(final URI uri) throws IOException { 303 final Properties properties; 304 try (InputStream in = uri.toURL().openStream()) { 305 properties = new Properties(); 306 if (uri.toString().endsWith(".xml")) { 307 properties.loadFromXML(in); 308 } else { 309 properties.load(in); 310 } 311 } 312 return properties; 313 } 314}