001package io.jstach.rainbowgum; 002 003import java.lang.System.Logger.Level; 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.Collections; 007import java.util.EnumSet; 008import java.util.IdentityHashMap; 009import java.util.List; 010import java.util.Locale; 011import java.util.Objects; 012import java.util.Set; 013import java.util.concurrent.ConcurrentLinkedQueue; 014import java.util.concurrent.CopyOnWriteArrayList; 015import java.util.concurrent.locks.ReentrantLock; 016import java.util.function.Consumer; 017import java.util.function.Function; 018 019import org.jspecify.annotations.Nullable; 020 021import io.jstach.rainbowgum.LogConfig.ChangePublisher.ChangeType; 022import io.jstach.rainbowgum.LogPublisher.PublisherFactory; 023import io.jstach.rainbowgum.LogRouter.RootRouter; 024import io.jstach.rainbowgum.LogRouter.Route; 025import io.jstach.rainbowgum.LogRouter.Router; 026import io.jstach.rainbowgum.annotation.CaseChanging; 027 028/** 029 * Routes messages to a publisher by providing a {@link Route} from a logger name and 030 * level. 031 */ 032public sealed interface LogRouter extends LogLifecycle { 033 034 /** 035 * Finds a route. 036 * @param loggerName topic. 037 * @param level level. 038 * @return route never <code>null</code>. 039 */ 040 public Route route(String loggerName, java.lang.System.Logger.Level level); 041 042 /** 043 * Global router which is always available. 044 * @return global root router. 045 */ 046 public static RootRouter global() { 047 return GlobalLogRouter.INSTANCE; 048 } 049 050 /** 051 * A route is similar to a SLF4J Logger or System Logger but has a much simpler 052 * contract. 053 * 054 * The proper usage of Route in most cases is to call {@link #isEnabled()} before 055 * calling {@link #log(LogEvent)}. 056 * 057 * That is {@link #log(LogEvent)} does not do any checking if the event is allowed 058 * furthermore by first checking if {@link #isEnabled()} is true one can decide 059 * whether or not to create a {@link LogEvent}. 060 */ 061 public sealed interface Route extends LogEventLogger { 062 063 /** 064 * Determines if {@link #log(LogEvent)} maybe called. 065 * @return true if log can be called. 066 */ 067 public boolean isEnabled(); 068 069 /** 070 * Route singletons and utilities. 071 */ 072 @CaseChanging 073 public enum Routes implements Route { 074 075 /** 076 * A route that is a NOOP and is always disabled. 077 */ 078 NotFound { 079 @Override 080 public void log(LogEvent event) { 081 } 082 083 @Override 084 public boolean isEnabled() { 085 return false; 086 } 087 088 }; 089 090 } 091 092 } 093 094 /** 095 * Router flags for adhoc router customization. 096 */ 097 @CaseChanging 098 public enum RouteFlag { 099 100 /** 101 * The route will not use the global level resolver and will only use the level 102 * config directly on the router. 103 */ 104 IGNORE_GLOBAL_LEVEL_RESOLVER; 105 106 static Set<RouteFlag> parse(Collection<String> value) { 107 if (value.isEmpty()) { 108 return EnumSet.noneOf(RouteFlag.class); 109 } 110 var s = EnumSet.noneOf(RouteFlag.class); 111 for (var v : value) { 112 s.add(parse(v)); 113 } 114 return s; 115 } 116 117 static RouteFlag parse(String value) { 118 return LogProperty.enumValue(RouteFlag.class, value); 119 } 120 121 } 122 123 /** 124 * Root router is a router that has child routers. 125 */ 126 sealed interface RootRouter extends LogRouter permits InternalRootRouter { 127 128 /** 129 * The sink to dispatch events to for a logger name, with <strong>no level based 130 * gating</strong> - unlike {@link #route(String, java.lang.System.Logger.Level)}, 131 * the returned {@link LogEventLogger} always accepts 132 * {@link LogEventLogger#log(LogEvent)} regardless of what level is currently 133 * enabled. Use this when level gating is already handled independently (e.g. a 134 * logger facade caching its own resolved level and re-checking it on every call) 135 * and a level would otherwise have to be made up just to obtain a working sink. 136 * @param loggerName topic. 137 * @return event logger, never <code>null</code>. 138 */ 139 public LogEventLogger eventLogger(String loggerName); 140 141 /** 142 * Level resolver to find levels for a log name. 143 * @return level resolver. 144 */ 145 public LevelResolver levelResolver(); 146 147 /** 148 * Register for a router changes usually involving the level resolver being 149 * updated by config. 150 * @param router consumer that will be called with the updated router. 151 * @apiNote its best to use the router in the consumer accept argument and not 152 * this router even if they are usually the same. 153 */ 154 public void onChange(Consumer<? super RootRouter> router); 155 156 /** 157 * If a logger name configuration is changeable. 158 * @param loggerName logger name. 159 * @return true if config changes are allowed. 160 */ 161 public boolean isChangeable(String loggerName); 162 163 } 164 165 /** 166 * Router routes messages to a publisher. 167 */ 168 sealed interface Router extends LogRouter, LogEventLogger { 169 170 /** 171 * The router name given if no router is explicitly declared. 172 */ 173 public static final String DEFAULT_ROUTER_NAME = LogProperties.DEFAULT_NAME; 174 175 /** 176 * Level resolver. 177 * @return level resolver. 178 */ 179 LevelResolver levelResolver(); 180 181 /** 182 * Log publisher. 183 * @return publisher. 184 */ 185 LogPublisher publisher(); 186 187 /** 188 * Whether or not the publisher is synchronous. A synchronous publisher generally 189 * blocks. 190 * @return true if the publisher is synchronous. 191 */ 192 default boolean synchronous() { 193 return publisher().synchronous(); 194 } 195 196 @Override 197 default void log(LogEvent event) { 198 /* 199 * Only async publishers need a frozen (immutable, defensively copied) event. 200 * A synchronous publisher fully encodes and writes the event on the calling 201 * thread before this call returns, so there is no other thread that could 202 * observe or race with further mutation (e.g. MDC.put/remove) of the live 203 * event data - freezing there would just be wasted allocation on the hot 204 * path. Freeze is a noop if the event is already frozen. 205 */ 206 if (!synchronous()) { 207 event = event.freeze(); 208 } 209 publisher().log(event); 210 } 211 212 /** 213 * Creates a builder from config. 214 * @param name name of the route which the publisher will inherit. 215 * @param config config. 216 * @return builder. 217 */ 218 static Builder builder(String name, LogConfig config) { 219 /* 220 * Eagerly triggers LogProperties' own key-parameter-value validation now: 221 * needed because a fully-explicit route (explicit appenders, level, 222 * publisher) never triggers a single keyed property lookup on its own, so 223 * without this a malformed name would otherwise never be validated. Wrapped 224 * into a ValidationException the same way a generated builder's constructor 225 * is, so a bad name reports the same exception type regardless of whether it 226 * came from logging.routes (RainbowGum.Builder's own validateNames call) or a 227 * direct programmatic builder(name, config) call like this one. 228 */ 229 try { 230 LogProperties.interpolateNamedKey(LogProperties.ROUTE_LEVEL_PREFIX, name); 231 } 232 catch (IllegalArgumentException e) { 233 throw LogProperty.ValidationException.of(LogRouter.class, e); 234 } 235 return new Builder(name, config); 236 } 237 238 /** 239 * Creates a router. 240 * 241 * @see AbstractRouter 242 */ 243 @FunctionalInterface 244 public interface RouterFactory { 245 246 /** 247 * Builder will call this factory if passed in. 248 * @param publisher publisher from builder. 249 * @param levelResolver level resolver from builder. 250 * @param name name from builder. 251 * @param config config from builder. 252 * @return router. 253 */ 254 public Router create(LogPublisher publisher, LevelResolver levelResolver, String name, LogConfig config); 255 256 /** 257 * Creates a router from a function. 258 * @param function <strong>the function may return <code>null</code></strong> 259 * which indicates to ignore. 260 * @return factory. 261 */ 262 static RouterFactory of(@SuppressWarnings("exports") Function<LogEvent, @Nullable LogEvent> function) { 263 return (pub, lr, n, c) -> new AbstractRouter(n, pub, lr) { 264 @Override 265 protected @Nullable LogEvent transformOrNull(LogEvent event) { 266 return function.apply(event); 267 } 268 }; 269 } 270 271 } 272 273 /** 274 * Router builder. 275 */ 276 public final class Builder extends LevelResolver.AbstractBuilder<Builder> implements LogConfig.ConfigSupport { 277 278 private final LogConfig config; 279 280 private final String name; 281 282 private final EnumSet<RouteFlag> flags = EnumSet.noneOf(RouteFlag.class); 283 284 private RouterFactory factory = (publisher, levelResolver, n, c) -> new SimpleRouter(n, publisher, 285 levelResolver); 286 287 private List<LogProvider<LogAppender>> appenders = new ArrayList<>(); 288 289 private Builder(String name, LogConfig config) { 290 this.name = Objects.requireNonNull(name); 291 this.config = config; 292 } 293 294 private @Nullable PublisherFactory publisher; 295 296 @Override 297 protected Builder self() { 298 return this; 299 } 300 301 /** 302 * Gets the currently bound config. 303 * @return config. 304 */ 305 @Override 306 public LogConfig config() { 307 return this.config; 308 } 309 310 /** 311 * Adds a flag. 312 * @param flag flag added if not already added. 313 * @return this. 314 */ 315 public Builder flag(RouteFlag flag) { 316 flags.add(flag); 317 return this; 318 } 319 320 /** 321 * Adds an appender by giving an appender builder to a consumer. 322 * @param name appender name. 323 * @param consumer consumer. 324 * @return this builder. 325 */ 326 public Builder appender(String name, Consumer<LogAppender.Builder> consumer) { 327 var builder = LogAppender.builder(name); 328 consumer.accept(builder); 329 this.appenders.add(builder.build()); 330 return this; 331 } 332 333 /** 334 * Adds an appender. 335 * @param appender appender provider. 336 * @return this builder. 337 */ 338 public Builder appender(LogProvider<LogAppender> appender) { 339 this.appenders.add(Objects.requireNonNull(appender)); 340 return self(); 341 } 342 343 /** 344 * Sets the publisher. Only one publisher to router. 345 * @param publisher publisher. 346 * @return this builder. 347 */ 348 public Builder publisher(PublisherFactory publisher) { 349 this.publisher = publisher; 350 return self(); 351 } 352 353 /** 354 * Factory to use for creating the router. 355 * @param factory router factory. 356 * @return this. 357 */ 358 public Builder factory(RouterFactory factory) { 359 this.factory = Objects.requireNonNull(factory); 360 return this; 361 } 362 363 Router build() { 364 return build(this.factory); 365 } 366 367 /** 368 * Builds the router. 369 * @param factory to create the router. 370 * @return router. 371 */ 372 Router build(RouterFactory factory) { 373 String name = this.name; 374 flags.addAll(rawValue(config.properties() 375 .forKey(LogProperties.ROUTE_FLAGS_PROPERTY, name) 376 .ofList() 377 .map(RouteFlag::parse) 378 .or(EnumSet.noneOf(RouteFlag.class))).value()); 379 String routerLevelPrefix = LogProperties.interpolateNamedKey(LogProperties.ROUTE_LEVEL_PREFIX, name); 380 381 /* 382 * The route's own level config is resolved in isolation from the global 383 * level resolver so that a route level override takes priority per logger 384 * name instead of merging with global by prefix specificity (a more 385 * specific global override would otherwise win over a coarser route 386 * override even though the route is meant to take precedence). If the 387 * route has no opinion at all for a given name (resolves to Level.ALL) 388 * resolution falls through to the global resolver for that name, unless 389 * IGNORE_GLOBAL_LEVEL_RESOLVER is set in which case global is never 390 * consulted. 391 */ 392 var routeLevelResolverBuilder = LevelResolver.builder(); 393 var currentConfig = buildLevelConfigOrNull(); 394 if (currentConfig != null) { 395 routeLevelResolverBuilder.config(currentConfig); 396 } 397 routeLevelResolverBuilder.config(config.properties(), routerLevelPrefix); 398 /* 399 * config.levelResolver() (the global resolver) has no caching of its own 400 * - the previous single merged CompositeLevelConfig relied on being 401 * wrapped in one outer cache for the whole route+global composition. 402 * LevelResolver.Builder.build() restores that guarantee (level resolvers 403 * are cached / static, see LevelResolver's class documentation) for the 404 * combined route+global resolver below. 405 */ 406 if (!flags.contains(RouteFlag.IGNORE_GLOBAL_LEVEL_RESOLVER)) { 407 routeLevelResolverBuilder.fallback(config.levelResolver()); 408 } 409 var levelResolver = routeLevelResolverBuilder.build().provide(name, config); 410 411 /* 412 * The root level might not be set by the route or globally, even when the 413 * route sets levels for some loggers. 414 */ 415 var level = levelResolver.resolveLevel(""); 416 Objects.requireNonNull(level); 417 if (level == System.Logger.Level.ALL) { 418 /* 419 * Neither the route nor the global level resolver have a root level. 420 * Without this fallback loggers they have no opinion on would be ALL, 421 * which the builders and other places turn into TRACE. 422 */ 423 levelResolver = LevelResolver.builder() 424 .resolver(levelResolver) 425 .fallback(StaticLevelResolver.INFO) 426 .build() 427 .provide(name, config); 428 } 429 430 var _levelResolver = levelResolver; 431 /* 432 * This routers level resolver needs to be notified if config changes. 433 */ 434 config.changePublisher().subscribe(c -> { 435 _levelResolver.clear(); 436 }); 437 var publisher = this.publisher; 438 439 List<LogProvider<LogAppender>> appenders = new ArrayList<>(this.appenders); 440 if (appenders.isEmpty()) { 441 DefaultAppenderRegistry.appenders(config, name) // 442 .stream() // 443 .forEach(appenders::add); 444 } 445 446 if (publisher == null) { 447 var properties = config.properties(); 448 publisher = rawValue(properties.forKey(LogProperties.ROUTE_PUBLISHER_PROPERTY, name) 449 .ofProviderRef() 450 .map(r -> config.publisherRegistry().provide(r)) 451 .or(() -> LogPublisher.SyncLogPublisher.builder().build())).value(); 452 } 453 454 var apps = new LogAppender.Appenders(name, config, appenders); 455 var pub = publisher.create(name, config, apps); 456 /* 457 * Register the publisher and its appenders for lookup like status checks 458 * (see LogReporter). Registering "apps" itself rather than its resolved 459 * list lets a lookup happen after the fact regardless of whether the 460 * publisher called asList()/asSingle() eagerly here or lazily later, and 461 * Appenders.resolvedOrNull() returns null if the publisher never calls 462 * either (meaning it does not use the appenders this ordinary way at 463 * all). 464 */ 465 config.serviceRegistry().put(LogPublisher.class, name, pub); 466 config.serviceRegistry().put(LogAppender.Appenders.class, name, apps); 467 return factory.create(pub, levelResolver, name, config); 468 } 469 470 /* 471 * Both flags and publisher above already have a fallback applied (Missing is 472 * impossible), but a present-but-malformed value must still surface its raw, 473 * unwrapped exception - not a "Validation failed for Builder:" wrapping, 474 * which would also change the exception's public type away from 475 * PropertyConvertException (see ConfigFailureTest's bogus-scheme publisher 476 * case). LogProperty.Result no longer exposes value() itself (a bare Result 477 * might still be Missing), so this just narrows to 478 * LogProperty.ValidatedResult - true for every concrete Result - without 479 * going through a Validator at all. 480 */ 481 private static <T> LogProperty.ValidatedResult<T> rawValue(LogProperty.Result<T> result) { 482 return switch (result) { 483 case LogProperty.ValidatedResult<T> vr -> vr; 484 }; 485 } 486 487 } 488 489 } 490 491 /** 492 * A user supplied router usually for filtering or transforming purposes. The method 493 * required to implement is {@link #transformOrNull(LogEvent)}. 494 * 495 * @see RouterFactory 496 */ 497 @SuppressWarnings("javadoc") // TODO Eclipse bug. 498 public non-sealed abstract class AbstractRouter implements Router, Route { 499 500 private final String name; 501 502 private final LogPublisher publisher; 503 504 private final LevelResolver levelResolver; 505 506 /** 507 * Router tuple of publisher and resolver. 508 * @param name router name not <code>null</code>. 509 * @param publisher not <code>null</code>. 510 * @param levelResolver not <code>null</code>. 511 */ 512 protected AbstractRouter(String name, LogPublisher publisher, LevelResolver levelResolver) { 513 super(); 514 this.name = name; 515 this.publisher = publisher; 516 this.levelResolver = levelResolver; 517 } 518 519 @Override 520 public final boolean synchronous() { 521 return Router.super.synchronous(); 522 } 523 524 @Override 525 public final void log(LogEvent event) { 526 var e = transformOrNull(event); 527 if (e != null) { 528 Router.super.log(e); 529 } 530 } 531 532 /** 533 * Implement for custom filtering or transformation. 534 * @param event usually from logging facade. 535 * @return event to be pushed to logger or not if <code>null</code>. 536 */ 537 protected abstract @Nullable LogEvent transformOrNull(LogEvent event); 538 539 @Override 540 public final Route route(String loggerName, Level level) { 541 if (levelResolver().isEnabled(loggerName, level)) { 542 return this; 543 } 544 return Route.Routes.NotFound; 545 } 546 547 @Override 548 public final void start(LogConfig config) { 549 publisher.start(config); 550 } 551 552 @Override 553 public final void close() { 554 publisher.close(); 555 556 } 557 558 @Override 559 public final boolean isEnabled() { 560 return true; 561 } 562 563 @Override 564 public final LevelResolver levelResolver() { 565 return this.levelResolver; 566 } 567 568 @Override 569 public final LogPublisher publisher() { 570 return this.publisher; 571 } 572 573 @Override 574 public String toString() { 575 return getClass() + "[name=" + name + ", publisher=" + publisher + ", levelResolver=" + levelResolver + "]"; 576 } 577 578 } 579 580} 581 582record SimpleRouter(String name, LogPublisher publisher, LevelResolver levelResolver) implements Router, Route { 583 584 @Override 585 public void close() { 586 publisher.close(); 587 } 588 589 @Override 590 public Route route(String loggerName, java.lang.System.Logger.Level level) { 591 if (levelResolver().isEnabled(loggerName, level)) { 592 return this; 593 } 594 return Route.Routes.NotFound; 595 } 596 597 @Override 598 public boolean isEnabled() { 599 return true; 600 } 601 602 @Override 603 public void start(LogConfig config) { 604 publisher.start(config); 605 } 606 607} 608 609sealed interface InternalRootRouter extends RootRouter { 610 611 /** 612 * Sets the root router. 613 * @param router root router. 614 */ 615 static void setRouter(RootRouter router) { 616 if (GlobalLogRouter.INSTANCE == router) { 617 return; 618 } 619 GlobalLogRouter.INSTANCE.drain((InternalRootRouter) router); 620 } 621 622 static InternalRootRouter of(List<? extends Router> routes, LogConfig config) { 623 624 if (routes.isEmpty()) { 625 throw new IllegalArgumentException("atleast one route is required"); 626 } 627 List<Router> sorted = new ArrayList<>(); 628 /* 629 * We add the async routers first 630 */ 631 Set<Router> matched = Collections.newSetFromMap(new IdentityHashMap<Router, Boolean>()); 632 for (var r : routes) { 633 if (!r.synchronous()) { 634 if (matched.add(r)) { 635 sorted.add(r); 636 } 637 } 638 } 639 for (var r : routes) { 640 if (r.synchronous()) { 641 if (matched.add(r)) { 642 sorted.add(r); 643 } 644 } 645 } 646 routes = sorted; 647 LevelResolver.Builder resolverBuilder = LevelResolver.builder(); 648 routes.stream().map(Router::levelResolver).forEach(resolverBuilder::resolver); 649 var globalLevelResolver = resolverBuilder.build().provide("", config); 650 651 Router[] array = routes.toArray(new Router[] {}); 652 653 RouteChangePublisher changePublisher = new RouteChangePublisher( 654 s -> config.changePublisher().allowedChanges(s).contains(ChangeType.LEVEL)); 655 if (array.length == 1) { 656 var r = array[0]; 657 return new SingleRootRouter(r, changePublisher); 658 } 659 return new CompositeLogRouter(array, globalLevelResolver, changePublisher); 660 } 661 662 default boolean isEnabled(String loggerName, java.lang.System.Logger.Level level) { 663 return route(loggerName, level).isEnabled(); 664 665 } 666 667 default void drain(InternalRootRouter delegate) { 668 669 } 670 671 @Override 672 default void onChange(Consumer<? super RootRouter> router) { 673 changePublisher().add(router); 674 } 675 676 public RouteChangePublisher changePublisher(); 677 678 @Override 679 default boolean isChangeable(String loggerName) { 680 return changePublisher().loggerChangeable.apply(loggerName); 681 } 682 683 final class RouteChangePublisher { 684 685 private final Collection<Consumer<? super RootRouter>> consumers = new CopyOnWriteArrayList<Consumer<? super RootRouter>>(); 686 687 private final Function<String, Boolean> loggerChangeable; 688 689 RouteChangePublisher(Function<String, Boolean> loggerChangeable) { 690 super(); 691 this.loggerChangeable = loggerChangeable; 692 } 693 694 void add(Consumer<? super RootRouter> consumer) { 695 consumers.add(consumer); 696 } 697 698 void publish(RootRouter router) { 699 for (var c : consumers) { 700 c.accept(router); 701 } 702 } 703 704 void transferTo(RouteChangePublisher changePublisher) { 705 changePublisher.consumers.addAll(consumers); 706 this.consumers.clear(); 707 } 708 709 } 710 711} 712 713record SingleRootRouter(Router router, RouteChangePublisher changePublisher) implements InternalRootRouter { 714 715 @Override 716 public void start(LogConfig config) { 717 router.start(config); 718 } 719 720 @Override 721 public void close() { 722 router.close(); 723 } 724 725 @Override 726 public LevelResolver levelResolver() { 727 return router.levelResolver(); 728 } 729 730 @Override 731 public Route route(String loggerName, Level level) { 732 return router.route(loggerName, level); 733 } 734 735 @Override 736 public LogEventLogger eventLogger(String loggerName) { 737 return router; 738 } 739 740} 741 742@SuppressWarnings("ArrayRecordComponent") // TODO revisit perf 743record CompositeLogRouter(Router[] routers, LevelResolver levelResolver, 744 RouteChangePublisher changePublisher) implements InternalRootRouter, Route { 745 746 @Override 747 public Route route(String loggerName, Level level) { 748 /* 749 * The assumption is the provided level resolver is a cached composite level 750 * resolver of all the routers. 751 */ 752 if (levelResolver.isEnabled(loggerName, level)) { 753 return this; 754 } 755 return Routes.NotFound; 756 } 757 758 @Override 759 public LogEventLogger eventLogger(String loggerName) { 760 /* 761 * log(LogEvent) below already fans out to every child router regardless of how it 762 * was reached, so this composite is already its own sink - no per-child selection 763 * happens until log() actually runs. 764 */ 765 return this; 766 } 767 768 @Override 769 public void log(LogEvent event) { 770 for (var router : routers) { 771 var route = router.route(event.loggerName(), event.level()); 772 if (route.isEnabled()) { 773 /* 774 * We assume async routers are earlier in the array. 775 */ 776 if (!router.synchronous()) { 777 /* 778 * Now all events are frozen from here onward to guarantee that the 779 * synchronous routers see the same thing as the async routers. 780 * 781 * Freeze is a noop if it already frozen. 782 */ 783 event = event.freeze(); 784 } 785 route.log(event); 786 } 787 } 788 } 789 790 @Override 791 public boolean isEnabled() { 792 return true; 793 } 794 795 @Override 796 public void start(LogConfig config) { 797 for (var r : routers) { 798 r.start(config); 799 } 800 } 801 802 @Override 803 public void close() { 804 for (var r : routers) { 805 r.close(); 806 } 807 } 808} 809 810final class QueueEventsRouter implements InternalRootRouter, Route { 811 812 private final ConcurrentLinkedQueue<LogEvent> events = new ConcurrentLinkedQueue<>(); 813 814 private final LevelResolver levelResolver; 815 816 private final LevelResolver errorLevelResolver; 817 818 private final RouteChangePublisher changePublisher = new RouteChangePublisher(s -> true); 819 820 static QueueEventsRouter of() { 821 var validator = LogProperty.Validator.of(QueueEventsRouter.class); 822 var queueLevel = queueLevel().validate(validator); 823 var errorLevel = errorLevel().validate(validator); 824 validator.validate(); 825 return new QueueEventsRouter(StaticLevelResolver.of(queueLevel.value()), 826 StaticLevelResolver.of(errorLevel.value())); 827 } 828 829 private static LogProperty.Result<Level> queueLevel() { 830 var properties = LogProperties.StandardProperties.SYSTEM_PROPERTIES; 831 return properties.forKey(LogProperties.GLOBAL_QUEUE_LEVEL_PROPERTY) 832 .ofString() 833 .map(LevelResolver::parseLevel) 834 .or(Level.INFO); 835 } 836 837 private static final LogProperty.Result<Level> errorLevel() { 838 var properties = LogProperties.StandardProperties.SYSTEM_PROPERTIES; 839 return properties.forKey(LogProperties.GLOBAL_QUEUE_ERROR_PROPERTY) 840 .ofString() 841 .map(LevelResolver::parseLevel) 842 .or(Level.ERROR); 843 } 844 845 private QueueEventsRouter(LevelResolver levelResolver, LevelResolver errorLevelResolver) { 846 super(); 847 this.levelResolver = levelResolver; 848 this.errorLevelResolver = errorLevelResolver; 849 } 850 851 @Override 852 public LevelResolver levelResolver() { 853 return levelResolver; 854 } 855 856 @Override 857 public Route route(String loggerName, Level level) { 858 if (levelResolver.isEnabled(loggerName, level)) { 859 return this; 860 } 861 return Routes.NotFound; 862 } 863 864 @Override 865 public LogEventLogger eventLogger(String loggerName) { 866 return this; 867 } 868 869 @Override 870 public void start(LogConfig config) { 871 } 872 873 @Override 874 public void close() { 875 events.clear(); 876 } 877 878 @Override 879 public void log(LogEvent event) { 880 events.add(event); 881 if (errorLevelResolver.isEnabled(event.loggerName(), event.level())) { 882 MetaLog.error(event); 883 } 884 } 885 886 @Override 887 public void drain(InternalRootRouter delegate) { 888 LogEvent e; 889 while ((e = this.events.poll()) != null) { 890 delegate.route(e.loggerName(), e.level()).log(e); 891 } 892 } 893 894 @Override 895 public boolean isEnabled() { 896 return true; 897 } 898 899 @Override 900 public RouteChangePublisher changePublisher() { 901 return this.changePublisher; 902 } 903 904} 905 906@SuppressWarnings("ImmutableEnumChecker") 907enum GlobalLogRouter implements InternalRootRouter, Route { 908 909 INSTANCE; 910 911 private volatile InternalRootRouter delegate = QueueEventsRouter.of(); 912 913 private final ReentrantLock drainLock = new ReentrantLock(); 914 915 @Override 916 public LevelResolver levelResolver() { 917 return delegate.levelResolver(); 918 } 919 920 @Override 921 public boolean isEnabled() { 922 return true; 923 } 924 925 @Override 926 public void log(LogEvent event) { 927 InternalRootRouter d = delegate; 928 if (d instanceof QueueEventsRouter q) { 929 q.log(event); 930 } 931 else { 932 var route = d.route(event.loggerName(), event.level()); 933 if (route.isEnabled()) { 934 route.log(event); 935 } 936 } 937 } 938 939 @Override 940 public Route route(String loggerName, Level level) { 941 return this.delegate.route(loggerName, level); 942 } 943 944 @Override 945 public LogEventLogger eventLogger(String loggerName) { 946 return this.delegate.eventLogger(loggerName); 947 } 948 949 @Override 950 public boolean isEnabled(String loggerName, Level level) { 951 return this.delegate.isEnabled(loggerName, level); 952 } 953 954 @Override 955 public RouteChangePublisher changePublisher() { 956 return this.delegate.changePublisher(); 957 } 958 959 @Override 960 public void drain(InternalRootRouter delegate) { 961 _drain(delegate); 962 } 963 964 private InternalRootRouter _drain(InternalRootRouter delegate) { 965 if (delegate == this) { 966 throw new IllegalStateException("bug"); 967 } 968 drainLock.lock(); 969 try { 970 var original = this.delegate; 971 if (original instanceof QueueEventsRouter q) { 972 q.changePublisher().transferTo(delegate.changePublisher()); 973 delegate.changePublisher().publish(delegate); 974 } 975 this.delegate = Objects.requireNonNull(delegate); 976 original.drain(delegate); 977 return original; 978 } 979 finally { 980 drainLock.unlock(); 981 } 982 } 983 984 @Override 985 public void start(LogConfig config) { 986 } 987 988 @Override 989 public void close() { 990 var d = _drain(QueueEventsRouter.of()); 991 d.close(); 992 } 993 994}