1 /*
   2  * Copyright (c) 2015, 2016, 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.internal.logger;
  27 
  28 import java.io.PrintStream;
  29 import java.io.PrintWriter;
  30 import java.io.StringWriter;
  31 import java.lang.StackWalker.StackFrame;
  32 import java.security.AccessController;
  33 import java.security.PrivilegedAction;
  34 import java.time.ZonedDateTime;
  35 import java.util.Optional;
  36 import java.util.ResourceBundle;
  37 import java.util.function.Function;
  38 import java.lang.System.Logger;
  39 import java.util.function.Predicate;
  40 import java.util.function.Supplier;
  41 import sun.security.action.GetPropertyAction;
  42 import sun.util.logging.PlatformLogger;
  43 import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration;
  44 
  45 /**
  46  * A simple console logger to emulate the behavior of JUL loggers when
  47  * in the default configuration. SimpleConsoleLoggers are also used when
  48  * JUL is not present and no DefaultLoggerFinder is installed.
  49  */
  50 public class SimpleConsoleLogger extends LoggerConfiguration
  51     implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge {
  52 
  53     static final Level DEFAULT_LEVEL = getDefaultLevel();
  54     static final PlatformLogger.Level DEFAULT_PLATFORM_LEVEL =
  55             PlatformLogger.toPlatformLevel(DEFAULT_LEVEL);
  56 
  57     static Level getDefaultLevel() {
  58         String levelName = GetPropertyAction
  59                 .getProperty("jdk.system.logger.level", "INFO");
  60         try {
  61             return Level.valueOf(levelName);
  62         } catch (IllegalArgumentException iae) {
  63             return Level.INFO;
  64         }
  65     }
  66 
  67     final String name;
  68     volatile PlatformLogger.Level  level;
  69     final boolean usePlatformLevel;
  70     SimpleConsoleLogger(String name, boolean usePlatformLevel) {
  71         this.name = name;
  72         this.usePlatformLevel = usePlatformLevel;
  73     }
  74 
  75     String getSimpleFormatString() {
  76         return Formatting.SIMPLE_CONSOLE_LOGGER_FORMAT;
  77     }
  78 
  79     PlatformLogger.Level defaultPlatformLevel() {
  80         return DEFAULT_PLATFORM_LEVEL;
  81     }
  82 
  83     @Override
  84     public final String getName() {
  85         return name;
  86     }
  87 
  88     private Enum<?> logLevel(PlatformLogger.Level level) {
  89         return usePlatformLevel ? level : level.systemLevel();
  90     }
  91 
  92     private Enum<?> logLevel(Level level) {
  93         return usePlatformLevel ? PlatformLogger.toPlatformLevel(level) : level;
  94     }
  95 
  96     // ---------------------------------------------------
  97     //                 From Logger
  98     // ---------------------------------------------------
  99 
 100     @Override
 101     public final boolean isLoggable(Level level) {
 102         return isLoggable(PlatformLogger.toPlatformLevel(level));
 103     }
 104 
 105     @Override
 106     public final void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
 107         if (isLoggable(level)) {
 108             if (bundle != null) {
 109                 key = bundle.getString(key);
 110             }
 111             publish(getCallerInfo(), logLevel(level), key, thrown);
 112         }
 113     }
 114 
 115     @Override
 116     public final void log(Level level, ResourceBundle bundle, String format, Object... params) {
 117         if (isLoggable(level)) {
 118             if (bundle != null) {
 119                 format = bundle.getString(format);
 120             }
 121             publish(getCallerInfo(), logLevel(level), format, params);
 122         }
 123     }
 124 
 125     // ---------------------------------------------------
 126     //             From PlatformLogger.Bridge
 127     // ---------------------------------------------------
 128 
 129     @Override
 130     public final boolean isLoggable(PlatformLogger.Level level) {
 131         final PlatformLogger.Level effectiveLevel =  effectiveLevel();
 132         return level != PlatformLogger.Level.OFF
 133                 && level.ordinal() >= effectiveLevel.ordinal();
 134     }
 135 
 136     @Override
 137     public final boolean isEnabled() {
 138         return level != PlatformLogger.Level.OFF;
 139     }
 140 
 141     @Override
 142     public final void log(PlatformLogger.Level level, String msg) {
 143         if (isLoggable(level)) {
 144             publish(getCallerInfo(), logLevel(level), msg);
 145         }
 146     }
 147 
 148     @Override
 149     public final void log(PlatformLogger.Level level, String msg, Throwable thrown) {
 150         if (isLoggable(level)) {
 151             publish(getCallerInfo(), logLevel(level), msg, thrown);
 152         }
 153     }
 154 
 155     @Override
 156     public final void log(PlatformLogger.Level level, String msg, Object... params) {
 157         if (isLoggable(level)) {
 158             publish(getCallerInfo(), logLevel(level), msg, params);
 159         }
 160     }
 161 
 162     private PlatformLogger.Level effectiveLevel() {
 163         if (level == null) return defaultPlatformLevel();
 164         return level;
 165     }
 166 
 167     @Override
 168     public final PlatformLogger.Level getPlatformLevel() {
 169         return level;
 170     }
 171 
 172     @Override
 173     public final void setPlatformLevel(PlatformLogger.Level newLevel) {
 174         level = newLevel;
 175     }
 176 
 177     @Override
 178     public final LoggerConfiguration getLoggerConfiguration() {
 179         return this;
 180     }
 181 
 182     /**
 183      * Default platform logging support - output messages to System.err -
 184      * equivalent to ConsoleHandler with SimpleFormatter.
 185      */
 186     static PrintStream outputStream() {
 187         return System.err;
 188     }
 189 
 190     // Returns the caller's class and method's name; best effort
 191     // if cannot infer, return the logger's name.
 192     private String getCallerInfo() {
 193         Optional<StackWalker.StackFrame> frame = new CallerFinder().get();
 194         if (frame.isPresent()) {
 195             return frame.get().getClassName() + " " + frame.get().getMethodName();
 196         } else {
 197             return name;
 198         }
 199     }
 200 
 201     /*
 202      * CallerFinder is a stateful predicate.
 203      */
 204     static final class CallerFinder implements Predicate<StackWalker.StackFrame> {
 205         private static final StackWalker WALKER;
 206         static {
 207             final PrivilegedAction<StackWalker> action = new PrivilegedAction<>() {
 208                 @Override
 209                 public StackWalker run() {
 210                     return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
 211                 }
 212             };
 213             WALKER = AccessController.doPrivileged(action);
 214         }
 215 
 216         /**
 217          * Returns StackFrame of the caller's frame.
 218          * @return StackFrame of the caller's frame.
 219          */
 220         Optional<StackWalker.StackFrame> get() {
 221             return WALKER.walk((s) -> s.filter(this).findFirst());
 222         }
 223 
 224         private boolean lookingForLogger = true;
 225         /**
 226          * Returns true if we have found the caller's frame, false if the frame
 227          * must be skipped.
 228          *
 229          * @param t The frame info.
 230          * @return true if we have found the caller's frame, false if the frame
 231          * must be skipped.
 232          */
 233         @Override
 234         public boolean test(StackWalker.StackFrame t) {
 235             final String cname = t.getClassName();
 236             // We should skip all frames until we have found the logger,
 237             // because these frames could be frames introduced by e.g. custom
 238             // sub classes of Handler.
 239             if (lookingForLogger) {
 240                 // Skip all frames until we have found the first logger frame.
 241                 lookingForLogger = !isLoggerImplFrame(cname);
 242                 return false;
 243             }
 244             // Continue walking until we've found the relevant calling frame.
 245             // Skips logging/logger infrastructure.
 246             return !Formatting.isFilteredFrame(t);
 247         }
 248 
 249         private boolean isLoggerImplFrame(String cname) {
 250             return (cname.equals("sun.util.logging.PlatformLogger") ||
 251                     cname.equals("jdk.internal.logger.SimpleConsoleLogger"));
 252         }
 253     }
 254 
 255     private String getCallerInfo(String sourceClassName, String sourceMethodName) {
 256         if (sourceClassName == null) return name;
 257         if (sourceMethodName == null) return sourceClassName;
 258         return sourceClassName + " " + sourceMethodName;
 259     }
 260 
 261     private String toString(Throwable thrown) {
 262         String throwable = "";
 263         if (thrown != null) {
 264             StringWriter sw = new StringWriter();
 265             PrintWriter pw = new PrintWriter(sw);
 266             pw.println();
 267             thrown.printStackTrace(pw);
 268             pw.close();
 269             throwable = sw.toString();
 270         }
 271         return throwable;
 272     }
 273 
 274     private synchronized String format(Enum<?> level,
 275             String msg, Throwable thrown, String callerInfo) {
 276 
 277         ZonedDateTime zdt = ZonedDateTime.now();
 278         String throwable = toString(thrown);
 279 
 280         return String.format(getSimpleFormatString(),
 281                          zdt,
 282                          callerInfo,
 283                          name,
 284                          level.name(),
 285                          msg,
 286                          throwable);
 287     }
 288 
 289     // publish accepts both PlatformLogger Levels and LoggerFinder Levels.
 290     private void publish(String callerInfo, Enum<?> level, String msg) {
 291         outputStream().print(format(level, msg, null, callerInfo));
 292     }
 293     // publish accepts both PlatformLogger Levels and LoggerFinder Levels.
 294     private void publish(String callerInfo, Enum<?> level, String msg, Throwable thrown) {
 295         outputStream().print(format(level, msg, thrown, callerInfo));
 296     }
 297     // publish accepts both PlatformLogger Levels and LoggerFinder Levels.
 298     private void publish(String callerInfo, Enum<?> level, String msg, Object... params) {
 299         msg = params == null || params.length == 0 ? msg
 300                 : Formatting.formatMessage(msg, params);
 301         outputStream().print(format(level, msg, null, callerInfo));
 302     }
 303 
 304     public static SimpleConsoleLogger makeSimpleLogger(String name) {
 305         return new SimpleConsoleLogger(name, false);
 306     }
 307 
 308     @Override
 309     public final void log(PlatformLogger.Level level, Supplier<String> msgSupplier) {
 310         if (isLoggable(level)) {
 311             publish(getCallerInfo(), logLevel(level), msgSupplier.get());
 312         }
 313     }
 314 
 315     @Override
 316     public final void log(PlatformLogger.Level level, Throwable thrown,
 317             Supplier<String> msgSupplier) {
 318         if (isLoggable(level)) {
 319             publish(getCallerInfo(), logLevel(level), msgSupplier.get(), thrown);
 320         }
 321     }
 322 
 323     @Override
 324     public final void logp(PlatformLogger.Level level, String sourceClass,
 325             String sourceMethod, String msg) {
 326         if (isLoggable(level)) {
 327             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg);
 328         }
 329     }
 330 
 331     @Override
 332     public final void logp(PlatformLogger.Level level, String sourceClass,
 333             String sourceMethod, Supplier<String> msgSupplier) {
 334         if (isLoggable(level)) {
 335             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get());
 336         }
 337     }
 338 
 339     @Override
 340     public final void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod,
 341             String msg, Object... params) {
 342         if (isLoggable(level)) {
 343             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params);
 344         }
 345     }
 346 
 347     @Override
 348     public final void logp(PlatformLogger.Level level, String sourceClass,
 349             String sourceMethod, String msg, Throwable thrown) {
 350         if (isLoggable(level)) {
 351             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown);
 352         }
 353     }
 354 
 355     @Override
 356     public final void logp(PlatformLogger.Level level, String sourceClass,
 357             String sourceMethod, Throwable thrown, Supplier<String> msgSupplier) {
 358         if (isLoggable(level)) {
 359             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get(), thrown);
 360         }
 361     }
 362 
 363     @Override
 364     public final void logrb(PlatformLogger.Level level, String sourceClass,
 365             String sourceMethod, ResourceBundle bundle, String key, Object... params) {
 366         if (isLoggable(level)) {
 367             String msg = bundle == null ? key : bundle.getString(key);
 368             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params);
 369         }
 370     }
 371 
 372     @Override
 373     public final void logrb(PlatformLogger.Level level, String sourceClass,
 374             String sourceMethod, ResourceBundle bundle, String key, Throwable thrown) {
 375         if (isLoggable(level)) {
 376             String msg = bundle == null ? key : bundle.getString(key);
 377             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown);
 378         }
 379     }
 380 
 381     @Override
 382     public final void logrb(PlatformLogger.Level level, ResourceBundle bundle,
 383             String key, Object... params) {
 384         if (isLoggable(level)) {
 385             String msg = bundle == null ? key : bundle.getString(key);
 386             publish(getCallerInfo(), logLevel(level), msg, params);
 387         }
 388     }
 389 
 390     @Override
 391     public final void logrb(PlatformLogger.Level level, ResourceBundle bundle,
 392             String key, Throwable thrown) {
 393         if (isLoggable(level)) {
 394             String msg = bundle == null ? key : bundle.getString(key);
 395             publish(getCallerInfo(), logLevel(level), msg, thrown);
 396         }
 397     }
 398 
 399     static final class Formatting {
 400         // The default simple log format string.
 401         // Used both by SimpleConsoleLogger when java.logging is not present,
 402         // and by SurrogateLogger and java.util.logging.SimpleFormatter when
 403         // java.logging is present.
 404         static final String DEFAULT_FORMAT =
 405             "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
 406 
 407         // The system property key that allows to change the default log format
 408         // when java.logging is not present. This is used to control the formatting
 409         // of the SimpleConsoleLogger.
 410         static final String DEFAULT_FORMAT_PROP_KEY =
 411             "jdk.system.logger.format";
 412 
 413         // The system property key that allows to change the default log format
 414         // when java.logging is present. This is used to control the formatting
 415         // of the SurrogateLogger (used before java.util.logging.LogManager is
 416         // initialized) and the java.util.logging.SimpleFormatter (used after
 417         // java.util.logging.LogManager is  initialized).
 418         static final String JUL_FORMAT_PROP_KEY =
 419             "java.util.logging.SimpleFormatter.format";
 420 
 421         // The simple console logger format string
 422         static final String SIMPLE_CONSOLE_LOGGER_FORMAT =
 423                 getSimpleFormat(DEFAULT_FORMAT_PROP_KEY, null);
 424 
 425         // Make it easier to wrap Logger...
 426         static private final String[] skips;
 427         static {
 428             String additionalPkgs =
 429                     GetPropertyAction.getProperty("jdk.logger.packages");
 430             skips = additionalPkgs == null ? new String[0] : additionalPkgs.split(",");
 431         }
 432 
 433         static boolean isFilteredFrame(StackFrame st) {
 434             // skip logging/logger infrastructure
 435             if (System.Logger.class.isAssignableFrom(st.getDeclaringClass())) {
 436                 return true;
 437             }
 438 
 439             // fast escape path: all the prefixes below start with 's' or 'j' and
 440             // have more than 12 characters.
 441             final String cname = st.getClassName();
 442             char c = cname.length() < 12 ? 0 : cname.charAt(0);
 443             if (c == 's') {
 444                 // skip internal machinery classes
 445                 if (cname.startsWith("sun.util.logging."))   return true;
 446                 if (cname.startsWith("sun.rmi.runtime.Log")) return true;
 447             } else if (c == 'j') {
 448                 // Message delayed at Bootstrap: no need to go further up.
 449                 if (cname.startsWith("jdk.internal.logger.BootstrapLogger$LogEvent")) return false;
 450                 // skip public machinery classes
 451                 if (cname.startsWith("jdk.internal.logger."))          return true;
 452                 if (cname.startsWith("java.util.logging."))            return true;
 453                 if (cname.startsWith("java.lang.invoke.MethodHandle")) return true;
 454                 if (cname.startsWith("java.security.AccessController")) return true;
 455             }
 456 
 457             // check additional prefixes if any are specified.
 458             if (skips.length > 0) {
 459                 for (int i=0; i<skips.length; i++) {
 460                     if (!skips[i].isEmpty() && cname.startsWith(skips[i])) {
 461                         return true;
 462                     }
 463                 }
 464             }
 465 
 466             return false;
 467         }
 468 
 469         static String getSimpleFormat(String key, Function<String, String> defaultPropertyGetter) {
 470             // Double check that 'key' is one of the expected property names:
 471             // - DEFAULT_FORMAT_PROP_KEY is used to control the
 472             //   SimpleConsoleLogger format when java.logging is
 473             //   not present.
 474             // - JUL_FORMAT_PROP_KEY is used when this method is called
 475             //   from the SurrogateLogger subclass. It is used to control the
 476             //   SurrogateLogger format and java.util.logging.SimpleFormatter
 477             //   format when java.logging is present.
 478             // This method should not be called with any other key.
 479             if (!DEFAULT_FORMAT_PROP_KEY.equals(key)
 480                     && !JUL_FORMAT_PROP_KEY.equals(key)) {
 481                 throw new IllegalArgumentException("Invalid property name: " + key);
 482             }
 483 
 484             // Do not use any lambda in this method. Using a lambda here causes
 485             //    jdk/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java
 486             // to fail - because that test has a testcase which somehow references
 487             // PlatformLogger and counts the number of generated lambda classes.
 488             String format = GetPropertyAction.getProperty(key);
 489 
 490             if (format == null && defaultPropertyGetter != null) {
 491                 format = defaultPropertyGetter.apply(key);
 492             }
 493             if (format != null) {
 494                 try {
 495                     // validate the user-defined format string
 496                     String.format(format, ZonedDateTime.now(), "", "", "", "", "");
 497                 } catch (IllegalArgumentException e) {
 498                     // illegal syntax; fall back to the default format
 499                     format = DEFAULT_FORMAT;
 500                 }
 501             } else {
 502                 format = DEFAULT_FORMAT;
 503             }
 504             return format;
 505         }
 506 
 507 
 508         // Copied from java.util.logging.Formatter.formatMessage
 509         static String formatMessage(String format, Object... parameters) {
 510             // Do the formatting.
 511             try {
 512                 if (parameters == null || parameters.length == 0) {
 513                     // No parameters.  Just return format string.
 514                     return format;
 515                 }
 516                 // Is it a java.text style format?
 517                 // Ideally we could match with
 518                 // Pattern.compile("\\{\\d").matcher(format).find())
 519                 // However the cost is 14% higher, so we cheaply check for
 520                 //
 521                 boolean isJavaTestFormat = false;
 522                 final int len = format.length();
 523                 for (int i=0; i<len-2; i++) {
 524                     final char c = format.charAt(i);
 525                     if (c == '{') {
 526                         final int d = format.charAt(i+1);
 527                         if (d >= '0' && d <= '9') {
 528                             isJavaTestFormat = true;
 529                             break;
 530                         }
 531                     }
 532                 }
 533                 if (isJavaTestFormat) {
 534                     return java.text.MessageFormat.format(format, parameters);
 535                 }
 536                 return format;
 537             } catch (Exception ex) {
 538                 // Formatting failed: use format string.
 539                 return format;
 540             }
 541         }
 542     }
 543 }