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