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