Home | History | Annotate | Download | only in logging
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
      4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      5  *
      6  * This code is free software; you can redistribute it and/or modify it
      7  * under the terms of the GNU General Public License version 2 only, as
      8  * published by the Free Software Foundation.  Oracle designates this
      9  * particular file as subject to the "Classpath" exception as provided
     10  * by Oracle in the LICENSE file that accompanied this code.
     11  *
     12  * This code is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     15  * version 2 for more details (a copy is included in the LICENSE file that
     16  * accompanied this code).
     17  *
     18  * You should have received a copy of the GNU General Public License version
     19  * 2 along with this work; if not, write to the Free Software Foundation,
     20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     21  *
     22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     23  * or visit www.oracle.com if you need additional information or have any
     24  * questions.
     25  */
     26 
     27 
     28 package sun.util.logging;
     29 
     30 import java.lang.ref.WeakReference;
     31 import java.io.PrintStream;
     32 import java.io.PrintWriter;
     33 import java.io.StringWriter;
     34 import java.security.AccessController;
     35 import java.security.PrivilegedAction;
     36 import java.util.Arrays;
     37 import java.util.Date;
     38 import java.util.HashMap;
     39 import java.util.Map;
     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 
     88     // The integer values must match that of {@code java.util.logging.Level}
     89     // objects.
     90     private static final int OFF     = Integer.MAX_VALUE;
     91     private static final int SEVERE  = 1000;
     92     private static final int WARNING = 900;
     93     private static final int INFO    = 800;
     94     private static final int CONFIG  = 700;
     95     private static final int FINE    = 500;
     96     private static final int FINER   = 400;
     97     private static final int FINEST  = 300;
     98     private static final int ALL     = Integer.MIN_VALUE;
     99 
    100     /**
    101      * PlatformLogger logging levels.
    102      */
    103     public static enum Level {
    104         // The name and value must match that of {@code java.util.logging.Level}s.
    105         // Declare in ascending order of the given value for binary search.
    106         ALL,
    107         FINEST,
    108         FINER,
    109         FINE,
    110         CONFIG,
    111         INFO,
    112         WARNING,
    113         SEVERE,
    114         OFF;
    115 
    116         /**
    117          * Associated java.util.logging.Level lazily initialized in
    118          * JavaLoggerProxy's static initializer only once
    119          * when java.util.logging is available and enabled.
    120          * Only accessed by JavaLoggerProxy.
    121          */
    122         /* java.util.logging.Level */ Object javaLevel;
    123 
    124         // ascending order for binary search matching the list of enum constants
    125         private static final int[] LEVEL_VALUES = new int[] {
    126             PlatformLogger.ALL, PlatformLogger.FINEST, PlatformLogger.FINER,
    127             PlatformLogger.FINE, PlatformLogger.CONFIG, PlatformLogger.INFO,
    128             PlatformLogger.WARNING, PlatformLogger.SEVERE, PlatformLogger.OFF
    129         };
    130 
    131         public int intValue() {
    132             return LEVEL_VALUES[this.ordinal()];
    133         }
    134 
    135         static Level valueOf(int level) {
    136             switch (level) {
    137                 // ordering per the highest occurrences in the jdk source
    138                 // finest, fine, finer, info first
    139                 case PlatformLogger.FINEST  : return Level.FINEST;
    140                 case PlatformLogger.FINE    : return Level.FINE;
    141                 case PlatformLogger.FINER   : return Level.FINER;
    142                 case PlatformLogger.INFO    : return Level.INFO;
    143                 case PlatformLogger.WARNING : return Level.WARNING;
    144                 case PlatformLogger.CONFIG  : return Level.CONFIG;
    145                 case PlatformLogger.SEVERE  : return Level.SEVERE;
    146                 case PlatformLogger.OFF     : return Level.OFF;
    147                 case PlatformLogger.ALL     : return Level.ALL;
    148             }
    149             // return the nearest Level value >= the given level,
    150             // for level > SEVERE, return SEVERE and exclude OFF
    151             int i = Arrays.binarySearch(LEVEL_VALUES, 0, LEVEL_VALUES.length-2, level);
    152             return values()[i >= 0 ? i : (-i-1)];
    153         }
    154     }
    155 
    156     private static final Level DEFAULT_LEVEL = Level.INFO;
    157     private static boolean loggingEnabled;
    158     static {
    159         loggingEnabled = AccessController.doPrivileged(
    160             new PrivilegedAction<Boolean>() {
    161                 public Boolean run() {
    162                     String cname = System.getProperty("java.util.logging.config.class");
    163                     String fname = System.getProperty("java.util.logging.config.file");
    164                     return (cname != null || fname != null);
    165                 }
    166             });
    167 
    168         // Android-removed: JavaLoggerProxy is unneeded, complicates obfuscated releases.
    169         /*
    170         // force loading of all JavaLoggerProxy (sub)classes to make JIT de-optimizations
    171         // less probable.  Don't initialize JavaLoggerProxy class since
    172         // java.util.logging may not be enabled.
    173         try {
    174             Class.forName("sun.util.logging.PlatformLogger$DefaultLoggerProxy",
    175                           false,
    176                           PlatformLogger.class.getClassLoader());
    177             Class.forName("sun.util.logging.PlatformLogger$JavaLoggerProxy",
    178                           false,   // do not invoke class initializer
    179                           PlatformLogger.class.getClassLoader());
    180         } catch (ClassNotFoundException ex) {
    181             throw new InternalError(ex);
    182         }
    183         */
    184     }
    185 
    186     // Table of known loggers.  Maps names to PlatformLoggers.
    187     private static Map<String,WeakReference<PlatformLogger>> loggers =
    188         new HashMap<>();
    189 
    190     /**
    191      * Returns a PlatformLogger of a given name.
    192      */
    193     public static synchronized PlatformLogger getLogger(String name) {
    194         PlatformLogger log = null;
    195         WeakReference<PlatformLogger> ref = loggers.get(name);
    196         if (ref != null) {
    197             log = ref.get();
    198         }
    199         if (log == null) {
    200             log = new PlatformLogger(name);
    201             loggers.put(name, new WeakReference<>(log));
    202         }
    203         return log;
    204     }
    205 
    206     /**
    207      * Initialize java.util.logging.Logger objects for all platform loggers.
    208      * This method is called from LogManager.readPrimordialConfiguration().
    209      */
    210     public static synchronized void redirectPlatformLoggers() {
    211         if (loggingEnabled || !LoggingSupport.isAvailable()) return;
    212 
    213         loggingEnabled = true;
    214         for (Map.Entry<String, WeakReference<PlatformLogger>> entry : loggers.entrySet()) {
    215             WeakReference<PlatformLogger> ref = entry.getValue();
    216             PlatformLogger plog = ref.get();
    217             if (plog != null) {
    218                 plog.redirectToJavaLoggerProxy();
    219             }
    220         }
    221     }
    222 
    223     /**
    224      * Creates a new JavaLoggerProxy and redirects the platform logger to it
    225      */
    226     private void redirectToJavaLoggerProxy() {
    227         DefaultLoggerProxy lp = DefaultLoggerProxy.class.cast(this.loggerProxy);
    228         JavaLoggerProxy jlp = new JavaLoggerProxy(lp.name, lp.level);
    229         // the order of assignments is important
    230         this.javaLoggerProxy = jlp;   // isLoggable checks javaLoggerProxy if set
    231         this.loggerProxy = jlp;
    232     }
    233 
    234     // DefaultLoggerProxy may be replaced with a JavaLoggerProxy object
    235     // when the java.util.logging facility is enabled
    236     private volatile LoggerProxy loggerProxy;
    237     // javaLoggerProxy is only set when the java.util.logging facility is enabled
    238     private volatile JavaLoggerProxy javaLoggerProxy;
    239     private PlatformLogger(String name) {
    240         if (loggingEnabled) {
    241             this.loggerProxy = this.javaLoggerProxy = new JavaLoggerProxy(name);
    242         } else {
    243             this.loggerProxy = new DefaultLoggerProxy(name);
    244         }
    245     }
    246 
    247     /**
    248      * A convenience method to test if the logger is turned off.
    249      * (i.e. its level is OFF).
    250      */
    251     public boolean isEnabled() {
    252         return loggerProxy.isEnabled();
    253     }
    254 
    255     /**
    256      * Gets the name for this platform logger.
    257      */
    258     public String getName() {
    259         return loggerProxy.name;
    260     }
    261 
    262     /**
    263      * Returns true if a message of the given level would actually
    264      * be logged by this logger.
    265      */
    266     public boolean isLoggable(Level level) {
    267         if (level == null) {
    268             throw new NullPointerException();
    269         }
    270         // performance-sensitive method: use two monomorphic call-sites
    271         JavaLoggerProxy jlp = javaLoggerProxy;
    272         return jlp != null ? jlp.isLoggable(level) : loggerProxy.isLoggable(level);
    273     }
    274 
    275     /**
    276      * Get the log level that has been specified for this PlatformLogger.
    277      * The result may be null, which means that this logger's
    278      * effective level will be inherited from its parent.
    279      *
    280      * @return  this PlatformLogger's level
    281      */
    282     public Level level() {
    283         return loggerProxy.getLevel();
    284     }
    285 
    286     /**
    287      * Set the log level specifying which message levels will be
    288      * logged by this logger.  Message levels lower than this
    289      * value will be discarded.  The level value {@link #OFF}
    290      * can be used to turn off logging.
    291      * <p>
    292      * If the new level is null, it means that this node should
    293      * inherit its level from its nearest ancestor with a specific
    294      * (non-null) level value.
    295      *
    296      * @param newLevel the new value for the log level (may be null)
    297      */
    298     public void setLevel(Level newLevel) {
    299         loggerProxy.setLevel(newLevel);
    300     }
    301 
    302     /**
    303      * Logs a SEVERE message.
    304      */
    305     public void severe(String msg) {
    306         loggerProxy.doLog(Level.SEVERE, msg);
    307     }
    308 
    309     public void severe(String msg, Throwable t) {
    310         loggerProxy.doLog(Level.SEVERE, msg, t);
    311     }
    312 
    313     public void severe(String msg, Object... params) {
    314         loggerProxy.doLog(Level.SEVERE, msg, params);
    315     }
    316 
    317     /**
    318      * Logs a WARNING message.
    319      */
    320     public void warning(String msg) {
    321         loggerProxy.doLog(Level.WARNING, msg);
    322     }
    323 
    324     public void warning(String msg, Throwable t) {
    325         loggerProxy.doLog(Level.WARNING, msg, t);
    326     }
    327 
    328     public void warning(String msg, Object... params) {
    329         loggerProxy.doLog(Level.WARNING, msg, params);
    330     }
    331 
    332     /**
    333      * Logs an INFO message.
    334      */
    335     public void info(String msg) {
    336         loggerProxy.doLog(Level.INFO, msg);
    337     }
    338 
    339     public void info(String msg, Throwable t) {
    340         loggerProxy.doLog(Level.INFO, msg, t);
    341     }
    342 
    343     public void info(String msg, Object... params) {
    344         loggerProxy.doLog(Level.INFO, msg, params);
    345     }
    346 
    347     /**
    348      * Logs a CONFIG message.
    349      */
    350     public void config(String msg) {
    351         loggerProxy.doLog(Level.CONFIG, msg);
    352     }
    353 
    354     public void config(String msg, Throwable t) {
    355         loggerProxy.doLog(Level.CONFIG, msg, t);
    356     }
    357 
    358     public void config(String msg, Object... params) {
    359         loggerProxy.doLog(Level.CONFIG, msg, params);
    360     }
    361 
    362     /**
    363      * Logs a FINE message.
    364      */
    365     public void fine(String msg) {
    366         loggerProxy.doLog(Level.FINE, msg);
    367     }
    368 
    369     public void fine(String msg, Throwable t) {
    370         loggerProxy.doLog(Level.FINE, msg, t);
    371     }
    372 
    373     public void fine(String msg, Object... params) {
    374         loggerProxy.doLog(Level.FINE, msg, params);
    375     }
    376 
    377     /**
    378      * Logs a FINER message.
    379      */
    380     public void finer(String msg) {
    381         loggerProxy.doLog(Level.FINER, msg);
    382     }
    383 
    384     public void finer(String msg, Throwable t) {
    385         loggerProxy.doLog(Level.FINER, msg, t);
    386     }
    387 
    388     public void finer(String msg, Object... params) {
    389         loggerProxy.doLog(Level.FINER, msg, params);
    390     }
    391 
    392     /**
    393      * Logs a FINEST message.
    394      */
    395     public void finest(String msg) {
    396         loggerProxy.doLog(Level.FINEST, msg);
    397     }
    398 
    399     public void finest(String msg, Throwable t) {
    400         loggerProxy.doLog(Level.FINEST, msg, t);
    401     }
    402 
    403     public void finest(String msg, Object... params) {
    404         loggerProxy.doLog(Level.FINEST, msg, params);
    405     }
    406 
    407     /**
    408      * Abstract base class for logging support, defining the API and common field.
    409      */
    410     private static abstract class LoggerProxy {
    411         final String name;
    412 
    413         protected LoggerProxy(String name) {
    414             this.name = name;
    415         }
    416 
    417         abstract boolean isEnabled();
    418 
    419         abstract Level getLevel();
    420         abstract void setLevel(Level newLevel);
    421 
    422         abstract void doLog(Level level, String msg);
    423         abstract void doLog(Level level, String msg, Throwable thrown);
    424         abstract void doLog(Level level, String msg, Object... params);
    425 
    426         abstract boolean isLoggable(Level level);
    427     }
    428 
    429 
    430     private static final class DefaultLoggerProxy extends LoggerProxy {
    431         /**
    432          * Default platform logging support - output messages to System.err -
    433          * equivalent to ConsoleHandler with SimpleFormatter.
    434          */
    435         private static PrintStream outputStream() {
    436             return System.err;
    437         }
    438 
    439         volatile Level effectiveLevel; // effective level (never null)
    440         volatile Level level;          // current level set for this node (may be null)
    441 
    442         DefaultLoggerProxy(String name) {
    443             super(name);
    444             this.effectiveLevel = deriveEffectiveLevel(null);
    445             this.level = null;
    446         }
    447 
    448         boolean isEnabled() {
    449             return effectiveLevel != Level.OFF;
    450         }
    451 
    452         Level getLevel() {
    453             return level;
    454         }
    455 
    456         void setLevel(Level newLevel) {
    457             Level oldLevel = level;
    458             if (oldLevel != newLevel) {
    459                 level = newLevel;
    460                 effectiveLevel = deriveEffectiveLevel(newLevel);
    461             }
    462         }
    463 
    464         void doLog(Level level, String msg) {
    465             if (isLoggable(level)) {
    466                 outputStream().print(format(level, msg, null));
    467             }
    468         }
    469 
    470         void doLog(Level level, String msg, Throwable thrown) {
    471             if (isLoggable(level)) {
    472                 outputStream().print(format(level, msg, thrown));
    473             }
    474         }
    475 
    476         void doLog(Level level, String msg, Object... params) {
    477             if (isLoggable(level)) {
    478                 String newMsg = formatMessage(msg, params);
    479                 outputStream().print(format(level, newMsg, null));
    480             }
    481         }
    482 
    483         boolean isLoggable(Level level) {
    484             Level effectiveLevel = this.effectiveLevel;
    485             return level.intValue() >= effectiveLevel.intValue() && effectiveLevel != Level.OFF;
    486         }
    487 
    488         // derive effective level (could do inheritance search like j.u.l.Logger)
    489         private Level deriveEffectiveLevel(Level level) {
    490             return level == null ? DEFAULT_LEVEL : level;
    491         }
    492 
    493         // Copied from java.util.logging.Formatter.formatMessage
    494         private String formatMessage(String format, Object... parameters) {
    495             // Do the formatting.
    496             try {
    497                 if (parameters == null || parameters.length == 0) {
    498                     // No parameters.  Just return format string.
    499                     return format;
    500                 }
    501                 // Is it a java.text style format?
    502                 // Ideally we could match with
    503                 // Pattern.compile("\\{\\d").matcher(format).find())
    504                 // However the cost is 14% higher, so we cheaply check for
    505                 // 1 of the first 4 parameters
    506                 if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 ||
    507                             format.indexOf("{2") >=0|| format.indexOf("{3") >=0) {
    508                     return java.text.MessageFormat.format(format, parameters);
    509                 }
    510                 return format;
    511             } catch (Exception ex) {
    512                 // Formatting failed: use format string.
    513                 return format;
    514             }
    515         }
    516 
    517         private static final String formatString =
    518             LoggingSupport.getSimpleFormat(false); // don't check logging.properties
    519 
    520         // minimize memory allocation
    521         private Date date = new Date();
    522         private synchronized String format(Level level, String msg, Throwable thrown) {
    523             date.setTime(System.currentTimeMillis());
    524             String throwable = "";
    525             if (thrown != null) {
    526                 StringWriter sw = new StringWriter();
    527                 PrintWriter pw = new PrintWriter(sw);
    528                 pw.println();
    529                 thrown.printStackTrace(pw);
    530                 pw.close();
    531                 throwable = sw.toString();
    532             }
    533 
    534             return String.format(formatString,
    535                                  date,
    536                                  getCallerInfo(),
    537                                  name,
    538                                  level.name(),
    539                                  msg,
    540                                  throwable);
    541         }
    542 
    543         // Returns the caller's class and method's name; best effort
    544         // if cannot infer, return the logger's name.
    545         private String getCallerInfo() {
    546             String sourceClassName = null;
    547             String sourceMethodName = null;
    548 
    549             Throwable throwable = new Throwable();
    550 
    551             String logClassName = "sun.util.logging.PlatformLogger";
    552             boolean lookingForLogger = true;
    553             // Android-changed: Different way to access throwable.getStackTrace()
    554             // OpenJDK's faster way via SharedSecrets.getJavaLangAccess()
    555             // is not available on Android.
    556             /*
    557             for (int ix = 0; ix < depth; ix++) {
    558                 // Calling getStackTraceElement directly prevents the VM
    559                 // from paying the cost of building the entire stack frame.
    560                 StackTraceElement frame =
    561                     access.getStackTraceElement(throwable, ix);
    562             */
    563             for (StackTraceElement frame : throwable.getStackTrace()) {
    564                 String cname = frame.getClassName();
    565                 if (lookingForLogger) {
    566                     // Skip all frames until we have found the first logger frame.
    567                     if (cname.equals(logClassName)) {
    568                         lookingForLogger = false;
    569                     }
    570                 } else {
    571                     if (!cname.equals(logClassName)) {
    572                         // We've found the relevant frame.
    573                         sourceClassName = cname;
    574                         sourceMethodName = frame.getMethodName();
    575                         break;
    576                     }
    577                 }
    578             }
    579 
    580             if (sourceClassName != null) {
    581                 return sourceClassName + " " + sourceMethodName;
    582             } else {
    583                 return name;
    584             }
    585         }
    586     }
    587 
    588     /**
    589      * JavaLoggerProxy forwards all the calls to its corresponding
    590      * java.util.logging.Logger object.
    591      */
    592     private static final class JavaLoggerProxy extends LoggerProxy {
    593         // initialize javaLevel fields for mapping from Level enum -> j.u.l.Level object
    594         static {
    595             for (Level level : Level.values()) {
    596                 level.javaLevel = LoggingSupport.parseLevel(level.name());
    597             }
    598         }
    599 
    600         private final /* java.util.logging.Logger */ Object javaLogger;
    601 
    602         JavaLoggerProxy(String name) {
    603             this(name, null);
    604         }
    605 
    606         JavaLoggerProxy(String name, Level level) {
    607             super(name);
    608             this.javaLogger = LoggingSupport.getLogger(name);
    609             if (level != null) {
    610                 // level has been updated and so set the Logger's level
    611                 LoggingSupport.setLevel(javaLogger, level.javaLevel);
    612             }
    613         }
    614 
    615         void doLog(Level level, String msg) {
    616             LoggingSupport.log(javaLogger, level.javaLevel, msg);
    617         }
    618 
    619         void doLog(Level level, String msg, Throwable t) {
    620             LoggingSupport.log(javaLogger, level.javaLevel, msg, t);
    621         }
    622 
    623         void doLog(Level level, String msg, Object... params) {
    624             if (!isLoggable(level)) {
    625                 return;
    626             }
    627             // only pass String objects to the j.u.l.Logger which may
    628             // be created by untrusted code
    629             int len = (params != null) ? params.length : 0;
    630             Object[] sparams = new String[len];
    631             for (int i = 0; i < len; i++) {
    632                 sparams [i] = String.valueOf(params[i]);
    633             }
    634             LoggingSupport.log(javaLogger, level.javaLevel, msg, sparams);
    635         }
    636 
    637         boolean isEnabled() {
    638             return LoggingSupport.isLoggable(javaLogger, Level.OFF.javaLevel);
    639         }
    640 
    641         /**
    642          * Returns the PlatformLogger.Level mapped from j.u.l.Level
    643          * set in the logger.  If the j.u.l.Logger is set to a custom Level,
    644          * this method will return the nearest Level.
    645          */
    646         Level getLevel() {
    647             Object javaLevel = LoggingSupport.getLevel(javaLogger);
    648             if (javaLevel == null) return null;
    649 
    650             try {
    651                 return Level.valueOf(LoggingSupport.getLevelName(javaLevel));
    652             } catch (IllegalArgumentException e) {
    653                 return Level.valueOf(LoggingSupport.getLevelValue(javaLevel));
    654             }
    655         }
    656 
    657         void setLevel(Level level) {
    658             LoggingSupport.setLevel(javaLogger, level == null ? null : level.javaLevel);
    659         }
    660 
    661         boolean isLoggable(Level level) {
    662             return LoggingSupport.isLoggable(javaLogger, level.javaLevel);
    663         }
    664     }
    665 }
    666