ソースコード内で簡単に設定してjava.util.logging(JUL)を利用したい

# はじめに

自分はお堅いSIer勤めということもあり、外部ライブラリが利用できないことがよくあります。そういう環境で、Javaを使って、ちょっとしたツールなどを作成したいという場合、ロギングにはjava.util.logging(JUL)を利用することになります。

JULの設定方法はいろいろありますが、ちょっとしたツールを作りたいだけなので、ソースコード内で簡単に設定して終わりにしたいです。こういう場合に自分はどうしているのか、というのをここで紹介したいと思います。

# 設定方法

自分がログにもとめる要件は以下の通りです。

- コンソールとファイルにログを記録する
- ログフォーマットはコンソールとファイルで同じでよいが、SimpleFormatterのデフォルトだとわかりにくいので、わかりやすいものにしたい
- ファイル名はapp_*yyyymmdd*.logとする (yyyymmddは実行日付)。ログファイルは追記して使う。

これを実現したい場合、mainメソッドの一番最初に、JULの設定処理を記載すればよいです。

```java
public static void main(String[] args) {
    Properties properties = new Properties();
    properties.setProperty("handlers", "java.util.logging.FileHandler, java.util.logging.ConsoleHandler");
    properties.setProperty("java.util.logging.SimpleFormatter.format", "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$s %2$s %5$s%6$s%n");
    properties.setProperty("java.util.logging.FileHandler.pattern", "app_%s.log".formatted(DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDateTime.now())));
    properties.setProperty("java.util.logging.FileHandler.formatter", "java.util.logging.SimpleFormatter");
    properties.setProperty("java.util.logging.FileHandler.append", "true");

    try {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        properties.store(byteArrayOutputStream, "");
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());

        LogManager logManager = LogManager.getLogManager();
        logManager.reset();
        logManager.readConfiguration(byteArrayInputStream);
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    }

    // Main Routine
}
```

やっていることは単純。JULの設定ファイルであるlogging.propertiesファイルに書く内容をソースコード上にハードコードして、ロガーの設定を上書きしているだけです。

# 使用例

では、実際に利用してみましょう。以下のようなソースコードがあるとします。

```java
public class XXXService {
    private static final Logger logger = Logger.getLogger(XXXService.class.getName());

    public void run() {
        logger.log(Level.INFO, "START");
        try {
            List<String> lines = Files.readAllLines(Path.of("/not/found/text"));
            /// Do Something
        } catch (IOException e) {
            logger.log(Level.SEVERE, e.getMessage(), e);
            throw new UncheckedIOException(e);
        }
        logger.log(Level.INFO, "END");
    }
}
```

これを実行すると、コンソールとapp_*yyyymmdd*.logファイルに次のようなログが記録されるはずです。

```
2023-10-01 12:25:58.585 情報 com.jp.tool.XXXService run START
2023-10-01 12:25:58.653 重大 com.jp.tool.XXXService run \not\found\text
java.nio.file.NoSuchFileException: \not\found\text
	at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:85)
	at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
	at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
	at java.base/sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:236)
	at java.base/java.nio.file.Files.newByteChannel(Files.java:380)
	at java.base/java.nio.file.Files.newByteChannel(Files.java:432)
	at java.base/java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:422)
	at java.base/java.nio.file.Files.newInputStream(Files.java:160)
	at java.base/java.nio.file.Files.newBufferedReader(Files.java:2922)
	at java.base/java.nio.file.Files.readAllLines(Files.java:3412)
	at java.base/java.nio.file.Files.readAllLines(Files.java:3453)
	at com.jp.tool.XXXService.run(XXXService.java:17)
	at com.jp.tool.Main.main(Main.java:35)
```

# 環境情報

```
D:\>javac -version
javac 17.0.3

D:\>java -version
openjdk version "17.0.3" 2022-04-19
OpenJDK Runtime Environment Temurin-17.0.3+7 (build 17.0.3+7)
OpenJDK 64-Bit Server VM Temurin-17.0.3+7 (build 17.0.3+7, mixed mode, sharing)

```