changeset 60042:183436d75caa

8245302: Upgrade LogRecord to support long thread ids and remove its usage of ThreadLocal Summary: java.util.logging.LogRecord has been updated to use long thread ids instead of int thread id Reviewed-by: alanb, dfuchs Contributed-by: Rahul Yadav <rahul.r.yadav@oracle.com>
author pconcannon
date Fri, 03 Jul 2020 10:19:54 +0100
parents e7f126fb2655
children a7c030723240
files src/java.logging/share/classes/java/util/logging/LogRecord.java src/java.logging/share/classes/java/util/logging/XMLFormatter.java test/jdk/java/util/logging/LogRecordThreadIdTest.java test/jdk/java/util/logging/SerializeLogRecordTest.java
diffstat 4 files changed, 495 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.logging/share/classes/java/util/logging/LogRecord.java	Mon Jun 29 09:59:45 2020 +0200
+++ b/src/java.logging/share/classes/java/util/logging/LogRecord.java	Fri Jul 03 10:19:54 2020 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2020, 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,7 +26,6 @@
 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.security.AccessController;
@@ -76,21 +75,6 @@
         = new AtomicLong(0);
 
     /**
-     * The default value of threadID will be the current thread's
-     * thread id, for ease of correlation, unless it is greater than
-     * MIN_SEQUENTIAL_THREAD_ID, in which case we try harder to keep
-     * our promise to keep threadIDs unique by avoiding collisions due
-     * to 32-bit wraparound.  Unfortunately, LogRecord.getThreadID()
-     * returns int, while Thread.getId() returns long.
-     */
-    private static final int MIN_SEQUENTIAL_THREAD_ID = Integer.MAX_VALUE / 2;
-
-    private static final AtomicInteger nextThreadId
-        = new AtomicInteger(MIN_SEQUENTIAL_THREAD_ID);
-
-    private static final ThreadLocal<Integer> threadIds = new ThreadLocal<>();
-
-    /**
      * Logging message level
      */
     private Level level;
@@ -121,6 +105,11 @@
     private int threadID;
 
     /**
+     * long value of Thread ID for thread that issued logging call.
+     */
+    private long longThreadID;
+
+    /**
      * The Throwable (if any) associated with log message
      */
     private Throwable thrown;
@@ -147,7 +136,10 @@
      * @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 threadID int this is deprecated and is available for backward compatibility.
+     *              Values may have been synthesized. If present, {@code longThreadID} represents
+     *              the actual thread id.
+     * @serialField longThreadID long 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
@@ -164,6 +156,7 @@
      * @serialField resourceBundleName String Resource bundle name to localized
      *              log message
      */
