首页 > 解决方案 > 如何通过属性配置 java.util.logging 以使用标准输出?

问题描述

如何通过属性配置 java.util.logging 以使用标准输出而不是标准错误?

我当前的属性文件

# 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

标签: javaloggingproperties

解决方案


从<logger>.handlers属性派生一个新的处理程序java.util.logging.StreamHandler并将其完全限定名称。

(如果使用 Apache Maven,请注意使用 surefire 插件stdout作为IPC及其分叉子代的一种方式,因此在测试期间会收到损坏警告,请参阅此处。)

例如(Java 9+)...

布局目录:

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
文件:///tmp/记录器
|-- 构建
| `-- 模块
| `-- org.foo
|-- 分布
`--src
    `-- org.foo
        |-- 班级
        | `--组织
        | `-- 富
        | `-- 记录
        | `--内部
        |-- 资源
        `-- 测试
            `--组织
                `-- 富
                    `-- 记录
                        `--内部

src/org.foo/classes在分支下编写类。

一个处理程序。

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

将模块声明编写为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;
}

编译类:

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

将属性文件写为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

复制一份用于包装:

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/ .

尝试使用重定向stdout的 ,运行stderr

Stdout记录。当本地时间允许时,取消注释中与时间相关的过滤器行logging.properties

java -Xdiag --module-path dist/logger-0.0.1.jar --module org.foo raison d\'être 2>/dev/null

Stderr记录。将 Java 安装目录的真实路径替换为/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

推荐阅读