Reputation: 1566
How can I configure the java.util.logging via properties to use standard output instead of standard err?
My current property file
# Logging
handlers = java.util.logging.ConsoleHandler
# Console Logging
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n
Upvotes: 3
Views: 5682
Reputation: 307
Derive a new handler from java.util.logging.StreamHandler
and use its
fully-qualified name with a <logger>.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.)
For example (Java 9+)...
Layout directories:
mkdir -p /tmp/logger/{src/org.foo/{{classes,tests}/org/foo/logging{,/internal},resources},{build/modules/org.foo,dist}}
Verify layout:
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
Write classes under the src/org.foo/classes
branch.
A handler.
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(); }
}
A filter (optionally).
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);
}
}
A properties configurer.
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;
}));
}
}
}
A dummy.
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)));
}
}
Write a module declaration as src/org.foo/classes/module-info.java
.
module org.foo {
requires transitive java.logging;
exports org.foo.logging;
exports org.foo.logging.internal to
java.logging;
}
Compile classes:
javac -Xlint -d build/modules --module-source-path src/\*/classes/ $(find src/*/classes/ -type f -name \*.java)
Describe a module:
java --describe-module org.foo --module-path build/modules
Write a properties file as src/org.foo/resources/logging.properties
.
## 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
Make a copy of it for packaging:
cp -t build/modules/org.foo src/org.foo/resources/logging.properties
Package classes and copied resources (observe the . at the end):
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/ .
Try running with redirected stdout
, stderr
.
Stdout
logging. When local time permits, uncomment the time-related filter line in logging.properties
:
java -Xdiag --module-path dist/logger-0.0.1.jar --module org.foo raison d\'être 2>/dev/null
Stderr
logging. Substitute the real path to the Java installation directory for /path/to/jdk:
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
Upvotes: 6
Reputation: 29
You need to tell the java log manager to read your config file.
try {
InputStream configFile = MyApp.class.getResourceAsStream("/path/to/app.properties");
LogManager.getLogManager().readConfiguration(configFile);
} catch (IOException e)
{
e.printStackTrace();
}
You can pass in the path to your property file as an argument when you run the JVM.
java -Djava.util.logging.config.file=/path/to/log.properties
Upvotes: -1