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