1 /* 2 * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 27 package sun.util.logging; 28 29 import java.lang.ref.WeakReference; 30 import java.io.PrintStream; 31 import java.io.PrintWriter; 32 import java.io.StringWriter; 33 import java.security.AccessController; 34 import java.security.PrivilegedAction; 35 import java.time.ZoneId; 36 import java.time.ZonedDateTime; 37 import java.util.Arrays; 38 import java.util.HashMap; 39 import java.util.Map; 40 import java.util.Optional; 41 import java.util.function.Predicate; 42 43 /** 44 * Platform logger provides an API for the JRE components to log 45 * messages. This enables the runtime components to eliminate the 46 * static dependency of the logging facility and also defers the 47 * java.util.logging initialization until it is enabled. 48 * In addition, the PlatformLogger API can be used if the logging 49 * module does not exist. 50 * 51 * If the logging facility is not enabled, the platform loggers 52 * will output log messages per the default logging configuration 53 * (see below). In this implementation, it does not log the 54 * the stack frame information issuing the log message. 55 * 56 * When the logging facility is enabled (at startup or runtime), 57 * the java.util.logging.Logger will be created for each platform 58 * logger and all log messages will be forwarded to the Logger 59 * to handle. 60 * 61 * Logging facility is "enabled" when one of the following 62 * conditions is met: 63 * 1) a system property "java.util.logging.config.class" or 64 * "java.util.logging.config.file" is set 65 * 2) java.util.logging.LogManager or java.util.logging.Logger 66 * is referenced that will trigger the logging initialization. 67 * 68 * Default logging configuration: 69 * global logging level = INFO 70 * handlers = java.util.logging.ConsoleHandler 71 * java.util.logging.ConsoleHandler.level = INFO 72 * java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter 73 * 74 * Limitation: 75 * {@code <JAVA_HOME>/conf/logging.properties} is the system-wide logging 76 * configuration defined in the specification and read in the 77 * default case to configure any java.util.logging.Logger instances. 78 * Platform loggers will not detect if {@code <JAVA_HOME>/conf/logging.properties} 79 * is modified. In other words, unless the java.util.logging API 80 * is used at runtime or the logging system properties is set, 81 * the platform loggers will use the default setting described above. 82 * The platform loggers are designed for JDK developers use and 83 * this limitation can be workaround with setting 84 * -Djava.util.logging.config.file system property. 85 * 86 * @since 1.7 87 */ 88 public class PlatformLogger { 89 90 // The integer values must match that of {@code java.util.logging.Level} 91 // objects. 92 private static final int OFF = Integer.MAX_VALUE; 93 private static final int SEVERE = 1000; 94 private static final int WARNING = 900; 95 private static final int INFO = 800; 96 private static final int CONFIG = 700; 97 private static final int FINE = 500; 98 private static final int FINER = 400; 99 private static final int FINEST = 300; 100 private static final int ALL = Integer.MIN_VALUE; 101 102 /** 103 * PlatformLogger logging levels. 104 */ 105 public static enum Level { 106 // The name and value must match that of {@code java.util.logging.Level}s. 107 // Declare in ascending order of the given value for binary search. 108 ALL, 109 FINEST, 110 FINER, 111 FINE, 112 CONFIG, 113 INFO, 114 WARNING, 115 SEVERE, 116 OFF; 117 118 /** 119 * Associated java.util.logging.Level lazily initialized in 120 * JavaLoggerProxy's static initializer only once 121 * when java.util.logging is available and enabled. 122 * Only accessed by JavaLoggerProxy. 123 */ 124 /* java.util.logging.Level */ Object javaLevel; 125 126 // ascending order for binary search matching the list of enum constants 127 private static final int[] LEVEL_VALUES = new int[] { 128 PlatformLogger.ALL, PlatformLogger.FINEST, PlatformLogger.FINER, 129 PlatformLogger.FINE, PlatformLogger.CONFIG, PlatformLogger.INFO, 130 PlatformLogger.WARNING, PlatformLogger.SEVERE, PlatformLogger.OFF 131 }; 132 133 public int intValue() { 134 return LEVEL_VALUES[this.ordinal()]; 135 } 136 137 static Level valueOf(int level) { 138 switch (level) { 139 // ordering per the highest occurrences in the jdk source 140 // finest, fine, finer, info first 141 case PlatformLogger.FINEST : return Level.FINEST; 142 case PlatformLogger.FINE : return Level.FINE; 143 case PlatformLogger.FINER : return Level.FINER; 144 case PlatformLogger.INFO : return Level.INFO; 145 case PlatformLogger.WARNING : return Level.WARNING; 146 case PlatformLogger.CONFIG : return Level.CONFIG; 147 case PlatformLogger.SEVERE : return Level.SEVERE; 148 case PlatformLogger.OFF : return Level.OFF; 149 case PlatformLogger.ALL : return Level.ALL; 150 } 151 // return the nearest Level value >= the given level, 152 // for level > SEVERE, return SEVERE and exclude OFF 153 int i = Arrays.binarySearch(LEVEL_VALUES, 0, LEVEL_VALUES.length-2, level); 154 return values()[i >= 0 ? i : (-i-1)]; 155 } 156 } 157 158 private static final Level DEFAULT_LEVEL = Level.INFO; 159 private static boolean loggingEnabled; 160 static { 161 loggingEnabled = AccessController.doPrivileged( 162 new PrivilegedAction<>() { 163 public Boolean run() { 164 String cname = System.getProperty("java.util.logging.config.class"); 165 String fname = System.getProperty("java.util.logging.config.file"); 166 return (cname != null || fname != null); 167 } 168 }); 169 170 // force loading of all JavaLoggerProxy (sub)classes to make JIT de-optimizations 171 // less probable. Don't initialize JavaLoggerProxy class since 172 // java.util.logging may not be enabled. 173 try { 174 Class.forName("sun.util.logging.PlatformLogger$DefaultLoggerProxy", 175 false, 176 PlatformLogger.class.getClassLoader()); 177 Class.forName("sun.util.logging.PlatformLogger$JavaLoggerProxy", 178 false, // do not invoke class initializer 179 PlatformLogger.class.getClassLoader()); 180 } catch (ClassNotFoundException ex) { 181 throw new InternalError(ex); 182 } 183 } 184 185 // Table of known loggers. Maps names to PlatformLoggers. 186 private static Map<String,WeakReference<PlatformLogger>> loggers = 187 new HashMap<>(); 188 189 /** 190 * Returns a PlatformLogger of a given name. 191 */ 192 public static synchronized PlatformLogger getLogger(String name) { 193 PlatformLogger log = null; 194 WeakReference<PlatformLogger> ref = loggers.get(name); 195 if (ref != null) { 196 log = ref.get(); 197 } 198 if (log == null) { 199 log = new PlatformLogger(name); 200 loggers.put(name, new WeakReference<>(log)); 201 } 202 return log; 203 } 204 205 /** 206 * Initialize java.util.logging.Logger objects for all platform loggers. 207 * This method is called from LogManager.readPrimordialConfiguration(). 208 */ 209 public static synchronized void redirectPlatformLoggers() { 210 if (loggingEnabled || !LoggingSupport.isAvailable()) return; 211 212 loggingEnabled = true; 213 for (Map.Entry<String, WeakReference<PlatformLogger>> entry : loggers.entrySet()) { 214 WeakReference<PlatformLogger> ref = entry.getValue(); 215 PlatformLogger plog = ref.get(); 216 if (plog != null) { 217 plog.redirectToJavaLoggerProxy(); 218 } 219 } 220 } 221 222 /** 223 * Creates a new JavaLoggerProxy and redirects the platform logger to it 224 */ 225 private void redirectToJavaLoggerProxy() { 226 DefaultLoggerProxy lp = DefaultLoggerProxy.class.cast(this.loggerProxy); 227 JavaLoggerProxy jlp = new JavaLoggerProxy(lp.name, lp.level); 228 // the order of assignments is important 229 this.javaLoggerProxy = jlp; // isLoggable checks javaLoggerProxy if set 230 this.loggerProxy = jlp; 231 } 232 233 // DefaultLoggerProxy may be replaced with a JavaLoggerProxy object 234 // when the java.util.logging facility is enabled 235 private volatile LoggerProxy loggerProxy; 236 // javaLoggerProxy is only set when the java.util.logging facility is enabled 237 private volatile JavaLoggerProxy javaLoggerProxy; 238 private PlatformLogger(String name) { 239 if (loggingEnabled) { 240 this.loggerProxy = this.javaLoggerProxy = new JavaLoggerProxy(name); 241 } else { 242 this.loggerProxy = new DefaultLoggerProxy(name); 243 } 244 } 245 246 /** 247 * A convenience method to test if the logger is turned off. 248 * (i.e. its level is OFF). 249 */ 250 public boolean isEnabled() { 251 return loggerProxy.isEnabled(); 252 } 253 254 /** 255 * Gets the name for this platform logger. 256 */ 257 public String getName() { 258 return loggerProxy.name; 259 } 260 261 /** 262 * Returns true if a message of the given level would actually 263 * be logged by this logger. 264 */ 265 public boolean isLoggable(Level level) { 266 if (level == null) { 267 throw new NullPointerException(); 268 } 269 // performance-sensitive method: use two monomorphic call-sites 270 JavaLoggerProxy jlp = javaLoggerProxy; 271 return jlp != null ? jlp.isLoggable(level) : loggerProxy.isLoggable(level); 272 } 273 274 /** 275 * Get the log level that has been specified for this PlatformLogger. 276 * The result may be null, which means that this logger's 277 * effective level will be inherited from its parent. 278 * 279 * @return this PlatformLogger's level 280 */ 281 public Level level() { 282 return loggerProxy.getLevel(); 283 } 284 285 /** 286 * Set the log level specifying which message levels will be 287 * logged by this logger. Message levels lower than this 288 * value will be discarded. The level value {@link #OFF} 289 * can be used to turn off logging. 290 * <p> 291 * If the new level is null, it means that this node should 292 * inherit its level from its nearest ancestor with a specific 293 * (non-null) level value. 294 * 295 * @param newLevel the new value for the log level (may be null) 296 */ 297 public void setLevel(Level newLevel) { 298 loggerProxy.setLevel(newLevel); 299 } 300 301 /** 302 * Logs a SEVERE message. 303 */ 304 public void severe(String msg) { 305 loggerProxy.doLog(Level.SEVERE, msg); 306 } 307 308 public void severe(String msg, Throwable t) { 309 loggerProxy.doLog(Level.SEVERE, msg, t); 310 } 311 312 public void severe(String msg, Object... params) { 313 loggerProxy.doLog(Level.SEVERE, msg, params); 314 } 315 316 /** 317 * Logs a WARNING message. 318 */ 319 public void warning(String msg) { 320 loggerProxy.doLog(Level.WARNING, msg); 321 } 322 323 public void warning(String msg, Throwable t) { 324 loggerProxy.doLog(Level.WARNING, msg, t); 325 } 326 327 public void warning(String msg, Object... params) { 328 loggerProxy.doLog(Level.WARNING, msg, params); 329 } 330 331 /** 332 * Logs an INFO message. 333 */ 334 public void info(String msg) { 335 loggerProxy.doLog(Level.INFO, msg); 336 } 337 338 public void info(String msg, Throwable t) { 339 loggerProxy.doLog(Level.INFO, msg, t); 340 } 341 342 public void info(String msg, Object... params) { 343 loggerProxy.doLog(Level.INFO, msg, params); 344 } 345 346 /** 347 * Logs a CONFIG message. 348 */ 349 public void config(String msg) { 350 loggerProxy.doLog(Level.CONFIG, msg); 351 } 352 353 public void config(String msg, Throwable t) { 354 loggerProxy.doLog(Level.CONFIG, msg, t); 355 } 356 357 public void config(String msg, Object... params) { 358 loggerProxy.doLog(Level.CONFIG, msg, params); 359 } 360 361 /** 362 * Logs a FINE message. 363 */ 364 public void fine(String msg) { 365 loggerProxy.doLog(Level.FINE, msg); 366 } 367 368 public void fine(String msg, Throwable t) { 369 loggerProxy.doLog(Level.FINE, msg, t); 370 } 371 372 public void fine(String msg, Object... params) { 373 loggerProxy.doLog(Level.FINE, msg, params); 374 } 375 376 /** 377 * Logs a FINER message. 378 */ 379 public void finer(String msg) { 380 loggerProxy.doLog(Level.FINER, msg); 381 } 382 383 public void finer(String msg, Throwable t) { 384 loggerProxy.doLog(Level.FINER, msg, t); 385 } 386 387 public void finer(String msg, Object... params) { 388 loggerProxy.doLog(Level.FINER, msg, params); 389 } 390 391 /** 392 * Logs a FINEST message. 393 */ 394 public void finest(String msg) { 395 loggerProxy.doLog(Level.FINEST, msg); 396 } 397 398 public void finest(String msg, Throwable t) { 399 loggerProxy.doLog(Level.FINEST, msg, t); 400 } 401 402 public void finest(String msg, Object... params) { 403 loggerProxy.doLog(Level.FINEST, msg, params); 404 } 405 406 /** 407 * Abstract base class for logging support, defining the API and common field. 408 */ 409 private abstract static class LoggerProxy { 410 final String name; 411 412 protected LoggerProxy(String name) { 413 this.name = name; 414 } 415 416 abstract boolean isEnabled(); 417 418 abstract Level getLevel(); 419 abstract void setLevel(Level newLevel); 420 421 abstract void doLog(Level level, String msg); 422 abstract void doLog(Level level, String msg, Throwable thrown); 423 abstract void doLog(Level level, String msg, Object... params); 424 425 abstract boolean isLoggable(Level level); 426 } 427 428 429 private static final class DefaultLoggerProxy extends LoggerProxy { 430 /** 431 * Default platform logging support - output messages to System.err - 432 * equivalent to ConsoleHandler with SimpleFormatter. 433 */ 434 private static PrintStream outputStream() { 435 return System.err; 436 } 437 438 volatile Level effectiveLevel; // effective level (never null) 439 volatile Level level; // current level set for this node (may be null) 440 441 DefaultLoggerProxy(String name) { 442 super(name); 443 this.effectiveLevel = deriveEffectiveLevel(null); 444 this.level = null; 445 } 446 447 boolean isEnabled() { 448 return effectiveLevel != Level.OFF; 449 } 450 451 Level getLevel() { 452 return level; 453 } 454 455 void setLevel(Level newLevel) { 456 Level oldLevel = level; 457 if (oldLevel != newLevel) { 458 level = newLevel; 459 effectiveLevel = deriveEffectiveLevel(newLevel); 460 } 461 } 462 463 void doLog(Level level, String msg) { 464 if (isLoggable(level)) { 465 outputStream().print(format(level, msg, null)); 466 } 467 } 468 469 void doLog(Level level, String msg, Throwable thrown) { 470 if (isLoggable(level)) { 471 outputStream().print(format(level, msg, thrown)); 472 } 473 } 474 475 void doLog(Level level, String msg, Object... params) { 476 if (isLoggable(level)) { 477 String newMsg = formatMessage(msg, params); 478 outputStream().print(format(level, newMsg, null)); 479 } 480 } 481 482 boolean isLoggable(Level level) { 483 Level effectiveLevel = this.effectiveLevel; 484 return level.intValue() >= effectiveLevel.intValue() && effectiveLevel != Level.OFF; 485 } 486 487 // derive effective level (could do inheritance search like j.u.l.Logger) 488 private Level deriveEffectiveLevel(Level level) { 489 return level == null ? DEFAULT_LEVEL : level; 490 } 491 492 // Copied from java.util.logging.Formatter.formatMessage 493 private String formatMessage(String format, Object... parameters) { 494 // Do the formatting. 495 try { 496 if (parameters == null || parameters.length == 0) { 497 // No parameters. Just return format string. 498 return format; 499 } 500 // Is it a java.text style format? 501 // Ideally we could match with 502 // Pattern.compile("\\{\\d").matcher(format).find()) 503 // However the cost is 14% higher, so we cheaply check for 504 // 1 of the first 4 parameters 505 if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 || 506 format.indexOf("{2") >=0|| format.indexOf("{3") >=0) { 507 return java.text.MessageFormat.format(format, parameters); 508 } 509 return format; 510 } catch (Exception ex) { 511 // Formatting failed: use format string. 512 return format; 513 } 514 } 515 516 private static final String formatString = 517 LoggingSupport.getSimpleFormat(false); // don't check logging.properties 518 private final ZoneId zoneId = ZoneId.systemDefault(); 519 private synchronized String format(Level level, String msg, Throwable thrown) { 520 ZonedDateTime zdt = ZonedDateTime.now(zoneId); 521 String throwable = ""; 522 if (thrown != null) { 523 StringWriter sw = new StringWriter(); 524 PrintWriter pw = new PrintWriter(sw); 525 pw.println(); 526 thrown.printStackTrace(pw); 527 pw.close(); 528 throwable = sw.toString(); 529 } 530 531 return String.format(formatString, 532 zdt, 533 getCallerInfo(), 534 name, 535 level.name(), 536 msg, 537 throwable); 538 } 539 540 /* 541 * CallerFinder is a stateful predicate. 542 */ 543 static final class CallerFinder implements Predicate<StackWalker.StackFrame> { 544 static final StackWalker WALKER = StackWalker.create(); 545 546 /** 547 * Returns StackTraceElement of the caller's frame. 548 * @return StackTraceElement of the caller's frame. 549 */ 550 Optional<StackWalker.StackFrame> get() { 551 return WALKER.walk(s -> s.filter(this).findFirst()); 552 } 553 554 private boolean lookingForLogger = true; 555 556 /** 557 * Returns true if we have found the caller's frame, false if the frame 558 * must be skipped. 559 * 560 * @param t The frame info. 561 * @return true if we have found the caller's frame, false if the frame 562 * must be skipped. 563 */ 564 @Override 565 public boolean test(StackWalker.StackFrame t) { 566 final String cname = t.getClassName(); 567 // We should skip all frames until we have found the logger, 568 // because these frames could be frames introduced by e.g. custom 569 // sub classes of Handler. 570 if (lookingForLogger) { 571 lookingForLogger = !cname.equals("sun.util.logging.PlatformLogger"); 572 return false; 573 } 574 // Once the logger is found - we should skip all frames that 575 // point to packages which contain artifacts that could be 576 // inserted between the logger and its caller. These could be 577 // logger wrappers from j.u.l or sun.util.logging (e.g. the 578 // PlatformLogger or artifacts between the PlatformLogger and 579 // the actual logger) or frames inserted by use of reflection 580 // and/or doPrivileged calls. 581 return !cname.startsWith("java.util.logging.") 582 && !cname.startsWith("sun.util.logging.") 583 && !cname.startsWith("java.security.AccessController"); 584 } 585 } 586 587 private String getCallerInfo() { 588 Optional<StackWalker.StackFrame> frame = new CallerFinder().get(); 589 return frame.map(f -> f.getClassName() + " " + f.getMethodName()).orElse(name); 590 } 591 } 592 593 /** 594 * JavaLoggerProxy forwards all the calls to its corresponding 595 * java.util.logging.Logger object. 596 */ 597 private static final class JavaLoggerProxy extends LoggerProxy { 598 // initialize javaLevel fields for mapping from Level enum -> j.u.l.Level object 599 static { 600 for (Level level : Level.values()) { 601 level.javaLevel = LoggingSupport.parseLevel(level.name()); 602 } 603 } 604 605 private final /* java.util.logging.Logger */ Object javaLogger; 606 607 JavaLoggerProxy(String name) { 608 this(name, null); 609 } 610 611 JavaLoggerProxy(String name, Level level) { 612 super(name); 613 this.javaLogger = LoggingSupport.getLogger(name); 614 if (level != null) { 615 // level has been updated and so set the Logger's level 616 LoggingSupport.setLevel(javaLogger, level.javaLevel); 617 } 618 } 619 620 void doLog(Level level, String msg) { 621 LoggingSupport.log(javaLogger, level.javaLevel, msg); 622 } 623 624 void doLog(Level level, String msg, Throwable t) { 625 LoggingSupport.log(javaLogger, level.javaLevel, msg, t); 626 } 627 628 void doLog(Level level, String msg, Object... params) { 629 if (!isLoggable(level)) { 630 return; 631 } 632 // only pass String objects to the j.u.l.Logger which may 633 // be created by untrusted code 634 int len = (params != null) ? params.length : 0; 635 Object[] sparams = new String[len]; 636 for (int i = 0; i < len; i++) { 637 sparams [i] = String.valueOf(params[i]); 638 } 639 LoggingSupport.log(javaLogger, level.javaLevel, msg, sparams); 640 } 641 642 boolean isEnabled() { 643 return LoggingSupport.isLoggable(javaLogger, Level.OFF.javaLevel); 644 } 645 646 /** 647 * Returns the PlatformLogger.Level mapped from j.u.l.Level 648 * set in the logger. If the j.u.l.Logger is set to a custom Level, 649 * this method will return the nearest Level. 650 */ 651 Level getLevel() { 652 Object javaLevel = LoggingSupport.getLevel(javaLogger); 653 if (javaLevel == null) return null; 654 655 try { 656 return Level.valueOf(LoggingSupport.getLevelName(javaLevel)); 657 } catch (IllegalArgumentException e) { 658 return Level.valueOf(LoggingSupport.getLevelValue(javaLevel)); 659 } 660 } 661 662 void setLevel(Level level) { 663 LoggingSupport.setLevel(javaLogger, level == null ? null : level.javaLevel); 664 } 665 666 boolean isLoggable(Level level) { 667 return LoggingSupport.isLoggable(javaLogger, level.javaLevel); 668 } 669 } 670 }