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 com.randomnoun.common.log4j2; 018 019import java.io.IOException; 020import java.io.StringReader; 021import java.util.Enumeration; 022import java.util.Map; 023import java.util.Properties; 024import java.util.SortedMap; 025import java.util.TreeMap; 026 027import org.apache.log4j.Layout; 028import org.apache.log4j.bridge.LogEventAdapter; // need to use our own adapter 029import org.apache.log4j.config.PropertySetter; 030import org.apache.log4j.helpers.OptionConverter; 031import org.apache.log4j.spi.Filter; 032// import org.apache.log4j.Appender; 033import org.apache.logging.log4j.core.Appender; 034import org.apache.logging.log4j.core.Core; 035import org.apache.logging.log4j.core.LogEvent; 036import org.apache.logging.log4j.core.appender.AbstractAppender; 037import org.apache.logging.log4j.core.config.Property; 038import org.apache.logging.log4j.core.config.plugins.Plugin; 039import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 040import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; 041import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 042import org.apache.logging.log4j.core.config.plugins.PluginFactory; 043import org.apache.logging.log4j.status.StatusLogger; 044import org.apache.logging.log4j.util.LoaderUtil; 045 046/** 047 * Binds a Log4j 1.x Appender to Log4j 2. 048 */ 049@Plugin(name = Log4j1WrapperAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) 050public class Log4j1WrapperAppender extends AbstractAppender { 051 052 // the log4j1 appender 053 org.apache.log4j.Appender log4j1Appender; 054 055 public static final String PLUGIN_NAME = "Log4j1Wrapper"; 056 057 /** 058 * Builds HttpAppender instances. 059 * @param <B> The type to build 060 */ 061 public static class Log4j1WrapperBuilder<B extends Log4j1WrapperBuilder<B>> extends AbstractAppender.Builder<B> 062 implements org.apache.logging.log4j.core.util.Builder<Log4j1WrapperAppender> { 063 064 065 // so even though this attribute is an object, somewhere in the bowels of this fucking 066 // builder constructor strategy pattern it's converting it into a string. 067 068 @PluginBuilderAttribute 069 private org.apache.log4j.Appender log4j1Appender = null; 070 071 @Override 072 public Log4j1WrapperAppender build() { 073 Log4j1WrapperAppender lw = new Log4j1WrapperAppender(getName(), log4j1Appender); 074 return lw; 075 } 076 077 public org.apache.log4j.Appender getLog4j1Appender() { 078 return log4j1Appender; 079 } 080 081 public B setLog4j1Appender(final org.apache.log4j.Appender log4j1Appender) { 082 this.log4j1Appender = log4j1Appender; 083 return asBuilder(); 084 } 085 } 086 087 /** 088 * @return a builder for a MemoryAppender. 089 */ 090 @PluginBuilderFactory 091 public static <B extends Log4j1WrapperBuilder<B>> B newBuilder() { 092 return new Log4j1WrapperBuilder<B>().asBuilder(); 093 } 094 095 096 097 // well log4j2 went all-in on the annotations, didn't they 098 099 @PluginFactory 100 public static Log4j1WrapperAppender createAppender( 101 @PluginAttribute(value = "name", defaultString = "null") final String name, 102 @PluginAttribute(value = "className", defaultString = "null") final String className, 103 @PluginAttribute(value = "properties", defaultString = "null") final String propertiesString ) 104 { 105 Log4j1WrapperAppender lw = new Log4j1WrapperAppender(name, className, propertiesString ); 106 return lw; 107 } 108 109 public static Log4j1WrapperAppender createAppender(final String name, final org.apache.log4j.Appender log4j1Appender) 110 { 111 Log4j1WrapperAppender lw = new Log4j1WrapperAppender(name, log4j1Appender); 112 return lw; 113 } 114 115 116 /* 117 protected Log4j1WrapperAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout, 118 final boolean ignoreExceptions, final Property[] properties) { 119 super(name, filter, layout, ignoreExceptions, properties); 120 } 121 */ 122 123 private Log4j1WrapperAppender(final String name, final org.apache.log4j.Appender log4j1Appender) { 124 super(name, null, null, true, Property.EMPTY_ARRAY); 125 this.log4j1Appender = log4j1Appender; 126 } 127 128 private Log4j1WrapperAppender(final String name, final String className, final String propertiesString) { 129 super(name, null, null, true, Property.EMPTY_ARRAY); 130 131 // org.apache.log4j.Appender log4j1Appender 132 Properties properties = new Properties(); 133 try { 134 properties.load(new StringReader(propertiesString)); 135 } catch (IOException e) { 136 throw new IllegalStateException("IOException in StringReader"); 137 } 138 139 final String prefix = "log4j.appender." + name; 140 final String layoutPrefix = "log4j.appender." + name + ".layout"; 141 final String filterPrefix = "log4j.appender." + name + ".filter."; 142 this.log4j1Appender = buildLog4j1Appender(name, className, prefix, layoutPrefix, filterPrefix, properties); 143 } 144 145 146 @Override 147 public void append(LogEvent event) { 148 log4j1Appender.doAppend(new LogEventAdapter(event)); 149 } 150 151 @Override 152 public void stop() { 153 log4j1Appender.close(); 154 } 155 156 public org.apache.log4j.Appender getLog4j1Appender() { 157 return log4j1Appender; 158 } 159 160 161 private org.apache.log4j.Appender buildLog4j1Appender(final String appenderName, final String className, final String prefix, 162 final String layoutPrefix, final String filterPrefix, final Properties props) { 163 org.apache.log4j.Appender appender = newInstanceOf(className, "Appender"); 164 if (appender == null) { 165 return null; 166 } 167 appender.setName(appenderName); 168 appender.setLayout(parseLayout(layoutPrefix, appenderName, props)); 169 final String errorHandlerPrefix = prefix + ".errorhandler"; 170 // String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props); 171 String errorHandlerClass = props.getProperty(errorHandlerPrefix); 172 if (errorHandlerClass != null) { 173 org.apache.log4j.spi.ErrorHandler eh = parseErrorHandler(props, errorHandlerPrefix, errorHandlerClass, appender); 174 if (eh != null) { 175 appender.setErrorHandler(eh); 176 } 177 } 178 parseAppenderFilters(props, filterPrefix, appenderName); 179 String[] keys = new String[] { 180 layoutPrefix, 181 }; 182 addProperties(appender, keys, props, prefix); 183 return appender; 184 } 185 186 private <T> T newInstanceOf(String className, String type) { 187 try { 188 return LoaderUtil.newInstanceOf(className); 189 } catch (Exception ex) { 190 StatusLogger.getLogger().warn("Log4j1WrapperAppender: Unable to create " + type + " " + className + " due to " + ex.getClass().getSimpleName() + ": " + ex.getMessage()); 191 return null; 192 } 193 } 194 195 public Layout parseLayout(String layoutPrefix, String appenderName, Properties props) { 196 // String layoutClass = OptionConverter.findAndSubst(layoutPrefix, props); 197 String layoutClass = props.getProperty(layoutPrefix); 198 if (layoutClass == null) { 199 return null; 200 } 201 Layout layout = buildLayout(layoutPrefix, layoutClass, appenderName, props); 202 return layout; 203 } 204 205 private Layout buildLayout(String layoutPrefix, String className, String appenderName, Properties props) { 206 Layout layout = newInstanceOf(className, "Layout"); 207 if (layout == null) { 208 return null; 209 } 210 PropertySetter.setProperties(layout, props, layoutPrefix + "."); 211 return layout; 212 } 213 214 public org.apache.log4j.spi.ErrorHandler parseErrorHandler(final Properties props, final String errorHandlerPrefix, 215 final String errorHandlerClass, final org.apache.log4j.Appender appender) { 216 org.apache.log4j.spi.ErrorHandler eh = newInstanceOf(errorHandlerClass, "ErrorHandler"); 217 final String[] keys = new String[] { 218 errorHandlerPrefix + ".root-ref", 219 errorHandlerPrefix + ".logger-ref", 220 errorHandlerPrefix + ".appender-ref" 221 }; 222 addProperties(eh, keys, props, errorHandlerPrefix); 223 return eh; 224 } 225 226 public void addProperties(final Object obj, final String[] keys, final Properties props, final String prefix) { 227 final Properties edited = new Properties(); 228 for (String name : props.stringPropertyNames()) { 229 boolean filter = false; 230 if (name.startsWith(prefix)) { 231 filter = true; 232 for (String key : keys) { 233 if (name.equals(key)) { 234 filter = false; 235 } 236 } 237 } else { 238 filter = false; 239 } 240 if (filter) { 241 edited.put(name, props.getProperty(name)); 242 } 243 } 244 PropertySetter.setProperties(obj, edited, prefix + "."); 245 } 246 247 public Filter parseAppenderFilters(Properties props, String filterPrefix, String appenderName) { 248 // extract filters and filter options from props into a hashtable mapping 249 // the property name defining the filter class to a list of pre-parsed 250 // name-value pairs associated to that filter 251 252 // so instead of List<NameValue>, I'm usnig a properties object 253 int fIdx = filterPrefix.length(); 254 SortedMap<String, Properties> filters = new TreeMap<String, Properties>(); 255 Enumeration e = props.keys(); 256 String name = ""; 257 while (e.hasMoreElements()) { 258 String key = (String) e.nextElement(); 259 if (key.startsWith(filterPrefix)) { 260 int dotIdx = key.indexOf('.', fIdx); 261 String filterKey = key; 262 if (dotIdx != -1) { 263 filterKey = key.substring(0, dotIdx); 264 name = key.substring(dotIdx + 1); 265 } 266 // Properties filterOpts = filters.computeIfAbsent(filterKey, k -> new ArrayList<>()); 267 Properties filterOpts = filters.get(filterKey); 268 if (filterOpts == null) { 269 filterOpts = new Properties(); 270 filters.put(filterKey, filterOpts); 271 } 272 273 if (dotIdx != -1) { 274 String value = OptionConverter.findAndSubst(key, props); 275 filterOpts.put(name, value); 276 } 277 } 278 } 279 280 Filter head = null; 281 Filter next = null; 282 for (Map.Entry<String, Properties> entry : filters.entrySet()) { 283 String clazz = props.getProperty(entry.getKey()); 284 Filter filter = null; 285 if (clazz != null) { 286 filter = buildFilter(clazz, appenderName, entry.getValue()); 287 } 288 if (filter != null) { 289 if (head != null) { 290 head = filter; 291 next = filter; 292 } else { 293 next.setNext(filter); 294 next = filter; 295 } 296 } 297 } 298 return head; 299 } 300 301 private Filter buildFilter(String className, String appenderName, Properties props) { 302 Filter filter = newInstanceOf(className, "Filter"); 303 if (filter != null) { 304 PropertySetter propSetter = new PropertySetter(filter); 305 for (Map.Entry<Object, Object> e : props.entrySet()) { 306 propSetter.setProperty((String) e.getKey(), (String) e.getValue()); 307 } 308 propSetter.activate(); 309 } 310 return filter; 311 } 312 313 314 315 316 317 318}