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;
025
026import org.apache.logging.log4j.Level;
027import org.apache.logging.log4j.LogManager;
028import org.apache.logging.log4j.Marker;
029import org.apache.logging.log4j.core.Appender;
030import org.apache.logging.log4j.core.Core;
031import org.apache.logging.log4j.core.Filter;
032import org.apache.logging.log4j.core.LogEvent;
033import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
034import org.apache.logging.log4j.core.config.plugins.Plugin;
035import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
036import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
037import org.apache.logging.log4j.core.config.plugins.PluginElement;
038import org.apache.logging.log4j.core.config.plugins.PluginFactory;
039import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
040import org.apache.logging.log4j.core.filter.AbstractFilterable;
041import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
042import org.apache.logging.log4j.core.impl.Log4jLogEvent;
043import org.apache.logging.log4j.core.impl.LogEventFactory;
044import org.apache.logging.log4j.core.impl.ReusableLogEventFactory;
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.message.Message;
049import org.apache.logging.log4j.util.LoaderUtil;
050import org.apache.logging.log4j.util.PerformanceSensitive;
051import org.apache.logging.log4j.util.PropertiesUtil;
052import org.apache.logging.log4j.util.Strings;
053
054/**
055 * Logger object that is created via configuration.
056 */
057@Plugin(name = "logger", category = Node.CATEGORY, printObject = true)
058public class LoggerConfig extends AbstractFilterable {
059
060    public static final String ROOT = "root";
061    private static LogEventFactory LOG_EVENT_FACTORY = null;
062
063    private List<AppenderRef> appenderRefs = new ArrayList<>();
064    private final AppenderControlArraySet appenders = new AppenderControlArraySet();
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 Map<Property, Boolean> propertiesMap;
072    private final List<Property> properties;
073    private final boolean propertiesRequireLookup;
074    private final Configuration config;
075    private final ReliabilityStrategy reliabilityStrategy;
076
077    static {
078        final String factory = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_LOG_EVENT_FACTORY);
079        if (factory != null) {
080            try {
081                final Class<?> clazz = LoaderUtil.loadClass(factory);
082                if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) {
083                    LOG_EVENT_FACTORY = (LogEventFactory) clazz.newInstance();
084                }
085            } catch (final Exception ex) {
086                LOGGER.error("Unable to create LogEventFactory {}", factory, ex);
087            }
088        }
089        if (LOG_EVENT_FACTORY == null) {
090            LOG_EVENT_FACTORY = Constants.ENABLE_THREADLOCALS
091                    ? new ReusableLogEventFactory()
092                    : new DefaultLogEventFactory();
093        }
094    }
095
096    /**
097     * Default constructor.
098     */
099    public LoggerConfig() {
100        this.logEventFactory = LOG_EVENT_FACTORY;
101        this.level = Level.ERROR;
102        this.name = Strings.EMPTY;
103        this.properties = null;
104        this.propertiesRequireLookup = false;
105        this.config = null;
106        this.reliabilityStrategy = new DefaultReliabilityStrategy(this);
107    }
108
109    /**
110     * Constructor that sets the name, level and additive values.
111     *
112     * @param name The Logger name.
113     * @param level The Level.
114     * @param additive true if the Logger is additive, false otherwise.
115     */
116    public LoggerConfig(final String name, final Level level, 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.propertiesRequireLookup = false;
123        this.config = null;
124        this.reliabilityStrategy = new DefaultReliabilityStrategy(this);
125    }
126
127    protected LoggerConfig(final String name, final List<AppenderRef> appenders, final Filter filter,
128            final Level level, final boolean additive, 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 = Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(
140                    properties, properties.length)));
141        } else {
142            this.properties = null;
143        }
144        this.propertiesRequireLookup = containsPropertyRequiringLookup(properties);
145        this.reliabilityStrategy = config.getReliabilityStrategy(this);
146    }
147
148    private static boolean containsPropertyRequiringLookup(final Property[] properties) {
149        if (properties == null) {
150            return false;
151        }
152        for (int i = 0; i < properties.length; i++) {
153            if (properties[i].isValueNeedsLookup()) {
154                return true;
155            }
156        }
157        return false;
158    }
159
160    @Override
161    public Filter getFilter() {
162        return super.getFilter();
163    }
164
165    /**
166     * Returns the name of the LoggerConfig.
167     *
168     * @return the name of the LoggerConfig.
169     */
170    public String getName() {
171        return name;
172    }
173
174    /**
175     * Sets the parent of this LoggerConfig.
176     *
177     * @param parent the parent LoggerConfig.
178     */
179    public void setParent(final LoggerConfig parent) {
180        this.parent = parent;
181    }
182
183    /**
184     * Returns the parent of this LoggerConfig.
185     *
186     * @return the LoggerConfig that is the parent of this one.
187     */
188    public LoggerConfig getParent() {
189        return this.parent;
190    }
191
192    /**
193     * Adds an Appender to the LoggerConfig.
194     *
195     * @param appender The Appender to add.
196     * @param level The Level to use.
197     * @param filter A Filter for the Appender reference.
198     */
199    public void addAppender(final Appender appender, final Level level, final Filter filter) {
200        appenders.add(new AppenderControl(appender, level, filter));
201    }
202
203    /**
204     * Removes the Appender with the specific name.
205     *
206     * @param name The name of the Appender.
207     */
208    public void removeAppender(final String name) {
209        AppenderControl removed = null;
210        while ((removed = appenders.remove(name)) != null) {
211            cleanupFilter(removed);
212        }
213    }
214
215    /**
216     * Returns all Appenders as a Map.
217     *
218     * @return a Map with the Appender name as the key and the Appender as the value.
219     */
220    public Map<String, Appender> getAppenders() {
221        return appenders.asMap();
222    }
223
224    /**
225     * Removes all Appenders.
226     */
227    protected void clearAppenders() {
228        do {
229            final AppenderControl[] original = appenders.clear();
230            for (final AppenderControl ctl : original) {
231                cleanupFilter(ctl);
232            }
233        } while (!appenders.isEmpty());
234    }
235
236    private void cleanupFilter(final AppenderControl ctl) {
237        final Filter filter = ctl.getFilter();
238        if (filter != null) {
239            ctl.removeFilter(filter);
240            filter.stop();
241        }
242    }
243
244    /**
245     * Returns the Appender references.
246     *
247     * @return a List of all the Appender names attached to this LoggerConfig.
248     */
249    public List<AppenderRef> getAppenderRefs() {
250        return appenderRefs;
251    }
252
253    /**
254     * Sets the logging Level.
255     *
256     * @param level The logging Level.
257     */
258    public void setLevel(final Level level) {
259        this.level = level;
260    }
261
262    /**
263     * Returns the logging Level.
264     *
265     * @return the logging Level.
266     */
267    public Level getLevel() {
268        return level == null ? parent.getLevel() : level;
269    }
270
271    /**
272     * Returns the LogEventFactory.
273     *
274     * @return the LogEventFactory.
275     */
276    public LogEventFactory getLogEventFactory() {
277        return logEventFactory;
278    }
279
280    /**
281     * Sets the LogEventFactory. Usually the LogEventFactory will be this LoggerConfig.
282     *
283     * @param logEventFactory the LogEventFactory.
284     */
285    public void setLogEventFactory(final LogEventFactory logEventFactory) {
286        this.logEventFactory = logEventFactory;
287    }
288
289    /**
290     * Returns the valid of the additive flag.
291     *
292     * @return true if the LoggerConfig is additive, false otherwise.
293     */
294    public boolean isAdditive() {
295        return additive;
296    }
297
298    /**
299     * Sets the additive setting.
300     *
301     * @param additive true if the LoggerConfig should be additive, false otherwise.
302     */
303    public void setAdditive(final boolean additive) {
304        this.additive = additive;
305    }
306
307    /**
308     * Returns the value of logger configuration attribute {@code includeLocation}, or, if no such attribute was
309     * configured, {@code true} if logging is synchronous or {@code false} if logging is asynchronous.
310     *
311     * @return whether location should be passed downstream
312     */
313    public boolean isIncludeLocation() {
314        return includeLocation;
315    }
316
317    /**
318     * Returns an unmodifiable map with the configuration properties, or {@code null} if this {@code LoggerConfig} does
319     * not have any configuration properties.
320     * <p>
321     * For each {@code Property} key in the map, the value is {@code true} if the property value has a variable that
322     * needs to be substituted.
323     *
324     * @return an unmodifiable map with the configuration properties, or {@code null}
325     * @see Configuration#getStrSubstitutor()
326     * @see StrSubstitutor
327     * @deprecated use {@link #getPropertyList()} instead
328     */
329    // LOG4J2-157
330    @Deprecated
331    public Map<Property, Boolean> getProperties() {
332        if (properties == null) {
333            return null;
334        }
335        if (propertiesMap == null) { // lazily initialize: only used by user custom code, not by Log4j any more
336            final Map<Property, Boolean> result = new HashMap<>(properties.size() * 2);
337            for (int i = 0; i < properties.size(); i++) {
338                result.put(properties.get(i), Boolean.valueOf(properties.get(i).isValueNeedsLookup()));
339            }
340            propertiesMap = Collections.unmodifiableMap(result);
341        }
342        return propertiesMap;
343    }
344
345    /**
346     * Returns an unmodifiable list with the configuration properties, or {@code null} if this {@code LoggerConfig} does
347     * not have any configuration properties.
348     * <p>
349     * Each {@code Property} in the list has an attribute {@link Property#isValueNeedsLookup() valueNeedsLookup} that
350     * is {@code true} if the property value has a variable that needs to be substituted.
351     *
352     * @return an unmodifiable list with the configuration properties, or {@code null}
353     * @see Configuration#getStrSubstitutor()
354     * @see StrSubstitutor
355     * @since 2.7
356     */
357    public List<Property> getPropertyList() {
358        return properties;
359    }
360
361    public boolean isPropertiesRequireLookup() {
362        return propertiesRequireLookup;
363    }
364
365    /**
366     * Logs an event.
367     *
368     * @param loggerName The name of the Logger.
369     * @param fqcn The fully qualified class name of the caller.
370     * @param marker A Marker or null if none is present.
371     * @param level The event Level.
372     * @param data The Message.
373     * @param t A Throwable or null.
374     */
375    @PerformanceSensitive("allocation")
376    public void log(final String loggerName, final String fqcn, final Marker marker, final Level level,
377            final Message data, final Throwable t) {
378        List<Property> props = null;
379        if (!propertiesRequireLookup) {
380            props = properties;
381        } else {
382            if (properties != null) {
383                props = new ArrayList<>(properties.size());
384                final LogEvent event = Log4jLogEvent.newBuilder()
385                        .setMessage(data)
386                        .setMarker(marker)
387                        .setLevel(level)
388                        .setLoggerName(loggerName)
389                        .setLoggerFqcn(fqcn)
390                        .setThrown(t)
391                        .build();
392                for (int i = 0; i < properties.size(); i++) {
393                    final Property prop = properties.get(i);
394                    final String value = prop.isValueNeedsLookup() // since LOG4J2-1575
395                            ? config.getStrSubstitutor().replace(event, prop.getValue()) //
396                            : prop.getValue();
397                    props.add(Property.createProperty(prop.getName(), value));
398                }
399            }
400        }
401        final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
402        try {
403            log(logEvent);
404        } finally {
405            // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
406            ReusableLogEventFactory.release(logEvent);
407        }
408    }
409
410    /**
411     * Logs an event.
412     *
413     * @param event The log event.
414     */
415    public void log(final LogEvent event) {
416        if (!isFiltered(event)) {
417            processLogEvent(event);
418        }
419    }
420
421    /**
422     * Returns the object responsible for ensuring log events are delivered to a working appender, even during or after
423     * a reconfiguration.
424     *
425     * @return the object responsible for delivery of log events to the appender
426     */
427    public ReliabilityStrategy getReliabilityStrategy() {
428        return reliabilityStrategy;
429    }
430
431    private void processLogEvent(final LogEvent event) {
432        event.setIncludeLocation(isIncludeLocation());
433        callAppenders(event);
434        logParent(event);
435    }
436
437    private void logParent(final LogEvent event) {
438        if (additive && parent != null) {
439            parent.log(event);
440        }
441    }
442
443    @PerformanceSensitive("allocation")
444    protected void callAppenders(final LogEvent event) {
445        final AppenderControl[] controls = appenders.get();
446        //noinspection ForLoopReplaceableByForEach
447        for (int i = 0; i < controls.length; i++) {
448            controls[i].callAppender(event);
449        }
450    }
451
452    @Override
453    public String toString() {
454        return Strings.isEmpty(name) ? ROOT : name;
455    }
456
457    /**
458     * Factory method to create a LoggerConfig.
459     *
460     * @param additivity True if additive, false otherwise.
461     * @param level The Level to be associated with the Logger.
462     * @param loggerName The name of the Logger.
463     * @param includeLocation whether location should be passed downstream
464     * @param refs An array of Appender names.
465     * @param properties Properties to pass to the Logger.
466     * @param config The Configuration.
467     * @param filter A Filter.
468     * @return A new LoggerConfig.
469     * @deprecated Deprecated in 2.7; use {@link #createLogger(boolean, Level, String, String, AppenderRef[], Property[], Configuration, Filter)}
470     */
471    @Deprecated
472    public static LoggerConfig createLogger(final String additivity,
473            // @formatter:off
474            final Level level,
475            @PluginAttribute("name") final String loggerName,
476            final String includeLocation,
477            final AppenderRef[] refs,
478            final Property[] properties,
479            @PluginConfiguration final Configuration config,
480            final Filter filter) {
481            // @formatter:on
482        if (loggerName == null) {
483            LOGGER.error("Loggers cannot be configured without a name");
484            return null;
485        }
486
487        final List<AppenderRef> appenderRefs = Arrays.asList(refs);
488        final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName;
489        final boolean additive = Booleans.parseBoolean(additivity, true);
490
491        return new LoggerConfig(name, appenderRefs, filter, level, additive, properties, config,
492                includeLocation(includeLocation));
493    }
494
495    /**
496     * Factory method to create a LoggerConfig.
497     *
498     * @param additivity true if additive, false otherwise.
499     * @param level The Level to be associated with the Logger.
500     * @param loggerName The name of the Logger.
501     * @param includeLocation whether location should be passed downstream
502     * @param refs An array of Appender names.
503     * @param properties Properties to pass to the Logger.
504     * @param config The Configuration.
505     * @param filter A Filter.
506     * @return A new LoggerConfig.
507     * @since 2.6
508     */
509    @PluginFactory
510    public static LoggerConfig createLogger(
511         // @formatter:off
512        @PluginAttribute(value = "additivity", defaultBoolean = true) final boolean additivity,
513        @PluginAttribute("level") final Level level,
514        @Required(message = "Loggers cannot be configured without a name") @PluginAttribute("name") final String loggerName,
515        @PluginAttribute("includeLocation") final String includeLocation,
516        @PluginElement("AppenderRef") final AppenderRef[] refs,
517        @PluginElement("Properties") final Property[] properties,
518        @PluginConfiguration final Configuration config,
519        @PluginElement("Filter") final Filter filter
520        // @formatter:on
521    ) {
522        final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName;
523        return new LoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config,
524            includeLocation(includeLocation));
525    }
526
527    // Note: for asynchronous loggers, includeLocation default is FALSE,
528    // for synchronous loggers, includeLocation default is TRUE.
529    protected static boolean includeLocation(final String includeLocationConfigValue) {
530        if (includeLocationConfigValue == null) {
531            final boolean sync = !AsyncLoggerContextSelector.isSelected();
532            return sync;
533        }
534        return Boolean.parseBoolean(includeLocationConfigValue);
535    }
536
537    /**
538     * The root Logger.
539     */
540    @Plugin(name = ROOT, category = Core.CATEGORY_NAME, printObject = true)
541    public static class RootLogger extends LoggerConfig {
542
543        @PluginFactory
544        public static LoggerConfig createLogger(
545                // @formatter:off
546                @PluginAttribute("additivity") final String additivity,
547                @PluginAttribute("level") final Level level,
548                @PluginAttribute("includeLocation") final String includeLocation,
549                @PluginElement("AppenderRef") final AppenderRef[] refs,
550                @PluginElement("Properties") final Property[] properties,
551                @PluginConfiguration final Configuration config,
552                @PluginElement("Filter") final Filter filter) {
553                // @formatter:on
554            final List<AppenderRef> appenderRefs = Arrays.asList(refs);
555            final Level actualLevel = level == null ? Level.ERROR : level;
556            final boolean additive = Booleans.parseBoolean(additivity, true);
557
558            return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive,
559                    properties, config, includeLocation(includeLocation));
560        }
561    }
562
563}