+    @Serial
     private static final ObjectStreamField[] serialPersistentFields =
         new ObjectStreamField[] {
             new ObjectStreamField("level", Level.class),
@@ -172,6 +165,7 @@
             new ObjectStreamField("sourceMethodName", String.class),
             new ObjectStreamField("message", String.class),
             new ObjectStreamField("threadID", int.class),
+            new ObjectStreamField("longThreadID", long.class),
             new ObjectStreamField("millis", long.class),
             new ObjectStreamField("nanoAdjustment", int.class),
             new ObjectStreamField("thrown", Throwable.class),
@@ -184,20 +178,22 @@
     private transient ResourceBundle resourceBundle;
 
     /**
-     * Returns the default value for a new LogRecord's threadID.
+     * Synthesizes a pseudo unique integer value from a long {@code id} value.
+     * For backward compatibility with previous releases,the returned integer is
+     * such that for any positive long less than or equals to {@code Integer.MAX_VALUE},
+     * the returned integer is equal to the original value.
+     * Otherwise - it is synthesized with a best effort hashing algorithm,
+     * and the returned value is negative.
+     * Calling this method multiple times with the same value always yields the same result.
+     *
+     * @return thread id
      */
-    private int defaultThreadID() {
-        long tid = Thread.currentThread().getId();
-        if (tid < MIN_SEQUENTIAL_THREAD_ID) {
-            return (int) tid;
-        } else {
-            Integer id = threadIds.get();
-            if (id == null) {
-                id = nextThreadId.getAndIncrement();
-                threadIds.set(id);
-            }
-            return id;
-        }
+
+    private int shortThreadID(long id) {
+        if (id >= 0 && id <= Integer.MAX_VALUE)
+            return (int) id;
+        int hash = Long.hashCode(id);
+        return hash < 0 ? hash : (-1 - hash);
     }
 
     /**
@@ -225,10 +221,13 @@
         message = msg;
         // Assign a thread ID and a unique sequence number.
         sequenceNumber = globalSequenceNumber.getAndIncrement();
-        threadID = defaultThreadID();
+        long id = Thread.currentThread().getId();
+        // threadID is deprecated and this value is synthesised for backward compatibility
+        threadID = shortThreadID(id);
+        longThreadID = id;
         instant = Instant.now();
         needToInferCaller = true;
-   }
+    }
 
     /**
      * Get the source Logger's name.
@@ -447,8 +446,12 @@
      * This is a thread identifier within the Java VM and may or
      * may not map to any operating system ID.
      *
+     * @deprecated  Values returned by this method may be synthesized,
+     *              and may not correspond to the actual {@linkplain Thread#getId() thread id},
+     *              use {@link #getLongThreadID()} instead.
      * @return thread ID
      */
+    @Deprecated(since = "16")
     public int getThreadID() {
         return threadID;
     }
@@ -456,9 +459,41 @@
     /**
      * Set an identifier for the thread where the message originated.
      * @param threadID  the thread ID
+     *
+     * @deprecated  This method doesn't allow to pass a long {@linkplain Thread#getId() thread id},
+     *              use {@link #setLongThreadID(long)} instead.
      */
+    @Deprecated(since = "16")
     public void setThreadID(int threadID) {
         this.threadID = threadID;
+        this.longThreadID = threadID;
+    }
+
+    /**
+     * Get a thread identifier for the thread where message originated
+     *
+     * <p>
+     * This is a thread identifier within the Java VM and may or
+     * may not map to any operating system ID.
+     *
+     * @return thread ID
+     * @since 16
+     */
+    public long getLongThreadID() {
+        return longThreadID;
+    }
+
+    /**
+     * Set an identifier for the thread where the message originated.
+     *
+     * @param longThreadID the thread ID
+     * @return this LogRecord
+     * @since 16
+     */
+    public LogRecord setLongThreadID(long longThreadID) {
+        this.threadID = shortThreadID(longThreadID);
+        this.longThreadID = longThreadID;
+        return this;
     }
 
     /**
@@ -552,6 +587,7 @@
         this.thrown = thrown;
     }
 
+    @Serial
     private static final long serialVersionUID = 5372048053134512534L;
 
     /**
@@ -564,6 +600,7 @@
      * a null String is written.  Otherwise the output of Object.toString()
      * is written.
      */
+    @Serial
     private void writeObject(ObjectOutputStream out) throws IOException {
         // We have to write serialized fields first.
         ObjectOutputStream.PutField pf = out.putFields();
@@ -573,6 +610,7 @@
         pf.put("sourceMethodName", sourceMethodName);
         pf.put("message", message);
         pf.put("threadID", threadID);
+        pf.put("longThreadID", longThreadID);
         pf.put("millis", instant.toEpochMilli());
         pf.put("nanoAdjustment", instant.getNano() % 1000_000);
         pf.put("thrown", thrown);
@@ -594,8 +632,25 @@
         }
     }
 
+    /**
+     * Initializes the LogRecord from deserialized data.
+     * <ul>
+     * <li>If {@code longThreadID} is present in the serial form, its value
+     * takes precedence over {@code threadID} and a value for {@code threadID}
+     * is synthesized from it, such that for {@code longThreadID} values between
+     * {@code 0} and {@code Integer.MAX_VALUE} inclusive, {@code longThreadID}
+     * and {@code threadID} will have the same value. For values outside of this
+     * range a negative synthesized value will be deterministically derived
+     * from {@code longThreadID}.
+     * <li>Otherwise, when only {@code threadID} is
+     * present, {@code longThreadID} is initialized with the value of
+     * {@code threadID} which may be anything between {@code Integer.MIN_VALUE}
+     * and {Integer.MAX_VALUE}.
+     * </ul>
+     */
+    @Serial
     private void readObject(ObjectInputStream in)
-                        throws IOException, ClassNotFoundException {
+        throws IOException, ClassNotFoundException {
         // We have to read serialized fields first.
         ObjectInputStream.GetField gf = in.readFields();
         level = (Level) gf.get("level", null);
@@ -603,7 +658,14 @@
         sourceClassName = (String) gf.get("sourceClassName", null);
         sourceMethodName = (String) gf.get("sourceMethodName", null);
         message = (String) gf.get("message", null);
-        threadID = gf.get("threadID", 0);
+        // If longthreadID is not present, it will be initialised with threadID value
+        // If longthreadID is present, threadID might have a synthesized value
+        int threadID = gf.get("threadID", 0);
+        long longThreadID = gf.get("longThreadID", (long)threadID);
+        if (threadID != longThreadID)
+            threadID = shortThreadID(longThreadID);
+        this.threadID = threadID;
+        this.longThreadID = longThreadID;
         long millis = gf.get("millis", 0L);
         int nanoOfMilli = gf.get("nanoAdjustment", 0);
         instant = Instant.ofEpochSecond(
@@ -641,9 +703,9 @@
                 // use system class loader to ensure the ResourceBundle
                 // instance is a different instance than null loader uses
                 final ResourceBundle bundle =
-                        ResourceBundle.getBundle(resourceBundleName,
-                                Locale.getDefault(),
-                                ClassLoader.getSystemClassLoader());
+                    ResourceBundle.getBundle(resourceBundleName,
+                        Locale.getDefault(),
+                        ClassLoader.getSystemClassLoader());
                 resourceBundle = bundle;
             } catch (MissingResourceException ex) {
                 // This is not a good place to throw an exception,
@@ -697,7 +759,7 @@
         private static final StackWalker WALKER;
         static {
             final PrivilegedAction<StackWalker> action =
-                    () -> StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+                () -> StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
             WALKER = AccessController.doPrivileged(action);
         }
 
@@ -736,7 +798,7 @@
 
         private boolean isLoggerImplFrame(String cname) {
             return (cname.equals("java.util.logging.Logger") ||
-                    cname.startsWith("sun.util.logging.PlatformLogger"));
+                cname.startsWith("sun.util.logging.PlatformLogger"));
         }
     }
 }
--- a/src/java.logging/share/classes/java/util/logging/XMLFormatter.java	Mon Jun 29 09:59:45 2020 +0200
+++ b/src/java.logging/share/classes/java/util/logging/XMLFormatter.java	Fri Jul 03 10:19:54 2020 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2020, 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
@@ -219,7 +219,7 @@
         }
 
         sb.append("  <thread>");
