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