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 }