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