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