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