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