| 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