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