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