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