8072645: java.util.logging should use java.time to get more precise time stamps
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. Co-authored-by: Peter Levart <peter.levart@gmail.com> Reviewed-by: scolebourne, plevart, rriggs
This commit is contained in:
parent
0f708456b2
commit
fc084beb84
@ -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 @@ package sun.util.logging;
|
||||
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 @@ public class LoggingSupport {
|
||||
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 @@ public class LoggingSupport {
|
||||
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;
|
||||
|
@ -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.PrintWriter;
|
||||
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 @@ public class PlatformLogger {
|
||||
|
||||
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 @@ public class PlatformLogger {
|
||||
}
|
||||
|
||||
return String.format(formatString,
|
||||
date,
|
||||
zdt,
|
||||
getCallerInfo(),
|
||||
name,
|
||||
level.name(),
|
||||
|
@ -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 @@ public class LogRecord implements java.io.Serializable {
|
||||
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 @@ public class LogRecord implements java.io.Serializable {
|
||||
* 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 @@ public class LogRecord implements java.io.Serializable {
|
||||
*
|
||||
* @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 @@ public class LogRecord implements java.io.Serializable {
|
||||
// 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 @@ public class LogRecord implements java.io.Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public class LogRecord implements java.io.Serializable {
|
||||
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 @@ public class LogRecord implements java.io.Serializable {
|
||||
* 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 @@ public class LogRecord implements java.io.Serializable {
|
||||
|
||||
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();
|
||||
|
@ -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 @@ import sun.util.logging.LoggingSupport;
|
||||
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 @@ public class SimpleFormatter extends Formatter {
|
||||
* 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 @@ public class SimpleFormatter extends Formatter {
|
||||
* 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 @@ public class SimpleFormatter extends Formatter {
|
||||
* @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 @@ public class SimpleFormatter extends Formatter {
|
||||
throwable = sw.toString();
|
||||
}
|
||||
return String.format(format,
|
||||
dat,
|
||||
zdt,
|
||||
source,
|
||||
record.getLoggerName(),
|
||||
record.getLevel().getLocalizedLevelName(),
|
||||
|
@ -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 @@ import java.util.*;
|
||||
* 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 @@ public class XMLFormatter extends Formatter {
|
||||
* @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 @@ public class XMLFormatter extends Formatter {
|
||||
* @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 @@ public class XMLFormatter extends Formatter {
|
||||
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 @@ public class XMLFormatter extends Formatter {
|
||||
* @param h The target handler (can be null)
|
||||
* @return a valid XML string
|
||||
*/
|
||||
@Override
|
||||
public String getTail(Handler h) {
|
||||
return "</log>\n";
|
||||
}
|
||||
|
@ -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.File;
|
||||
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 @@ public class LogGeneratedClassesTest extends LUtils {
|
||||
.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");
|
||||
}
|
||||
}
|
||||
|
@ -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 @@ public class FileHandlerLongLimit {
|
||||
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 @@ public class FileHandlerLongLimit {
|
||||
|
||||
// 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 @@ public class FileHandlerLongLimit {
|
||||
// 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.
|
||||
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user