1 /*
   2  * Copyright (c) 2009, 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<String,WeakReference<PlatformLogger>>();
 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<PlatformLogger>(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         private static final String lineSeparator = AccessController.doPrivileged(
 320             new PrivilegedAction<String>() {
 321                 public String run() {
 322                     return System.getProperty("line.separator");
 323                 }
 324             });
 325 
 326         final String name;
 327         volatile int levelValue;
 328         volatile int effectiveLevel = 0; // current effective level value
 329 
 330         LoggerProxy(String name) {
 331             this(name, defaultLevel);
 332         }
 333 
 334         LoggerProxy(String name, int level) {
 335             this.name = name;
 336             this.levelValue = level == 0 ? defaultLevel : level;
 337         }
 338 
 339         boolean isEnabled() {
 340             return levelValue != OFF;
 341         }
 342 
 343         int getLevel() {
 344             return effectiveLevel;
 345         }
 346 
 347         void setLevel(int newLevel) {
 348             levelValue = newLevel;
 349             effectiveLevel = newLevel;
 350         }
 351 
 352         void doLog(int level, String msg) {
 353             if (level < levelValue || levelValue == OFF) {
 354                 return;
 355             }
 356             defaultStream.println(format(level, msg, null));
 357         }
 358 
 359         void doLog(int level, String msg, Throwable thrown) {
 360             if (level < levelValue || levelValue == OFF) {
 361                 return;
 362             }
 363             defaultStream.println(format(level, msg, thrown));
 364         }
 365 
 366         void doLog(int level, String msg, Object... params) {
 367             if (level < levelValue || levelValue == OFF) {
 368                 return;
 369             }
 370             String newMsg = formatMessage(msg, params);
 371             defaultStream.println(format(level, newMsg, null));
 372         }
 373 
 374         public boolean isLoggable(int level) {
 375             if (level < levelValue || levelValue == OFF) {
 376                 return false;
 377             }
 378             return true;
 379         }
 380 
 381         private static final String format = "{0,date} {0,time}";
 382 
 383         private Object args[] = new Object[1];
 384         private MessageFormat formatter;
 385         private Date dat;
 386 
 387         // Copied from java.util.logging.Formatter.formatMessage
 388         private String formatMessage(String format, Object... parameters) {
 389             // Do the formatting.
 390             try {
 391                 if (parameters == null || parameters.length == 0) {
 392                     // No parameters.  Just return format string.
 393                     return format;
 394                 }
 395                 // Is it a java.text style format?
 396                 // Ideally we could match with
 397                 // Pattern.compile("\\{\\d").matcher(format).find())
 398                 // However the cost is 14% higher, so we cheaply check for
 399                 // 1 of the first 4 parameters
 400                 if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 ||
 401                             format.indexOf("{2") >=0|| format.indexOf("{3") >=0) {
 402                     return java.text.MessageFormat.format(format, parameters);
 403                 }
 404                 return format;
 405             } catch (Exception ex) {
 406                 // Formatting failed: use format string.
 407                 return format;
 408             }
 409         }
 410 
 411         private synchronized String format(int level, String msg, Throwable thrown) {
 412             StringBuffer sb = new StringBuffer();
 413             // Minimize memory allocations here.
 414             if (dat == null) {
 415                 dat = new Date();
 416                 formatter = new MessageFormat(format);
 417             }
 418             dat.setTime(System.currentTimeMillis());
 419             args[0] = dat;
 420             StringBuffer text = new StringBuffer();
 421             formatter.format(args, text, null);
 422             sb.append(text);
 423             sb.append(" ");
 424             sb.append(getCallerInfo());
 425             sb.append(lineSeparator);
 426             sb.append(PlatformLogger.getLevelName(level));
 427             sb.append(": ");
 428             sb.append(msg);
 429             if (thrown != null) {
 430                 try {
 431                     StringWriter sw = new StringWriter();
 432                     PrintWriter pw = new PrintWriter(sw);
 433                     thrown.printStackTrace(pw);
 434                     pw.close();
 435                     sb.append(sw.toString());
 436                 } catch (Exception ex) {
 437                     throw new AssertionError(ex);
 438                 }
 439             }
 440 
 441             return sb.toString();
 442         }
 443 
 444         // Returns the caller's class and method's name; best effort
 445         // if cannot infer, return the logger's name.
 446         private String getCallerInfo() {
 447             String sourceClassName = null;
 448             String sourceMethodName = null;
 449 
 450             JavaLangAccess access = SharedSecrets.getJavaLangAccess();
 451             Throwable throwable = new Throwable();
 452             int depth = access.getStackTraceDepth(throwable);
 453 
 454             String logClassName = "sun.util.logging.PlatformLogger";
 455             boolean lookingForLogger = true;
 456             for (int ix = 0; ix < depth; ix++) {
 457                 // Calling getStackTraceElement directly prevents the VM
 458                 // from paying the cost of building the entire stack frame.
 459                 StackTraceElement frame =
 460                     access.getStackTraceElement(throwable, ix);
 461                 String cname = frame.getClassName();
 462                 if (lookingForLogger) {
 463                     // Skip all frames until we have found the first logger frame.
 464                     if (cname.equals(logClassName)) {
 465                         lookingForLogger = false;
 466                     }
 467                 } else {
 468                     if (!cname.equals(logClassName)) {
 469                         // We've found the relevant frame.
 470                         sourceClassName = cname;
 471                         sourceMethodName = frame.getMethodName();
 472                         break;
 473                     }
 474                 }
 475             }
 476 
 477             if (sourceClassName != null) {
 478                 return sourceClassName + " " + sourceMethodName;
 479             } else {
 480                 return name;
 481             }
 482         }
 483     }
 484 
 485     /**
 486      * JavaLogger forwards all the calls to its corresponding
 487      * java.util.logging.Logger object.
 488      */
 489     static class JavaLogger extends LoggerProxy {
 490         private static final Map<Integer, Object> levelObjects =
 491             new HashMap<Integer, Object>();
 492 
 493         static {
 494             if (LoggingSupport.isAvailable()) {
 495                 // initialize the map to Level objects
 496                 getLevelObjects();
 497             }
 498         }
 499 
 500         private static void getLevelObjects() {
 501             // get all java.util.logging.Level objects
 502             int[] levelArray = new int[] {OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL};
 503             for (int l : levelArray) {
 504                 Object level = LoggingSupport.parseLevel(getLevelName(l));
 505                 levelObjects.put(l, level);
 506             }
 507         }
 508 
 509         private final Object javaLogger;
 510         JavaLogger(String name) {
 511             this(name, 0);
 512         }
 513 
 514         JavaLogger(String name, int level) {
 515             super(name, level);
 516             this.javaLogger = LoggingSupport.getLogger(name);
 517             if (level != 0) {
 518                 // level has been updated and so set the Logger's level
 519                 LoggingSupport.setLevel(javaLogger, levelObjects.get(level));
 520             }
 521         }
 522 
 523        /**
 524         * Let Logger.log() do the filtering since if the level of a
 525         * platform logger is altered directly from
 526         * java.util.logging.Logger.setLevel(), the levelValue will
 527         * not be updated.
 528         */
 529         void doLog(int level, String msg) {
 530             LoggingSupport.log(javaLogger, levelObjects.get(level), msg);
 531         }
 532 
 533         void doLog(int level, String msg, Throwable t) {
 534             LoggingSupport.log(javaLogger, levelObjects.get(level), msg, t);
 535         }
 536 
 537         void doLog(int level, String msg, Object... params) {
 538             LoggingSupport.log(javaLogger, levelObjects.get(level), msg, params);
 539         }
 540 
 541         boolean isEnabled() {
 542             Object level = LoggingSupport.getLevel(javaLogger);
 543             return level == null || level.equals(levelObjects.get(OFF)) == false;
 544         }
 545 
 546         int getLevel() {
 547             Object level = LoggingSupport.getLevel(javaLogger);
 548             if (level != null) {
 549                 for (Map.Entry<Integer, Object> l : levelObjects.entrySet()) {
 550                     if (level == l.getValue()) {
 551                         return l.getKey();
 552                     }
 553                 }
 554             }
 555             return 0;
 556         }
 557 
 558         void setLevel(int newLevel) {
 559             levelValue = newLevel;
 560             LoggingSupport.setLevel(javaLogger, levelObjects.get(newLevel));
 561         }
 562 
 563         public boolean isLoggable(int level) {
 564             return LoggingSupport.isLoggable(javaLogger, levelObjects.get(level));
 565         }
 566     }
 567 
 568     private static String getLevelName(int level) {
 569         switch (level) {
 570             case OFF     : return "OFF";
 571             case SEVERE  : return "SEVERE";
 572             case WARNING : return "WARNING";
 573             case INFO    : return "INFO";
 574             case CONFIG  : return "CONFIG";
 575             case FINE    : return "FINE";
 576             case FINER   : return "FINER";
 577             case FINEST  : return "FINEST";
 578             case ALL     : return "ALL";
 579             default      : return "UNKNOWN";
 580         }
 581     }
 582 
 583 }