megloff
megloff

Reputation: 1566

How to configure java.util.logging via properties to use standard output?

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

Answers (2)

Aliaksei Budavei
Aliaksei Budavei

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

DevOrc
DevOrc

Reputation: 29

Method 1:

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();
}

Documentation

Method 2:

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

Related Questions