001package io.jstach.rainbowgum.systemlogger;
002
003import java.lang.System.Logger;
004import java.util.Locale;
005import java.util.function.Supplier;
006
007import org.jspecify.annotations.Nullable;
008
009import io.jstach.rainbowgum.LogProperties;
010import io.jstach.rainbowgum.LogRouter;
011import io.jstach.rainbowgum.RainbowGum;
012import io.jstach.rainbowgum.spi.RainbowGumServiceProvider;
013
014/**
015 * Abstract System Logger Finder to allow users to create their own custom
016 * System.LoggerFinder. <strong>This implementation does not cache System Loggers</strong>
017 * by name!
018 *
019 * @see #INITIALIZE_RAINBOW_GUM_PROPERTY
020 */
021public abstract class RainbowGumSystemLoggerFinder extends System.LoggerFinder {
022
023        /**
024         * Initialization flag.
025         * @see InitOption
026         */
027        public static final String INITIALIZE_RAINBOW_GUM_PROPERTY = LogProperties.ROOT_PREFIX + "systemlogger.initialize";
028
029        private final RouterProvider routerProvider;
030
031        /**
032         * Values (case is ignored) for {@value #INITIALIZE_RAINBOW_GUM_PROPERTY}.
033         */
034        public enum InitOption {
035
036                /**
037                 * Will not initialize rainbow gum.
038                 */
039                FALSE,
040                /**
041                 * Will initialize rainbow gum.
042                 */
043                TRUE,
044                /**
045                 * (default) Will check if there are implementations of
046                 * {@link RainbowGumServiceProvider.RainbowGumEagerLoad} and if there are not will
047                 * load rainbow gum.
048                 */
049                CHECK,
050                /**
051                 * Will reuse an existing rainbow gum or fail.
052                 */
053                REUSE;
054
055                /**
056                 * Parses an init option from a property value.
057                 * @param input from properties.
058                 * @return init option.
059                 */
060                public static InitOption parse(String input) {
061                        if (input.isBlank())
062                                return FALSE;
063                        return InitOption.valueOf(input.toUpperCase(Locale.ROOT));
064                }
065
066        }
067
068        /**
069         * Creates the logger inder based on init option.
070         * @param optSupplier can be resolved with {@link #initOption(LogProperties)}.
071         */
072        protected RainbowGumSystemLoggerFinder(Supplier<? extends InitOption> optSupplier) {
073                try {
074                        var opt = optSupplier.get();
075                        this.routerProvider = switch (opt) {
076                                case FALSE -> n -> LogRouter.global();
077                                case TRUE -> new InitRouterProvider(RainbowGum::of);
078                                case CHECK -> {
079                                        if (RainbowGumServiceProvider.RainbowGumEagerLoad.exists()) {
080                                                yield n -> LogRouter.global();
081                                        }
082                                        yield new InitRouterProvider(RainbowGum::of);
083                                }
084                                case REUSE -> new InitRouterProvider(() -> {
085                                        var gum = RainbowGum.getOrNull();
086                                        if (gum == null) {
087                                                throw new IllegalStateException(
088                                                                "SystemLogging was configured to reuse a loaded Rainbow Gum but none was found. "
089                                                                                + INITIALIZE_RAINBOW_GUM_PROPERTY + "=" + opt);
090                                        }
091                                        return gum;
092                                });
093                        };
094                }
095                catch (Exception e) {
096                        // We have to do this because it because very difficult
097                        // to determine why the System Logging fails as it does not even print the
098                        // exception.
099                        var gum = RainbowGum.getOrNull();
100                        if (gum != null) {
101                                gum.config().alerts().error(getClass(), "Failed to create System.LoggerFinder", e);
102                        }
103                        else {
104                                System.err.println("[ERROR] - RAINBOW_GUM Failed to create System.LoggerFinder");
105                                e.printStackTrace();
106                        }
107                        throw e;
108                }
109        }
110
111        @Override
112        public Logger getLogger(String name, Module module) {
113                var router = routerProvider.router(name);
114                if (!router.isChangeable(name)) {
115                        var level = router.levelResolver().resolveLevel(name);
116                        return LevelSystemLogger.of(name, level, router.route(name, level));
117                }
118                return RainbowGumSystemLogger.of(name, router);
119        }
120
121        /**
122         * Gets the init option from properties.
123         * @param properties usually system properties.
124         * @return initialization option.
125         */
126        protected static InitOption initOption(LogProperties properties) {
127                return properties.forKey(INITIALIZE_RAINBOW_GUM_PROPERTY)
128                        .ofString()
129                        .map(InitOption::parse)
130                        .or(InitOption.CHECK)
131                        .validateNow(RainbowGumSystemLoggerFinder.class);
132        }
133
134        private interface RouterProvider {
135
136                LogRouter.RootRouter router(String loggerName);
137
138        }
139
140        private class InitRouterProvider implements RouterProvider {
141
142                private final Supplier<RainbowGum> supplier;
143
144                private volatile @Nullable RainbowGum gum = null;
145
146                public InitRouterProvider(Supplier<RainbowGum> supplier) {
147                        super();
148                        this.supplier = supplier;
149                }
150
151                @Override
152                public LogRouter.RootRouter router(String loggerName) {
153                        LogRouter.RootRouter router;
154                        RainbowGum gum = this.gum;
155                        if (gum == null) {
156                                gum = this.gum = supplier.get();
157                        }
158                        if (gum.config().changePublisher().isEnabled(loggerName)) {
159                                router = LogRouter.global();
160                        }
161                        else {
162                                router = gum.router();
163                        }
164                        return router;
165                }
166
167        }
168
169}