changeset 11521:710db3eff010

8072645: java.util.logging should use java.time to get more precise time stamps Summary: j.u.logging uses j.t.Instant to store LogRecord time stamps. XMLFormatter format is updated to allow for a new optional <nanos> element containing a nano second adjustment. SimpleFormatter passes a ZonedDateTime object to String.format. LogRecord getMillis/setMillis are deprecated, replaced by getInstant/setInstant. Reviewed-by: scolebourne, plevart, rriggs Contributed-by: daniel.fuchs@oracle.com, peter.levart@gmail.com
author dfuchs
date Wed, 25 Feb 2015 18:41:07 +0100
parents 367cd3425a59
children 7acbc61bf3fb
files src/java.base/share/classes/sun/util/logging/LoggingSupport.java src/java.base/share/classes/sun/util/logging/PlatformLogger.java src/java.logging/share/classes/java/util/logging/LogRecord.java src/java.logging/share/classes/java/util/logging/SimpleFormatter.java src/java.logging/share/classes/java/util/logging/XMLFormatter.java test/java/lang/invoke/lambda/LogGeneratedClassesTest.java test/java/util/logging/FileHandlerLongLimit.java test/java/util/logging/HigherResolutionTimeStamps/LogRecordWithNanos.java test/java/util/logging/HigherResolutionTimeStamps/LogRecordWithNanosAPI.java test/java/util/logging/HigherResolutionTimeStamps/SerializeLogRecord.java test/java/util/logging/HigherResolutionTimeStamps/SimpleFormatterNanos.java test/java/util/logging/HigherResolutionTimeStamps/XmlFormatterNanos.java
diffstat 12 files changed, 1535 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/sun/util/logging/LoggingSupport.java	Wed Feb 25 17:24:13 2015 +0000
+++ b/src/java.base/share/classes/sun/util/logging/LoggingSupport.java	Wed Feb 25 18:41:07 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -29,7 +29,7 @@
 import java.lang.reflect.Field;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.util.Date;
+import java.time.ZonedDateTime;
 
 /**
  * Internal API to support JRE implementation to detect if the java.util.logging
@@ -145,6 +145,11 @@
         return proxy.getLevelValue(level);
     }
 
+    // Since JDK 9, logging uses java.time to get more precise time stamps.
+    // It is possible to configure the simple format to print nano seconds (.%1$tN)
+    // by specifying:
+    // java.util.logging.SimpleFormatter.format=%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp %2$s%n%4$s: %5$s%6$s%n
+    // in the logging configuration
     private static final String DEFAULT_FORMAT =
         "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
 
@@ -171,7 +176,7 @@
         if (format != null) {
             try {
                 // validate the user-defined format string
-                String.format(format, new Date(), "", "", "", "", "");
+                String.format(format, ZonedDateTime.now(), "", "", "", "", "");
             } catch (IllegalArgumentException e) {
                 // illegal syntax; fall back to the default format
                 format = DEFAULT_FORMAT;
--- a/src/java.base/share/classes/sun/util/logging/PlatformLogger.java	Wed Feb 25 17:24:13 2015 +0000
+++ b/src/java.base/share/classes/sun/util/logging/PlatformLogger.java	Wed Feb 25 18:41:07 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -32,8 +32,11 @@
 import java.io.StringWriter;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import sun.misc.JavaLangAccess;
@@ -514,11 +517,9 @@
 
         private static final String formatString =
             LoggingSupport.getSimpleFormat(false); // don't check logging.properties
-
-        // minimize memory allocation
-        private Date date = new Date();
+        private final ZoneId zoneId = ZoneId.systemDefault();
         private synchronized String format(Level level, String msg, Throwable thrown) {
-            date.setTime(System.currentTimeMillis());
+            ZonedDateTime zdt = ZonedDateTime.now(zoneId);
             String throwable = "";
             if (thrown != null) {
                 StringWriter sw = new StringWriter();
@@ -530,7 +531,7 @@
             }
 
             return String.format(formatString,
-                                 date,
+                                 zdt,
                                  getCallerInfo(),
                                  name,
                                  level.name(),
--- a/src/java.logging/share/classes/java/util/logging/LogRecord.java	Wed Feb 25 17:24:13 2015 +0000
+++ b/src/java.logging/share/classes/java/util/logging/LogRecord.java	Wed Feb 25 18:41:07 2015 +0100
@@ -24,10 +24,12 @@
  */
 
 package java.util.logging;
+import java.time.Instant;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.io.*;
+import java.time.Clock;
 
 import sun.misc.JavaLangAccess;
 import sun.misc.SharedSecrets;
@@ -88,55 +90,94 @@
     private static final ThreadLocal<Integer> threadIds = new ThreadLocal<>();
 
     /**
-     * @serial Logging message level
+     * Logging message level
      */
     private Level level;
 
     /**
-     * @serial Sequence number
+     * Sequence number
      */
     private long sequenceNumber;
 
     /**
-     * @serial Class that issued logging call
+     * Class that issued logging call
      */
     private String sourceClassName;
 
     /**
-     * @serial Method that issued logging call
+     * Method that issued logging call
      */
     private String sourceMethodName;
 
     /**
-     * @serial Non-localized raw message text
+     * Non-localized raw message text
      */
     private String message;
 
     /**
-     * @serial Thread ID for thread that issued logging call.
+     * Thread ID for thread that issued logging call.
      */
     private int threadID;
 
     /**
-     * @serial Event time in milliseconds since 1970
-     */
-    private long millis;
-
-    /**
-     * @serial The Throwable (if any) associated with log message
+     * The Throwable (if any) associated with log message
      */
     private Throwable thrown;
 
     /**
-     * @serial Name of the source Logger.
+     * Name of the source Logger.
      */
     private String loggerName;
 
     /**
-     * @serial Resource bundle name to localized log message.
+     * Resource bundle name to localized log message.
      */
     private String resourceBundleName;
 
+    /**
+     * Event time.
+     * @since 1.9
+     */
+    private Instant instant;
+
+    /**
+     * @serialField level Level Logging message level
+     * @serialField sequenceNumber long Sequence number
+     * @serialField sourceClassName String Class that issued logging call
+     * @serialField sourceMethodName String Method that issued logging call
+     * @serialField message String Non-localized raw message text
+     * @serialField threadID int Thread ID for thread that issued logging call
+     * @serialField millis long Truncated event time in milliseconds since 1970
+     *              - calculated as getInstant().toEpochMilli().
+     *               The event time instant can be reconstructed using
+     * <code>Instant.ofEpochSecond(millis/1000, (millis % 1000) * 1000_000 + nanoAdjustment)</code>
+     * @serialField nanoAdjustment int Nanoseconds adjustment to the millisecond of
+     *              event time - calculated as getInstant().getNano() % 1000_000
+     *               The event time instant can be reconstructed using
+     * <code>Instant.ofEpochSecond(millis/1000, (millis % 1000) * 1000_000 + nanoAdjustment)</code>
+     *              <p>
+     *              Since: 1.9
+     * @serialField thrown Throwable The Throwable (if any) associated with log
+     *              message
+     * @serialField loggerName String Name of the source Logger
+     * @serialField resourceBundleName String Resource bundle name to localized
+     *              log message
+     */
+    private static final ObjectStreamField[] serialPersistentFields =
+        new ObjectStreamField[] {
+            new ObjectStreamField("level", Level.class),
+            new ObjectStreamField("sequenceNumber", long.class),
+            new ObjectStreamField("sourceClassName", String.class),
+            new ObjectStreamField("sourceMethodName", String.class),
+            new ObjectStreamField("message", String.class),
+            new ObjectStreamField("threadID", int.class),
+            new ObjectStreamField("millis", long.class),
+            new ObjectStreamField("nanoAdjustment", int.class),
+            new ObjectStreamField("thrown", Throwable.class),
+            new ObjectStreamField("loggerName", String.class),
+            new ObjectStreamField("resourceBundleName", String.class),
+        };
+
     private transient boolean needToInferCaller;
     private transient Object parameters[];
     private transient ResourceBundle resourceBundle;
@@ -164,7 +205,10 @@
      * The sequence property will be initialized with a new unique value.
      * These sequence values are allocated in increasing order within a VM.
      * <p>