-        sb.append(record.getThreadID());
+        sb.append(record.getLongThreadID());
         sb.append("</thread>\n");
 
         if (record.getMessage() != null) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/util/logging/LogRecordThreadIdTest.java	Fri Jul 03 10:19:54 2020 +0100
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+
+
+/*
+ * @test
+ * @bug 8245302
+ * @summary test the relationship between
+ * thread id long and int methods
+ * @build LogRecordThreadIdTest
+ * @run testng/othervm  LogRecordThreadIdTest
+ */
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+
+
+
+public class LogRecordThreadIdTest {
+
+    LogRecord record, record1, record2;
+
+    @BeforeTest
+    public void setUp() throws Exception {
+        record  = new LogRecord(Level.INFO, "record");
+        record1 = new LogRecord(Level.INFO, "record1");
+        record2 = new LogRecord(Level.INFO, "record2");
+    }
+
+    /**
+     * Tests threadID setter methods for consistency
+     * with longThreadID
+     */
+    @Test
+    public void testSetThreadId() {
+        record.setThreadID(Integer.MAX_VALUE - 20);
+        record1.setThreadID(Integer.MAX_VALUE - 1);
+        assertEquals(record.getLongThreadID(), Integer.MAX_VALUE - 20L);
+        assertEquals(record.getThreadID(), Integer.MAX_VALUE - 20);
+        assertEquals(record1.getThreadID(), Integer.MAX_VALUE - 1);
+        assertEquals(record1.getLongThreadID(), Integer.MAX_VALUE - 1);
+    }
+
+    /**
+     * Tests longThreadID methods for consistency
+     * with threadID
+     */
+    @Test
+    public void testSetLongThreadId() {
+      record.setLongThreadID(Integer.MAX_VALUE - 20L);
+      record1.setLongThreadID(Integer.MAX_VALUE + 10L);
+      record2.setLongThreadID(Integer.MAX_VALUE);
+      assertEquals(record.getThreadID(), Integer.MAX_VALUE - 20);
+      assertEquals(record.getLongThreadID(), Integer.MAX_VALUE - 20L);
+      assertNotEquals(record1.getThreadID(), Integer.MAX_VALUE + 10L);
+      assertEquals(record1.getLongThreadID(), Integer.MAX_VALUE + 10L);
+      assertEquals(record2.getThreadID(), Integer.MAX_VALUE);
+      assertEquals(record2.getLongThreadID(), Integer.MAX_VALUE);
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/util/logging/SerializeLogRecordTest.java	Fri Jul 03 10:19:54 2020 +0100
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2020, 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.Base64;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.XMLFormatter;
+import java.util.stream.Stream;
+
+/**
+ * @test
+ * @bug 8245302
+ * @summary tests the deprecation of threadID and a new field longThreadID,
+ * test should be run on jdk16 and subsequent versions
+ * @run main/othervm SerializeLogRecordTest
+ */
+public class SerializeLogRecordTest {
+
+    /**
+     * 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 {
+
+        XMLFormatter formatter = new XMLFormatter();
+        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();
+
+        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);
+
+        // 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("     * threadID: ").append(record.getThreadID()).append('\n');
+        sb.append("     * longThreadID: ").append(record.getLongThreadID()).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        + ");
+            }
+        }
+        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 Jdk11SerializedLog and Jdk16SerializedLog below)
+     */
+    public abstract static class SerializedLog {
+        public abstract String getBase64();
+
+        /**
+         * Deserializes the Base64 encoded string returned by {@link
+         * #getBase64()}, and checks that the string representation obtained
+         * matches the original string representation returned by
+         */
+        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();
+                check(record);
+                System.out.println("PASSED: "+this.getClass().getName()+"\n");
+            } catch (IOException | ClassNotFoundException x) {
+                throw new RuntimeException(x);
+            }
+        }
+        /**
+         * Check that the values of threadID and longThreadID
+         * are correct and consistent when de-serialized from
+         * earlier jdk versions(jdk11)
+         */
+        protected void check(LogRecord r1) {
+            XMLFormatter formatter = new XMLFormatter();
+            int check = Integer.MAX_VALUE - 10;
+            String str = formatter.format(r1);
+            String loggerName = r1.getLoggerName();
+
+            if (loggerName.equals("test2")) {
+                int id = -2147483639;
+                if (r1.getLongThreadID() != Integer.MAX_VALUE + 10L || r1.getThreadID() != id) {
+                    throw new RuntimeException(this.getClass().getName());
+                }
+                else {
+                    System.out.println("Test Integer Check Passed");
+                }
+
+            }
+
+            if (loggerName.equals("test1") || loggerName.equals("test")) {
+                if (loggerName.equals("test1")) {
+                    check = Integer.MAX_VALUE / 2 - 20;
+                }
+
+                int tid = r1.getThreadID();
+                long longThreadID = r1.getLongThreadID();
+                if (tid != check || longThreadID != check || !str.contains("<thread>" + String.valueOf(longThreadID))) {
+                    throw new RuntimeException(this.getClass().getName());
+                }
+                else {
+                    System.out.println("Test Integer Check Passed");
+                }
+            }
+        }
+    }
+
+    public static void generate() {
+        try {
+            LogRecord record = new LogRecord(Level.INFO, "Java Version: {0}");
+            record.setLoggerName("test");
+            record.setThreadID(Integer.MAX_VALUE - 10);
+            record.setParameters(new Object[] {System.getProperty("java.version")});
+            System.out.println(generate(record));
+            LogRecord record1 = new LogRecord(Level.INFO, "Java Version: {0}");
+            record1.setLoggerName("test1");
+            record1.setLongThreadID(Integer.MAX_VALUE/2 - 20);
+            record1.setParameters(new Object[] {System.getProperty("java.version")});
+            System.out.println(generate(record1));
+            LogRecord record2 = new LogRecord(Level.INFO, "Java Version: {0}");
+            record2.setLoggerName("test2");
+            record2.setLongThreadID(Integer.MAX_VALUE + 10L);
+            record2.setParameters(new Object[] {System.getProperty("java.version")});
+            System.out.println(generate(record2));
+        } catch (IOException | ClassNotFoundException x) {
+            throw new RuntimeException(x);
+        }
+    }
+
+    public static class Jdk11SerializedLog extends SerializedLog {
+
+        /**
+         * Base64 encoded string for LogRecord object.
+         * It is generated using generate method and
+         * can be copy pasted as base64 value, for generating
+         * this value, execute this class with an argument value
+         * generate and the test will print out the base64 value
+         * that can be copy pasted below
+         * Java version: 11.0.6
+         **/
+        final String base64 =
+            "rO0ABXNyABtqYXZhLnV0aWwubG9nZ2luZy5Mb2dSZWNvcmRKjVk982lRlgMAC0oA"
+                + "Bm1pbGxpc0kADm5hbm9BZGp1c3RtZW50SgAOc2VxdWVuY2VOdW1iZXJJAAh0aHJl"
+                + "YWRJREwABWxldmVsdAAZTGphdmEvdXRpbC9sb2dnaW5nL0xldmVsO0wACmxvZ2dl"
+                + "ck5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMAAdtZXNzYWdlcQB+AAJMABJyZXNv"
+                + "dXJjZUJ1bmRsZU5hbWVxAH4AAkwAD3NvdXJjZUNsYXNzTmFtZXEAfgACTAAQc291"
+                + "cmNlTWV0aG9kTmFtZXEAfgACTAAGdGhyb3dudAAVTGphdmEvbGFuZy9UaHJvd2Fi"
+                + "bGU7eHAAAAFycRovVgAFN/AAAAAAAAAAAH////VzcgAXamF2YS51dGlsLmxvZ2dp"
+                + "bmcuTGV2ZWyOiHETUXM2kgIAA0kABXZhbHVlTAAEbmFtZXEAfgACTAAScmVzb3Vy"
+                + "Y2VCdW5kbGVOYW1lcQB+AAJ4cAAAAyB0AARJTkZPdAAic3VuLnV0aWwubG9nZ2lu"
+                + "Zy5yZXNvdXJjZXMubG9nZ2luZ3QABHRlc3R0ABFKYXZhIFZlcnNpb246IHswfXBw"
+                + "cHB3BgEAAAAAAXQABjExLjAuNng=";
+
+
+        @Override
+        public String getBase64() {
+            return base64;
+        }
+
+        @Override
+        protected void check(LogRecord r1) {
+            super.check(r1);
+        }
+
+        public static void test() {
+            new Jdk11SerializedLog().dotest();
+        }
+    }
+
+    public static class Jdk16SerializedLog extends SerializedLog {
+
+        /**
+         * Base64 encoded string for LogRecord object.
+         * It is generated using generate method and
+         * can be copy pasted as base64 value, for generating
+         * this value, execute this class with an argument value
+         * generate and the test will print out the base64 value
+         * that can be copy pasted below
+         * Java version: 16-internal
+         **/
+        final String base64 =
+            "rO0ABXNyABtqYXZhLnV0aWwubG9nZ2luZy5Mb2dSZWNvcmRKjVk982lRlgMADEoA"
+                + "DGxvbmdUaHJlYWRJREoABm1pbGxpc0kADm5hbm9BZGp1c3RtZW50SgAOc2VxdWVu"
+                + "Y2VOdW1iZXJJAAh0aHJlYWRJREwABWxldmVsdAAZTGphdmEvdXRpbC9sb2dnaW5n"
+                + "L0xldmVsO0wACmxvZ2dlck5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMAAdtZXNz"
+                + "YWdlcQB+AAJMABJyZXNvdXJjZUJ1bmRsZU5hbWVxAH4AAkwAD3NvdXJjZUNsYXNz"
+                + "TmFtZXEAfgACTAAQc291cmNlTWV0aG9kTmFtZXEAfgACTAAGdGhyb3dudAAVTGph"
+                + "dmEvbGFuZy9UaHJvd2FibGU7eHAAAAAAgAAACQAAAXLMALDdAAS+2AAAAAAAAAAC"
+                + "gAAACXNyABdqYXZhLnV0aWwubG9nZ2luZy5MZXZlbI6IcRNRczaSAgADSQAFdmFs"
+                + "dWVMAARuYW1lcQB+AAJMABJyZXNvdXJjZUJ1bmRsZU5hbWVxAH4AAnhwAAADIHQA"
+                + "BElORk90ACJzdW4udXRpbC5sb2dnaW5nLnJlc291cmNlcy5sb2dnaW5ndAAFdGVz"
+                + "dDJ0ABFKYXZhIFZlcnNpb246IHswfXBwcHB3BgEAAAAAAXQACzE2LWludGVybmFs"
+                + "eA==";
+
+
+        @Override
+        public String getBase64() {
+            return base64;
+        }
+
+        @Override
+        protected void check(LogRecord r1) {
+            super.check(r1);
+        }
+
+        public static void test() {
+            new Jdk16SerializedLog().dotest();
+        }
+    }
+
+    static enum TestCase { GENERATE, TESTJDK11, TESTJDK16 };
+
+    public static void main(String[] args) {
+
+
+        // If no args, then run everything....
+        if (args == null || args.length == 0) {
+            args = new String[] { "GENERATE", "TESTJDK11", "TESTJDK16" };
+        }
+
+        // Run the specified test case(s)
+        Stream.of(args).map(x -> TestCase.valueOf(x)).forEach((x) -> {
+            switch(x) {
+                  case GENERATE:  generate(); break;
+                  case TESTJDK11: Jdk11SerializedLog.test(); break;
+                  case TESTJDK16: Jdk16SerializedLog.test(); break;
+            }
+        });
+    }
+}