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