this will help Derive a new handler from java.util.logging.StreamHandler and use its fully-qualified name with a
.handlers property.
(If Apache Maven is used, be aware of the surefire plugin using stdout as a means of IPC with its forked children, so expect corruption warnings during testing, see here.)mkdir -p /tmp/logger/{src/org.foo/{{classes,tests}/org/foo/logging{,/internal},resources},{build/modules/org.foo,dist}}
cd /tmp/logger && gio tree --hidden
file:///tmp/logger
|-- build
| `-- modules
| `-- org.foo
|-- dist
`-- src
`-- org.foo
|-- classes
| `-- org
| `-- foo
| `-- logging
| `-- internal
|-- resources
`-- tests
`-- org
`-- foo
`-- logging
`-- internal
package org.foo.logging.internal;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.util.Objects;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;
public class StandardOutConsoleHandler extends StreamHandler
{
public StandardOutConsoleHandler(Formatter formatter)
{
super(new FileOutputStream(FileDescriptor.out),
Objects.requireNonNull(formatter, "formatter"));
}
public StandardOutConsoleHandler() { this(new SimpleFormatter()); }
/* Taken from java.logging/java.util.logging.ConsoleHandler. */
@Override
public void publish(LogRecord record)
{
super.publish(record);
flush();
}
/* Taken from java.logging/java.util.logging.ConsoleHandler. */
@Override
public void close() { flush(); }
}
package org.foo.logging.internal;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQuery;
import java.util.Objects;
import java.util.logging.Filter;
import java.util.logging.LogRecord;
public class WallClockTimeFilter implements Filter
{
private static final TemporalQuery<Boolean> BUSINESS_HOURS
= new BusinessHours();
static class BusinessHours implements TemporalQuery<Boolean>
{
private static final LocalTime FROM = LocalTime.of(9, 0);
private static final LocalTime TO = LocalTime.of(17, 0);
@Override
public Boolean queryFrom(TemporalAccessor temporal)
{
final LocalTime now = LocalTime.from(temporal);
return (now.isAfter(FROM) && now.isBefore(TO));
}
}
@Override
public boolean isLoggable(LogRecord record)
{
Objects.requireNonNull(record, "record");
final LocalTime now = LocalTime.ofInstant(record.getInstant(),
ZoneId.systemDefault());
return now.query(BUSINESS_HOURS);
}
}
package org.foo.logging.internal;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
/*
* This class could be referenced on the command-line as follows
*
* -Djava.util.logging.config.class=org.foo.logging.internal.LoggingPropertiesConfigurer
*
* See java.logging/java.util.logging.LogManager#readConfiguration().
*/
public class LoggingPropertiesConfigurer
{
private static final String RESOURCE = "/logging.properties";
public LoggingPropertiesConfigurer() throws IOException
{
try (final InputStream is = getClass().getResourceAsStream(
RESOURCE)) {
if (is == null)
throw new IllegalStateException(
String.format("Unavailable resource: '%s'",
RESOURCE));
/* Prefer new non-null values over old values. */
LogManager.getLogManager().updateConfiguration(is,
property ->
((oldValue, newValue) -> {
return (oldValue == null && newValue == null)
? null /* Discard the property. */
: (newValue == null)
? oldValue
: newValue;
}));
}
}
}
package org.foo.logging;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import java.util.logging.Logger;
import org.foo.logging.internal.LoggingPropertiesConfigurer;
public class Dummy
{
static {
try {
final String fileName = System.getProperty(
"java.util.logging.config.file");
final String klassName = System.getProperty(
"java.util.logging.config.class");
if (klassName == null && fileName == null)
new LoggingPropertiesConfigurer();
} catch (final IOException e) {
throw new ExceptionInInitializerError(e);
}
}
static Optional<Logger> getLogger()
{
/*
* Note that for any org.foo.Bar.Baz.Quux member class
* Class::getName returns an org.foo.Bar$Baz$Quux string,
* therefore name accordingly these loggers, if any, in
* the properties files, e.g.
* org.foo.Bar$Baz$Quux.level = WARNING
*/
return Optional.ofNullable(Logger.getLogger(
Dummy.class.getName()));
}
public static void main(String[] args)
{
/*
* A weakly-reachable logger.
*
* See java.base/java.lang.ref.Reference#reachabilityFence(Object)
*/
Dummy.getLogger().ifPresent(logger -> logger.warning(() ->
Arrays.toString(args)));
}
}
module org.foo {
requires transitive java.logging;
exports org.foo.logging;
exports org.foo.logging.internal to
java.logging;
}
javac -Xlint -d build/modules --module-source-path src/\*/classes/ $(find src/*/classes/ -type f -name \*.java)
java --describe-module org.foo --module-path build/modules
## From [java.home]/conf/logging.properties:
# handlers = java.util.logging.ConsoleHandler
handlers = org.foo.logging.internal.StandardOutConsoleHandler
java.util.logging.SimpleFormatter.format = %1$tY-%<tm-%<td %<tH:%<tM:%<tS %4$s %2$s %5$s%6$s%n
## See the Javadoc of java.logging/java.util.logging.StreamHandler.
org.foo.logging.internal.StandardOutConsoleHandler.level = ALL
# org.foo.logging.internal.StandardOutConsoleHandler.filter = org.foo.logging.internal.WallClockTimeFilter
org.foo.logging.internal.StandardOutConsoleHandler.formatter = java.util.logging.SimpleFormatter
org.foo.logging.internal.StandardOutConsoleHandler.encoding = ISO-8859-1
cp -t build/modules/org.foo src/org.foo/resources/logging.properties
jar --create --module-version 0.0.1 --file dist/logger-0.0.1.jar --main-class org.foo.logging.Dummy -C build/modules/org.foo/ .
java -Xdiag --module-path dist/logger-0.0.1.jar --module org.foo raison d\'ĂȘtre 2>/dev/null
java -enablesystemassertions -Xdiag -Djava.util.logging.config.file=/path/to/jdk/conf/logging.properties --module-path dist/logger-0.0.1.jar --module org.foo raison d\'ĂȘtre 2>/dev/null