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