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