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