001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.common.logging;
019
020import java.util.logging.Handler;
021import java.util.logging.Level;
022import java.util.logging.Logger;
023
024/**
025 * Configures {@link java.util.logging} (JUL).
026 *
027 * <p>This is intended to be called once at application startup (main method). It has global side
028 * effects, and is not meant to be used from libraries.
029 */
030public final class JavaUtilLogging {
031    private JavaUtilLogging() {}
032
033    /**
034     * Configures logging.
035     *
036     * <p>This sets two {@link System#setProperty(String, String)} properties to work around other
037     * logging frameworks, and to configure the format.
038     *
039     * <p>This removes all existing handlers from the root logger, and adds a single new handler
040     * which logs all messages at or above the given {@link Level} to standard error, with colors.
041     *
042     * @param level the {@link Level} for the root logger and its new handler.
043     */
044    public static void configure(Level level) {
045        // We don't want Spring Boot, which is used in ADK Web, to do configure logging:
046        System.setProperty("org.springframework.boot.logging.LoggingSystem", "none");
047
048        System.setProperty(
049                "java.util.logging.SimpleFormatter.format",
050                "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n");
051
052        // Root Logger, so for dev.enola.* but also e.g. ch.vorburger.exec or gRPC, etc.
053        var rootLogger = Logger.getLogger("");
054        rootLogger.setLevel(level);
055
056        // Remove all existing handlers
057        Handler[] handlers = rootLogger.getHandlers();
058        for (Handler handler : handlers) {
059            rootLogger.removeHandler(handler);
060        }
061
062        // Add (only) our fancy colouring handler
063        var handler = new LoggingColorConsoleHandler();
064        handler.setLevel(level);
065        rootLogger.addHandler(handler);
066
067        // =============================
068        // Configure specific frameworks
069        //   TODO Make these modular and injectable!
070
071        // Disable Java WebSockets logging entirely. The problem is that it, at FINEST, prints
072        //   BINARY data, which BREAKS subsequent logging, and that's (very) confusing.
073        //   Our own LoggingWebSocketServer already logs what we are interested in.
074        Logger.getLogger("org.java_websocket").setLevel(Level.OFF);
075    }
076}