LogRecord.java revision 47f3c98d3c706c02c898cd15fbe6ee19d840c2c6
1/* 2 * Copyright (c) 2000, 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 26package java.util.logging; 27import dalvik.system.VMStack; 28import java.util.*; 29import java.util.concurrent.atomic.AtomicInteger; 30import java.util.concurrent.atomic.AtomicLong; 31import java.io.*; 32 33/** 34 * LogRecord objects are used to pass logging requests between 35 * the logging framework and individual log Handlers. 36 * <p> 37 * When a LogRecord is passed into the logging framework it 38 * logically belongs to the framework and should no longer be 39 * used or updated by the client application. 40 * <p> 41 * Note that if the client application has not specified an 42 * explicit source method name and source class name, then the 43 * LogRecord class will infer them automatically when they are 44 * first accessed (due to a call on getSourceMethodName or 45 * getSourceClassName) by analyzing the call stack. Therefore, 46 * if a logging Handler wants to pass off a LogRecord to another 47 * thread, or to transmit it over RMI, and if it wishes to subsequently 48 * obtain method name or class name information it should call 49 * one of getSourceClassName or getSourceMethodName to force 50 * the values to be filled in. 51 * <p> 52 * <b> Serialization notes:</b> 53 * <ul> 54 * <li>The LogRecord class is serializable. 55 * 56 * <li> Because objects in the parameters array may not be serializable, 57 * during serialization all objects in the parameters array are 58 * written as the corresponding Strings (using Object.toString). 59 * 60 * <li> The ResourceBundle is not transmitted as part of the serialized 61 * form, but the resource bundle name is, and the recipient object's 62 * readObject method will attempt to locate a suitable resource bundle. 63 * 64 * </ul> 65 * 66 * @since 1.4 67 */ 68 69public class LogRecord implements java.io.Serializable { 70 private static final AtomicLong globalSequenceNumber 71 = new AtomicLong(0); 72 73 /** 74 * The default value of threadID will be the current thread's 75 * thread id, for ease of correlation, unless it is greater than 76 * MIN_SEQUENTIAL_THREAD_ID, in which case we try harder to keep 77 * our promise to keep threadIDs unique by avoiding collisions due 78 * to 32-bit wraparound. Unfortunately, LogRecord.getThreadID() 79 * returns int, while Thread.getId() returns long. 80 */ 81 private static final int MIN_SEQUENTIAL_THREAD_ID = Integer.MAX_VALUE / 2; 82 83 private static final AtomicInteger nextThreadId 84 = new AtomicInteger(MIN_SEQUENTIAL_THREAD_ID); 85 86 private static final ThreadLocal<Integer> threadIds = new ThreadLocal<>(); 87 88 /** 89 * @serial Logging message level 90 */ 91 private Level level; 92 93 /** 94 * @serial Sequence number 95 */ 96 private long sequenceNumber; 97 98 /** 99 * @serial Class that issued logging call 100 */ 101 private String sourceClassName; 102 103 /** 104 * @serial Method that issued logging call 105 */ 106 private String sourceMethodName; 107 108 /** 109 * @serial Non-localized raw message text 110 */ 111 private String message; 112 113 /** 114 * @serial Thread ID for thread that issued logging call. 115 */ 116 private int threadID; 117 118 /** 119 * @serial Event time in milliseconds since 1970 120 */ 121 private long millis; 122 123 /** 124 * @serial The Throwable (if any) associated with log message 125 */ 126 private Throwable thrown; 127 128 /** 129 * @serial Name of the source Logger. 130 */ 131 private String loggerName; 132 133 /** 134 * @serial Resource bundle name to localized log message. 135 */ 136 private String resourceBundleName; 137 138 private transient boolean needToInferCaller; 139 private transient Object parameters[]; 140 private transient ResourceBundle resourceBundle; 141 142 /** 143 * Returns the default value for a new LogRecord's threadID. 144 */ 145 private int defaultThreadID() { 146 long tid = Thread.currentThread().getId(); 147 if (tid < MIN_SEQUENTIAL_THREAD_ID) { 148 return (int) tid; 149 } else { 150 Integer id = threadIds.get(); 151 if (id == null) { 152 id = nextThreadId.getAndIncrement(); 153 threadIds.set(id); 154 } 155 return id; 156 } 157 } 158 159 /** 160 * Construct a LogRecord with the given level and message values. 161 * <p> 162 * The sequence property will be initialized with a new unique value. 163 * These sequence values are allocated in increasing order within a VM. 164 * <p> 165 * The millis property will be initialized to the current time. 166 * <p> 167 * The thread ID property will be initialized with a unique ID for 168 * the current thread. 169 * <p> 170 * All other properties will be initialized to "null". 171 * 172 * @param level a logging level value 173 * @param msg the raw non-localized logging message (may be null) 174 */ 175 public LogRecord(Level level, String msg) { 176 // Make sure level isn't null, by calling random method. 177 level.getClass(); 178 this.level = level; 179 message = msg; 180 // Assign a thread ID and a unique sequence number. 181 sequenceNumber = globalSequenceNumber.getAndIncrement(); 182 threadID = defaultThreadID(); 183 millis = System.currentTimeMillis(); 184 needToInferCaller = true; 185 } 186 187 /** 188 * Get the source Logger's name. 189 * 190 * @return source logger name (may be null) 191 */ 192 public String getLoggerName() { 193 return loggerName; 194 } 195 196 /** 197 * Set the source Logger's name. 198 * 199 * @param name the source logger name (may be null) 200 */ 201 public void setLoggerName(String name) { 202 loggerName = name; 203 } 204 205 /** 206 * Get the localization resource bundle 207 * <p> 208 * This is the ResourceBundle that should be used to localize 209 * the message string before formatting it. The result may 210 * be null if the message is not localizable, or if no suitable 211 * ResourceBundle is available. 212 */ 213 public ResourceBundle getResourceBundle() { 214 return resourceBundle; 215 } 216 217 /** 218 * Set the localization resource bundle. 219 * 220 * @param bundle localization bundle (may be null) 221 */ 222 public void setResourceBundle(ResourceBundle bundle) { 223 resourceBundle = bundle; 224 } 225 226 /** 227 * Get the localization resource bundle name 228 * <p> 229 * This is the name for the ResourceBundle that should be 230 * used to localize the message string before formatting it. 231 * The result may be null if the message is not localizable. 232 */ 233 public String getResourceBundleName() { 234 return resourceBundleName; 235 } 236 237 /** 238 * Set the localization resource bundle name. 239 * 240 * @param name localization bundle name (may be null) 241 */ 242 public void setResourceBundleName(String name) { 243 resourceBundleName = name; 244 } 245 246 /** 247 * Get the logging message level, for example Level.SEVERE. 248 * @return the logging message level 249 */ 250 public Level getLevel() { 251 return level; 252 } 253 254 /** 255 * Set the logging message level, for example Level.SEVERE. 256 * @param level the logging message level 257 */ 258 public void setLevel(Level level) { 259 if (level == null) { 260 throw new NullPointerException(); 261 } 262 this.level = level; 263 } 264 265 /** 266 * Get the sequence number. 267 * <p> 268 * Sequence numbers are normally assigned in the LogRecord 269 * constructor, which assigns unique sequence numbers to 270 * each new LogRecord in increasing order. 271 * @return the sequence number 272 */ 273 public long getSequenceNumber() { 274 return sequenceNumber; 275 } 276 277 /** 278 * Set the sequence number. 279 * <p> 280 * Sequence numbers are normally assigned in the LogRecord constructor, 281 * so it should not normally be necessary to use this method. 282 */ 283 public void setSequenceNumber(long seq) { 284 sequenceNumber = seq; 285 } 286 287 /** 288 * Get the name of the class that (allegedly) issued the logging request. 289 * <p> 290 * Note that this sourceClassName is not verified and may be spoofed. 291 * This information may either have been provided as part of the 292 * logging call, or it may have been inferred automatically by the 293 * logging framework. In the latter case, the information may only 294 * be approximate and may in fact describe an earlier call on the 295 * stack frame. 296 * <p> 297 * May be null if no information could be obtained. 298 * 299 * @return the source class name 300 */ 301 public String getSourceClassName() { 302 if (needToInferCaller) { 303 inferCaller(); 304 } 305 return sourceClassName; 306 } 307 308 /** 309 * Set the name of the class that (allegedly) issued the logging request. 310 * 311 * @param sourceClassName the source class name (may be null) 312 */ 313 public void setSourceClassName(String sourceClassName) { 314 this.sourceClassName = sourceClassName; 315 needToInferCaller = false; 316 } 317 318 /** 319 * Get the name of the method that (allegedly) issued the logging request. 320 * <p> 321 * Note that this sourceMethodName is not verified and may be spoofed. 322 * This information may either have been provided as part of the 323 * logging call, or it may have been inferred automatically by the 324 * logging framework. In the latter case, the information may only 325 * be approximate and may in fact describe an earlier call on the 326 * stack frame. 327 * <p> 328 * May be null if no information could be obtained. 329 * 330 * @return the source method name 331 */ 332 public String getSourceMethodName() { 333 if (needToInferCaller) { 334 inferCaller(); 335 } 336 return sourceMethodName; 337 } 338 339 /** 340 * Set the name of the method that (allegedly) issued the logging request. 341 * 342 * @param sourceMethodName the source method name (may be null) 343 */ 344 public void setSourceMethodName(String sourceMethodName) { 345 this.sourceMethodName = sourceMethodName; 346 needToInferCaller = false; 347 } 348 349 /** 350 * Get the "raw" log message, before localization or formatting. 351 * <p> 352 * May be null, which is equivalent to the empty string "". 353 * <p> 354 * This message may be either the final text or a localization key. 355 * <p> 356 * During formatting, if the source logger has a localization 357 * ResourceBundle and if that ResourceBundle has an entry for 358 * this message string, then the message string is replaced 359 * with the localized value. 360 * 361 * @return the raw message string 362 */ 363 public String getMessage() { 364 return message; 365 } 366 367 /** 368 * Set the "raw" log message, before localization or formatting. 369 * 370 * @param message the raw message string (may be null) 371 */ 372 public void setMessage(String message) { 373 this.message = message; 374 } 375 376 /** 377 * Get the parameters to the log message. 378 * 379 * @return the log message parameters. May be null if 380 * there are no parameters. 381 */ 382 public Object[] getParameters() { 383 return parameters; 384 } 385 386 /** 387 * Set the parameters to the log message. 388 * 389 * @param parameters the log message parameters. (may be null) 390 */ 391 public void setParameters(Object parameters[]) { 392 this.parameters = parameters; 393 } 394 395 /** 396 * Get an identifier for the thread where the message originated. 397 * <p> 398 * This is a thread identifier within the Java VM and may or 399 * may not map to any operating system ID. 400 * 401 * @return thread ID 402 */ 403 public int getThreadID() { 404 return threadID; 405 } 406 407 /** 408 * Set an identifier for the thread where the message originated. 409 * @param threadID the thread ID 410 */ 411 public void setThreadID(int threadID) { 412 this.threadID = threadID; 413 } 414 415 /** 416 * Get event time in milliseconds since 1970. 417 * 418 * @return event time in millis since 1970 419 */ 420 public long getMillis() { 421 return millis; 422 } 423 424 /** 425 * Set event time. 426 * 427 * @param millis event time in millis since 1970 428 */ 429 public void setMillis(long millis) { 430 this.millis = millis; 431 } 432 433 /** 434 * Get any throwable associated with the log record. 435 * <p> 436 * If the event involved an exception, this will be the 437 * exception object. Otherwise null. 438 * 439 * @return a throwable 440 */ 441 public Throwable getThrown() { 442 return thrown; 443 } 444 445 /** 446 * Set a throwable associated with the log event. 447 * 448 * @param thrown a throwable (may be null) 449 */ 450 public void setThrown(Throwable thrown) { 451 this.thrown = thrown; 452 } 453 454 private static final long serialVersionUID = 5372048053134512534L; 455 456 /** 457 * @serialData Default fields, followed by a two byte version number 458 * (major byte, followed by minor byte), followed by information on 459 * the log record parameter array. If there is no parameter array, 460 * then -1 is written. If there is a parameter array (possible of zero 461 * length) then the array length is written as an integer, followed 462 * by String values for each parameter. If a parameter is null, then 463 * a null String is written. Otherwise the output of Object.toString() 464 * is written. 465 */ 466 private void writeObject(ObjectOutputStream out) throws IOException { 467 // We have to call defaultWriteObject first. 468 out.defaultWriteObject(); 469 470 // Write our version number. 471 out.writeByte(1); 472 out.writeByte(0); 473 if (parameters == null) { 474 out.writeInt(-1); 475 return; 476 } 477 out.writeInt(parameters.length); 478 // Write string values for the parameters. 479 for (int i = 0; i < parameters.length; i++) { 480 if (parameters[i] == null) { 481 out.writeObject(null); 482 } else { 483 out.writeObject(parameters[i].toString()); 484 } 485 } 486 } 487 488 private void readObject(ObjectInputStream in) 489 throws IOException, ClassNotFoundException { 490 // We have to call defaultReadObject first. 491 in.defaultReadObject(); 492 493 // Read version number. 494 byte major = in.readByte(); 495 byte minor = in.readByte(); 496 if (major != 1) { 497 throw new IOException("LogRecord: bad version: " + major + "." + minor); 498 } 499 int len = in.readInt(); 500 if (len == -1) { 501 parameters = null; 502 } else { 503 parameters = new Object[len]; 504 for (int i = 0; i < parameters.length; i++) { 505 parameters[i] = in.readObject(); 506 } 507 } 508 // If necessary, try to regenerate the resource bundle. 509 if (resourceBundleName != null) { 510 try { 511 resourceBundle = ResourceBundle.getBundle(resourceBundleName); 512 } catch (MissingResourceException ex) { 513 // This is not a good place to throw an exception, 514 // so we simply leave the resourceBundle null. 515 resourceBundle = null; 516 } 517 } 518 519 needToInferCaller = false; 520 } 521 522 // Private method to infer the caller's class and method names 523 private void inferCaller() { 524 needToInferCaller = false; 525 // Android-changed: Use VMStack.getThreadStackTrace. 526 StackTraceElement[] stack = VMStack.getThreadStackTrace(Thread.currentThread()); 527 int depth = stack.length; 528 529 boolean lookingForLogger = true; 530 for (int ix = 0; ix < depth; ix++) { 531 // Calling getStackTraceElement directly prevents the VM 532 // from paying the cost of building the entire stack frame. 533 // 534 // Android-changed: Use value from getThreadStackTrace. 535 StackTraceElement frame = stack[ix]; 536 String cname = frame.getClassName(); 537 boolean isLoggerImpl = isLoggerImplFrame(cname); 538 if (lookingForLogger) { 539 // Skip all frames until we have found the first logger frame. 540 if (isLoggerImpl) { 541 lookingForLogger = false; 542 } 543 } else { 544 if (!isLoggerImpl) { 545 // skip reflection call 546 if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) { 547 // We've found the relevant frame. 548 setSourceClassName(cname); 549 setSourceMethodName(frame.getMethodName()); 550 return; 551 } 552 } 553 } 554 } 555 // We haven't found a suitable frame, so just punt. This is 556 // OK as we are only committed to making a "best effort" here. 557 } 558 559 private boolean isLoggerImplFrame(String cname) { 560 // the log record could be created for a platform logger 561 return (cname.equals("java.util.logging.Logger") || 562 cname.startsWith("java.util.logging.LoggingProxyImpl") || 563 cname.startsWith("sun.util.logging.")); 564 } 565} 566