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 018package org.apache.commons.logging.impl; 019 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.PrintWriter; 023import java.io.Serializable; 024import java.io.StringWriter; 025import java.security.AccessController; 026import java.security.PrivilegedAction; 027import java.text.DateFormat; 028import java.text.SimpleDateFormat; 029import java.util.Date; 030import java.util.Properties; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogConfigurationException; 034 035/** 036 * Simple implementation of Log that sends all enabled log messages, 037 * for all defined loggers, to System.err. The following system properties 038 * are supported to configure the behavior of this logger: 039 * <ul> 040 * <li>{@code org.apache.commons.logging.simplelog.defaultlog} - 041 * Default logging detail level for all instances of SimpleLog. 042 * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). 043 * If not specified, defaults to "info". </li> 044 * <li>{@code org.apache.commons.logging.simplelog.log.xxxxx} - 045 * Logging detail level for a SimpleLog instance named "xxxxx". 046 * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). 047 * If not specified, the default logging detail level is used.</li> 048 * <li>{@code org.apache.commons.logging.simplelog.showlogname} - 049 * Set to {@code true} if you want the Log instance name to be 050 * included in output messages. Defaults to {@code false}.</li> 051 * <li>{@code org.apache.commons.logging.simplelog.showShortLogname} - 052 * Set to {@code true} if you want the last component of the name to be 053 * included in output messages. Defaults to {@code true}.</li> 054 * <li>{@code org.apache.commons.logging.simplelog.showdatetime} - 055 * Set to {@code true} if you want the current date and time 056 * to be included in output messages. Default is {@code false}.</li> 057 * <li>{@code org.apache.commons.logging.simplelog.dateTimeFormat} - 058 * The date and time format to be used in the output messages. 059 * The pattern describing the date and time format is the same that is 060 * used in {@link java.text.SimpleDateFormat}. If the format is not 061 * specified or is invalid, the default format is used. 062 * The default format is {@code yyyy/MM/dd HH:mm:ss:SSS zzz}.</li> 063 * </ul> 064 * <p> 065 * In addition to looking for system properties with the names specified 066 * above, this implementation also checks for a class loader resource named 067 * {@code "simplelog.properties"}, and includes any matching definitions 068 * from this resource (if it exists). 069 */ 070public class SimpleLog implements Log, Serializable { 071 072 /** Serializable version identifier. */ 073 private static final long serialVersionUID = 136942970684951178L; 074 075 /** All system properties used by {@code SimpleLog} start with this */ 076 static protected final String systemPrefix = "org.apache.commons.logging.simplelog."; 077 078 /** Properties loaded from simplelog.properties */ 079 static protected final Properties simpleLogProps = new Properties(); 080 081 /** The default format to use when formating dates */ 082 static protected final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz"; 083 084 /** Include the instance name in the log message? */ 085 static volatile protected boolean showLogName; 086 087 /** 088 * Include the short name (last component) of the logger in the log 089 * message. Defaults to true - otherwise we'll be lost in a flood of 090 * messages without knowing who sends them. 091 */ 092 static volatile protected boolean showShortName = true; 093 094 /** Include the current time in the log message */ 095 static volatile protected boolean showDateTime; 096 097 /** The date and time format to use in the log message */ 098 static volatile protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; 099 100 /** 101 * Used to format times. 102 * <p> 103 * Any code that accesses this object should first obtain a lock on it, 104 * that is, use synchronized(dateFormatter); this requirement was introduced 105 * in 1.1.1 to fix an existing thread safety bug (SimpleDateFormat.format 106 * is not thread-safe). 107 * </p> 108 */ 109 static protected DateFormat dateFormatter; 110 111 /** "Trace" level logging. */ 112 public static final int LOG_LEVEL_TRACE = 1; 113 /** "Debug" level logging. */ 114 public static final int LOG_LEVEL_DEBUG = 2; 115 /** "Info" level logging. */ 116 public static final int LOG_LEVEL_INFO = 3; 117 /** "Warn" level logging. */ 118 public static final int LOG_LEVEL_WARN = 4; 119 /** "Error" level logging. */ 120 public static final int LOG_LEVEL_ERROR = 5; 121 /** "Fatal" level logging. */ 122 public static final int LOG_LEVEL_FATAL = 6; 123 124 /** Enable all logging levels */ 125 public static final int LOG_LEVEL_ALL = LOG_LEVEL_TRACE - 1; 126 127 /** Enable no logging levels */ 128 public static final int LOG_LEVEL_OFF = LOG_LEVEL_FATAL + 1; 129 130 // Initialize class attributes. 131 // Load properties file, if found. 132 // Override with system properties. 133 static { 134 // Add props from the resource simplelog.properties 135 try (InputStream in = getResourceAsStream("simplelog.properties")) { 136 if (null != in) { 137 simpleLogProps.load(in); 138 } 139 } catch (final IOException ignore) { 140 // Ignore 141 } 142 143 showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName); 144 showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName); 145 showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime); 146 147 if (showDateTime) { 148 dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat", dateTimeFormat); 149 try { 150 dateFormatter = new SimpleDateFormat(dateTimeFormat); 151 } catch (final IllegalArgumentException e) { 152 // If the format pattern is invalid - use the default format 153 dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; 154 dateFormatter = new SimpleDateFormat(dateTimeFormat); 155 } 156 } 157 } 158 159 private static boolean getBooleanProperty(final String name, final boolean defaultValue) { 160 final String prop = getStringProperty(name); 161 return prop == null ? defaultValue : Boolean.parseBoolean(prop); 162 } 163 164 /** 165 * Gets the thread context class loader if available. Otherwise return null. 166 * 167 * The thread context class loader is available if certain security conditions are met. 168 * 169 * @throws LogConfigurationException if a suitable class loader cannot be identified. 170 */ 171 private static ClassLoader getContextClassLoader() { 172 ClassLoader classLoader = null; 173 174 // Get the thread context class loader (if there is one) 175 try { 176 classLoader = Thread.currentThread().getContextClassLoader(); 177 } catch (final RuntimeException e) { 178 /** 179 * getContextClassLoader() throws SecurityException when the context class loader isn't an ancestor of the calling class's class loader, or if 180 * security permissions are restricted. 181 * 182 * In the first case (not related), we want to ignore and keep going. We cannot help but also ignore the second with the logic below, but other 183 * calls elsewhere (to obtain a class loader) will trigger this exception where we can make a distinction. 184 */ 185 // Capture 'e.getTargetException()' exception for details 186 // alternate: log 'e.getTargetException()', and pass back 'e'. 187 if (!(e instanceof SecurityException)) { 188 throw new LogConfigurationException("Unexpected SecurityException", e); 189 } 190 } 191 192 if (classLoader == null) { 193 classLoader = SimpleLog.class.getClassLoader(); 194 } 195 196 // Return the selected class loader 197 return classLoader; 198 } 199 200 private static InputStream getResourceAsStream(final String name) { 201 return AccessController.doPrivileged((PrivilegedAction<InputStream>) () -> { 202 final ClassLoader threadCL = getContextClassLoader(); 203 if (threadCL != null) { 204 return threadCL.getResourceAsStream(name); 205 } 206 return ClassLoader.getSystemResourceAsStream(name); 207 }); 208 } 209 210 private static String getStringProperty(final String name) { 211 String prop = null; 212 try { 213 prop = System.getProperty(name); 214 } catch (final SecurityException e) { 215 // Ignore 216 } 217 return prop == null ? simpleLogProps.getProperty(name) : prop; 218 } 219 private static String getStringProperty(final String name, final String defaultValue) { 220 final String prop = getStringProperty(name); 221 return prop == null ? defaultValue : prop; 222 } 223 /** The name of this simple log instance */ 224 protected volatile String logName; 225 226 /** The current log level */ 227 protected volatile int currentLogLevel; 228 229 /** The short name of this simple log instance */ 230 private volatile String shortLogName; 231 232 /** 233 * Constructs a simple log with given name. 234 * 235 * @param name log name 236 */ 237 public SimpleLog(String name) { 238 logName = name; 239 240 // Set initial log level 241 // Used to be: set default log level to ERROR 242 // IMHO it should be lower, but at least info (costin). 243 setLevel(SimpleLog.LOG_LEVEL_INFO); 244 245 // Set log level from properties 246 String lvl = getStringProperty(systemPrefix + "log." + logName); 247 int i = String.valueOf(name).lastIndexOf("."); 248 while(null == lvl && i > -1) { 249 name = name.substring(0,i); 250 lvl = getStringProperty(systemPrefix + "log." + name); 251 i = String.valueOf(name).lastIndexOf("."); 252 } 253 254 if (null == lvl) { 255 lvl = getStringProperty(systemPrefix + "defaultlog"); 256 } 257 258 if ("all".equalsIgnoreCase(lvl)) { 259 setLevel(SimpleLog.LOG_LEVEL_ALL); 260 } else if ("trace".equalsIgnoreCase(lvl)) { 261 setLevel(SimpleLog.LOG_LEVEL_TRACE); 262 } else if ("debug".equalsIgnoreCase(lvl)) { 263 setLevel(SimpleLog.LOG_LEVEL_DEBUG); 264 } else if ("info".equalsIgnoreCase(lvl)) { 265 setLevel(SimpleLog.LOG_LEVEL_INFO); 266 } else if ("warn".equalsIgnoreCase(lvl)) { 267 setLevel(SimpleLog.LOG_LEVEL_WARN); 268 } else if ("error".equalsIgnoreCase(lvl)) { 269 setLevel(SimpleLog.LOG_LEVEL_ERROR); 270 } else if ("fatal".equalsIgnoreCase(lvl)) { 271 setLevel(SimpleLog.LOG_LEVEL_FATAL); 272 } else if ("off".equalsIgnoreCase(lvl)) { 273 setLevel(SimpleLog.LOG_LEVEL_OFF); 274 } 275 } 276 277 /** 278 * Logs a message with 279 * {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG}. 280 * 281 * @param message to log 282 * @see org.apache.commons.logging.Log#debug(Object) 283 */ 284 @Override 285 public final void debug(final Object message) { 286 if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) { 287 log(SimpleLog.LOG_LEVEL_DEBUG, message, null); 288 } 289 } 290 291 /** 292 * Logs a message with 293 * {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG}. 294 * 295 * @param message to log 296 * @param t log this cause 297 * @see org.apache.commons.logging.Log#debug(Object, Throwable) 298 */ 299 @Override 300 public final void debug(final Object message, final Throwable t) { 301 if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) { 302 log(SimpleLog.LOG_LEVEL_DEBUG, message, t); 303 } 304 } 305 306 /** 307 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}. 308 * 309 * @param message to log 310 * @see org.apache.commons.logging.Log#error(Object) 311 */ 312 @Override 313 public final void error(final Object message) { 314 if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) { 315 log(SimpleLog.LOG_LEVEL_ERROR, message, null); 316 } 317 } 318 319 /** 320 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}. 321 * 322 * @param message to log 323 * @param t log this cause 324 * @see org.apache.commons.logging.Log#error(Object, Throwable) 325 */ 326 @Override 327 public final void error(final Object message, final Throwable t) { 328 if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) { 329 log(SimpleLog.LOG_LEVEL_ERROR, message, t); 330 } 331 } 332 333 /** 334 * Log a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}. 335 * 336 * @param message to log 337 * @see org.apache.commons.logging.Log#fatal(Object) 338 */ 339 @Override 340 public final void fatal(final Object message) { 341 if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) { 342 log(SimpleLog.LOG_LEVEL_FATAL, message, null); 343 } 344 } 345 346 /** 347 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}. 348 * 349 * @param message to log 350 * @param t log this cause 351 * @see org.apache.commons.logging.Log#fatal(Object, Throwable) 352 */ 353 @Override 354 public final void fatal(final Object message, final Throwable t) { 355 if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) { 356 log(SimpleLog.LOG_LEVEL_FATAL, message, t); 357 } 358 } 359 360 /** 361 * Gets logging level. 362 * 363 * @return logging level. 364 */ 365 public int getLevel() { 366 return currentLogLevel; 367 } 368 369 /** 370 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}. 371 * 372 * @param message to log 373 * @see org.apache.commons.logging.Log#info(Object) 374 */ 375 @Override 376 public final void info(final Object message) { 377 if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) { 378 log(SimpleLog.LOG_LEVEL_INFO,message,null); 379 } 380 } 381 382 /** 383 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}. 384 * 385 * @param message to log 386 * @param t log this cause 387 * @see org.apache.commons.logging.Log#info(Object, Throwable) 388 */ 389 @Override 390 public final void info(final Object message, final Throwable t) { 391 if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) { 392 log(SimpleLog.LOG_LEVEL_INFO, message, t); 393 } 394 } 395 396 /** 397 * Are debug messages currently enabled? 398 * <p> 399 * This allows expensive operations such as {@code String} 400 * concatenation to be avoided when the message will be ignored by the 401 * logger. 402 */ 403 @Override 404 public final boolean isDebugEnabled() { 405 return isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG); 406 } 407 408 /** 409 * Are error messages currently enabled? 410 * <p> 411 * This allows expensive operations such as {@code String} 412 * concatenation to be avoided when the message will be ignored by the 413 * logger. 414 */ 415 @Override 416 public final boolean isErrorEnabled() { 417 return isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR); 418 } 419 420 /** 421 * Are fatal messages currently enabled? 422 * <p> 423 * This allows expensive operations such as {@code String} 424 * concatenation to be avoided when the message will be ignored by the 425 * logger. 426 */ 427 @Override 428 public final boolean isFatalEnabled() { 429 return isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL); 430 } 431 432 /** 433 * Are info messages currently enabled? 434 * <p> 435 * This allows expensive operations such as {@code String} 436 * concatenation to be avoided when the message will be ignored by the 437 * logger. 438 */ 439 @Override 440 public final boolean isInfoEnabled() { 441 return isLevelEnabled(SimpleLog.LOG_LEVEL_INFO); 442 } 443 444 /** 445 * Tests whether the given log level currently enabled. 446 * 447 * @param logLevel is this level enabled? 448 * @return whether the given log level currently enabled. 449 */ 450 protected boolean isLevelEnabled(final int logLevel) { 451 // log level are numerically ordered so can use simple numeric 452 // comparison 453 return logLevel >= currentLogLevel; 454 } 455 456 /** 457 * Are trace messages currently enabled? 458 * <p> 459 * This allows expensive operations such as {@code String} 460 * concatenation to be avoided when the message will be ignored by the 461 * logger. 462 */ 463 @Override 464 public final boolean isTraceEnabled() { 465 return isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE); 466 } 467 468 /** 469 * Are warn messages currently enabled? 470 * <p> 471 * This allows expensive operations such as {@code String} 472 * concatenation to be avoided when the message will be ignored by the 473 * logger. 474 */ 475 @Override 476 public final boolean isWarnEnabled() { 477 return isLevelEnabled(SimpleLog.LOG_LEVEL_WARN); 478 } 479 480 /** 481 * Do the actual logging. 482 * <p> 483 * This method assembles the message and then calls {@code write()} 484 * to cause it to be written. 485 * 486 * @param type One of the LOG_LEVEL_XXX constants defining the log level 487 * @param message The message itself (typically a String) 488 * @param t The exception whose stack trace should be logged 489 */ 490 protected void log(final int type, final Object message, final Throwable t) { 491 // Use a string buffer for better performance 492 final StringBuilder buf = new StringBuilder(); 493 494 // Append date-time if so configured 495 if (showDateTime) { 496 final Date now = new Date(); 497 String dateText; 498 synchronized (dateFormatter) { 499 dateText = dateFormatter.format(now); 500 } 501 buf.append(dateText); 502 buf.append(" "); 503 } 504 505 // Append a readable representation of the log level 506 switch (type) { 507 case SimpleLog.LOG_LEVEL_TRACE: 508 buf.append("[TRACE] "); 509 break; 510 case SimpleLog.LOG_LEVEL_DEBUG: 511 buf.append("[DEBUG] "); 512 break; 513 case SimpleLog.LOG_LEVEL_INFO: 514 buf.append("[INFO] "); 515 break; 516 case SimpleLog.LOG_LEVEL_WARN: 517 buf.append("[WARN] "); 518 break; 519 case SimpleLog.LOG_LEVEL_ERROR: 520 buf.append("[ERROR] "); 521 break; 522 case SimpleLog.LOG_LEVEL_FATAL: 523 buf.append("[FATAL] "); 524 break; 525 default: 526 // Or throw? 527 buf.append("[UNDEFINED] "); 528 break; 529 } 530 531 // Append the name of the log instance if so configured 532 if (showShortName) { 533 if (shortLogName == null) { 534 // Cut all but the last component of the name for both styles 535 final String slName = logName.substring(logName.lastIndexOf(".") + 1); 536 shortLogName = slName.substring(slName.lastIndexOf("/") + 1); 537 } 538 buf.append(String.valueOf(shortLogName)).append(" - "); 539 } else if (showLogName) { 540 buf.append(String.valueOf(logName)).append(" - "); 541 } 542 543 // Append the message 544 buf.append(String.valueOf(message)); 545 546 // Append stack trace if not null 547 if (t != null) { 548 buf.append(" <"); 549 buf.append(t.toString()); 550 buf.append(">"); 551 552 final StringWriter sw = new StringWriter(1024); 553 try (PrintWriter pw = new PrintWriter(sw)) { 554 t.printStackTrace(pw); 555 } 556 buf.append(sw.toString()); 557 } 558 559 // Print to the appropriate destination 560 write(buf); 561 } 562 563 /** 564 * Sets logging level. 565 * 566 * @param currentLogLevel new logging level 567 */ 568 public void setLevel(final int currentLogLevel) { 569 this.currentLogLevel = currentLogLevel; 570 } 571 572 /** 573 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}. 574 * 575 * @param message to log 576 * @see org.apache.commons.logging.Log#trace(Object) 577 */ 578 @Override 579 public final void trace(final Object message) { 580 if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) { 581 log(SimpleLog.LOG_LEVEL_TRACE, message, null); 582 } 583 } 584 585 /** 586 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}. 587 * 588 * @param message to log 589 * @param t log this cause 590 * @see org.apache.commons.logging.Log#trace(Object, Throwable) 591 */ 592 @Override 593 public final void trace(final Object message, final Throwable t) { 594 if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) { 595 log(SimpleLog.LOG_LEVEL_TRACE, message, t); 596 } 597 } 598 599 /** 600 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}. 601 * 602 * @param message to log 603 * @see org.apache.commons.logging.Log#warn(Object) 604 */ 605 @Override 606 public final void warn(final Object message) { 607 if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) { 608 log(SimpleLog.LOG_LEVEL_WARN, message, null); 609 } 610 } 611 612 /** 613 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}. 614 * 615 * @param message to log 616 * @param t log this cause 617 * @see org.apache.commons.logging.Log#warn(Object, Throwable) 618 */ 619 @Override 620 public final void warn(final Object message, final Throwable t) { 621 if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) { 622 log(SimpleLog.LOG_LEVEL_WARN, message, t); 623 } 624 } 625 626 /** 627 * Write the content of the message accumulated in the specified 628 * {@code StringBuffer} to the appropriate output destination. The 629 * default implementation writes to {@code System.err}. 630 * 631 * @param buffer A {@code StringBuffer} containing the accumulated 632 * text to be logged 633 */ 634 private void write(final Object buffer) { 635 System.err.println(buffer.toString()); 636 } 637 638 /** 639 * Write the content of the message accumulated in the specified 640 * {@code StringBuffer} to the appropriate output destination. The 641 * default implementation writes to {@code System.err}. 642 * 643 * @param buffer A {@code StringBuffer} containing the accumulated 644 * text to be logged 645 */ 646 protected void write(final StringBuffer buffer) { 647 System.err.println(buffer.toString()); 648 } 649} 650