-     * The millis property will be initialized to the current time.
+     * Since JDK 1.9, the event time is represented by an {@link Instant}.
+     * The instant property will be initialized to the {@linkplain
+     * Instant#now() current instant}, using the best available
+     * {@linkplain Clock#systemUTC() clock} on the system.
      * <p>
      * The thread ID property will be initialized with a unique ID for
      * the current thread.
@@ -173,6 +217,7 @@
      *
      * @param level  a logging level value
      * @param msg  the raw non-localized logging message (may be null)
+     * @see java.time.Clock#systemUTC()
      */
     public LogRecord(Level level, String msg) {
         this.level = Objects.requireNonNull(level);
@@ -180,7 +225,7 @@
         // Assign a thread ID and a unique sequence number.
         sequenceNumber = globalSequenceNumber.getAndIncrement();
         threadID = defaultThreadID();
-        millis = System.currentTimeMillis();
+        instant = Instant.now();
         needToInferCaller = true;
    }
 
@@ -416,21 +461,63 @@
     }
 
     /**
-     * Get event time in milliseconds since 1970.
+     * Get truncated event time in milliseconds since 1970.
      *
-     * @return event time in millis since 1970
+     * @return truncated event time in millis since 1970
+     *
+     * @implSpec This is equivalent to calling
+     *      {@link #getInstant() getInstant().toEpochMilli()}.
+     *
+     * @deprecated To get the full nanosecond resolution event time,
+     *             use {@link #getInstant()}.
+     *
+     * @see #getInstant()
      */
+    @Deprecated
     public long getMillis() {
-        return millis;
+        return instant.toEpochMilli();
     }
 
     /**
      * Set event time.
      *
-     * @param millis event time in millis since 1970
+     * @param millis event time in millis since 1970.
+     *
+     * @implSpec This is equivalent to calling
+     *      {@link #setInstant(java.time.Instant)
+     *      setInstant(Instant.ofEpochMilli(millis))}.
+     *
+     * @deprecated To set event time with nanosecond resolution,
+     *             use {@link #setInstant(java.time.Instant)}.
+     *
+     * @see #setInstant(java.time.Instant)
      */
+    @Deprecated
     public void setMillis(long millis) {
-        this.millis = millis;
+        this.instant = Instant.ofEpochMilli(millis);
+    }
+
+    /**
+     * Gets the instant that the event occurred.
+     *
+     * @return the instant that the event occurred.
+     *
+     * @since 1.9
+     */
+    public Instant getInstant() {
+        return instant;
+    }
+
+    /**
+     * Sets the instant that the event occurred.
+     *
+     * @param instant the instant that the event occurred.
+     *
+     * @throws NullPointerException if {@code instant} is null.
+     * @since 1.9
+     */
+    public void setInstant(Instant instant) {
+        this.instant = Objects.requireNonNull(instant);
     }
 
     /**
@@ -457,7 +544,7 @@
     private static final long serialVersionUID = 5372048053134512534L;
 
     /**
-     * @serialData Default fields, followed by a two byte version number
+     * @serialData Serialized fields, followed by a two byte version number
      * (major byte, followed by minor byte), followed by information on
      * the log record parameter array.  If there is no parameter array,
      * then -1 is written.  If there is a parameter array (possible of zero
@@ -467,8 +554,20 @@
      * is written.
      */
     private void writeObject(ObjectOutputStream out) throws IOException {
-        // We have to call defaultWriteObject first.
-        out.defaultWriteObject();
+        // We have to write serialized fields first.
+        ObjectOutputStream.PutField pf = out.putFields();
+        pf.put("level", level);
+        pf.put("sequenceNumber", sequenceNumber);
+        pf.put("sourceClassName", sourceClassName);
+        pf.put("sourceMethodName", sourceMethodName);
+        pf.put("message", message);
+        pf.put("threadID", threadID);
+        pf.put("millis", instant.toEpochMilli());
+        pf.put("nanoAdjustment", instant.getNano() % 1000_000);
+        pf.put("thrown", thrown);
+        pf.put("loggerName", loggerName);
+        pf.put("resourceBundleName", resourceBundleName);
+        out.writeFields();
 
         // Write our version number.
         out.writeByte(1);
@@ -486,8 +585,21 @@
 
     private void readObject(ObjectInputStream in)
                         throws IOException, ClassNotFoundException {
-        // We have to call defaultReadObject first.
-        in.defaultReadObject();
+        // We have to read serialized fields first.
+        ObjectInputStream.GetField gf = in.readFields();
+        level = (Level) gf.get("level", null);
+        sequenceNumber = gf.get("sequenceNumber", 0L);
+        sourceClassName = (String) gf.get("sourceClassName", null);
+        sourceMethodName = (String) gf.get("sourceMethodName", null);
+        message = (String) gf.get("message", null);
+        threadID = gf.get("threadID", 0);
+        long millis = gf.get("millis", 0L);
+        int nanoOfMilli = gf.get("nanoAdjustment", 0);
+        instant = Instant.ofEpochSecond(
+            millis / 1000L, (millis % 1000L) * 1000_000L + nanoOfMilli);
+        thrown = (Throwable) gf.get("thrown", null);
+        loggerName = (String) gf.get("loggerName", null);
+        resourceBundleName = (String) gf.get("resourceBundleName", null);
 
         // Read version number.
         byte major = in.readByte();
--- a/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java	Wed Feb 25 17:24:13 2015 +0000
+++ b/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java	Wed Feb 25 18:41:07 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,8 +27,9 @@
 package java.util.logging;
 
 import java.io.*;
-import java.text.*;
-import java.util.Date;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import sun.util.logging.LoggingSupport;
 
 /**
@@ -59,8 +60,8 @@
 public class SimpleFormatter extends Formatter {
 
     // format string for printing the log record
-    private static final String format = LoggingSupport.getSimpleFormat();
-    private final Date dat = new Date();
+    private final String format = LoggingSupport.getSimpleFormat();
+    private final ZoneId zoneId = ZoneId.systemDefault();
 
     /**
      * Format the given LogRecord.
@@ -79,8 +80,9 @@
      *     java.util.Formatter} format string specified in the
      *     {@code java.util.logging.SimpleFormatter.format} property
      *     or the default format.</li>
-     * <li>{@code date} - a {@link Date} object representing
-     *     {@linkplain LogRecord#getMillis event time} of the log record.</li>
+     * <li>{@code date} - a {@link ZonedDateTime} object representing
+     *     {@linkplain LogRecord#getInstant() event time} of the log record
+     *      in the {@link ZoneId#systemDefault()} system time zone.</li>
      * <li>{@code source} - a string representing the caller, if available;
      *     otherwise, the logger's name.</li>
      * <li>{@code logger} - the logger's name.</li>
@@ -129,6 +131,16 @@
      *     Mar 22, 2011 1:11:31 PM MyClass fatal
      *     SEVERE: several message with an exception
      *     </pre></li>
+     * <li> {@code java.util.logging.SimpleFormatter.format="%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp %2$s%n%4$s: %5$s%6$s%n"}
+     *      <p>Since JDK 1.9, {@code java.util.logging} uses {@link
+     *         java.time.Clock#systemUTC() java.time} to create more precise time
+     *         stamps.
+     *         The format above can be used to add a {@code .%1$tN} to the
+     *         date/time formatting so that nanoseconds will also be printed:
+     *     <pre>
+     *     Feb 06, 2015 5:33:10.279216000 PM example.Main main
+     *     INFO: This is a test
+     *     </pre></li>
      * </ul>
      * <p>This method can also be overridden in a subclass.
      * It is recommended to use the {@link Formatter#formatMessage}
@@ -137,8 +149,10 @@
      * @param record the log record to be formatted.
      * @return a formatted log record
      */
+    @Override
     public synchronized String format(LogRecord record) {
-        dat.setTime(record.getMillis());
+        ZonedDateTime zdt = ZonedDateTime.ofInstant(
+                record.getInstant(), zoneId);
         String source;
         if (record.getSourceClassName() != null) {
             source = record.getSourceClassName();
@@ -159,7 +173,7 @@
             throwable = sw.toString();
         }
         return String.format(format,
-                             dat,
+                             zdt,
                              source,
                              record.getLoggerName(),
                              record.getLevel().getLocalizedLevelName(),
--- a/src/java.logging/share/classes/java/util/logging/XMLFormatter.java	Wed Feb 25 17:24:13 2015 +0000
+++ b/src/java.logging/share/classes/java/util/logging/XMLFormatter.java	Wed Feb 25 18:41:07 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,8 +26,9 @@
 
 package java.util.logging;
 
-import java.io.*;
 import java.nio.charset.Charset;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 
 /**
@@ -40,11 +41,70 @@
  * but it is recommended that it normally be used with UTF-8.  The
  * character encoding can be set on the output Handler.
  *
+ * @implSpec Since JDK 1.9, instances of {@linkplain LogRecord} contain
+ * an {@link LogRecord#getInstant() Instant} which can have nanoseconds below
+ * the millisecond resolution.
+ * The DTD specification has been updated to allow for an optional
+ * {@code <nanos>} element. By default, the XMLFormatter will compute the
+ * nanosecond adjustment below the millisecond resolution (using
+ * {@code LogRecord.getInstant().getNano() % 1000_000}) - and if this is not 0,
+ * this adjustment value will be printed in the new {@code <nanos>} element.
+ * The event instant can then be reconstructed using
+ * {@code Instant.ofEpochSecond(millis/1000L, (millis % 1000L) * 1000_000L + nanos)}
+ * where {@code millis} and {@code nanos} represent the numbers serialized in
+ * the {@code <millis>} and {@code <nanos>} elements, respectively.
+ * <br>
+ * The {@code <date>} element will now contain the whole instant as formatted
+ * by the {@link DateTimeFormatter#ISO_INSTANT DateTimeFormatter.ISO_INSTANT}
+ * formatter.
+ * <p>
+ * For compatibility with old parsers, XMLFormatters can
+ * be configured to revert to the old format by specifying a
+ * {@code <xml-formatter-fully-qualified-class-name>.useInstant = false}
+ * {@linkplain LogManager#getProperty(java.lang.String) property} in the
+ * logging configuration. When {@code useInstant} is {@code false}, the old
+ * formatting will be preserved. When {@code useInstant} is {@code true}
+ * (the default), the {@code <nanos>} element will be printed and the
+ * {@code <date>} element will contain the {@linkplain
+ * DateTimeFormatter#ISO_INSTANT formatted} instant.
+ * <p>
+ * For instance, in order to configure plain instances of XMLFormatter to omit
+ * the new {@code <nano>} element,
+ * {@code java.util.logging.XMLFormatter.useInstant = false} can be specified
+ * in the logging configuration.
+ *
  * @since 1.4
  */
 
 public class XMLFormatter extends Formatter {
-    private LogManager manager = LogManager.getLogManager();
+    private final LogManager manager = LogManager.getLogManager();
+    private final boolean useInstant;
+
+    /**
+     * Creates a new instance of XMLFormatter.
+     *
+     * @implSpec
+     *    Since JDK 1.9, the XMLFormatter will print out the record {@linkplain
+     *    LogRecord#getInstant() event time} as an Instant. This instant
+     *    has the best resolution available on the system. The {@code <date>}
+     *    element will contain the instant as formatted by the {@link
+     *    DateTimeFormatter#ISO_INSTANT}.
+     *    In addition, an optional {@code <nanos>} element containing a
+     *    nanosecond adjustment will be printed if the instant contains some
+     *    nanoseconds below the millisecond resolution.
+     *    <p>
+     *    This new behavior can be turned off, and the old formatting restored,
+     *    by specifying a property in the {@linkplain
+     *    LogManager#getProperty(java.lang.String) logging configuration}.
+     *    If {@code LogManager.getLogManager().getProperty(
+     *    this.getClass().getName()+".useInstant")} is {@code "false"} or
+     *    {@code "0"}, the old formatting will be restored.
+     */
+    public XMLFormatter() {
+        useInstant = (manager == null)
+            || manager.getBooleanProperty(
+                    this.getClass().getName()+".useInstant", true);
+    }
 
     // Append a two digit number.
     private void a2(StringBuilder sb, int x) {
@@ -102,18 +162,35 @@
      * @param record the log record to be formatted.
      * @return a formatted log record
      */
+    @Override
     public String format(LogRecord record) {
         StringBuilder sb = new StringBuilder(500);
         sb.append("<record>\n");
 
+        final Instant instant = record.getInstant();
+
         sb.append("  <date>");
-        appendISO8601(sb, record.getMillis());
+        if (useInstant) {
+            // If useInstant is true - we will print the instant in the
+            // date field, using the ISO_INSTANT formatter.
+            DateTimeFormatter.ISO_INSTANT.formatTo(instant, sb);
+        } else {
+            // If useInstant is false - we will keep the 'old' formating
+            appendISO8601(sb, instant.toEpochMilli());
+        }
         sb.append("</date>\n");
 
         sb.append("  <millis>");
-        sb.append(record.getMillis());
+        sb.append(instant.toEpochMilli());
         sb.append("</millis>\n");
 
+        final int nanoAdjustment = instant.getNano() % 1000_000;
+        if (useInstant && nanoAdjustment != 0) {
+            sb.append("  <nanos>");
+            sb.append(nanoAdjustment);
+            sb.append("</nanos>\n");
+        }
+
         sb.append("  <sequence>");
         sb.append(record.getSequenceNumber());
         sb.append("</sequence>\n");
@@ -223,6 +300,7 @@
      * @param   h  The target handler (can be null)
      * @return  a valid XML string
      */
+    @Override
     public String getHead(Handler h) {
         StringBuilder sb = new StringBuilder();
         String encoding;
@@ -251,6 +329,7 @@
         sb.append(encoding);
         sb.append("\"");
         sb.append(" standalone=\"no\"?>\n");
+
         sb.append("<!DOCTYPE log SYSTEM \"logger.dtd\">\n");
         sb.append("<log>\n");
         return sb.toString();
@@ -262,6 +341,7 @@
      * @param   h  The target handler (can be null)
      * @return  a valid XML string
      */
+    @Override
     public String getTail(Handler h) {
         return "</log>\n";
     }
--- a/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java	Wed Feb 25 17:24:13 2015 +0000
+++ b/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java	Wed Feb 25 18:41:07 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -32,6 +32,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Predicate;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -235,8 +236,23 @@
                                   .filter(s -> s.startsWith("WARNING: Exception"))
                                   .count(),
                      2, "show error each capture");
-        // dumpLong/com/example/nosense/nosense
-        assertEquals(Files.walk(Paths.get("dumpLong")).count(), 5, "Two lambda captured failed to log");
+        // dumpLong/com/example/nonsense/nonsense
+        Path dumpPath = Paths.get("dumpLong/com/example/nonsense");
+        Predicate<Path> filter = p -> p.getParent() == null || dumpPath.startsWith(p) || p.startsWith(dumpPath);
+        boolean debug = true;
+        if (debug) {
+           Files.walk(Paths.get("dumpLong"))
+                .forEachOrdered(p -> {
+                    if (filter.test(p)) {
+                        System.out.println("accepted: " + p.toString());
+                    } else {
+                        System.out.println("filetered out: " + p.toString());
+                    }
+                 });
+        }
+        assertEquals(Files.walk(Paths.get("dumpLong"))
+                .filter(filter)
+                .count(), 5, "Two lambda captured failed to log");
         tr.assertZero("Should still return 0");
     }
 }
--- a/test/java/util/logging/FileHandlerLongLimit.java	Wed Feb 25 17:24:13 2015 +0000
+++ b/test/java/util/logging/FileHandlerLongLimit.java	Wed Feb 25 18:41:07 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -346,26 +346,29 @@
             assertEquals(0, getWritten(metered), "written");
 
             // now we're going to publish a series of log records
+            // we're using the same log record over and over to make
+            // sure we get the same amount of bytes.
             String msg = "this is at least 10 chars long";
-            fh.publish(new LogRecord(Level.SEVERE, msg));
+            LogRecord record = new LogRecord(Level.SEVERE, msg);
+            fh.publish(record);
             fh.flush();
             long w = getWritten(metered);
             long offset = getWritten(metered);
             System.out.println("first offset is:  " + offset);
 
-            fh.publish(new LogRecord(Level.SEVERE, msg));
+            fh.publish(record);
             fh.flush();
             offset = getWritten(metered) - w;
             w = getWritten(metered);
             System.out.println("second offset is: " + offset);
 
-            fh.publish(new LogRecord(Level.SEVERE, msg));
+            fh.publish(record);
             fh.flush();
             offset = getWritten(metered) - w;
             w = getWritten(metered);
             System.out.println("third offset is:  " + offset);
 
-            fh.publish(new LogRecord(Level.SEVERE, msg));
+            fh.publish(record);
             fh.flush();
             offset = getWritten(metered) - w;
             System.out.println("fourth offset is: " + offset);
@@ -377,7 +380,7 @@
 
             // publish one more log record. we should still be just beneath
             // the limit
-            fh.publish(new LogRecord(Level.SEVERE, msg));
+            fh.publish(record);
             fh.flush();
             assertEquals(w+offset, getWritten(metered), "written");
 
@@ -392,9 +395,9 @@
             // writing the first log record or just before writing the next
             // one. We publich two - so we're sure that the log must have
             // rotated.
-            fh.publish(new LogRecord(Level.SEVERE, msg));
+            fh.publish(record);
             fh.flush();
-            fh.publish(new LogRecord(Level.SEVERE, msg));
+            fh.publish(record);
             fh.flush();
 
             // Check that fh.meter is a different instance of MeteredStream.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/logging/HigherResolutionTimeStamps/LogRecordWithNanos.java	Wed Feb 25 18:41:07 2015 +0100
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+
+/**
+ * @test
+ * @bug 8072645
+ * @summary tests that LogRecord has nanos...
+ * @run main LogRecordWithNanos
+ * @author danielfuchs
+ */
+public class LogRecordWithNanos {
+
+    static final int MILLIS_IN_SECOND = 1000;
+    static final int NANOS_IN_MILLI = 1000_000;
+    static final int NANOS_IN_SECOND = 1000_000_000;
+
+    static final boolean verbose = false;
+
+    static final class TestAssertException extends RuntimeException {
+        TestAssertException(String msg) { super(msg); }
+    }
+
+    private static void assertEquals(long expected, long received, String msg) {
+        if (expected != received) {
+            throw new TestAssertException("Unexpected result for " + msg
+                    + ".\n\texpected: " + expected
+                    +  "\n\tactual:   " + received);
+        } else if (verbose) {
+            System.out.println("Got expected " + msg + ": " + received);
+        }
+    }
+
+    /**
+     * Serializes a log record, then deserializes it and check that both
+     * records match.
+     * @param record the log record to serialize & deserialize.
+     * @throws IOException Unexpected.
+     * @throws ClassNotFoundException  Unexpected.
+     */
+    public static void test(LogRecord record)
+            throws IOException, ClassNotFoundException {
+
+        // Format the given logRecord using the SimpleFormatter
+        SimpleFormatter formatter = new SimpleFormatter();
+        String str = formatter.format(record);
+
+        // Serialize the given LogRecord
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final ObjectOutputStream oos = new ObjectOutputStream(baos);
+        oos.writeObject(record);
+        oos.flush();
+        oos.close();
+
+        // First checks that the log record can be deserialized
+        final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        final ObjectInputStream ois = new ObjectInputStream(bais);
+        final LogRecord record2 = (LogRecord)ois.readObject();
+
+        assertEquals(record.getMillis(), record2.getMillis(), "getMillis()");
+        assertEquals(record.getInstant().getEpochSecond(),
+                record2.getInstant().getEpochSecond(),
+                "getInstant().getEpochSecond()");
+        assertEquals(record.getInstant().getNano(),
+                record2.getInstant().getNano(),
+                "getInstant().getNano()");
+        assertEquals(record.getInstant().toEpochMilli(),
+                record2.getInstant().toEpochMilli(),
+                "getInstant().toEpochMilli()");
+        assertEquals(record.getMillis(),
+                record.getInstant().toEpochMilli(),
+                "getMillis()/getInstant().toEpochMilli()");
+        assertEquals(record2.getMillis(),
+                record2.getInstant().toEpochMilli(),
+                "getMillis()/getInstant().toEpochMilli()");
+        assertEquals((record.getMillis()%MILLIS_IN_SECOND)*NANOS_IN_MILLI
+                + (record.getInstant().getNano() % NANOS_IN_MILLI),
+                record.getInstant().getNano(),
+                "record.getMillis()%1000)*1000_000"
+                + " + record.getInstant().getNano()%1000_000 / getInstant().getNano()");
+        assertEquals((record2.getMillis()%MILLIS_IN_SECOND)*NANOS_IN_MILLI
+                + (record2.getInstant().getNano() % NANOS_IN_MILLI),
+                record2.getInstant().getNano(),
+                "record2.getMillis()%1000)*1000_000"
+                + " + record2.getInstant().getNano()%1000_000 / getInstant().getNano()");
+
+        // Format the deserialized LogRecord using the SimpleFormatter, and
+        // check that the string representation obtained matches the string
+        // representation of the original LogRecord
+        String str2 = formatter.format(record2);
+        if (!str.equals(str2))
+            throw new RuntimeException("Unexpected values in deserialized object:"
+                + "\n\tExpected:  " + str
+                + "\n\tRetrieved: "+str);
+
+    }
+
+
+    public static void main(String[] args) throws Exception {
+        int count=0;
+        for (int i=0; i<1000; i++) {
+            LogRecord record = new LogRecord(Level.INFO, "Java Version: {0}");
+            record.setLoggerName("test");
+            record.setParameters(new Object[] {System.getProperty("java.version")});
+            if (record.getInstant().getNano() % 1000_000 != 0) {
+                count++;
+            }
+            test(record);
+        }
+        if (count == 0) {
+            throw new RuntimeException("Expected at least one record to have nanos");
+        }
+        System.out.println(count + "/1000 records had nano adjustment.");
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/logging/HigherResolutionTimeStamps/LogRecordWithNanosAPI.java	Wed Feb 25 18:41:07 2015 +0100
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.time.Instant;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+
+/**
+ * @test
+ * @bug 8072645
+ * @summary tests the new methods added to LogRecord.
+ * @run main LogRecordWithNanosAPI
+ * @author danielfuchs
+ */
+public class LogRecordWithNanosAPI {
+
+    static final int MILLIS_IN_SECOND = 1000;
+    static final int NANOS_IN_MILLI = 1000_000;
+    static final int NANOS_IN_SECOND = 1000_000_000;
+
+    static final boolean verbose = true;
+
+    static final class TestAssertException extends RuntimeException {
+        TestAssertException(String msg) { super(msg); }
+    }
+
+    private static void assertEquals(long expected, long received, String msg) {
+        if (expected != received) {
+            throw new TestAssertException("Unexpected result for " + msg
+                    + ".\n\texpected: " + expected
+                    +  "\n\tactual:   " + received);
+        } else if (verbose) {
+            System.out.println("Got expected " + msg + ": " + received);
+        }
+    }
+
+    private static void assertEquals(Object expected, Object received, String msg) {
+        if (!Objects.equals(expected, received)) {
+            throw new TestAssertException("Unexpected result for " + msg
+                    + ".\n\texpected: " + expected
+                    +  "\n\tactual:   " + received);
+        } else if (verbose) {
+            System.out.println("Got expected " + msg + ": " + received);
+        }
+    }
+
+    // The nano second fractional part of a second, contained in a time expressed
+    // as a number of millisecond from the epoch.
+    private static long nanoInSecondFromEpochMilli(long millis) {
+        return (((millis%MILLIS_IN_SECOND) + MILLIS_IN_SECOND)%MILLIS_IN_SECOND)*NANOS_IN_MILLI;
+    }
+
+    /**
+     * Serializes a log record, then deserializes it and check that both
+     * records match.
+     * @param record the log record to serialize & deserialize.
+     * @param hasExceedingNanos whether the record has a nano adjustment whose
+     *            value exceeds 1ms.
+     * @throws IOException Unexpected.
+     * @throws ClassNotFoundException  Unexpected.
+     */
+    public static void test(LogRecord record, boolean hasExceedingNanos)
+            throws IOException, ClassNotFoundException {
+
+        // Format the given logRecord using the SimpleFormatter
+        SimpleFormatter formatter = new SimpleFormatter();
+        String str = formatter.format(record);
+
+        // Serialize the given LogRecord
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final ObjectOutputStream oos = new ObjectOutputStream(baos);
+        oos.writeObject(record);
+        oos.flush();
+        oos.close();
+
+        // First checks that the log record can be deserialized
+        final ByteArrayInputStream bais =
+                new ByteArrayInputStream(baos.toByteArray());
+        final ObjectInputStream ois = new ObjectInputStream(bais);
+        final LogRecord record2 = (LogRecord)ois.readObject();
+
+        assertEquals(record.getMillis(), record2.getMillis(), "getMillis()");
+        assertEquals(record.getInstant().getEpochSecond(),
+                record2.getInstant().getEpochSecond(),
+                "getInstant().getEpochSecond()");
+        assertEquals(record.getInstant().getNano(),
+                record2.getInstant().getNano(),
+                "getInstant().getNano()");
+        assertEquals(record.getInstant().toEpochMilli(),
+                record2.getInstant().toEpochMilli(),
+                "getInstant().toEpochMilli()");
+        long millis = record.getMillis();
+        millis = hasExceedingNanos
+                ? Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                        (millis%MILLIS_IN_SECOND)*NANOS_IN_MILLI
+                        + record.getInstant().getNano() % NANOS_IN_MILLI).toEpochMilli()
+                : millis;
+        assertEquals(millis, record.getInstant().toEpochMilli(),
+                "getMillis()/getInstant().toEpochMilli()");
+        millis = record2.getMillis();
+        millis = hasExceedingNanos
+                ? Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                        (millis%MILLIS_IN_SECOND)*NANOS_IN_MILLI
+                        + record2.getInstant().getNano() % NANOS_IN_MILLI).toEpochMilli()
+                : millis;
+        assertEquals(millis, record2.getInstant().toEpochMilli(),
+                "getMillis()/getInstant().toEpochMilli()");
+        long nanos = nanoInSecondFromEpochMilli(record.getMillis())
+                + record.getInstant().getNano() % NANOS_IN_MILLI;
+        assertEquals(nanos, record.getInstant().getNano(),
+                "nanoInSecondFromEpochMilli(record.getMillis())"
+                + " + record.getInstant().getNano() % NANOS_IN_MILLI /getInstant().getNano()");
+        nanos = nanoInSecondFromEpochMilli(record2.getMillis())
+                + record2.getInstant().getNano() % NANOS_IN_MILLI;
+        assertEquals(nanos, record2.getInstant().getNano(),
+                "nanoInSecondFromEpochMilli(record2.getMillis())"
+                + " + record2.getInstant().getNano() % NANOS_IN_MILLI /getInstant().getNano()");
+
+        // Format the deserialized LogRecord using the SimpleFormatter, and
+        // check that the string representation obtained matches the string
+        // representation of the original LogRecord
+        String str2 = formatter.format(record2);
+        if (!str.equals(str2))
+            throw new RuntimeException("Unexpected values in deserialized object:"
+                + "\n\tExpected:  " + str
+                + "\n\tRetrieved: "+str);
+
+    }
+
+
+    public static void main(String[] args) throws Exception {
+        int count=0;
+        LogRecord record = new LogRecord(Level.INFO, "Java Version: {0}");
+        record.setLoggerName("test");
+        record.setParameters(new Object[] {System.getProperty("java.version")});
+        final int nanos = record.getInstant().getNano() % NANOS_IN_MILLI;
+        final long millis = record.getMillis();
+        final Instant instant = record.getInstant();
+        if (millis != instant.toEpochMilli()) {
+            throw new RuntimeException("Unexpected millis: "
+                    + record.getMillis());
+        }
+        test(record, false);
+
+        // nano adjustment < 1ms (canonical case)
+        int newNanos = (nanos + 111111) % NANOS_IN_MILLI;
+        record.setInstant(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis % MILLIS_IN_SECOND) * NANOS_IN_MILLI + newNanos));
+        assertEquals(newNanos, record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+        assertEquals(millis, record.getMillis(), "record.getMillis()");
+        assertEquals(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis%MILLIS_IN_SECOND)*NANOS_IN_MILLI + newNanos),
+                record.getInstant(), "record.getInstant()");
+        test(record, false);
+        assertEquals(newNanos, record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+        assertEquals(millis, record.getMillis(), "record.getMillis()");
+        assertEquals(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis%MILLIS_IN_SECOND)*NANOS_IN_MILLI + newNanos),
+                record.getInstant(), "record.getInstant()");
+
+        // nano adjustment > 1ms - non canonical - should still work
+        int newExceedingNanos = 2111_111;
+        record.setInstant(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis % MILLIS_IN_SECOND) * NANOS_IN_MILLI + newExceedingNanos));
+        assertEquals(newExceedingNanos % NANOS_IN_MILLI,
+                record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+        assertEquals(millis + newExceedingNanos / NANOS_IN_MILLI,
+                record.getMillis(), "record.getMillis()");
+        assertEquals(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis%MILLIS_IN_SECOND)*NANOS_IN_MILLI + newExceedingNanos),
+                record.getInstant(), "record.getInstant()");
+        test(record, true);
+        assertEquals(newExceedingNanos % NANOS_IN_MILLI,
+                record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+        assertEquals(millis  + newExceedingNanos / NANOS_IN_MILLI,
+                record.getMillis(), "record.getMillis()");
+        assertEquals(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis%MILLIS_IN_SECOND)*NANOS_IN_MILLI + newExceedingNanos),
+                record.getInstant(), "record.getInstant()");
+
+        // nano adjustement > 1s - non canonical - should still work
+        newExceedingNanos = 1111_111_111;
+        record.setInstant(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis % MILLIS_IN_SECOND) * NANOS_IN_MILLI + newExceedingNanos));
+        assertEquals(newExceedingNanos % NANOS_IN_MILLI,
+                record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+        assertEquals(millis  + newExceedingNanos / NANOS_IN_MILLI,
+                record.getMillis(), "record.getMillis()");
+        assertEquals(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis%MILLIS_IN_SECOND)*NANOS_IN_MILLI + newExceedingNanos),
+                record.getInstant(), "record.getInstant()");
+        test(record, true);
+        assertEquals(newExceedingNanos % NANOS_IN_MILLI,
+                record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+        assertEquals(millis  + newExceedingNanos / NANOS_IN_MILLI,
+                record.getMillis(), "record.getMillis()");
+        assertEquals(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis%MILLIS_IN_SECOND)*NANOS_IN_MILLI + newExceedingNanos),
+                record.getInstant(), "record.getInstant()");
+
+        // nano adjustement < 0 - non canonical - should still work
+        newExceedingNanos = -1;
+        record.setInstant(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis % MILLIS_IN_SECOND) * NANOS_IN_MILLI + newExceedingNanos));
+        assertEquals(newExceedingNanos + NANOS_IN_MILLI,
+                record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+        assertEquals(millis -1, record.getMillis(), "record.getMillis()");
+        assertEquals(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis%MILLIS_IN_SECOND)*NANOS_IN_MILLI + newExceedingNanos),
+                record.getInstant(), "record.getInstant()");
+        test(record, true);
+        record.setInstant(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis % MILLIS_IN_SECOND) * NANOS_IN_MILLI + newExceedingNanos));
+        assertEquals(millis -1, record.getMillis(), "record.getMillis()");
+        assertEquals(Instant.ofEpochSecond(millis/MILLIS_IN_SECOND,
+                (millis%MILLIS_IN_SECOND)*NANOS_IN_MILLI + newExceedingNanos),
+                record.getInstant(), "record.getInstant()");
+
+        // setMillis
+        record.setMillis(millis-1);
+        assertEquals(millis-1, record.getInstant().toEpochMilli(),
+                "record.getInstant().toEpochMilli()");
+        assertEquals(millis-1, record.getMillis(),
+                "record.getMillis()");
+        assertEquals(0, record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+        test(record, false);
+        assertEquals(millis-1, record.getInstant().toEpochMilli(),
+                "record.getInstant().toEpochMilli()");
+        assertEquals(millis-1, record.getMillis(),
+                "record.getMillis()");
+        assertEquals(0, record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+
+        // setMillis to 0
+        record.setMillis(0);
+        assertEquals(0, record.getInstant().toEpochMilli(),
+                "record.getInstant().toEpochMilli()");
+        assertEquals(0, record.getMillis(),
+                "record.getMillis()");
+        assertEquals(0, record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+        test(record, false);
+        assertEquals(0, record.getInstant().toEpochMilli(),
+                "record.getInstant().toEpochMilli()");
+        assertEquals(0, record.getMillis(),
+                "record.getMillis()");
+        assertEquals(0, record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+
+        // setMillis to -1
+        record.setMillis(-1);
+        assertEquals(-1, record.getInstant().toEpochMilli(),
+                "record.getInstant().toEpochMilli()");
+        assertEquals(-1, record.getMillis(),
+                "record.getMillis()");
+        assertEquals(0, record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+        test(record, false);
+        assertEquals(-1, record.getInstant().toEpochMilli(),
+                "record.getInstant().toEpochMilli()");
+        assertEquals(-1, record.getMillis(),
+                "record.getMillis()");
+        assertEquals(0, record.getInstant().getNano() % NANOS_IN_MILLI,
+                "record.getInstant().getNano() % NANOS_IN_MILLI");
+
+        try {
+            record.setInstant(null);
+        } catch (NullPointerException x) {
+            System.out.println("Got expected NPE when trying to call record.setInstant(null): " + x);
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/logging/HigherResolutionTimeStamps/SerializeLogRecord.java	Wed Feb 25 18:41:07 2015 +0100
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.time.ZoneId;
+import java.util.Base64;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/**
+ * @test
+ * @bug 8072645
+ * @summary tests the compatibility of LogRecord serial form between
+ *          JDK 8 and JDK 9. Ideally this test should be run on both platforms.
+ *          (It is designed to run on both).
+ * @run main/othervm SerializeLogRecord
+ * @author danielfuchs
+ */
+public class SerializeLogRecord {
+
+    /**
+     * Serializes a log record, encode the serialized bytes in base 64, and
+     * prints pseudo java code that can be cut and pasted into this test.
+     * @param record the log record to serialize, encode in base 64, and for
+     *               which test data will be generated.
+     * @return A string containing the generated pseudo java code.
+     * @throws IOException Unexpected.
+     * @throws ClassNotFoundException  Unexpected.
+     */
+    public static String generate(LogRecord record) throws IOException, ClassNotFoundException {
+
+        // Format the given logRecord using the SimpleFormatter
+        SimpleFormatter formatter = new SimpleFormatter();
+        String str = formatter.format(record);
+
+        // Serialize the given LogRecord
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final ObjectOutputStream oos = new ObjectOutputStream(baos);
+        oos.writeObject(record);
+        oos.flush();
+        oos.close();
+
+        // Now we're going to perform a number of smoke tests before
+        // generating the Java pseudo code.
+        //
+        // First checks that the log record can be deserialized
+        final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        final ObjectInputStream ois = new ObjectInputStream(bais);
+        final LogRecord record2 = (LogRecord)ois.readObject();
+
+        // Format the deserialized LogRecord using the SimpleFormatter, and
+        // check that the string representation obtained matches the string
+        // representation of the original LogRecord
+        String str2 = formatter.format(record2);
+        if (!str.equals(str2)) throw new RuntimeException("Unexpected values in deserialized object:"
+                + "\n\tExpected:  " + str
+                + "\n\tRetrieved: "+str);
+
+        // Now get a Base64 string representation of the serialized bytes.
+        final String base64 = Base64.getEncoder().encodeToString(baos.toByteArray());
+
+        // Check that we can deserialize a log record from the Base64 string
+        // representation we just computed.
+        final ByteArrayInputStream bais2 = new ByteArrayInputStream(Base64.getDecoder().decode(base64));
+        final ObjectInputStream ois2 = new ObjectInputStream(bais2);
+        final LogRecord record3 = (LogRecord)ois2.readObject();
+
+        // Format the new deserialized LogRecord using the SimpleFormatter, and
+        // check that the string representation obtained matches the string
+        // representation of the original LogRecord
+        String str3 = formatter.format(record3);
+        if (!str.equals(str3)) throw new RuntimeException("Unexpected values in deserialized object:"
+                + "\n\tExpected:  " + str
+                + "\n\tRetrieved: "+str);
+        //System.out.println(base64);
+        //System.out.println();
+
+        // Generates the Java Pseudo code that can be cut & pasted into
+        // this test (see Jdk8SerializedLog and Jdk9SerializedLog below)
+        final StringBuilder sb = new StringBuilder();
+        sb.append("    /**").append('\n');
+        sb.append("     * Base64 encoded string for LogRecord object.").append('\n');
+        sb.append("     * Java version: ").append(System.getProperty("java.version")).append('\n');
+        sb.append("     **/").append('\n');
+        sb.append("    final String base64 = ").append("\n          ");
+        final int last = base64.length() - 1;
+        for (int i=0; i<base64.length();i++) {
+            if (i%64 == 0) sb.append("\"");
+            sb.append(base64.charAt(i));
+            if (i%64 == 63 || i == last) {
+                sb.append("\"");
+                if (i == last) sb.append(";\n");
+                else sb.append("\n        + ");
+            }
+        }
+        sb.append('\n');
+        sb.append("    /**").append('\n');
+        sb.append("     * SimpleFormatter output for LogRecord object.").append('\n');
+        sb.append("     * Java version: ").append(System.getProperty("java.version")).append('\n');
+        sb.append("     **/").append('\n');
+        sb.append("    final String str = ").append("\n          ");
+        sb.append("\"").append(str.replace("\n", "\\n")).append("\";\n");
+        return sb.toString();
+    }
+
+    /**
+     * An abstract class to test that a log record previously serialized on a
+     * different java version can be deserialized in the current java version.
+     * (see Jdk8SerializedLog and Jdk9SerializedLog below)
+     */
+    public static abstract class SerializedLog {
+        public abstract String getBase64();
+        public abstract String getString();
+
+        /**
+         * Deserializes the Base64 encoded string returned by {@link
+         * #getBase64()}, format the obtained LogRecord using a
+         * SimpleFormatter, and checks that the string representation obtained
+         * matches the original string representation returned by {@link
+         * #getString()}.
+         */
+        protected void dotest() {
+            try {
+                final String base64 = getBase64();
+                final ByteArrayInputStream bais =
+                        new ByteArrayInputStream(Base64.getDecoder().decode(base64));
+                final ObjectInputStream ois = new ObjectInputStream(bais);
+                final LogRecord record = (LogRecord)ois.readObject();
+                final SimpleFormatter formatter = new SimpleFormatter();
+                String expected = getString();
+                String str2 = formatter.format(record);
+                check(expected, str2);
+                System.out.println(str2);
+                System.out.println("PASSED: "+this.getClass().getName()+"\n");
+            } catch (IOException | ClassNotFoundException x) {
+                throw new RuntimeException(x);
+            }
+        }
+        /**
+         * Check that the actual String representation obtained matches the
+         * expected String representation.
+         * @param expected Expected String representation, as returned by
+         *                 {@link #getString()}.
+         * @param actual   Actual String representation obtained by formatting
+         *                 the LogRecord obtained by the deserialization of the
+         *                 bytes encoded in {@link #getBase64()}.
+         */
+        protected void check(String expected, String actual) {
+            if (!expected.equals(actual)) {
+                throw new RuntimeException(this.getClass().getName()
+                    + " - Unexpected values in deserialized object:"
+                    + "\n\tExpected:  " + expected
+                    + "\n\tRetrieved: "+ actual);
+            }
+        }
+    }
+
+    public static class Jdk8SerializedLog extends SerializedLog {
+
+        // Generated by generate() on JDK 8.
+        // --------------------------------
+        // BEGIN
+
+        /**
+         * Base64 encoded string for LogRecord object.
+         * Java version: 1.8.0_11
+         **/
+        final String base64 =
+              "rO0ABXNyABtqYXZhLnV0aWwubG9nZ2luZy5Mb2dSZWNvcmRKjVk982lRlgMACkoA"
+            + "Bm1pbGxpc0oADnNlcXVlbmNlTnVtYmVySQAIdGhyZWFkSURMAAVsZXZlbHQAGUxq"
+            + "YXZhL3V0aWwvbG9nZ2luZy9MZXZlbDtMAApsb2dnZXJOYW1ldAASTGphdmEvbGFu"
+            + "Zy9TdHJpbmc7TAAHbWVzc2FnZXEAfgACTAAScmVzb3VyY2VCdW5kbGVOYW1lcQB+"
+            + "AAJMAA9zb3VyY2VDbGFzc05hbWVxAH4AAkwAEHNvdXJjZU1ldGhvZE5hbWVxAH4A"
+            + "AkwABnRocm93bnQAFUxqYXZhL2xhbmcvVGhyb3dhYmxlO3hwAAABSjUCgo0AAAAA"
+            + "AAAAAAAAAAFzcgAXamF2YS51dGlsLmxvZ2dpbmcuTGV2ZWyOiHETUXM2kgIAA0kA"
+            + "BXZhbHVlTAAEbmFtZXEAfgACTAAScmVzb3VyY2VCdW5kbGVOYW1lcQB+AAJ4cAAA"
+            + "AyB0AARJTkZPdAAic3VuLnV0aWwubG9nZ2luZy5yZXNvdXJjZXMubG9nZ2luZ3QA"
+            + "BHRlc3R0ABFKYXZhIFZlcnNpb246IHswfXBwcHB3BgEAAAAAAXQACDEuOC4wXzEx"
+            + "eA==";
+
+        /**
+         * SimpleFormatter output for LogRecord object.
+         * Java version: 1.8.0_11
+         **/
+        final String str =
+              "Dec 10, 2014 4:22:44.621000000 PM test - INFO: Java Version: 1.8.0_11";
+              //                    ^^^
+              // Notice the milli second resolution above...
+
+        // END
+        // --------------------------------
+
+        @Override
+        public String getBase64() {
+            return base64;
+        }
+
+        @Override
+        public String getString() {
+            return str;
+        }
+
+        public static void test() {
+            new Jdk8SerializedLog().dotest();
+        }
+    }
+
+    public static class Jdk9SerializedLog extends SerializedLog {
+
+        // Generated by generate() on JDK 9.
+        // --------------------------------
+        // BEGIN
+
+        /**
+         * Base64 encoded string for LogRecord object.
+         * Java version: 1.9.0-internal
+         **/
+        final String base64 =
+              "rO0ABXNyABtqYXZhLnV0aWwubG9nZ2luZy5Mb2dSZWNvcmRKjVk982lRlgMAC0oA"
+            + "Bm1pbGxpc0kADm5hbm9BZGp1c3RtZW50SgAOc2VxdWVuY2VOdW1iZXJJAAh0aHJl"
+            + "YWRJREwABWxldmVsdAAZTGphdmEvdXRpbC9sb2dnaW5nL0xldmVsO0wACmxvZ2dl"
+            + "ck5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMAAdtZXNzYWdlcQB+AAJMABJyZXNv"
+            + "dXJjZUJ1bmRsZU5hbWVxAH4AAkwAD3NvdXJjZUNsYXNzTmFtZXEAfgACTAAQc291"
+            + "cmNlTWV0aG9kTmFtZXEAfgACTAAGdGhyb3dudAAVTGphdmEvbGFuZy9UaHJvd2Fi"
+            + "bGU7eHAAAAFLl3u6OAAOU/gAAAAAAAAAAAAAAAFzcgAXamF2YS51dGlsLmxvZ2dp"
+            + "bmcuTGV2ZWyOiHETUXM2kgIAA0kABXZhbHVlTAAEbmFtZXEAfgACTAAScmVzb3Vy"
+            + "Y2VCdW5kbGVOYW1lcQB+AAJ4cAAAAyB0AARJTkZPdAAic3VuLnV0aWwubG9nZ2lu"
+            + "Zy5yZXNvdXJjZXMubG9nZ2luZ3QABHRlc3R0ABFKYXZhIFZlcnNpb246IHswfXBw"
+            + "cHB3BgEAAAAAAXQADjEuOS4wLWludGVybmFseA==";
+
+        /**
+         * SimpleFormatter output for LogRecord object.
+         * Java version: 1.9.0-internal
+         **/
+        final String str =
+              "Feb 17, 2015 12:20:43.192939000 PM test - INFO: Java Version: 1.9.0-internal";
+              //                       ^^^
+              // Notice the micro second resolution above...
+
+        // END
+        // --------------------------------
+
+        @Override
+        public String getBase64() {
+            return base64;
+        }
+
+        @Override
+        public String getString() {
+            return str;
+        }
+
+        @Override
+        protected void check(String expected, String actual) {
+            if (System.getProperty("java.version").startsWith("1.8")) {
+                // If we are in JDK 8 and print a log record serialized in JDK 9,
+                // then we won't be able to print anything below the millisecond
+                // precision, since that hasn't been implemented in JDK 8.
+                // Therefore - we need to replace anything below millseconds by
+                // zeroes in the expected string (which was generated on JDK 9).
+                Pattern pattern = Pattern.compile("^"
+                        + "(.*\\.[0-9][0-9][0-9])" // group1: everything up to milliseconds
+                        + "([0-9][0-9][0-9][0-9][0-9][0-9])" // group 2: micros and nanos
+                        + "(.* - .*)$"); // group three: all the rest...
+                Matcher matcher = pattern.matcher(expected);
+                if (matcher.matches()) {
+                    expected = matcher.group(1) + "000000" + matcher.group(3);
+                }
+            }
+            super.check(expected, actual);
+        }
+
+        public static void test() {
+            new Jdk9SerializedLog().dotest();
+        }
+    }
+
+    public static void generate() {
+        try {
+            LogRecord record = new LogRecord(Level.INFO, "Java Version: {0}");
+            record.setLoggerName("test");
+            record.setParameters(new Object[] {System.getProperty("java.version")});
+            System.out.println(generate(record));
+        } catch (IOException | ClassNotFoundException x) {
+            throw new RuntimeException(x);
+        }
+    }
+
+    static enum TestCase { GENERATE, TESTJDK8, TESTJDK9 };
+
+    public static void main(String[] args) {
+        // Set the locale and time zone to make sure we won't depend on the
+        // test env - in particular we don't want to depend on the
+        // time zone in which the test machine might be located.
+        // So we're gong to use Locale English and Time Zone UTC for this test.
+        // (Maybe that should be Locale.ROOT?)
+        Locale.setDefault(Locale.ENGLISH);
+        TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC")));
+
+        // Set the format property to make sure we always have the nanos, and
+        // to make sure it's the same format than what we used when
+        // computing the formatted string for Jdk8SerializedLog and
+        // Jdk9SerializedLog above.
+        //
+        // If you change the formatting, then you will need to regenerate
+        // the data for Jdk8SerializedLog and Jdk9SerializedLog.
+        //
+        // To do that - just run this test on JDK 8, and cut & paste the
+        // pseudo code printed by generate() into Jdk8SerializedLog.
+        // Then run this test again on JDK 9, and cut & paste the
+        // pseudo code printed by generate() into Jdk9SerializedLog.
+        // [Note: you can pass GENERATE as single arg to main() to avoid
+        //        running the actual test]
+        // Finally run the test again to check that it still passes after
+        // your modifications.
+        //
+        System.setProperty("java.util.logging.SimpleFormatter.format",
+                "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp %2$s - %4$s: %5$s%6$s");
+
+        // If no args, then run everything....
+        if (args == null || args.length == 0) {
+            args = new String[] { "GENERATE", "TESTJDK8", "TESTJDK9" };
+        }
+
+        // Run the specified test case(s)
+        Stream.of(args).map(x -> TestCase.valueOf(x)).forEach((x) -> {
+            switch(x) {
+                case GENERATE: generate(); break;
+                case TESTJDK8: Jdk8SerializedLog.test(); break;
+                case TESTJDK9: Jdk9SerializedLog.test(); break;
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/logging/HigherResolutionTimeStamps/SimpleFormatterNanos.java	Wed Feb 25 18:41:07 2015 +0100
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+
+/**
+ * @test
+ * @bug 8072645
+ * @summary tests that SimpleFormatter can print out dates with the new
+ *                nanosecond precision.
+ * @run main/othervm SimpleFormatterNanos
+ * @author danielfuchs
+ */
+public class SimpleFormatterNanos {
+
+    static final int MILLIS_IN_SECOND = 1000;
+    static final int NANOS_IN_MILLI = 1000_000;
+    static final int NANOS_IN_MICRO = 1000;
+    static final int NANOS_IN_SECOND = 1000_000_000;
+
+    static final boolean verbose = true;
+
+    static final class TestAssertException extends RuntimeException {
+        TestAssertException(String msg) { super(msg); }
+    }
+
+    private static void assertEquals(long expected, long received, String msg) {
+        if (expected != received) {
+            throw new TestAssertException("Unexpected result for " + msg
+                    + ".\n\texpected: " + expected
+                    +  "\n\tactual:   " + received);
+        } else if (verbose) {
+            System.out.println("Got expected " + msg + ": " + received);
+        }
+    }
+
+    static int getNanoAdjustment(LogRecord record) {
+        return record.getInstant().getNano() % NANOS_IN_MILLI;
+    }
+    static void setNanoAdjustment(LogRecord record, int nanos) {
+        record.setInstant(Instant.ofEpochSecond(record.getInstant().getEpochSecond(),
+                (record.getInstant().getNano() / NANOS_IN_MILLI) * NANOS_IN_MILLI + nanos));
+    }
+
+    public static void main(String[] args) throws Exception {
+
+        Locale.setDefault(Locale.ENGLISH);
+        LogRecord record = new LogRecord(Level.INFO, "Java Version: {0}");
+        record.setLoggerName("test");
+        record.setParameters(new Object[] {System.getProperty("java.version")});
+        int nanos = getNanoAdjustment(record);
+        long millis = record.getMillis();
+        // make sure we don't have leading zeros when printing below
+        // the second precision
+        if (millis % MILLIS_IN_SECOND < 100) millis = millis + 100;
+        // make sure we have some nanos
+        if (nanos % NANOS_IN_MICRO == 0) nanos = nanos + 999;
+        record.setMillis(millis);
+        setNanoAdjustment(record, nanos);
+        final Instant instant = record.getInstant();
+        if (nanos < 0) {
+            throw new RuntimeException("Unexpected negative nano adjustment: "
+                    + getNanoAdjustment(record));
+        }
+        if (nanos >= NANOS_IN_MILLI) {
+            throw new RuntimeException("Nano adjustment exceeds 1ms: "
+                    + getNanoAdjustment(record));
+        }
+        if (millis != record.getMillis()) {
+            throw new RuntimeException("Unexpected millis: " + millis + " != "
+                    + record.getMillis());
+        }
+        if (millis != record.getInstant().toEpochMilli()) {
+            throw new RuntimeException("Unexpected millis: "
+                    + record.getInstant().toEpochMilli());
+        }
+        ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
+        int zdtNanos = zdt.getNano();
+        long expectedNanos = (millis % MILLIS_IN_SECOND) * NANOS_IN_MILLI + nanos;
+        assertEquals(expectedNanos, instant.getNano(), "Instant.getNano()");
+        assertEquals(expectedNanos, zdtNanos, "ZonedDateTime.getNano()");
+        String match = "."+expectedNanos+" ";
+
+        System.out.println("Testing with default format...");
+
+        SimpleFormatter formatter = new SimpleFormatter();
+        String str = formatter.format(record);
+        if (str.contains(match)) {
+            throw new RuntimeException("SimpleFormatter.format()"
+                    + " string contains unexpected nanos: "
+                    + "\n\tdid not expect match for: '" + match + "'"
+                    + "\n\tin: " + str);
+        }
+
+        System.out.println("Nanos omitted as expected: no '"+match+"' in "+str);
+
+
+        System.out.println("Changing format to print nanos...");
+        System.setProperty("java.util.logging.SimpleFormatter.format",
+                "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp %2$s%n%4$s: %5$s%6$s%n");
+
+        SimpleFormatter formatter2 = new SimpleFormatter();
+        str = formatter2.format(record);
+        if (!str.contains(match)) {
+            throw new RuntimeException("SimpleFormatter.format()"
+                    + " string does not contain expected nanos: "
+                    + "\n\texpected match for: '" + match + "'"
+                    + "\n\tin: " + str);
+        }
+        System.out.println("Found expected match for '"+match+"' in "+str);
+
+
+        System.out.println("Changing format to omit nanos...");
+        System.setProperty("java.util.logging.SimpleFormatter.format",
+                "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n");
+
+        SimpleFormatter formatter3 = new SimpleFormatter();
+        str = formatter3.format(record);
+        String notMatch = match;
+        if (str.contains(notMatch)) {
+            throw new RuntimeException("SimpleFormatter.format()"
+                    + " string contains unexpected nanos: "
+                    + "\n\tdid not expect match for: '" + notMatch + "'"
+                    + "\n\tin: " + str);
+        }
+        System.out.println("Nanos omitted as expected: no '"+notMatch+"' in "+str);
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/logging/HigherResolutionTimeStamps/XmlFormatterNanos.java	Wed Feb 25 18:41:07 2015 +0100
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+import java.util.logging.XMLFormatter;
+
+/**
+ * @test
+ * @bug 8072645
+ * @summary tests that XmlFormatter will print out dates with the new
+ *                nanosecond precision.
+ * @run main/othervm XmlFormatterNanos
+ * @author danielfuchs
+ */
+public class XmlFormatterNanos {
+
+    static final int MILLIS_IN_SECOND = 1000;
+    static final int NANOS_IN_MILLI = 1000_000;
+    static final int NANOS_IN_MICRO = 1000;
+    static final int NANOS_IN_SECOND = 1000_000_000;
+
+    static final boolean verbose = true;
+
+    static final class TestAssertException extends RuntimeException {
+        TestAssertException(String msg) { super(msg); }
+    }
+
+    private static void assertEquals(long expected, long received, String msg) {
+        if (expected != received) {
+            throw new TestAssertException("Unexpected result for " + msg
+                    + ".\n\texpected: " + expected
+                    +  "\n\tactual:   " + received);
+        } else if (verbose) {
+            System.out.println("Got expected " + msg + ": " + received);
+        }
+    }
+
+    private static void assertEquals(Object expected, Object received, String msg) {
+        if (!Objects.equals(expected, received)) {
+            throw new TestAssertException("Unexpected result for " + msg
+                    + ".\n\texpected: " + expected
+                    +  "\n\tactual:   " + received);
+        } else if (verbose) {
+            System.out.println("Got expected " + msg + ": " + received);
+        }
+    }
+
+    static class CustomXMLFormatter extends XMLFormatter {}
+
+    static class Configuration {
+        final Properties propertyFile;
+        private Configuration(Properties properties) {
+            propertyFile = properties;
+        }
+        public Configuration apply() {
+            try {
+                ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+                propertyFile.store(bytes, testName());
+                ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray());
+                LogManager.getLogManager().readConfiguration(bais);
+            } catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+            return this;
+        }
+
+        public static String useInstantProperty(Class<?> type) {
+            return type.getName()+".useInstant";
+        }
+
+        public boolean useInstant(XMLFormatter formatter) {
+            return Boolean.parseBoolean(propertyFile.getProperty(
+                    formatter.getClass().getName()+".useInstant", "true"));
+        }
+
+        public String testName() {
+            return propertyFile.getProperty("test.name", "????");
+        }
+
+        public static Configuration of(Properties props) {
+            return new Configuration(props);
+        }
+
+        public static Configuration apply(Properties props) {
+            return of(props).apply();
+        }
+    }
+
+    final static List<Properties> properties;
+    static {
+        Properties props1 = new Properties();
+        props1.setProperty("test.name", "with XML nano element (default)");
+        Properties props2 = new Properties();
+        props2.setProperty("test.name", "with XML nano element (standard=true, custom=false)");
+        props2.setProperty(Configuration.useInstantProperty(XMLFormatter.class), "true");
+        props2.setProperty(Configuration.useInstantProperty(CustomXMLFormatter.class), "false");
+        Properties props3 = new Properties();
+        props3.setProperty("test.name", "with XML nano element (standard=false, custom=true)");
+        props3.setProperty(Configuration.useInstantProperty(XMLFormatter.class), "false");
+        props3.setProperty(Configuration.useInstantProperty(CustomXMLFormatter.class), "true");
+
+        properties = Collections.unmodifiableList(Arrays.asList(
+                    props1,
+                    props2));
+    }
+
+    public static void main(String[] args) throws Exception {
+        Locale.setDefault(Locale.ENGLISH);
+        properties.stream().forEach(XmlFormatterNanos::test);
+    }
+
+    static int getNanoAdjustment(LogRecord record) {
+        return record.getInstant().getNano() % NANOS_IN_MILLI;
+    }
+    static void setNanoAdjustment(LogRecord record, int nanos) {
+        record.setInstant(Instant.ofEpochSecond(record.getInstant().getEpochSecond(),
+                (record.getInstant().getNano() / NANOS_IN_MILLI) * NANOS_IN_MILLI + nanos));
+    }
+
+    public static void test(Properties props) {
+        Configuration conf = Configuration.apply(props);
+
+        LogRecord record = new LogRecord(Level.INFO, "Test Name: {0}");
+        record.setLoggerName("test");
+        record.setParameters(new Object[] {conf.testName()});
+        int nanos = record.getInstant().getNano() % NANOS_IN_MILLI;
+        long millis = record.getMillis();
+        // make sure we don't have leading zeros when printing below
+        // the second precision
+        if (millis % MILLIS_IN_SECOND < 100) millis = millis + 100;
+        // make sure we some nanos - and make sure we don't have
+        // trailing zeros
+        if (nanos % 10 == 0) nanos = nanos + 7;
+
+        record.setMillis(millis);
+        setNanoAdjustment(record, nanos);
+        final Instant instant = record.getInstant();
+        if (nanos < 0) {
+            throw new RuntimeException("Unexpected negative nano adjustment: "
+                    + getNanoAdjustment(record));
+        }
+        if (nanos >= NANOS_IN_MILLI) {
+            throw new RuntimeException("Nano adjustment exceeds 1ms: "
+                    + getNanoAdjustment(record));
+        }
+        if (millis != record.getMillis()) {
+            throw new RuntimeException("Unexpected millis: " + millis + " != "
+                    + record.getMillis());
+        }
+        if (millis != record.getInstant().toEpochMilli()) {
+            throw new RuntimeException("Unexpected millis: "
+                    + record.getInstant().toEpochMilli());
+        }
+        long expectedNanos = (millis % MILLIS_IN_SECOND) * NANOS_IN_MILLI + nanos;
+        assertEquals(expectedNanos, instant.getNano(), "Instant.getNano()");
+
+        XMLFormatter formatter = new XMLFormatter();
+        testMatching(formatter, record, instant, expectedNanos, conf.useInstant(formatter));
+
+        XMLFormatter custom = new CustomXMLFormatter();
+        testMatching(custom, record, instant, expectedNanos, conf.useInstant(custom));
+    }
+
+    private static void testMatching(XMLFormatter formatter,
+            LogRecord record,  Instant instant, long expectedNanos,
+            boolean useInstant) {
+
+        ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
+        int zdtNanos = zdt.getNano();
+        assertEquals(expectedNanos, zdtNanos, "ZonedDateTime.getNano()");
+
+        String str = formatter.format(record);
+
+        String match = "."+expectedNanos;
+        if (str.contains(match) != useInstant) {
+            throw new RuntimeException(formatter.getClass().getSimpleName()
+                    + ".format()"
+                    + " string does not contain expected nanos: "
+                    + "\n\texpected match for: '" + match + "'"
+                    + "\n\tin: \n" + str);
+        }
+        System.out.println("Found expected match for '"+match+"' in \n"+str);
+
+        match = "<millis>"+instant.toEpochMilli()+"</millis>";
+        if (!str.contains(match)) {
+            throw new RuntimeException(formatter.getClass().getSimpleName()
+                    + ".format()"
+                    + " string does not contain expected millis: "
+                    + "\n\texpected match for: '" + match + "'"
+                    + "\n\tin: \n" + str);
+        }
+        System.out.println("Found expected match for '"+match+"' in \n"+str);
+
+        match = "<nanos>";
+        if (str.contains(match) != useInstant) {
+            throw new RuntimeException(formatter.getClass().getSimpleName()
+                    + ".format()"
+                    + " string "
+                    + (useInstant
+                            ? "does not contain expected nanos: "
+                            : "contains unexpected nanos: ")
+                    + "\n\t" + (useInstant ? "expected" : "unexpected")
+                    + " match for: '" + match + "'"
+                    + "\n\tin: \n" + str);
+        }
+        match = "<nanos>"+getNanoAdjustment(record)+"</nanos>";
+        if (str.contains(match) != useInstant) {
+            throw new RuntimeException(formatter.getClass().getSimpleName()
+                    + ".format()"
+                    + " string "
+                    + (useInstant
+                            ? "does not contain expected nanos: "
+                            : "contains unexpected nanos: ")
+                    + "\n\t" + (useInstant ? "expected" : "unexpected")
+                    + " match for: '" + match + "'"
+                    + "\n\tin: \n" + str);
+        }
+        if (useInstant) {
+            System.out.println("Found expected match for '"+match+"' in \n"+str);
+        } else {
+            System.out.println("As expected '"+match+"' is not present in \n"+str);
+        }
+
+        match = useInstant ? DateTimeFormatter.ISO_INSTANT.format(instant)
+                : zdt.truncatedTo(ChronoUnit.SECONDS)
+                        .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+        match = "<date>"+match+"</date>";
+        if (!str.contains(match)) {
+            throw new RuntimeException(formatter.getClass().getSimpleName()
+                    + ".format()"
+                    + " string does not contain expected date: "
+                    + "\n\texpected match for: '" + match + "'"
+                    + "\n\tin: \n" + str);
+        }
+        System.out.println("Found expected match for '"+match+"' in \n"+str);
+
+    }
+
+}