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     */
017    package org.apache.logging.log4j.core.config;
018    
019    import org.apache.logging.log4j.Level;
020    import org.apache.logging.log4j.LogManager;
021    import org.apache.logging.log4j.Logger;
022    import org.apache.logging.log4j.Marker;
023    import org.apache.logging.log4j.core.Appender;
024    import org.apache.logging.log4j.core.Filter;
025    import org.apache.logging.log4j.core.LifeCycle;
026    import org.apache.logging.log4j.core.LogEvent;
027    import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
028    import org.apache.logging.log4j.core.config.plugins.Plugin;
029    import org.apache.logging.log4j.core.config.plugins.PluginAttr;
030    import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
031    import org.apache.logging.log4j.core.config.plugins.PluginElement;
032    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
033    import org.apache.logging.log4j.core.filter.AbstractFilterable;
034    import org.apache.logging.log4j.core.helpers.Constants;
035    import org.apache.logging.log4j.core.impl.Log4jLogEvent;
036    import org.apache.logging.log4j.core.impl.LogEventFactory;
037    import org.apache.logging.log4j.core.lookup.StrSubstitutor;
038    import org.apache.logging.log4j.message.Message;
039    import org.apache.logging.log4j.status.StatusLogger;
040    
041    import java.io.Serializable;
042    import java.util.ArrayList;
043    import java.util.Arrays;
044    import java.util.Collection;
045    import java.util.Collections;
046    import java.util.HashMap;
047    import java.util.Iterator;
048    import java.util.List;
049    import java.util.Map;
050    import java.util.concurrent.ConcurrentHashMap;
051    import java.util.concurrent.atomic.AtomicInteger;
052    
053    /**
054     * Logger object that is created via configuration.
055     */
056    @Plugin(name = "logger", category = "Core", printObject = true)
057    public class LoggerConfig extends AbstractFilterable implements LogEventFactory {
058    
059        private static final Logger LOGGER = StatusLogger.getLogger();
060        private static final int MAX_RETRIES = 3;
061        private static final long WAIT_TIME = 1000;
062    
063        private List<AppenderRef> appenderRefs = new ArrayList<AppenderRef>();
064        private final Map<String, AppenderControl<?>> appenders = new ConcurrentHashMap<String, AppenderControl<?>>();
065        private final String name;
066        private LogEventFactory logEventFactory;
067        private Level level;
068        private boolean additive = true;
069        private boolean includeLocation = true;
070        private LoggerConfig parent;
071        private final AtomicInteger counter = new AtomicInteger();
072        private boolean shutdown = false;
073        private final Map<Property, Boolean> properties;
074        private final Configuration config;
075    
076        /**
077         * Default constructor.
078         */
079        public LoggerConfig() {
080            this.logEventFactory = this;
081            this.level = Level.ERROR;
082            this.name = "";
083            this.properties = null;
084            this.config = null;
085        }
086    
087        /**
088         * Constructor that sets the name, level and additive values.
089         *
090         * @param name The Logger name.
091         * @param level The Level.
092         * @param additive true if the Logger is additive, false otherwise.
093         */
094        public LoggerConfig(final String name, final Level level,
095                final boolean additive) {
096            this.logEventFactory = this;
097            this.name = name;
098            this.level = level;
099            this.additive = additive;
100            this.properties = null;
101            this.config = null;
102        }
103    
104        protected LoggerConfig(final String name,
105                final List<AppenderRef> appenders, final Filter filter,
106                final Level level, final boolean additive,
107                final Property[] properties, final Configuration config,
108                final boolean includeLocation) {
109            super(filter);
110            this.logEventFactory = this;
111            this.name = name;
112            this.appenderRefs = appenders;
113            this.level = level;
114            this.additive = additive;
115            this.includeLocation = includeLocation;
116            this.config = config;
117            if (properties != null && properties.length > 0) {
118                this.properties = new HashMap<Property, Boolean>(properties.length);
119                for (final Property prop : properties) {
120                    final boolean interpolate = prop.getValue().contains("${");
121                    this.properties.put(prop, interpolate);
122                }
123            } else {
124                this.properties = null;
125            }
126        }
127    
128        @Override
129        public Filter getFilter() {
130            return super.getFilter();
131        }
132    
133        /**
134         * Returns the name of the LoggerConfig.
135         *
136         * @return the name of the LoggerConfig.
137         */
138        public String getName() {
139            return name;
140        }
141    
142        /**
143         * Sets the parent of this LoggerConfig.
144         *
145         * @param parent the parent LoggerConfig.
146         */
147        public void setParent(final LoggerConfig parent) {
148            this.parent = parent;
149        }
150    
151        /**
152         * Returns the parent of this LoggerConfig.
153         *
154         * @return the LoggerConfig that is the parent of this one.
155         */
156        public LoggerConfig getParent() {
157            return this.parent;
158        }
159    
160        /**
161         * Adds an Appender to the LoggerConfig.
162         *
163         * @param appender The Appender to add.
164         * @param level The Level to use.
165         * @param filter A Filter for the Appender reference.
166         */
167        public <T extends Serializable> void addAppender(final Appender<T> appender, final Level level,
168                final Filter filter) {
169            appenders.put(appender.getName(), new AppenderControl<T>(appender, level,
170                    filter));
171        }
172    
173        /**
174         * Removes the Appender with the specific name.
175         *
176         * @param name The name of the Appender.
177         */
178        public void removeAppender(final String name) {
179            final AppenderControl ctl = appenders.remove(name);
180            if (ctl != null) {
181                cleanupFilter(ctl);
182            }
183        }
184    
185        /**
186         * Returns all Appenders as a Map.
187         *
188         * @return a Map with the Appender name as the key and the Appender as the
189         *         value.
190         */
191        public Map<String, Appender<?>> getAppenders() {
192            final Map<String, Appender<?>> map = new HashMap<String, Appender<?>>();
193            for (final Map.Entry<String, AppenderControl<?>> entry : appenders
194                    .entrySet()) {
195                map.put(entry.getKey(), entry.getValue().getAppender());
196            }
197            return map;
198        }
199    
200        /**
201         * Removes all Appenders.
202         */
203        protected void clearAppenders() {
204            waitForCompletion();
205            final Collection<AppenderControl<?>> controls = appenders.values();
206            final Iterator<AppenderControl<?>> iterator = controls.iterator();
207            while (iterator.hasNext()) {
208                final AppenderControl<?> ctl = iterator.next();
209                iterator.remove();
210                cleanupFilter(ctl);
211            }
212        }
213    
214        private void cleanupFilter(final AppenderControl ctl) {
215            final Filter filter = ctl.getFilter();
216            if (filter != null) {
217                ctl.removeFilter(filter);
218                if (filter instanceof LifeCycle) {
219                    ((LifeCycle) filter).stop();
220                }
221            }
222        }
223    
224        /**
225         * Returns the Appender references.
226         *
227         * @return a List of all the Appender names attached to this LoggerConfig.
228         */
229        public List<AppenderRef> getAppenderRefs() {
230            return appenderRefs;
231        }
232    
233        /**
234         * Sets the logging Level.
235         *
236         * @param level The logging Level.
237         */
238        public void setLevel(final Level level) {
239            this.level = level;
240        }
241    
242        /**
243         * Returns the logging Level.
244         *
245         * @return the logging Level.
246         */
247        public Level getLevel() {
248            return level;
249        }
250    
251        /**
252         * Returns the LogEventFactory.
253         *
254         * @return the LogEventFactory.
255         */
256        public LogEventFactory getLogEventFactory() {
257            return logEventFactory;
258        }
259    
260        /**
261         * Sets the LogEventFactory. Usually the LogEventFactory will be this
262         * LoggerConfig.
263         *
264         * @param logEventFactory the LogEventFactory.
265         */
266        public void setLogEventFactory(final LogEventFactory logEventFactory) {
267            this.logEventFactory = logEventFactory;
268        }
269    
270        /**
271         * Returns the valid of the additive flag.
272         *
273         * @return true if the LoggerConfig is additive, false otherwise.
274         */
275        public boolean isAdditive() {
276            return additive;
277        }
278    
279        /**
280         * Sets the additive setting.
281         *
282         * @param additive true if the LoggerConfig should be additive, false
283         *            otherwise.
284         */
285        public void setAdditive(final boolean additive) {
286            this.additive = additive;
287        }
288    
289        /**
290         * Returns the value of logger configuration attribute {@code includeLocation},
291         * or, if no such attribute was configured, {@code true} if logging is
292         * synchronous or {@code false} if logging is asynchronous.
293         *
294         * @return whether location should be passed downstream
295         */
296        public boolean isIncludeLocation() {
297            return includeLocation;
298        }
299    
300        /**
301         * Returns an unmodifiable map with the configuration properties, or
302         * {@code null} if this {@code LoggerConfig} does not have any configuration
303         * properties.
304         * <p>
305         * For each {@code Property} key in the map, the value is {@code true} if
306         * the property value has a variable that needs to be substituted.
307         *
308         * @return an unmodifiable map with the configuration properties, or
309         *         {@code null}
310         * @see Configuration#getSubst()
311         * @see StrSubstitutor
312         */
313        // LOG4J2-157
314        public Map<Property, Boolean> getProperties() {
315            return properties == null ? null : Collections
316                    .unmodifiableMap(properties);
317        }
318    
319        /**
320         * Logs an event.
321         *
322         * @param loggerName The name of the Logger.
323         * @param marker A Marker or null if none is present.
324         * @param fqcn The fully qualified class name of the caller.
325         * @param level The event Level.
326         * @param data The Message.
327         * @param t A Throwable or null.
328         */
329        public void log(final String loggerName, final Marker marker,
330                final String fqcn, final Level level, final Message data,
331                final Throwable t) {
332            List<Property> props = null;
333            if (properties != null) {
334                props = new ArrayList<Property>(properties.size());
335    
336                for (final Map.Entry<Property, Boolean> entry : properties
337                        .entrySet()) {
338                    final Property prop = entry.getKey();
339                    final String value = entry.getValue() ? config.getSubst()
340                            .replace(prop.getValue()) : prop.getValue();
341                    props.add(Property.createProperty(prop.getName(), value));
342                }
343            }
344            final LogEvent event = logEventFactory.createEvent(loggerName, marker,
345                    fqcn, level, data, props, t);
346            log(event);
347        }
348    
349        /**
350         * Waits for all log events to complete before shutting down this
351         * loggerConfig.
352         */
353        private synchronized void waitForCompletion() {
354            if (shutdown) {
355                return;
356            }
357            shutdown = true;
358            int retries = 0;
359            while (counter.get() > 0) {
360                try {
361                    wait(WAIT_TIME * (retries + 1));
362                } catch (final InterruptedException ie) {
363                    if (++retries > MAX_RETRIES) {
364                        break;
365                    }
366                }
367            }
368        }
369    
370        /**
371         * Logs an event.
372         *
373         * @param event The log event.
374         */
375        public void log(final LogEvent event) {
376    
377            counter.incrementAndGet();
378            try {
379                if (isFiltered(event)) {
380                    return;
381                }
382    
383                event.setIncludeLocation(isIncludeLocation());
384    
385                callAppenders(event);
386    
387                if (additive && parent != null) {
388                    parent.log(event);
389                }
390            } finally {
391                if (counter.decrementAndGet() == 0) {
392                    synchronized (this) {
393                        if (shutdown) {
394                            notifyAll();
395                        }
396                    }
397    
398                }
399            }
400        }
401    
402        protected void callAppenders(final LogEvent event) {
403            for (final AppenderControl control : appenders.values()) {
404                control.callAppender(event);
405            }
406        }
407    
408        /**
409         * Creates a log event.
410         *
411         * @param loggerName The name of the Logger.
412         * @param marker An optional Marker.
413         * @param fqcn The fully qualified class name of the caller.
414         * @param level The event Level.
415         * @param data The Message.
416         * @param properties Properties to be added to the log event.
417         * @param t An optional Throwable.
418         * @return The LogEvent.
419         */
420        public LogEvent createEvent(final String loggerName, final Marker marker,
421                final String fqcn, final Level level, final Message data,
422                final List<Property> properties, final Throwable t) {
423            return new Log4jLogEvent(loggerName, marker, fqcn, level, data,
424                    properties, t);
425        }
426    
427        @Override
428        public String toString() {
429            return name == null || name.length() == 0 ? "root" : name;
430        }
431    
432        /**
433         * Factory method to create a LoggerConfig.
434         *
435         * @param additivity True if additive, false otherwise.
436         * @param levelName The Level to be associated with the Logger.
437         * @param loggerName The name of the Logger.
438         * @param includeLocation whether location should be passed downstream
439         * @param refs An array of Appender names.
440         * @param properties Properties to pass to the Logger.
441         * @param config The Configuration.
442         * @param filter A Filter.
443         * @return A new LoggerConfig.
444         */
445        @PluginFactory
446        public static LoggerConfig createLogger(
447                @PluginAttr("additivity") final String additivity,
448                @PluginAttr("level") final String levelName,
449                @PluginAttr("name") final String loggerName,
450                @PluginAttr("includeLocation") final String includeLocation,
451                @PluginElement("appender-ref") final AppenderRef[] refs,
452                @PluginElement("properties") final Property[] properties,
453                @PluginConfiguration final Configuration config,
454                @PluginElement("filters") final Filter filter) {
455            if (loggerName == null) {
456                LOGGER.error("Loggers cannot be configured without a name");
457                return null;
458            }
459    
460            final List<AppenderRef> appenderRefs = Arrays.asList(refs);
461            Level level;
462            try {
463                level = Level.toLevel(levelName, Level.ERROR);
464            } catch (final Exception ex) {
465                LOGGER.error(
466                        "Invalid Log level specified: {}. Defaulting to Error",
467                        levelName);
468                level = Level.ERROR;
469            }
470            final String name = loggerName.equals("root") ? "" : loggerName;
471            final boolean additive = additivity == null ? true : Boolean
472                    .parseBoolean(additivity);
473    
474            return new LoggerConfig(name, appenderRefs, filter, level, additive,
475                    properties, config, includeLocation(includeLocation));
476        }
477    
478        // Note: for asynchronous loggers, includeLocation default is FALSE,
479        // for synchronous loggers, includeLocation default is TRUE.
480        private static boolean includeLocation(String includeLocationConfigValue) {
481            if (includeLocationConfigValue == null) {
482                final boolean sync = !AsyncLoggerContextSelector.class.getName()
483                        .equals(System.getProperty(Constants.LOG4J_CONTEXT_SELECTOR));
484                return sync;
485            }
486            return Boolean.parseBoolean(includeLocationConfigValue);
487        }
488    
489        /**
490         * The root Logger.
491         */
492        @Plugin(name = "root", category = "Core", printObject = true)
493        public static class RootLogger extends LoggerConfig {
494    
495            @PluginFactory
496            public static LoggerConfig createLogger(
497                    @PluginAttr("additivity") final String additivity,
498                    @PluginAttr("level") final String levelName,
499                    @PluginAttr("includeLocation") final String includeLocation,
500                    @PluginElement("appender-ref") final AppenderRef[] refs,
501                    @PluginElement("properties") final Property[] properties,
502                    @PluginConfiguration final Configuration config,
503                    @PluginElement("filters") final Filter filter) {
504                final List<AppenderRef> appenderRefs = Arrays.asList(refs);
505                Level level;
506                try {
507                    level = Level.toLevel(levelName, Level.ERROR);
508                } catch (final Exception ex) {
509                    LOGGER.error(
510                            "Invalid Log level specified: {}. Defaulting to Error",
511                            levelName);
512                    level = Level.ERROR;
513                }
514                final boolean additive = additivity == null ? true : Boolean
515                        .parseBoolean(additivity);
516    
517                return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs,
518                        filter, level, additive, properties, config,
519                        includeLocation(includeLocation));
520            }
521        }
522    
523    }