1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.util.logging;
19
20import java.io.IOException;
21import java.io.ObjectInputStream;
22import java.io.ObjectOutputStream;
23import java.io.Serializable;
24import java.util.MissingResourceException;
25import java.util.ResourceBundle;
26
27/**
28 * A {@code LogRecord} object represents a logging request. It is passed between
29 * the logging framework and individual logging handlers. Client applications
30 * should not modify a {@code LogRecord} object that has been passed into the
31 * logging framework.
32 * <p>
33 * The {@code LogRecord} class will infer the source method name and source
34 * class name the first time they are accessed if the client application didn't
35 * specify them explicitly. This automatic inference is based on the analysis of
36 * the call stack and is not guaranteed to be precise. Client applications
37 * should force the initialization of these two fields by calling
38 * {@code getSourceClassName} or {@code getSourceMethodName} if they expect to
39 * use them after passing the {@code LogRecord} object to another thread or
40 * transmitting it over RMI.
41 */
42public class LogRecord implements Serializable {
43
44    private static final long serialVersionUID = 5372048053134512534L;
45
46    // The major byte used in serialization.
47    private static final int MAJOR = 1;
48
49    // The minor byte used in serialization.
50    private static final int MINOR = 4;
51
52    // Store the current value for the sequence number.
53    private static long currentSequenceNumber = 0;
54
55    // Store the id for each thread.
56    private static ThreadLocal<Integer> currentThreadId = new ThreadLocal<Integer>();
57
58    // The base id as the starting point for thread ID allocation.
59    private static int initThreadId = 0;
60
61    /**
62     * The logging level.
63     *
64     * @serial
65     */
66    private Level level;
67
68    /**
69     * The sequence number.
70     *
71     * @serial
72     */
73    private long sequenceNumber;
74
75    /**
76     * The name of the class that issued the logging call.
77     *
78     * @serial
79     */
80    private String sourceClassName;
81
82    /**
83     * The name of the method that issued the logging call.
84     *
85     * @serial
86     */
87    private String sourceMethodName;
88
89    /**
90     * The original message text.
91     *
92     * @serial
93     */
94    private String message;
95
96    /**
97     * The ID of the thread that issued the logging call.
98     *
99     * @serial
100     */
101    private int threadID;
102
103    /**
104     * The time that the event occurred, in milliseconds since 1970.
105     *
106     * @serial
107     */
108    private long millis;
109
110    /**
111     * The associated {@code Throwable} object if any.
112     *
113     * @serial
114     */
115    private Throwable thrown;
116
117    /**
118     * The name of the source logger.
119     *
120     * @serial
121     */
122    private String loggerName;
123
124    /**
125     * The name of the resource bundle used to localize the log message.
126     *
127     * @serial
128     */
129    private String resourceBundleName;
130
131    // The associated resource bundle if any.
132    private transient ResourceBundle resourceBundle;
133
134    // The parameters.
135    private transient Object[] parameters;
136
137    // If the source method and source class has been initialized
138    private transient boolean sourceInitialized;
139
140    /**
141     * Constructs a {@code LogRecord} object using the supplied the logging
142     * level and message. The millis property is set to the current time. The
143     * sequence property is set to a new unique value, allocated in increasing
144     * order within the VM. The thread ID is set to a unique value
145     * for the current thread. All other properties are set to {@code null}.
146     *
147     * @param level
148     *            the logging level, may not be {@code null}.
149     * @param msg
150     *            the raw message.
151     * @throws NullPointerException
152     *             if {@code level} is {@code null}.
153     */
154    public LogRecord(Level level, String msg) {
155        if (level == null) {
156            throw new NullPointerException("level == null");
157        }
158        this.level = level;
159        this.message = msg;
160        this.millis = System.currentTimeMillis();
161
162        synchronized (LogRecord.class) {
163            this.sequenceNumber = currentSequenceNumber++;
164            Integer id = currentThreadId.get();
165            if (id == null) {
166                this.threadID = initThreadId;
167                currentThreadId.set(Integer.valueOf(initThreadId++));
168            } else {
169                this.threadID = id.intValue();
170            }
171        }
172
173        this.sourceClassName = null;
174        this.sourceMethodName = null;
175        this.loggerName = null;
176        this.parameters = null;
177        this.resourceBundle = null;
178        this.resourceBundleName = null;
179        this.thrown = null;
180    }
181
182    /**
183     * Gets the logging level.
184     *
185     * @return the logging level.
186     */
187    public Level getLevel() {
188        return level;
189    }
190
191    /**
192     * Sets the logging level.
193     *
194     * @param level
195     *            the level to set.
196     * @throws NullPointerException
197     *             if {@code level} is {@code null}.
198     */
199    public void setLevel(Level level) {
200        if (level == null) {
201            throw new NullPointerException("level == null");
202        }
203        this.level = level;
204    }
205
206    /**
207     * Gets the name of the logger.
208     *
209     * @return the logger name.
210     */
211    public String getLoggerName() {
212        return loggerName;
213    }
214
215    /**
216     * Sets the name of the logger.
217     *
218     * @param loggerName
219     *            the logger name to set.
220     */
221    public void setLoggerName(String loggerName) {
222        this.loggerName = loggerName;
223    }
224
225    /**
226     * Gets the raw message.
227     *
228     * @return the raw message, may be {@code null}.
229     */
230    public String getMessage() {
231        return message;
232    }
233
234    /**
235     * Sets the raw message. When this record is formatted by a logger that has
236     * a localization resource bundle that contains an entry for {@code message},
237     * then the raw message is replaced with its localized version.
238     *
239     * @param message
240     *            the raw message to set, may be {@code null}.
241     */
242    public void setMessage(String message) {
243        this.message = message;
244    }
245
246    /**
247     * Gets the time when this event occurred, in milliseconds since 1970.
248     *
249     * @return the time when this event occurred, in milliseconds since 1970.
250     */
251    public long getMillis() {
252        return millis;
253    }
254
255    /**
256     * Sets the time when this event occurred, in milliseconds since 1970.
257     *
258     * @param millis
259     *            the time when this event occurred, in milliseconds since 1970.
260     */
261    public void setMillis(long millis) {
262        this.millis = millis;
263    }
264
265    /**
266     * Gets the parameters.
267     *
268     * @return the array of parameters or {@code null} if there are no
269     *         parameters.
270     */
271    public Object[] getParameters() {
272        return parameters;
273    }
274
275    /**
276     * Sets the parameters.
277     *
278     * @param parameters
279     *            the array of parameters to set, may be {@code null}.
280     */
281    public void setParameters(Object[] parameters) {
282        this.parameters = parameters;
283    }
284
285    /**
286     * Gets the resource bundle used to localize the raw message during
287     * formatting.
288     *
289     * @return the associated resource bundle, {@code null} if none is
290     *         available or the message is not localizable.
291     */
292    public ResourceBundle getResourceBundle() {
293        return resourceBundle;
294    }
295
296    /**
297     * Sets the resource bundle used to localize the raw message during
298     * formatting.
299     *
300     * @param resourceBundle
301     *            the resource bundle to set, may be {@code null}.
302     */
303    public void setResourceBundle(ResourceBundle resourceBundle) {
304        this.resourceBundle = resourceBundle;
305    }
306
307    /**
308     * Gets the name of the resource bundle.
309     *
310     * @return the name of the resource bundle, {@code null} if none is
311     *         available or the message is not localizable.
312     */
313    public String getResourceBundleName() {
314        return resourceBundleName;
315    }
316
317    /**
318     * Sets the name of the resource bundle.
319     *
320     * @param resourceBundleName
321     *            the name of the resource bundle to set.
322     */
323    public void setResourceBundleName(String resourceBundleName) {
324        this.resourceBundleName = resourceBundleName;
325    }
326
327    /**
328     * Gets the sequence number.
329     *
330     * @return the sequence number.
331     */
332    public long getSequenceNumber() {
333        return sequenceNumber;
334    }
335
336    /**
337     * Sets the sequence number. It is usually not necessary to call this method
338     * to change the sequence number because the number is allocated when this
339     * instance is constructed.
340     *
341     * @param sequenceNumber
342     *            the sequence number to set.
343     */
344    public void setSequenceNumber(long sequenceNumber) {
345        this.sequenceNumber = sequenceNumber;
346    }
347
348    /**
349     * Gets the name of the class that is the source of this log record. This
350     * information can be changed, may be {@code null} and is untrusted.
351     *
352     * @return the name of the source class of this log record (possiblity {@code null})
353     */
354    public String getSourceClassName() {
355        initSource();
356        return sourceClassName;
357    }
358
359    /*
360     *  Init the sourceClass and sourceMethod fields.
361     */
362    private void initSource() {
363        if (sourceInitialized) {
364            return;
365        }
366
367        boolean sawLogger = false;
368        for (StackTraceElement element : new Throwable().getStackTrace()) {
369            String current = element.getClassName();
370            if (current.startsWith(Logger.class.getName())) {
371                sawLogger = true;
372            } else if (sawLogger) {
373                this.sourceClassName = element.getClassName();
374                this.sourceMethodName = element.getMethodName();
375                break;
376            }
377        }
378
379        sourceInitialized = true;
380    }
381
382    /**
383     * Sets the name of the class that is the source of this log record.
384     *
385     * @param sourceClassName
386     *            the name of the source class of this log record, may be
387     *            {@code null}.
388     */
389    public void setSourceClassName(String sourceClassName) {
390        sourceInitialized = true;
391        this.sourceClassName = sourceClassName;
392    }
393
394    /**
395     * Gets the name of the method that is the source of this log record.
396     *
397     * @return the name of the source method of this log record.
398     */
399    public String getSourceMethodName() {
400        initSource();
401        return sourceMethodName;
402    }
403
404    /**
405     * Sets the name of the method that is the source of this log record.
406     *
407     * @param sourceMethodName
408     *            the name of the source method of this log record, may be
409     *            {@code null}.
410     */
411    public void setSourceMethodName(String sourceMethodName) {
412        sourceInitialized = true;
413        this.sourceMethodName = sourceMethodName;
414    }
415
416    /**
417     * Gets a unique ID of the thread originating the log record. Every thread
418     * becomes a different ID.
419     * <p>
420     * Notice : the ID doesn't necessary map the OS thread ID
421     * </p>
422     *
423     * @return the ID of the thread originating this log record.
424     */
425    public int getThreadID() {
426        return threadID;
427    }
428
429    /**
430     * Sets the ID of the thread originating this log record.
431     *
432     * @param threadID
433     *            the new ID of the thread originating this log record.
434     */
435    public void setThreadID(int threadID) {
436        this.threadID = threadID;
437    }
438
439    /**
440     * Gets the {@code Throwable} object associated with this log record.
441     *
442     * @return the {@code Throwable} object associated with this log record.
443     */
444    public Throwable getThrown() {
445        return thrown;
446    }
447
448    /**
449     * Sets the {@code Throwable} object associated with this log record.
450     *
451     * @param thrown
452     *            the new {@code Throwable} object to associate with this log
453     *            record.
454     */
455    public void setThrown(Throwable thrown) {
456        this.thrown = thrown;
457    }
458
459    /*
460     * Customized serialization.
461     */
462    private void writeObject(ObjectOutputStream out) throws IOException {
463        out.defaultWriteObject();
464        out.writeByte(MAJOR);
465        out.writeByte(MINOR);
466        if (parameters == null) {
467            out.writeInt(-1);
468        } else {
469            out.writeInt(parameters.length);
470            for (Object element : parameters) {
471                out.writeObject((element == null) ? null : element.toString());
472            }
473        }
474    }
475
476    /*
477     * Customized deserialization.
478     */
479    private void readObject(ObjectInputStream in) throws IOException,
480            ClassNotFoundException {
481        in.defaultReadObject();
482        byte major = in.readByte();
483        byte minor = in.readByte();
484        // only check MAJOR version
485        if (major != MAJOR) {
486            throw new IOException("Different version " + Byte.valueOf(major) + "." + Byte.valueOf(minor));
487        }
488
489        int length = in.readInt();
490        if (length >= 0) {
491            parameters = new Object[length];
492            for (int i = 0; i < parameters.length; i++) {
493                parameters[i] = in.readObject();
494            }
495        }
496        if (resourceBundleName != null) {
497            try {
498                resourceBundle = Logger.loadResourceBundle(resourceBundleName);
499            } catch (MissingResourceException e) {
500                // Cannot find the specified resource bundle
501                resourceBundle = null;
502            }
503        }
504    }
505}
506