Skip to content

Commit

Permalink
CompactFormatter support for LogRecord::getLongThreadID (eclipse-ee4j…
Browse files Browse the repository at this point in the history
…#116)

CompactFormatter support for LogRecord::getLongThreadID
Signed-off-by: jmehrens <[email protected]>
  • Loading branch information
jmehrens authored Jan 12, 2024
1 parent fd8a2e6 commit 6ee9fec
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 40 deletions.
1 change: 1 addition & 0 deletions doc/src/main/resources/docs/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ for the Eclipse EE4J Angus Mail project:
----------------------------
The following bugs have been fixed in the 2.0.3 release.

52: CompactFormatter support for LogRecord::getLongThreadID
107: java.io.UnsupportedEncodingException: en_US.iso885915 if charset is "en_US.iso885915"
110: WildFly support for MailHandler

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2023 Jason Mehrens. All rights reserved.
* Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2024 Jason Mehrens. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -367,21 +367,22 @@ public String formatLoggerName(final LogRecord record) {
}

/**
* Formats the thread id property of the given log record. By default this
* is formatted as a {@code long} by an unsigned conversion.
* Formats the thread id property of the given log record. Long thread ids
* are preferred if supported. Otherwise, the integer thread id is
* formatted as a {@code long} by an unsigned conversion.
*
* @param record the record.
* @return the formatted thread id as a number.
* @throws NullPointerException if the given record is null.
* @since JavaMail 1.5.4
*/
@SuppressWarnings("deprecation") //See JDK-8245302
public Number formatThreadID(final LogRecord record) {
/**
* Thread.getID is defined as long and LogRecord.getThreadID is defined
* as int. Convert to unsigned as a means to better map the two types of
* thread identifiers.
*/
return (((long) record.getThreadID()) & 0xffffffffL);
Long id = LogManagerProperties.getLongThreadID(record);
if (id == null) {
id = Integer.toUnsignedLong(record.getThreadID());
}
return id;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2009, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2023 Jason Mehrens. All rights reserved.
* Copyright (c) 2009, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2024 Jason Mehrens. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -76,6 +76,12 @@ final class LogManagerProperties extends Properties {
*/
private static final Method LR_GET_INSTANT;

/**
* Holds the method used to get the long thread id if running on JDK 16 or
* later.
*/
private static final Method LR_GET_LONG_TID;

/**
* Holds the method used to get the default time zone if running on JDK 9 or
* later.
Expand All @@ -88,7 +94,21 @@ final class LogManagerProperties extends Properties {
*/
private static final Method ZDT_OF_INSTANT;

static {
/**
* MethodHandle is available starting at JDK7 and Android API 26.
*/
static { //Added in JDK16 see JDK-8245302
Method lrtid = null;
try {
lrtid = LogRecord.class.getMethod("getLongThreadID");
} catch (final RuntimeException ignore) {
} catch (final Exception ignore) { //No need for specific catch.
} catch (final LinkageError ignore) {
}
LR_GET_LONG_TID = lrtid;
}

static { //Added in JDK9 see JDK-8072645
Method lrgi = null;
Method zisd = null;
Method zdtoi = null;
Expand Down Expand Up @@ -339,6 +359,39 @@ static Comparable<?> getZonedDateTime(LogRecord record) {
return null;
}

/**
* Gets the long thread id from the given log record.
*
* @param record used to get the long thread id.
* @return null if LogRecord doesn't support long thread ids.
* @throws NullPointerException if record is null.
* @since Angus Mail 2.0.3
*/
static Long getLongThreadID(final LogRecord record) {
if (record == null) {
throw new NullPointerException();
}

final Method m = LR_GET_LONG_TID;
if (m != null) {
try {
return (Long) m.invoke(record);
} catch (final InvocationTargetException ite) {
final Throwable cause = ite.getCause();
if (cause instanceof Error) {
throw (Error) cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else { //Should never happen.
throw new UndeclaredThrowableException(ite);
}
} catch (final RuntimeException ignore) {
} catch (final Exception ignore) {
}
}
return null;
}

/**
* Gets the local host name from the given service.
*
Expand Down Expand Up @@ -376,6 +429,7 @@ static String getLocalHost(final Object s) throws Exception {
*
* @param value an ISO-8601 duration character sequence.
* @return the number of milliseconds parsed from the duration.
* @throws ArithmeticException if the duration is too large or too small.
* @throws ClassNotFoundException if the java.time classes are not present.
* @throws IllegalAccessException if the method is inaccessible.
* @throws InvocationTargetException if the method throws an exception.
Expand All @@ -389,6 +443,10 @@ static String getLocalHost(final Object s) throws Exception {
* @since JavaMail 1.5.5
*/
static long parseDurationToMillis(final CharSequence value) throws Exception {
if (value == null) {
throw new NullPointerException();
}

try {
final Class<?> k = findClass("java.time.Duration");
final Method parse = k.getMethod("parse", CharSequence.class);
Expand All @@ -406,7 +464,12 @@ static long parseDurationToMillis(final CharSequence value) throws Exception {
} catch (final ExceptionInInitializerError EIIE) {
throw wrapOrThrow(EIIE);
} catch (final InvocationTargetException ite) {
throw paramOrError(ite);
final Throwable cause = ite.getCause();
if (cause instanceof ArithmeticException) {
throw (ArithmeticException) cause;
} else {
throw paramOrError(ite);
}
}
}

Expand Down Expand Up @@ -528,9 +591,9 @@ static <T> Comparator<T> reverseOrder(final Comparator<T> c) {
}

Comparator<T> reverse = null;
//Comparator in Java 1.8 has 'reversed' as a default method.
//Comparator in JDK8 has 'reversed' as a default method.
//This code calls that method first to allow custom
//code to define what reverse order means.
//code to define what reverse order means in versions older than JDK8.
try {
//assert Modifier.isPublic(c.getClass().getModifiers()) :
// Modifier.toString(c.getClass().getModifiers());
Expand Down Expand Up @@ -651,6 +714,8 @@ private static String[] reflectionClassNames() throws Exception {
final Class<?> thisClass = LogManagerProperties.class;
assert Modifier.isFinal(thisClass.getModifiers()) : thisClass;
try {
//This code must use reflection to capture extra frames.
//The invoke API doesn't produce the frames needed.
final HashSet<String> traces = new HashSet<>();
Throwable t = Throwable.class.getConstructor().newInstance();
for (StackTraceElement ste : t.getStackTrace()) {
Expand All @@ -661,6 +726,8 @@ private static String[] reflectionClassNames() throws Exception {
}
}

//This code must use reflection to capture extra frames.
//The invoke API doesn't produce the frames needed.
Throwable.class.getMethod("fillInStackTrace").invoke(t);
for (StackTraceElement ste : t.getStackTrace()) {
if (!thisClass.getName().equals(ste.getClassName())) {
Expand Down Expand Up @@ -765,10 +832,10 @@ private static InvocationTargetException wrapOrThrow(
}

/**
* This code is modified from the LogManager, which explictly states
* This code is modified from the LogManager, which explicitly states
* searching the system class loader first, then the context class loader.
* There is resistance (compatibility) to change this behavior to simply
* searching the context class loader.
* searching the context class loader. See JDK-6878454.
*
* @param name full class name
* @return the class.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023 Jason Mehrens. All rights reserved.
* Copyright (c) 2016, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2024 Jason Mehrens. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -158,6 +158,48 @@ static boolean hasJavaTimeModule() {
return false;
}

/**
* Sets the int thread id for the given log record.
*
* @param record a non null log record.
* @param id the thread id.
* @throws NullPointerException if the given record is null.
*/
@SuppressWarnings("deprecation") //See JDK-8245302
static void setIntThreadID(final LogRecord record, int id) {
record.setThreadID(id);
}

/**
* Gets the int thread id for the given log record.
*
* @param record a non null log record.
* @param id the thread id.
* @throws NullPointerException if the given record is null.
*/
@SuppressWarnings("deprecation") //See JDK-8245302
static int getIntThreadID(final LogRecord record) {
return record.getThreadID();
}

/**
* Sets the long thread id for the given log record if it is supported.
*
* @param record a non-null record.
* @param id the long thread id.
* @throws Exception if there is a problem.
* @throws NoSuchMethodException if JDK is older than JDK 16.
* @throws NullPointerException if the given record is null.
*/
static void setLongThreadID(final LogRecord record, long id)
throws Exception {
if (record == null) {
throw new NullPointerException();
}
LogRecord.class.getMethod("setLongThreadID", Long.TYPE)
.invoke(record, id);
}

/**
* Fails if any declared types are outside of the logging-mailhandler.jar.
* This includes classes from the Jakarta Mail spec.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2023 Jason Mehrens. All rights reserved.
* Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2024 Jason Mehrens. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -1603,23 +1603,105 @@ public void testFormatThrownNullRecord() {
assertNotNull(cf.formatThrown((LogRecord) null));
}

@Test
public void testFormatIntThreadIDReturnType() throws Exception {
LogRecord record = new LogRecord(Level.SEVERE, "");
setIntThreadID(record, 10);
CompactFormatter cf = new CompactFormatter("%10$d");
Number id = cf.formatThreadID(record);

//Default should be long or wider.
if (id.getClass() != Long.class) {
fail(id.getClass().toString());
}
}

@Test
public void testFormatLongThreadIDReturnType() throws Exception {
LogRecord record = new LogRecord(Level.SEVERE, "");
try {
setLongThreadID(record, 11L);
CompactFormatter cf = new CompactFormatter("%10$d");
Number id = cf.formatThreadID(record);
//Default should be long or wider.
if (id.getClass() != Long.class) {
fail(id.getClass().toString());
}
} catch (NoSuchMethodException JDK8245302) {
try {
Method m = LogRecord.class.getMethod("getLongThreadID");
fail(m.toString());
} catch (NoSuchMethodException expect) {
}
}
}

@Test
public void testFormatLongThreadID() throws Exception {
LogRecord record = new LogRecord(Level.SEVERE, "");
try {
long expected = 10L;
if (Thread.currentThread().getId() == expected) {
++expected;
}
setLongThreadID(record, expected);
assertNotEquals(expected, Thread.currentThread().getId());

CompactFormatter cf = new CompactFormatter("%10$d");
String output = cf.format(record);
String expect = Long.toString(expected);
assertEquals(expect, output);

setLongThreadID(record, -1L);
output = cf.format(record);
expect = Long.toString(-1L);
assertEquals(expect, output);

//Test that downcast works right.
Number id = cf.formatThreadID(record);
assertEquals(-1, id.intValue());
assertEquals(expect, Long.toString(id.longValue()));

setLongThreadID(record, Long.MAX_VALUE >>> 1L);
output = cf.format(record);
expect = Long.toString(Long.MAX_VALUE >>> 1L);
assertEquals(expect, output);

int tid = getIntThreadID(record);
assertTrue(String.valueOf(tid), tid < 0);
} catch (NoSuchMethodException JDK8245302) {
try {
Method m = LogRecord.class.getMethod("getLongThreadID");
fail(m.toString());
} catch (NoSuchMethodException expect) {
assertNull(LogManagerProperties.getLongThreadID(record));
}
}
}

@Test
public void testFormatThreadID() {
CompactFormatter cf = new CompactFormatter("%10$d");
LogRecord record = new LogRecord(Level.SEVERE, "");
record.setThreadID(10);
setIntThreadID(record, 10);
String output = cf.format(record);
String expect = Long.toString(record.getThreadID());
assertEquals(expect, output);

record.setThreadID(-1); //Largest value for the CompactFormatter.
setIntThreadID(record, -1);
output = cf.format(record);
expect = Long.toString((1L << 32L) - 1L);
assertEquals(expect, output);
Long ltid = LogManagerProperties.getLongThreadID(record);
if (ltid == null) {
expect = Long.toString((1L << 32L) - 1L);
assertEquals(expect, output);
} else {
expect = Long.toString(-1L);
assertEquals(expect, output);
}

//Test that downcast works right.
Number id = cf.formatThreadID(record);
assertEquals(record.getThreadID(), id.intValue());
assertEquals(getIntThreadID(record), id.intValue());
assertEquals(expect, Long.toString(id.longValue()));
}

Expand Down Expand Up @@ -1794,7 +1876,7 @@ public void testFormatExample5() {
String p = "[%9$d][%1$tT][%10$d][%2$s] %5$s%n%6$s%n";
LogRecord r = new LogRecord(Level.SEVERE, "Unable to send notification.");
r.setSequenceNumber(125);
r.setThreadID(38);
setIntThreadID(r, 38);
r.setSourceClassName("MyClass");
r.setSourceMethodName("fatal");
setEpochMilli(r, 1248203502449L);
Expand Down
Loading

0 comments on commit 6ee9fec

Please sign in to comment.