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