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