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