1 /* 2 * Copyright (c) 2009, 2010, 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 27 package sun.util.logging; 28 29 import java.lang.ref.WeakReference; 30 import java.lang.reflect.InvocationTargetException; 31 import java.lang.reflect.Method; 32 import java.io.PrintStream; 33 import java.io.PrintWriter; 34 import java.io.StringWriter; 35 import java.security.AccessController; 36 import java.security.PrivilegedAction; 37 import java.text.MessageFormat; 38 import java.util.Date; 39 import java.util.HashMap; 40 import java.util.Map; 41 import sun.misc.JavaLangAccess; 42 import sun.misc.SharedSecrets; 43 44 /** 45 * Platform logger provides an API for the JRE components to log 46 * messages. This enables the runtime components to eliminate the 47 * static dependency of the logging facility and also defers the 48 * java.util.logging initialization until it is enabled. 49 * In addition, the PlatformLogger API can be used if the logging 50 * module does not exist. 51 * 52 * If the logging facility is not enabled, the platform loggers 53 * will output log messages per the default logging configuration 54 * (see below). In this implementation, it does not log the 55 * the stack frame information issuing the log message. 56 * 57 * When the logging facility is enabled (at startup or runtime), 58 * the java.util.logging.Logger will be created for each platform 59 * logger and all log messages will be forwarded to the Logger 60 * to handle. 61 * 62 * Logging facility is "enabled" when one of the following 63 * conditions is met: 64 * 1) a system property "java.util.logging.config.class" or 65 * "java.util.logging.config.file" is set 66 * 2) java.util.logging.LogManager or java.util.logging.Logger 67 * is referenced that will trigger the logging initialization. 68 * 69 * Default logging configuration: 70 * global logging level = INFO 71 * handlers = java.util.logging.ConsoleHandler 72 * java.util.logging.ConsoleHandler.level = INFO 73 * java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter 74 * 75 * Limitation: 76 * <JAVA_HOME>/lib/logging.properties is the system-wide logging 77 * configuration defined in the specification and read in the 78 * default case to configure any java.util.logging.Logger instances. 79 * Platform loggers will not detect if <JAVA_HOME>/lib/logging.properties 80 * is modified. In other words, unless the java.util.logging API 81 * is used at runtime or the logging system properties is set, 82 * the platform loggers will use the default setting described above. 83 * The platform loggers are designed for JDK developers use and 84 * this limitation can be workaround with setting 85 * -Djava.util.logging.config.file system property. 86 * 87 * @since 1.7 88 */ 89 public class PlatformLogger { 90 // Same values as java.util.logging.Level for easy mapping 91 public static final int OFF = Integer.MAX_VALUE; 92 public static final int SEVERE = 1000; 93 public static final int WARNING = 900; 94 public static final int INFO = 800; 95 public static final int CONFIG = 700; 96 public static final int FINE = 500; 97 public static final int FINER = 400; 98 public static final int FINEST = 300; 99 public static final int ALL = Integer.MIN_VALUE; 100 101 private static final int defaultLevel = INFO; 102 private static boolean loggingEnabled; 103 static { 104 loggingEnabled = AccessController.doPrivileged( 105 new PrivilegedAction<Boolean>() { 106 public Boolean run() { 107 String cname = System.getProperty("java.util.logging.config.class"); 108 String fname = System.getProperty("java.util.logging.config.file"); 109 return (cname != null || fname != null); 110 } 111 }); 112 } 113 114 // Table of known loggers. Maps names to PlatformLoggers. 115 private static Map<String,WeakReference<PlatformLogger>> loggers = 116 new HashMap<String,WeakReference<PlatformLogger>>(); 117 118 /** 119 * Returns a PlatformLogger of a given name. 120 */ 121 public static synchronized PlatformLogger getLogger(String name) { 122 PlatformLogger log = null; 123 WeakReference<PlatformLogger> ref = loggers.get(name); 124 if (ref != null) { 125 log = ref.get(); 126 } 127 if (log == null) { 128 log = new PlatformLogger(name); 129 loggers.put(name, new WeakReference<PlatformLogger>(log)); 130 } 131 return log; 132 } 133 134 /** 135 * Initialize java.util.logging.Logger objects for all platform loggers. 136 * This method is called from LogManager.readPrimordialConfiguration(). 137 */ 138 public static synchronized void redirectPlatformLoggers() { 139 if (loggingEnabled || !LoggingSupport.isAvailable()) return; 140 141 loggingEnabled = true; 142 for (Map.Entry<String, WeakReference<PlatformLogger>> entry : loggers.entrySet()) { 143 WeakReference<PlatformLogger> ref = entry.getValue(); 144 PlatformLogger plog = ref.get(); 145 if (plog != null) { 146 plog.newJavaLogger(); 147 } 148 } 149 } 150 151 /** 152 * Creates a new JavaLogger that the platform logger uses 153 */ 154 private void newJavaLogger() { 155 logger = new JavaLogger(logger.name, logger.effectiveLevel); 156 } 157 158 // logger may be replaced with a JavaLogger object 159 // when the logging facility is enabled 160 private volatile LoggerProxy logger; 161 162 private PlatformLogger(String name) { 163 if (loggingEnabled) { 164 this.logger = new JavaLogger(name); 165 } else { 166 this.logger = new LoggerProxy(name); 167 } 168 } 169 170 /** 171 * A convenience method to test if the logger is turned off. 172 * (i.e. its level is OFF). 173 */ 174 public boolean isEnabled() { 175 return logger.isEnabled(); 176 } 177 178 /** 179 * Gets the name for this platform logger. 180 */ 181 public String getName() { 182 return logger.name; 183 } 184 185 /** 186 * Returns true if a message of the given level would actually 187 * be logged by this logger. 188 */ 189 public boolean isLoggable(int level) { 190 return logger.isLoggable(level); 191 } 192 193 /** 194 * Gets the current log level. Returns 0 if the current effective level 195 * is not set (equivalent to Logger.getLevel() returns null). 196 */ 197 public int getLevel() { 198 return logger.getLevel(); 199 } 200 201 /** 202 * Sets the log level. 203 */ 204 public void setLevel(int newLevel) { 205 logger.setLevel(newLevel); 206 } 207 208 /** 209 * Logs a SEVERE message. 210 */ 211 public void severe(String msg) { 212 logger.doLog(SEVERE, msg); 213 } 214 215 public void severe(String msg, Throwable t) { 216 logger.doLog(SEVERE, msg, t); 217 } 218 219 public void severe(String msg, Object... params) { 220 logger.doLog(SEVERE, msg, params); 221 } 222 223 /** 224 * Logs a WARNING message. 225 */ 226 public void warning(String msg) { 227 logger.doLog(WARNING, msg); 228 } 229 230 public void warning(String msg, Throwable t) { 231 logger.doLog(WARNING, msg, t); 232 } 233 234 public void warning(String msg, Object... params) { 235 logger.doLog(WARNING, msg, params); 236 } 237 238 /** 239 * Logs an INFO message. 240 */ 241 public void info(String msg) { 242 logger.doLog(INFO, msg); 243 } 244 245 public void info(String msg, Throwable t) { 246 logger.doLog(INFO, msg, t); 247 } 248 249 public void info(String msg, Object... params) { 250 logger.doLog(INFO, msg, params); 251 } 252 253 /** 254 * Logs a CONFIG message. 255 */ 256 public void config(String msg) { 257 logger.doLog(CONFIG, msg); 258 } 259 260 public void config(String msg, Throwable t) { 261 logger.doLog(CONFIG, msg, t); 262 } 263 264 public void config(String msg, Object... params) { 265 logger.doLog(CONFIG, msg, params); 266 } 267 268 /** 269 * Logs a FINE message. 270 */ 271 public void fine(String msg) { 272 logger.doLog(FINE, msg); 273 } 274 275 public void fine(String msg, Throwable t) { 276 logger.doLog(FINE, msg, t); 277 } 278 279 public void fine(String msg, Object... params) { 280 logger.doLog(FINE, msg, params); 281 } 282 283 /** 284 * Logs a FINER message. 285 */ 286 public void finer(String msg) { 287 logger.doLog(FINER, msg); 288 } 289 290 public void finer(String msg, Throwable t) { 291 logger.doLog(FINER, msg, t); 292 } 293 294 public void finer(String msg, Object... params) { 295 logger.doLog(FINER, msg, params); 296 } 297 298 /** 299 * Logs a FINEST message. 300 */ 301 public void finest(String msg) { 302 logger.doLog(FINEST, msg); 303 } 304 305 public void finest(String msg, Throwable t) { 306 logger.doLog(FINEST, msg, t); 307 } 308 309 public void finest(String msg, Object... params) { 310 logger.doLog(FINEST, msg, params); 311 } 312 313 /** 314 * Default platform logging support - output messages to 315 * System.err - equivalent to ConsoleHandler with SimpleFormatter. 316 */ 317 static class LoggerProxy { 318 private static final PrintStream defaultStream = System.err; 319 private static final String lineSeparator = AccessController.doPrivileged( 320 new PrivilegedAction<String>() { 321 public String run() { 322 return System.getProperty("line.separator"); 323 } 324 }); 325 326 final String name; 327 volatile int levelValue; 328 volatile int effectiveLevel = 0; // current effective level value 329 330 LoggerProxy(String name) { 331 this(name, defaultLevel); 332 } 333 334 LoggerProxy(String name, int level) { 335 this.name = name; 336 this.levelValue = level == 0 ? defaultLevel : level; 337 } 338 339 boolean isEnabled() { 340 return levelValue != OFF; 341 } 342 343 int getLevel() { 344 return effectiveLevel; 345 } 346 347 void setLevel(int newLevel) { 348 levelValue = newLevel; 349 effectiveLevel = newLevel; 350 } 351 352 void doLog(int level, String msg) { 353 if (level < levelValue || levelValue == OFF) { 354 return; 355 } 356 defaultStream.println(format(level, msg, null)); 357 } 358 359 void doLog(int level, String msg, Throwable thrown) { 360 if (level < levelValue || levelValue == OFF) { 361 return; 362 } 363 defaultStream.println(format(level, msg, thrown)); 364 } 365 366 void doLog(int level, String msg, Object... params) { 367 if (level < levelValue || levelValue == OFF) { 368 return; 369 } 370 String newMsg = formatMessage(msg, params); 371 defaultStream.println(format(level, newMsg, null)); 372 } 373 374 public boolean isLoggable(int level) { 375 if (level < levelValue || levelValue == OFF) { 376 return false; 377 } 378 return true; 379 } 380 381 private static final String format = "{0,date} {0,time}"; 382 383 private Object args[] = new Object[1]; 384 private MessageFormat formatter; 385 private Date dat; 386 387 // Copied from java.util.logging.Formatter.formatMessage 388 private String formatMessage(String format, Object... parameters) { 389 // Do the formatting. 390 try { 391 if (parameters == null || parameters.length == 0) { 392 // No parameters. Just return format string. 393 return format; 394 } 395 // Is it a java.text style format? 396 // Ideally we could match with 397 // Pattern.compile("\\{\\d").matcher(format).find()) 398 // However the cost is 14% higher, so we cheaply check for 399 // 1 of the first 4 parameters 400 if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 || 401 format.indexOf("{2") >=0|| format.indexOf("{3") >=0) { 402 return java.text.MessageFormat.format(format, parameters); 403 } 404 return format; 405 } catch (Exception ex) { 406 // Formatting failed: use format string. 407 return format; 408 } 409 } 410 411 private synchronized String format(int level, String msg, Throwable thrown) { 412 StringBuffer sb = new StringBuffer(); 413 // Minimize memory allocations here. 414 if (dat == null) { 415 dat = new Date(); 416 formatter = new MessageFormat(format); 417 } 418 dat.setTime(System.currentTimeMillis()); 419 args[0] = dat; 420 StringBuffer text = new StringBuffer(); 421 formatter.format(args, text, null); 422 sb.append(text); 423 sb.append(" "); 424 sb.append(getCallerInfo()); 425 sb.append(lineSeparator); 426 sb.append(PlatformLogger.getLevelName(level)); 427 sb.append(": "); 428 sb.append(msg); 429 if (thrown != null) { 430 try { 431 StringWriter sw = new StringWriter(); 432 PrintWriter pw = new PrintWriter(sw); 433 thrown.printStackTrace(pw); 434 pw.close(); 435 sb.append(sw.toString()); 436 } catch (Exception ex) { 437 throw new AssertionError(ex); 438 } 439 } 440 441 return sb.toString(); 442 } 443 444 // Returns the caller's class and method's name; best effort 445 // if cannot infer, return the logger's name. 446 private String getCallerInfo() { 447 String sourceClassName = null; 448 String sourceMethodName = null; 449 450 JavaLangAccess access = SharedSecrets.getJavaLangAccess(); 451 Throwable throwable = new Throwable(); 452 int depth = access.getStackTraceDepth(throwable); 453 454 String logClassName = "sun.util.logging.PlatformLogger"; 455 boolean lookingForLogger = true; 456 for (int ix = 0; ix < depth; ix++) { 457 // Calling getStackTraceElement directly prevents the VM 458 // from paying the cost of building the entire stack frame. 459 StackTraceElement frame = 460 access.getStackTraceElement(throwable, ix); 461 String cname = frame.getClassName(); 462 if (lookingForLogger) { 463 // Skip all frames until we have found the first logger frame. 464 if (cname.equals(logClassName)) { 465 lookingForLogger = false; 466 } 467 } else { 468 if (!cname.equals(logClassName)) { 469 // We've found the relevant frame. 470 sourceClassName = cname; 471 sourceMethodName = frame.getMethodName(); 472 break; 473 } 474 } 475 } 476 477 if (sourceClassName != null) { 478 return sourceClassName + " " + sourceMethodName; 479 } else { 480 return name; 481 } 482 } 483 } 484 485 /** 486 * JavaLogger forwards all the calls to its corresponding 487 * java.util.logging.Logger object. 488 */ 489 static class JavaLogger extends LoggerProxy { 490 private static final Map<Integer, Object> levelObjects = 491 new HashMap<Integer, Object>(); 492 493 static { 494 if (LoggingSupport.isAvailable()) { 495 // initialize the map to Level objects 496 getLevelObjects(); 497 } 498 } 499 500 private static void getLevelObjects() { 501 // get all java.util.logging.Level objects 502 int[] levelArray = new int[] {OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL}; 503 for (int l : levelArray) { 504 Object level = LoggingSupport.parseLevel(getLevelName(l)); 505 levelObjects.put(l, level); 506 } 507 } 508 509 private final Object javaLogger; 510 JavaLogger(String name) { 511 this(name, 0); 512 } 513 514 JavaLogger(String name, int level) { 515 super(name, level); 516 this.javaLogger = LoggingSupport.getLogger(name); 517 if (level != 0) { 518 // level has been updated and so set the Logger's level 519 LoggingSupport.setLevel(javaLogger, levelObjects.get(level)); 520 } 521 } 522 523 /** 524 * Let Logger.log() do the filtering since if the level of a 525 * platform logger is altered directly from 526 * java.util.logging.Logger.setLevel(), the levelValue will 527 * not be updated. 528 */ 529 void doLog(int level, String msg) { 530 LoggingSupport.log(javaLogger, levelObjects.get(level), msg); 531 } 532 533 void doLog(int level, String msg, Throwable t) { 534 LoggingSupport.log(javaLogger, levelObjects.get(level), msg, t); 535 } 536 537 void doLog(int level, String msg, Object... params) { 538 LoggingSupport.log(javaLogger, levelObjects.get(level), msg, params); 539 } 540 541 boolean isEnabled() { 542 Object level = LoggingSupport.getLevel(javaLogger); 543 return level == null || level.equals(levelObjects.get(OFF)) == false; 544 } 545 546 int getLevel() { 547 Object level = LoggingSupport.getLevel(javaLogger); 548 if (level != null) { 549 for (Map.Entry<Integer, Object> l : levelObjects.entrySet()) { 550 if (level == l.getValue()) { 551 return l.getKey(); 552 } 553 } 554 } 555 return 0; 556 } 557 558 void setLevel(int newLevel) { 559 levelValue = newLevel; 560 LoggingSupport.setLevel(javaLogger, levelObjects.get(newLevel)); 561 } 562 563 public boolean isLoggable(int level) { 564 return LoggingSupport.isLoggable(javaLogger, levelObjects.get(level)); 565 } 566 } 567 568 private static String getLevelName(int level) { 569 switch (level) { 570 case OFF : return "OFF"; 571 case SEVERE : return "SEVERE"; 572 case WARNING : return "WARNING"; 573 case INFO : return "INFO"; 574 case CONFIG : return "CONFIG"; 575 case FINE : return "FINE"; 576 case FINER : return "FINER"; 577 case FINEST : return "FINEST"; 578 case ALL : return "ALL"; 579 default : return "UNKNOWN"; 580 } 581 } 582 583 }