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