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.getWhenPresent().getClassName() + " " + frame.getWhenPresent().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 }