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 
  26 package jdk.nashorn.internal.runtime.logging;
  27 
  28 import java.io.PrintWriter;
  29 import java.security.AccessControlContext;
  30 import java.security.AccessController;
  31 import java.security.Permissions;
  32 import java.security.PrivilegedAction;
  33 import java.security.ProtectionDomain;
  34 import java.util.logging.ConsoleHandler;
  35 import java.util.logging.Formatter;
  36 import java.util.logging.Handler;
  37 import java.util.logging.Level;
  38 import java.util.logging.LogRecord;
  39 import java.util.logging.Logger;
  40 import java.util.logging.LoggingPermission;
  41 import jdk.nashorn.internal.objects.Global;
  42 import jdk.nashorn.internal.runtime.Context;
  43 import jdk.nashorn.internal.runtime.ScriptFunction;
  44 import jdk.nashorn.internal.runtime.ScriptObject;
  45 import jdk.nashorn.internal.runtime.ScriptRuntime;
  46 import 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 
  52 public 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 event of given level will be logged.
 231      * @see java.util.logging.Level
 232      *
 233      * @param level logging level
 234      * @return true if event of given level will be logged.
 235      */
 236     public boolean isLoggable(final Level level) {
 237         return logger.isLoggable(level);
 238     }
 239 
 240     /**
 241      * Shorthand for outputting a log string as log level {@link java.util.logging.Level#FINEST} on this logger
 242      * @param str the string to log
 243      */
 244     public void finest(final String str) {
 245         log(Level.FINEST, str);
 246     }
 247 
 248     /**
 249      * Shorthand for outputting a log string as log level {@link java.util.logging.Level#FINEST} on this logger
 250      * @param event optional runtime event to log
 251      * @param str the string to log
 252      */
 253     public void finest(final RuntimeEvent<?> event, final String str) {
 254         finest(str);
 255         logEvent(event);
 256     }
 257 
 258     /**
 259      * Shorthand for outputting a log string as log level
 260      * {@link java.util.logging.Level#FINEST} on this logger
 261      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 262      */
 263     public void finest(final Object... objs) {
 264         log(Level.FINEST, objs);
 265     }
 266 
 267     /**
 268      * Shorthand for outputting a log string as log level
 269      * {@link java.util.logging.Level#FINEST} on this logger
 270      * @param event optional runtime event to log
 271      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 272      */
 273     public void finest(final RuntimeEvent<?> event, final Object... objs) {
 274         finest(objs);
 275         logEvent(event);
 276     }
 277 
 278     /**
 279      * Shorthand for outputting a log string as log level
 280      * {@link java.util.logging.Level#FINER} on this logger
 281      * @param str the string to log
 282      */
 283     public void finer(final String str) {
 284         log(Level.FINER, str);
 285     }
 286 
 287     /**
 288      * Shorthand for outputting a log string as log level
 289      * {@link java.util.logging.Level#FINER} on this logger
 290      * @param event optional runtime event to log
 291      * @param str the string to log
 292      */
 293     public void finer(final RuntimeEvent<?> event, final String str) {
 294         finer(str);
 295         logEvent(event);
 296     }
 297 
 298     /**
 299      * Shorthand for outputting a log string as log level
 300      * {@link java.util.logging.Level#FINER} on this logger
 301      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 302      */
 303     public void finer(final Object... objs) {
 304         log(Level.FINER, objs);
 305     }
 306 
 307     /**
 308      * Shorthand for outputting a log string as log level
 309      * {@link java.util.logging.Level#FINER} on this logger
 310      * @param event optional runtime event to log
 311      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 312      */
 313     public void finer(final RuntimeEvent<?> event, final Object... objs) {
 314         finer(objs);
 315         logEvent(event);
 316     }
 317 
 318     /**
 319      * Shorthand for outputting a log string as log level
 320      * {@link java.util.logging.Level#FINE} on this logger
 321      * @param str the string to log
 322      */
 323     public void fine(final String str) {
 324         log(Level.FINE, str);
 325     }
 326 
 327     /**
 328      * Shorthand for outputting a log string as log level
 329      * {@link java.util.logging.Level#FINE} on this logger
 330      * @param event optional runtime event to log
 331      * @param str the string to log
 332      */
 333     public void fine(final RuntimeEvent<?> event, final String str) {
 334         fine(str);
 335         logEvent(event);
 336     }
 337 
 338     /**
 339      * Shorthand for outputting a log string as log level
 340      * {@link java.util.logging.Level#FINE} on this logger
 341      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 342      */
 343     public void fine(final Object... objs) {
 344         log(Level.FINE, objs);
 345     }
 346 
 347     /**
 348      * Shorthand for outputting a log string as log level
 349      * {@link java.util.logging.Level#FINE} on this logger
 350      * @param event optional runtime event to log
 351      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 352      */
 353     public void fine(final RuntimeEvent<?> event, final Object... objs) {
 354         fine(objs);
 355         logEvent(event);
 356     }
 357 
 358     /**
 359      * Shorthand for outputting a log string as log level
 360      * {@link java.util.logging.Level#CONFIG} on this logger
 361      * @param str the string to log
 362      */
 363     public void config(final String str) {
 364         log(Level.CONFIG, str);
 365     }
 366 
 367     /**
 368      * Shorthand for outputting a log string as log level
 369      * {@link java.util.logging.Level#CONFIG} on this logger
 370      * @param event optional runtime event to log
 371      * @param str the string to log
 372      */
 373     public void config(final RuntimeEvent<?> event, final String str) {
 374         config(str);
 375         logEvent(event);
 376     }
 377 
 378     /**
 379      * Shorthand for outputting a log string as log level
 380      * {@link java.util.logging.Level#CONFIG} on this logger
 381      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 382      */
 383     public void config(final Object... objs) {
 384         log(Level.CONFIG, objs);
 385     }
 386 
 387     /**
 388      * Shorthand for outputting a log string as log level
 389      * {@link java.util.logging.Level#CONFIG} on this logger
 390      * @param event optional runtime event to log
 391      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 392      */
 393     public void config(final RuntimeEvent<?> event, final Object... objs) {
 394         config(objs);
 395         logEvent(event);
 396     }
 397 
 398     /**
 399      * Shorthand for outputting a log string as log level
 400      * {@link java.util.logging.Level#INFO} on this logger
 401      * @param str the string to log
 402      */
 403     public void info(final String str) {
 404         log(Level.INFO, str);
 405     }
 406 
 407     /**
 408      * Shorthand for outputting a log string as log level
 409      * {@link java.util.logging.Level#INFO} on this logger
 410      * @param event optional runtime event to log
 411      * @param str the string to log
 412      */
 413     public void info(final RuntimeEvent<?> event, final String str) {
 414         info(str);
 415         logEvent(event);
 416     }
 417 
 418     /**
 419      * Shorthand for outputting a log string as log level
 420      * {@link java.util.logging.Level#FINE} on this logger
 421      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 422      */
 423     public void info(final Object... objs) {
 424         log(Level.INFO, objs);
 425     }
 426 
 427     /**
 428      * Shorthand for outputting a log string as log level
 429      * {@link java.util.logging.Level#FINE} on this logger
 430      * @param event optional runtime event to log
 431      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 432      */
 433     public void info(final RuntimeEvent<?> event, final Object... objs) {
 434         info(objs);
 435         logEvent(event);
 436     }
 437 
 438     /**
 439      * Shorthand for outputting a log string as log level
 440      * {@link java.util.logging.Level#WARNING} on this logger
 441      * @param str the string to log
 442      */
 443     public void warning(final String str) {
 444         log(Level.WARNING, str);
 445     }
 446 
 447     /**
 448      * Shorthand for outputting a log string as log level
 449      * {@link java.util.logging.Level#WARNING} on this logger
 450      * @param event optional runtime event to log
 451      * @param str the string to log
 452      */
 453     public void warning(final RuntimeEvent<?> event, final String str) {
 454         warning(str);
 455         logEvent(event);
 456     }
 457 
 458     /**
 459      * Shorthand for outputting a log string as log level
 460      * {@link java.util.logging.Level#FINE} on this logger
 461      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 462      */
 463     public void warning(final Object... objs) {
 464         log(Level.WARNING, objs);
 465     }
 466 
 467     /**
 468      * Shorthand for outputting a log string as log level
 469      * {@link java.util.logging.Level#FINE} on this logger
 470      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 471      * @param event optional runtime event to log
 472      */
 473     public void warning(final RuntimeEvent<?> event, final Object... objs) {
 474         warning(objs);
 475         logEvent(event);
 476     }
 477 
 478     /**
 479      * Shorthand for outputting a log string as log level
 480      * {@link java.util.logging.Level#SEVERE} on this logger
 481      * @param str the string to log
 482      */
 483     public void severe(final String str) {
 484         log(Level.SEVERE, str);
 485     }
 486 
 487     /**
 488      * Shorthand for outputting a log string as log level
 489      * {@link java.util.logging.Level#SEVERE} on this logger
 490      * @param str the string to log
 491      * @param event optional runtime event to log
 492      */
 493     public void severe(final RuntimeEvent<?> event, final String str) {
 494         severe(str);
 495         logEvent(event);
 496     }
 497 
 498     /**
 499      * Shorthand for outputting a log string as log level
 500      * {@link java.util.logging.Level#SEVERE} on this logger
 501      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 502      */
 503     public void severe(final Object... objs) {
 504         log(Level.SEVERE, objs);
 505     }
 506 
 507     /**
 508      * Shorthand for outputting a log string as log level
 509      * {@link java.util.logging.Level#FINE} on this logger
 510      * @param event optional runtime event to log
 511      * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
 512      */
 513     public void severe(final RuntimeEvent<?> event, final Object... objs) {
 514         severe(objs);
 515         logEvent(event);
 516     }
 517 
 518     /**
 519      * Output log line on this logger at a given level of verbosity
 520      * @see java.util.logging.Level
 521      *
 522      * @param level minimum log level required for logging to take place
 523      * @param str   string to log
 524      */
 525     public void log(final Level level, final String str) {
 526         if (isEnabled && !isQuiet && logger.isLoggable(level)) {
 527             final StringBuilder sb = new StringBuilder();
 528             for (int i = 0 ; i < indent ; i++) {
 529                 sb.append(' ');
 530             }
 531             sb.append(str);
 532             logger.log(level, sb.toString());
 533         }
 534     }
 535 
 536     /**
 537      * Output log line on this logger at a given level of verbosity
 538      * @see java.util.logging.Level
 539      *
 540      * @param level minimum log level required for logging to take place
 541      * @param objs  objects for which to invoke toString and concatenate to log
 542      */
 543     public void log(final Level level, final Object... objs) {
 544         if (isEnabled && !isQuiet && logger.isLoggable(level)) {
 545             final StringBuilder sb = new StringBuilder();
 546             for (final Object obj : objs) {
 547                 sb.append(obj);
 548             }
 549             log(level, sb.toString());
 550         }
 551     }
 552 
 553     /**
 554      * Access control context for logger level and instantiation permissions
 555      * @return access control context
 556      */
 557     private static AccessControlContext createLoggerControlAccCtxt() {
 558         final Permissions perms = new Permissions();
 559         perms.add(new LoggingPermission("control", null));
 560         return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
 561     }
 562 
 563 }