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