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