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