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}