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