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 }