emul/compact/src/main/java/java/util/logging/LogRecord.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Sat, 07 Sep 2013 13:51:24 +0200
branchjdk7-b147
changeset 1258 724f3e1ea53e
permissions -rw-r--r--
Additional set of classes to make porting of lookup library more easier
     1 /*
     2  * Copyright (c) 2000, 2010, 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      */
   215     public ResourceBundle getResourceBundle() {
   216         return resourceBundle;
   217     }
   218 
   219     /**
   220      * Set the localization resource bundle.
   221      *
   222      * @param bundle  localization bundle (may be null)
   223      */
   224     public void setResourceBundle(ResourceBundle bundle) {
   225         resourceBundle = bundle;
   226     }
   227 
   228     /**
   229      * Get the localization resource bundle name
   230      * <p>
   231      * This is the name for the ResourceBundle that should be
   232      * used to localize the message string before formatting it.
   233      * The result may be null if the message is not localizable.
   234      */
   235     public String getResourceBundleName() {
   236         return resourceBundleName;
   237     }
   238 
   239     /**
   240      * Set the localization resource bundle name.
   241      *
   242      * @param name  localization bundle name (may be null)
   243      */
   244     public void setResourceBundleName(String name) {
   245         resourceBundleName = name;
   246     }
   247 
   248     /**
   249      * Get the logging message level, for example Level.SEVERE.
   250      * @return the logging message level
   251      */
   252     public Level getLevel() {
   253         return level;
   254     }
   255 
   256     /**
   257      * Set the logging message level, for example Level.SEVERE.
   258      * @param level the logging message level
   259      */
   260     public void setLevel(Level level) {
   261         if (level == null) {
   262             throw new NullPointerException();
   263         }
   264         this.level = level;
   265     }
   266 
   267     /**
   268      * Get the sequence number.
   269      * <p>
   270      * Sequence numbers are normally assigned in the LogRecord
   271      * constructor, which assigns unique sequence numbers to
   272      * each new LogRecord in increasing order.
   273      * @return the sequence number
   274      */
   275     public long getSequenceNumber() {
   276         return sequenceNumber;
   277     }
   278 
   279     /**
   280      * Set the sequence number.
   281      * <p>
   282      * Sequence numbers are normally assigned in the LogRecord constructor,
   283      * so it should not normally be necessary to use this method.
   284      */
   285     public void setSequenceNumber(long seq) {
   286         sequenceNumber = seq;
   287     }
   288 
   289     /**
   290      * Get the  name of the class that (allegedly) issued the logging request.
   291      * <p>
   292      * Note that this sourceClassName is not verified and may be spoofed.
   293      * This information may either have been provided as part of the
   294      * logging call, or it may have been inferred automatically by the
   295      * logging framework.  In the latter case, the information may only
   296      * be approximate and may in fact describe an earlier call on the
   297      * stack frame.
   298      * <p>
   299      * May be null if no information could be obtained.
   300      *
   301      * @return the source class name
   302      */
   303     public String getSourceClassName() {
   304         if (needToInferCaller) {
   305             inferCaller();
   306         }
   307         return sourceClassName;
   308     }
   309 
   310     /**
   311      * Set the name of the class that (allegedly) issued the logging request.
   312      *
   313      * @param sourceClassName the source class name (may be null)
   314      */
   315     public void setSourceClassName(String sourceClassName) {
   316         this.sourceClassName = sourceClassName;
   317         needToInferCaller = false;
   318     }
   319 
   320     /**
   321      * Get the  name of the method that (allegedly) issued the logging request.
   322      * <p>
   323      * Note that this sourceMethodName is not verified and may be spoofed.
   324      * This information may either have been provided as part of the
   325      * logging call, or it may have been inferred automatically by the
   326      * logging framework.  In the latter case, the information may only
   327      * be approximate and may in fact describe an earlier call on the
   328      * stack frame.
   329      * <p>
   330      * May be null if no information could be obtained.
   331      *
   332      * @return the source method name
   333      */
   334     public String getSourceMethodName() {
   335         if (needToInferCaller) {
   336             inferCaller();
   337         }
   338         return sourceMethodName;
   339     }
   340 
   341     /**
   342      * Set the name of the method that (allegedly) issued the logging request.
   343      *
   344      * @param sourceMethodName the source method name (may be null)
   345      */
   346     public void setSourceMethodName(String sourceMethodName) {
   347         this.sourceMethodName = sourceMethodName;
   348         needToInferCaller = false;
   349     }
   350 
   351     /**
   352      * Get the "raw" log message, before localization or formatting.
   353      * <p>
   354      * May be null, which is equivalent to the empty string "".
   355      * <p>
   356      * This message may be either the final text or a localization key.
   357      * <p>
   358      * During formatting, if the source logger has a localization
   359      * ResourceBundle and if that ResourceBundle has an entry for
   360      * this message string, then the message string is replaced
   361      * with the localized value.
   362      *
   363      * @return the raw message string
   364      */
   365     public String getMessage() {
   366         return message;
   367     }
   368 
   369     /**
   370      * Set the "raw" log message, before localization or formatting.
   371      *
   372      * @param message the raw message string (may be null)
   373      */
   374     public void setMessage(String message) {
   375         this.message = message;
   376     }
   377 
   378     /**
   379      * Get the parameters to the log message.
   380      *
   381      * @return the log message parameters.  May be null if
   382      *                  there are no parameters.
   383      */
   384     public Object[] getParameters() {
   385         return parameters;
   386     }
   387 
   388     /**
   389      * Set the parameters to the log message.
   390      *
   391      * @param parameters the log message parameters. (may be null)
   392      */
   393     public void setParameters(Object parameters[]) {
   394         this.parameters = parameters;
   395     }
   396 
   397     /**
   398      * Get an identifier for the thread where the message originated.
   399      * <p>
   400      * This is a thread identifier within the Java VM and may or
   401      * may not map to any operating system ID.
   402      *
   403      * @return thread ID
   404      */
   405     public int getThreadID() {
   406         return threadID;
   407     }
   408 
   409     /**
   410      * Set an identifier for the thread where the message originated.
   411      * @param threadID  the thread ID
   412      */
   413     public void setThreadID(int threadID) {
   414         this.threadID = threadID;
   415     }
   416 
   417     /**
   418      * Get event time in milliseconds since 1970.
   419      *
   420      * @return event time in millis since 1970
   421      */
   422     public long getMillis() {
   423         return millis;
   424     }
   425 
   426     /**
   427      * Set event time.
   428      *
   429      * @param millis event time in millis since 1970
   430      */
   431     public void setMillis(long millis) {
   432         this.millis = millis;
   433     }
   434 
   435     /**
   436      * Get any throwable associated with the log record.
   437      * <p>
   438      * If the event involved an exception, this will be the
   439      * exception object. Otherwise null.
   440      *
   441      * @return a throwable
   442      */
   443     public Throwable getThrown() {
   444         return thrown;
   445     }
   446 
   447     /**
   448      * Set a throwable associated with the log event.
   449      *
   450      * @param thrown  a throwable (may be null)
   451      */
   452     public void setThrown(Throwable thrown) {
   453         this.thrown = thrown;
   454     }
   455 
   456     private static final long serialVersionUID = 5372048053134512534L;
   457 
   458     /**
   459      * @serialData Default fields, followed by a two byte version number
   460      * (major byte, followed by minor byte), followed by information on
   461      * the log record parameter array.  If there is no parameter array,
   462      * then -1 is written.  If there is a parameter array (possible of zero
   463      * length) then the array length is written as an integer, followed
   464      * by String values for each parameter.  If a parameter is null, then
   465      * a null String is written.  Otherwise the output of Object.toString()
   466      * is written.
   467      */
   468     private void writeObject(ObjectOutputStream out) throws IOException {
   469         // We have to call defaultWriteObject first.
   470         out.defaultWriteObject();
   471 
   472         // Write our version number.
   473         out.writeByte(1);
   474         out.writeByte(0);
   475         if (parameters == null) {
   476             out.writeInt(-1);
   477             return;
   478         }
   479         out.writeInt(parameters.length);
   480         // Write string values for the parameters.
   481         for (int i = 0; i < parameters.length; i++) {
   482             if (parameters[i] == null) {
   483                 out.writeObject(null);
   484             } else {
   485                 out.writeObject(parameters[i].toString());
   486             }
   487         }
   488     }
   489 
   490     private void readObject(ObjectInputStream in)
   491                         throws IOException, ClassNotFoundException {
   492         // We have to call defaultReadObject first.
   493         in.defaultReadObject();
   494 
   495         // Read version number.
   496         byte major = in.readByte();
   497         byte minor = in.readByte();
   498         if (major != 1) {
   499             throw new IOException("LogRecord: bad version: " + major + "." + minor);
   500         }
   501         int len = in.readInt();
   502         if (len == -1) {
   503             parameters = null;
   504         } else {
   505             parameters = new Object[len];
   506             for (int i = 0; i < parameters.length; i++) {
   507                 parameters[i] = in.readObject();
   508             }
   509         }
   510         // If necessary, try to regenerate the resource bundle.
   511         if (resourceBundleName != null) {
   512             try {
   513                 resourceBundle = ResourceBundle.getBundle(resourceBundleName);
   514             } catch (MissingResourceException ex) {
   515                 // This is not a good place to throw an exception,
   516                 // so we simply leave the resourceBundle null.
   517                 resourceBundle = null;
   518             }
   519         }
   520 
   521         needToInferCaller = false;
   522     }
   523 
   524     // Private method to infer the caller's class and method names
   525     private void inferCaller() {
   526         needToInferCaller = false;
   527         JavaLangAccess access = SharedSecrets.getJavaLangAccess();
   528         Throwable throwable = new Throwable();
   529         int depth = access.getStackTraceDepth(throwable);
   530 
   531         boolean lookingForLogger = true;
   532         for (int ix = 0; ix < depth; ix++) {
   533             // Calling getStackTraceElement directly prevents the VM
   534             // from paying the cost of building the entire stack frame.
   535             StackTraceElement frame =
   536                 access.getStackTraceElement(throwable, ix);
   537             String cname = frame.getClassName();
   538             boolean isLoggerImpl = isLoggerImplFrame(cname);
   539             if (lookingForLogger) {
   540                 // Skip all frames until we have found the first logger frame.
   541                 if (isLoggerImpl) {
   542                     lookingForLogger = false;
   543                 }
   544             } else {
   545                 if (!isLoggerImpl) {
   546                     // skip reflection call
   547                     if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {
   548                        // We've found the relevant frame.
   549                        setSourceClassName(cname);
   550                        setSourceMethodName(frame.getMethodName());
   551                        return;
   552                     }
   553                 }
   554             }
   555         }
   556         // We haven't found a suitable frame, so just punt.  This is
   557         // OK as we are only committed to making a "best effort" here.
   558     }
   559 
   560     private boolean isLoggerImplFrame(String cname) {
   561         // the log record could be created for a platform logger
   562         return (cname.equals("java.util.logging.Logger") ||
   563                 cname.startsWith("java.util.logging.LoggingProxyImpl") ||
   564                 cname.startsWith("sun.util.logging."));
   565     }
   566 }