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 }