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