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