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