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<>(); 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<>(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 320 final String name; 321 volatile int levelValue; 322 volatile int effectiveLevel = 0; // current effective level value 323 324 LoggerProxy(String name) { 325 this(name, defaultLevel); 326 } 327 328 LoggerProxy(String name, int level) { 329 this.name = name; 330 this.levelValue = level == 0 ? defaultLevel : level; 331 } 332 333 boolean isEnabled() { 334 return levelValue != OFF; 335 } 336 337 int getLevel() { 338 return effectiveLevel; 339 } 340 341 void setLevel(int newLevel) { 342 levelValue = newLevel; 343 effectiveLevel = newLevel; 344 } 345 346 void doLog(int level, String msg) { 347 if (level < levelValue || levelValue == OFF) { 348 return; 349 } 350 defaultStream.print(format(level, msg, null)); 351 } 352 353 void doLog(int level, String msg, Throwable thrown) { 354 if (level < levelValue || levelValue == OFF) { 355 return; 356 } 357 defaultStream.print(format(level, msg, thrown)); 358 } 359 360 void doLog(int level, String msg, Object... params) { 361 if (level < levelValue || levelValue == OFF) { 362 return; 363 } 364 String newMsg = formatMessage(msg, params); 365 defaultStream.print(format(level, newMsg, null)); 366 } 367 368 public boolean isLoggable(int level) { 369 if (level < levelValue || levelValue == OFF) { 370 return false; 371 } 372 return true; 373 } 374 375 // Copied from java.util.logging.Formatter.formatMessage 376 private String formatMessage(String format, Object... parameters) { 377 // Do the formatting. 378 try { 379 if (parameters == null || parameters.length == 0) { 380 // No parameters. Just return format string. 381 return format; 382 } 383 // Is it a java.text style format? 384 // Ideally we could match with 385 // Pattern.compile("\\{\\d").matcher(format).find()) 386 // However the cost is 14% higher, so we cheaply check for 387 // 1 of the first 4 parameters 388 if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 || 389 format.indexOf("{2") >=0|| format.indexOf("{3") >=0) { 390 return java.text.MessageFormat.format(format, parameters); 391 } 392 return format; 393 } catch (Exception ex) { 394 // Formatting failed: use format string. 395 return format; 396 } 397 } 398 399 private static final String formatString = 400 LoggingSupport.getSimpleFormat(false); // don't check logging.properties 401 402 // minimize memory allocation 403 private Date date = new Date(); 404 private synchronized String format(int level, String msg, Throwable thrown) { 405 date.setTime(System.currentTimeMillis()); 406 String throwable = ""; 407 if (thrown != null) { 408 try { 409 StringWriter sw = new StringWriter(); 410 sw.append('\n'); 411 PrintWriter pw = new PrintWriter(sw); 412 thrown.printStackTrace(pw); 413 pw.close(); 414 throwable = sw.toString(); 415 } catch (Exception ex) { 416 throw new AssertionError(ex); 417 } 418 } 419 420 return String.format(formatString, 421 date, 422 getCallerInfo(), 423 name, 424 PlatformLogger.getLevelName(level), 425 msg, 426 throwable); 427 } 428 429 // Returns the caller's class and method's name; best effort 430 // if cannot infer, return the logger's name. 431 private String getCallerInfo() { 432 String sourceClassName = null; 433 String sourceMethodName = null; 434 435 JavaLangAccess access = SharedSecrets.getJavaLangAccess(); 436 Throwable throwable = new Throwable(); 437 int depth = access.getStackTraceDepth(throwable); 438 439 String logClassName = "sun.util.logging.PlatformLogger"; 440 boolean lookingForLogger = true; 441 for (int ix = 0; ix < depth; ix++) { 442 // Calling getStackTraceElement directly prevents the VM 443 // from paying the cost of building the entire stack frame. 444 StackTraceElement frame = 445 access.getStackTraceElement(throwable, ix); 446 String cname = frame.getClassName(); 447 if (lookingForLogger) { 448 // Skip all frames until we have found the first logger frame. 449 if (cname.equals(logClassName)) { 450 lookingForLogger = false; 451 } 452 } else { 453 if (!cname.equals(logClassName)) { 454 // We've found the relevant frame. 455 sourceClassName = cname; 456 sourceMethodName = frame.getMethodName(); 457 break; 458 } 459 } 460 } 461 462 if (sourceClassName != null) { 463 return sourceClassName + " " + sourceMethodName; 464 } else { 465 return name; 466 } 467 } 468 } 469 470 /** 471 * JavaLogger forwards all the calls to its corresponding 472 * java.util.logging.Logger object. 473 */ 474 static class JavaLogger extends LoggerProxy { 475 private static final Map<Integer, Object> levelObjects = 476 new HashMap<>(); 477 478 static { 479 if (LoggingSupport.isAvailable()) { 480 // initialize the map to Level objects 481 getLevelObjects(); 482 } 483 } 484 485 private static void getLevelObjects() { 486 // get all java.util.logging.Level objects 487 int[] levelArray = new int[] {OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL}; 488 for (int l : levelArray) { 489 Object level = LoggingSupport.parseLevel(getLevelName(l)); 490 levelObjects.put(l, level); 491 } 492 } 493 494 private final Object javaLogger; 495 JavaLogger(String name) { 496 this(name, 0); 497 } 498 499 JavaLogger(String name, int level) { 500 super(name, level); 501 this.javaLogger = LoggingSupport.getLogger(name); 502 if (level != 0) { 503 // level has been updated and so set the Logger's level 504 LoggingSupport.setLevel(javaLogger, levelObjects.get(level)); 505 } 506 } 507 508 /** 509 * Let Logger.log() do the filtering since if the level of a 510 * platform logger is altered directly from 511 * java.util.logging.Logger.setLevel(), the levelValue will 512 * not be updated. 513 */ 514 void doLog(int level, String msg) { 515 LoggingSupport.log(javaLogger, levelObjects.get(level), msg); 516 } 517 518 void doLog(int level, String msg, Throwable t) { 519 LoggingSupport.log(javaLogger, levelObjects.get(level), msg, t); 520 } 521 522 void doLog(int level, String msg, Object... params) { 523 // only pass String objects to the j.u.l.Logger which may 524 // be created by untrusted code 525 int len = (params != null) ? params.length : 0; 526 Object[] sparams = new String[len]; 527 for (int i = 0; i < len; i++) { 528 sparams [i] = String.valueOf(params[i]); 529 } 530 LoggingSupport.log(javaLogger, levelObjects.get(level), msg, sparams); 531 } 532 533 boolean isEnabled() { 534 Object level = LoggingSupport.getLevel(javaLogger); 535 return level == null || level.equals(levelObjects.get(OFF)) == false; 536 } 537 538 int getLevel() { 539 Object level = LoggingSupport.getLevel(javaLogger); 540 if (level != null) { 541 for (Map.Entry<Integer, Object> l : levelObjects.entrySet()) { 542 if (level == l.getValue()) { 543 return l.getKey(); 544 } 545 } 546 } 547 return 0; 548 } 549 550 void setLevel(int newLevel) { 551 levelValue = newLevel; 552 LoggingSupport.setLevel(javaLogger, levelObjects.get(newLevel)); 553 } 554 555 public boolean isLoggable(int level) { 556 return LoggingSupport.isLoggable(javaLogger, levelObjects.get(level)); 557 } 558 } 559 560 private static String getLevelName(int level) { 561 switch (level) { 562 case OFF : return "OFF"; 563 case SEVERE : return "SEVERE"; 564 case WARNING : return "WARNING"; 565 case INFO : return "INFO"; 566 case CONFIG : return "CONFIG"; 567 case FINE : return "FINE"; 568 case FINER : return "FINER"; 569 case FINEST : return "FINEST"; 570 case ALL : return "ALL"; 571 default : return "UNKNOWN"; 572 } 573 } 574 575 }