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