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