DebugLogger.java revision 953:221a84ef44c0
1/*
2 * Copyright (c) 2010, 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
26package jdk.nashorn.internal.runtime.logging;
27
28import java.io.PrintWriter;
29import java.security.AccessControlContext;
30import java.security.AccessController;
31import java.security.Permissions;
32import java.security.PrivilegedAction;
33import java.security.ProtectionDomain;
34import java.util.logging.ConsoleHandler;
35import java.util.logging.Formatter;
36import java.util.logging.Handler;
37import java.util.logging.Level;
38import java.util.logging.LogRecord;
39import java.util.logging.Logger;
40import java.util.logging.LoggingPermission;
41import jdk.nashorn.internal.objects.Global;
42import jdk.nashorn.internal.runtime.Context;
43import jdk.nashorn.internal.runtime.ScriptFunction;
44import jdk.nashorn.internal.runtime.ScriptObject;
45import jdk.nashorn.internal.runtime.ScriptRuntime;
46import jdk.nashorn.internal.runtime.events.RuntimeEvent;
47
48/**
49 * Wrapper class for Logging system. This is how you are supposed to register a logger and use it
50 */
51
52public final class DebugLogger {
53
54    /** Disabled logger used for all loggers that need an instance, but shouldn't output anything */
55    public static final DebugLogger DISABLED_LOGGER = new DebugLogger("disabled", Level.OFF, false);
56
57    private final Logger  logger;
58    private final boolean isEnabled;
59
60    private int indent;
61
62    private static final int INDENT_SPACE = 4;
63
64    /** A quiet logger only logs {@link RuntimeEvent}s and does't output any text, regardless of level */
65    private final boolean isQuiet;
66
67    /**
68     * Constructor
69     *
70     * A logger can be paired with a property, e.g. {@code --log:codegen:info} is equivalent to {@code -Dnashorn.codegen.log}
71     *
72     * @param loggerName  name of logger - this is the unique key with which it can be identified
73     * @param loggerLevel level of the logger
74     * @param isQuiet     is this a quiet logger, i.e. enabled for things like e.g. RuntimeEvent:s, but quiet otherwise
75     */
76    public DebugLogger(final String loggerName, final Level loggerLevel, final boolean isQuiet) {
77        this.logger  = instantiateLogger(loggerName, loggerLevel);
78        this.isQuiet = isQuiet;
79        assert logger != null;
80        this.isEnabled = getLevel() != Level.OFF;
81    }
82
83    private static Logger instantiateLogger(final String name, final Level level) {
84        final Logger logger = java.util.logging.Logger.getLogger(name);
85        AccessController.doPrivileged(new PrivilegedAction<Void>() {
86            @Override
87            public Void run() {
88                for (final Handler h : logger.getHandlers()) {
89                    logger.removeHandler(h);
90                }
91
92                logger.setLevel(level);
93                logger.setUseParentHandlers(false);
94                final Handler c = new ConsoleHandler();
95
96                c.setFormatter(new Formatter() {
97                    @Override
98                    public String format(final LogRecord record) {
99                        final StringBuilder sb = new StringBuilder();
100
101                        sb.append('[')
102                           .append(record.getLoggerName())
103                           .append("] ")
104                           .append(record.getMessage())
105                           .append('\n');
106
107                        return sb.toString();
108                    }
109                });
110                logger.addHandler(c);
111                c.setLevel(level);
112                return null;
113            }
114        }, createLoggerControlAccCtxt());
115
116        return logger;
117    }
118
119    /**
120     * Do not currently support chaining this with parent logger. Logger level null
121     * means disabled
122     * @return level
123     */
124    public Level getLevel() {
125        return logger.getLevel() == null ? Level.OFF : logger.getLevel();
126    }
127
128    /**
129     * Get the output writer for the logger. Loggers always default to
130     * stderr for output as they are used mainly to output debug info
131     *
132     * Can be inherited so this should not be static.
133     *
134     * @return print writer for log output.
135     */
136    @SuppressWarnings("static-method")
137    public PrintWriter getOutputStream() {
138        return Context.getCurrentErr();
139    }
140
141    /**
142     * Add quotes around a string
143     * @param str string
144     * @return quoted string
145     */
146    public static String quote(final String str) {
147        if (str.isEmpty()) {
148            return "''";
149        }
150
151        char startQuote = '\0';
152        char endQuote   = '\0';
153        char quote      = '\0';
154
155        if (str.startsWith("\\") || str.startsWith("\"")) {
156            startQuote = str.charAt(0);
157        }
158        if (str.endsWith("\\") || str.endsWith("\"")) {
159            endQuote = str.charAt(str.length() - 1);
160        }
161
162        if (startQuote == '\0' || endQuote == '\0') {
163            quote = startQuote == '\0' ? endQuote : startQuote;
164        }
165        if (quote == '\0') {
166            quote = '\'';
167        }
168
169        return (startQuote == '\0' ? quote : startQuote) + str + (endQuote == '\0' ? quote : endQuote);
170    }
171
172    /**
173     * Check if the logger is enabled
174     * @return true if enabled
175     */
176    public boolean isEnabled() {
177        return isEnabled;
178    }
179
180    /**
181     * Check if the logger is enabled
182     * @param logger logger to check, null will return false
183     * @return true if enabled
184     */
185    public static boolean isEnabled(final DebugLogger logger) {
186        return logger != null && logger.isEnabled();
187    }
188
189    /**
190     * If you want to change the indent level of your logger, call indent with a new position.
191     * Positions start at 0 and are increased by one for a new "tab"
192     *
193     * @param pos indent position
194     */
195    public void indent(final int pos) {
196        if (isEnabled) {
197           indent += pos * INDENT_SPACE;
198        }
199    }
200
201    /**
202     * Add an indent position
203     */
204    public void indent() {
205        indent += INDENT_SPACE;
206    }
207
208    /**
209     * Unindent a position
210     */
211    public void unindent() {
212        indent -= INDENT_SPACE;
213        if (indent < 0) {
214            indent = 0;
215        }
216    }
217
218    private static void logEvent(final RuntimeEvent<?> event) {
219        if (event != null) {
220            final Global global = Context.getGlobal();
221            if (global.has("Debug")) {
222                final ScriptObject debug = (ScriptObject)global.get("Debug");
223                final ScriptFunction addRuntimeEvent = (ScriptFunction)debug.get("addRuntimeEvent");
224                ScriptRuntime.apply(addRuntimeEvent, debug, event);
225            }
226        }
227    }
228
229    /**
230     * Check if the logger is above the level of detail given
231     * @see java.util.logging.Level
232     *
233     * The higher the level, the more severe the warning
234     *
235     * @param level logging level
236     * @return true if level is above the given one
237     */
238    public boolean levelCoarserThan(final Level level) {
239        return getLevel().intValue() > level.intValue();
240    }
241
242    /**
243     * Check if the logger is above or equal to the level
244     * of detail given
245     * @see java.util.logging.Level
246     *
247     * The higher the level, the more severe the warning
248     *
249     * @param level logging level
250     * @return true if level is above the given one
251     */
252    public boolean levelCoarserThanOrEqual(final Level level) {
253        return getLevel().intValue() >= level.intValue();
254    }
255
256    /**
257     * Check if the logger is below the level of detail given
258     * @see java.util.logging.Level
259     *
260     * The higher the level, the more severe the warning
261     *
262     * @param level logging level
263     * @return true if level is above the given one
264     */
265    public boolean levelFinerThan(final Level level) {
266        return getLevel().intValue() < level.intValue();
267    }
268
269    /**
270     * Check if the logger is below or equal to the level
271     * of detail given
272     * @see java.util.logging.Level
273     *
274     * The higher the level, the more severe the warning
275     *
276     * @param level logging level
277     * @return true if level is above the given one
278     */
279    public boolean levelFinerThanOrEqual(final Level level) {
280        return getLevel().intValue() <= level.intValue();
281    }
282
283    /**
284     * Shorthand for outputting a log string as log level {@link java.util.logging.Level#FINEST} on this logger
285     * @param str the string to log
286     */
287    public void finest(final String str) {
288        log(Level.FINEST, str);
289    }
290
291    /**
292     * Shorthand for outputting a log string as log level {@link java.util.logging.Level#FINEST} on this logger
293     * @param event optional runtime event to log
294     * @param str the string to log
295     */
296    public void finest(final RuntimeEvent<?> event, final String str) {
297        finest(str);
298        logEvent(event);
299    }
300
301    /**
302     * Shorthand for outputting a log string as log level
303     * {@link java.util.logging.Level#FINEST} on this logger
304     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
305     */
306    public void finest(final Object... objs) {
307        log(Level.FINEST, objs);
308    }
309
310    /**
311     * Shorthand for outputting a log string as log level
312     * {@link java.util.logging.Level#FINEST} on this logger
313     * @param event optional runtime event to log
314     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
315     */
316    public void finest(final RuntimeEvent<?> event, final Object... objs) {
317        finest(objs);
318        logEvent(event);
319    }
320
321    /**
322     * Shorthand for outputting a log string as log level
323     * {@link java.util.logging.Level#FINER} on this logger
324     * @param str the string to log
325     */
326    public void finer(final String str) {
327        log(Level.FINER, str);
328    }
329
330    /**
331     * Shorthand for outputting a log string as log level
332     * {@link java.util.logging.Level#FINER} on this logger
333     * @param event optional runtime event to log
334     * @param str the string to log
335     */
336    public void finer(final RuntimeEvent<?> event, final String str) {
337        finer(str);
338        logEvent(event);
339    }
340
341    /**
342     * Shorthand for outputting a log string as log level
343     * {@link java.util.logging.Level#FINER} on this logger
344     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
345     */
346    public void finer(final Object... objs) {
347        log(Level.FINER, objs);
348    }
349
350    /**
351     * Shorthand for outputting a log string as log level
352     * {@link java.util.logging.Level#FINER} on this logger
353     * @param event optional runtime event to log
354     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
355     */
356    public void finer(final RuntimeEvent<?> event, final Object... objs) {
357        finer(objs);
358        logEvent(event);
359    }
360
361    /**
362     * Shorthand for outputting a log string as log level
363     * {@link java.util.logging.Level#FINE} on this logger
364     * @param str the string to log
365     */
366    public void fine(final String str) {
367        log(Level.FINE, str);
368    }
369
370    /**
371     * Shorthand for outputting a log string as log level
372     * {@link java.util.logging.Level#FINE} on this logger
373     * @param event optional runtime event to log
374     * @param str the string to log
375     */
376    public void fine(final RuntimeEvent<?> event, final String str) {
377        fine(str);
378        logEvent(event);
379    }
380
381    /**
382     * Shorthand for outputting a log string as log level
383     * {@link java.util.logging.Level#FINE} on this logger
384     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
385     */
386    public void fine(final Object... objs) {
387        log(Level.FINE, objs);
388    }
389
390    /**
391     * Shorthand for outputting a log string as log level
392     * {@link java.util.logging.Level#FINE} on this logger
393     * @param event optional runtime event to log
394     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
395     */
396    public void fine(final RuntimeEvent<?> event, final Object... objs) {
397        fine(objs);
398        logEvent(event);
399    }
400
401    /**
402     * Shorthand for outputting a log string as log level
403     * {@link java.util.logging.Level#CONFIG} on this logger
404     * @param str the string to log
405     */
406    public void config(final String str) {
407        log(Level.CONFIG, str);
408    }
409
410    /**
411     * Shorthand for outputting a log string as log level
412     * {@link java.util.logging.Level#CONFIG} on this logger
413     * @param event optional runtime event to log
414     * @param str the string to log
415     */
416    public void config(final RuntimeEvent<?> event, final String str) {
417        config(str);
418        logEvent(event);
419    }
420
421    /**
422     * Shorthand for outputting a log string as log level
423     * {@link java.util.logging.Level#CONFIG} on this logger
424     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
425     */
426    public void config(final Object... objs) {
427        log(Level.CONFIG, objs);
428    }
429
430    /**
431     * Shorthand for outputting a log string as log level
432     * {@link java.util.logging.Level#CONFIG} on this logger
433     * @param event optional runtime event to log
434     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
435     */
436    public void config(final RuntimeEvent<?> event, final Object... objs) {
437        config(objs);
438        logEvent(event);
439    }
440
441    /**
442     * Shorthand for outputting a log string as log level
443     * {@link java.util.logging.Level#INFO} on this logger
444     * @param str the string to log
445     */
446    public void info(final String str) {
447        log(Level.INFO, str);
448    }
449
450    /**
451     * Shorthand for outputting a log string as log level
452     * {@link java.util.logging.Level#INFO} on this logger
453     * @param event optional runtime event to log
454     * @param str the string to log
455     */
456    public void info(final RuntimeEvent<?> event, final String str) {
457        info(str);
458        logEvent(event);
459    }
460
461    /**
462     * Shorthand for outputting a log string as log level
463     * {@link java.util.logging.Level#FINE} on this logger
464     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
465     */
466    public void info(final Object... objs) {
467        log(Level.INFO, objs);
468    }
469
470    /**
471     * Shorthand for outputting a log string as log level
472     * {@link java.util.logging.Level#FINE} on this logger
473     * @param event optional runtime event to log
474     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
475     */
476    public void info(final RuntimeEvent<?> event, final Object... objs) {
477        info(objs);
478        logEvent(event);
479    }
480
481    /**
482     * Shorthand for outputting a log string as log level
483     * {@link java.util.logging.Level#WARNING} on this logger
484     * @param str the string to log
485     */
486    public void warning(final String str) {
487        log(Level.WARNING, str);
488    }
489
490    /**
491     * Shorthand for outputting a log string as log level
492     * {@link java.util.logging.Level#WARNING} on this logger
493     * @param event optional runtime event to log
494     * @param str the string to log
495     */
496    public void warning(final RuntimeEvent<?> event, final String str) {
497        warning(str);
498        logEvent(event);
499    }
500
501    /**
502     * Shorthand for outputting a log string as log level
503     * {@link java.util.logging.Level#FINE} on this logger
504     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
505     */
506    public void warning(final Object... objs) {
507        log(Level.WARNING, objs);
508    }
509
510    /**
511     * Shorthand for outputting a log string as log level
512     * {@link java.util.logging.Level#FINE} on this logger
513     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
514     * @param event optional runtime event to log
515     */
516    public void warning(final RuntimeEvent<?> event, final Object... objs) {
517        warning(objs);
518        logEvent(event);
519    }
520
521    /**
522     * Shorthand for outputting a log string as log level
523     * {@link java.util.logging.Level#SEVERE} on this logger
524     * @param str the string to log
525     */
526    public void severe(final String str) {
527        log(Level.SEVERE, str);
528    }
529
530    /**
531     * Shorthand for outputting a log string as log level
532     * {@link java.util.logging.Level#SEVERE} on this logger
533     * @param str the string to log
534     * @param event optional runtime event to log
535     */
536    public void severe(final RuntimeEvent<?> event, final String str) {
537        severe(str);
538        logEvent(event);
539    }
540
541    /**
542     * Shorthand for outputting a log string as log level
543     * {@link java.util.logging.Level#SEVERE} on this logger
544     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
545     */
546    public void severe(final Object... objs) {
547        log(Level.SEVERE, objs);
548    }
549
550    /**
551     * Shorthand for outputting a log string as log level
552     * {@link java.util.logging.Level#FINE} on this logger
553     * @param event optional runtime event to log
554     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
555     */
556    public void severe(final RuntimeEvent<?> event, final Object... objs) {
557        severe(objs);
558        logEvent(event);
559    }
560
561    /**
562     * Output log line on this logger at a given level of verbosity
563     * @see java.util.logging.Level
564     *
565     * @param level minimum log level required for logging to take place
566     * @param str   string to log
567     */
568    public void log(final Level level, final String str) {
569        if (isEnabled && !isQuiet) {
570            final StringBuilder sb = new StringBuilder();
571            for (int i = 0 ; i < indent ; i++) {
572                sb.append(' ');
573            }
574            sb.append(str);
575            logger.log(level, sb.toString());
576        }
577    }
578
579    /**
580     * Output log line on this logger at a given level of verbosity
581     * @see java.util.logging.Level
582     *
583     * @param level minimum log level required for logging to take place
584     * @param objs  objects for which to invoke toString and concatenate to log
585     */
586    public void log(final Level level, final Object... objs) {
587        if (isEnabled && !isQuiet) {
588            final StringBuilder sb = new StringBuilder();
589            for (final Object obj : objs) {
590                sb.append(obj);
591            }
592            log(level, sb.toString());
593        }
594    }
595
596    /**
597     * Access control context for logger level and instantiation permissions
598     * @return access control context
599     */
600    private static AccessControlContext createLoggerControlAccCtxt() {
601        final Permissions perms = new Permissions();
602        perms.add(new LoggingPermission("control", null));
603        return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
604    }
605
606}
607