001package io.jstach.rainbowgum; 002 003import java.io.UncheckedIOException; 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.EnumSet; 007import java.util.List; 008import java.util.Locale; 009import java.util.Objects; 010import java.util.Set; 011import java.util.concurrent.atomic.AtomicBoolean; 012import java.util.concurrent.locks.ReentrantLock; 013import java.util.function.Function; 014 015import org.eclipse.jdt.annotation.NonNull; 016import org.eclipse.jdt.annotation.Nullable; 017 018import io.jstach.rainbowgum.LogResponse.Status; 019import io.jstach.rainbowgum.annotation.CaseChanging; 020 021/** 022 * Appenders are guaranteed to be written synchronously much like an actor in actor 023 * concurrency. They safely hold onto and communicate with the encoder and output. 024 * Appenders largely deal with correct locking, buffer reuse and flushing. 025 * {@linkplain LogAppender.AppenderFlag Flags } can be set to control the behavior the 026 * appenders and publishers can request different appender behavior through the flags. 027 * 028 * @see LogAppender.AppenderFlag 029 * @apiNote because appenders require complicated implementation and to guarantee 030 * integrity the implementations are encapsulated (sealed). 031 */ 032public sealed interface LogAppender extends LogLifecycle, LogEventConsumer { 033 034 /** 035 * Default Console appender name. 036 */ 037 static final String CONSOLE_APPENDER_NAME = "console"; 038 039 /** 040 * Default output file appender name. 041 */ 042 static final String FILE_APPENDER_NAME = "file"; 043 044 /** 045 * Output appender property. 046 */ 047 static final String APPENDER_OUTPUT_PROPERTY = LogProperties.APPENDER_OUTPUT_PROPERTY; 048 049 /** 050 * Encoder appender property. 051 */ 052 static final String APPENDER_ENCODER_PROPERTY = LogProperties.APPENDER_ENCODER_PROPERTY; 053 054 /** 055 * Appender flags. A list of flags (usually comma separated). 056 * @see AppenderFlag 057 */ 058 static final String APPENDER_FLAGS_PROPERTY = LogProperties.APPENDER_FLAGS_PROPERTY; 059 060 /** 061 * Batch of events. <strong>DO NOT MODIFY THE ARRAY</strong>. Do not use the 062 * <code>length</code> of the passed in array but instead use <code>count</code> 063 * parameter. 064 * @param events an array guaranteed to be smaller than count. 065 * @param count the number of items. 066 */ 067 public void append(LogEvent[] events, int count); 068 069 @Override 070 public void append(LogEvent event); 071 072 /** 073 * Boolean like flags for appender that can be set with 074 * {@link LogAppender#APPENDER_FLAGS_PROPERTY}. Publisher may choose to add flags to 075 * the appenders and will be added if no flags are set on the appenders. Consequently 076 * great care should be taken when setting flags as performance maybe greatly impacted 077 * if a publisher is not designed for the flag. 078 */ 079 @CaseChanging 080 public enum AppenderFlag { 081 082 /** 083 * The appender will create a single buffer that will be reused and will be 084 * protected by the appenders locking. 085 */ 086 REUSE_BUFFER, 087 /** 088 * The appender will call flush on each item appended or if in async batch mode 089 * for each batch. 090 */ 091 IMMEDIATE_FLUSH, 092 /** 093 * The appender will drop events on reentry which happens if an appender during 094 * its append causes recursive appending in the same thread. This is an analog to 095 * what 096 * <a href="https://logback.qos.ch/manual/appenders.html#AppenderBase">Logback 097 * does by default</a>. Note that this is done using {@link ReentrantLock} and not 098 * ThreadLocal like logback <strong>and is not done by default hence the 099 * flag!</strong> 100 * <p> 101 * This flag is to allow outputs that do logging themselves. For performance 102 * reasons and to allow async publishers it is recommended that you fix the output 103 * code such that it does not do logging. This flag is ignored if 104 * {@link #REENTRY_LOG} is set. 105 * <p> 106 * <strong>This flag will not fix outputs causing lool like logging if an async 107 * publisher is used!</strong> That is why it is recommended you fix the output by 108 * dropping events that would cause infinite loop like logging. 109 * @see #REENTRY_LOG 110 */ 111 REENTRY_DROP, 112 /** 113 * The appender will log events as errors to std error on reentry which happens if 114 * an appender during its append causes recursive appending in the same thread. 115 * This is an analog to what 116 * <a href="https://logback.qos.ch/manual/appenders.html#AppenderBase">Logback 117 * does by default</a>. Note that this is done using {@link ReentrantLock} and not 118 * ThreadLocal like logback <strong>and is not done by default hence the 119 * flag!</strong> This flag is to resolve failures of outputs that then do 120 * logging. 121 * <p> 122 * This flag takes precedence over {@link #REENTRY_DROP}. 123 */ 124 REENTRY_LOG; 125 126 static Set<AppenderFlag> parse(Collection<String> value) { 127 if (value.isEmpty()) { 128 return EnumSet.noneOf(AppenderFlag.class); 129 } 130 var s = EnumSet.noneOf(AppenderFlag.class); 131 for (var v : value) { 132 s.add(parse(v)); 133 } 134 return s; 135 } 136 137 static AppenderFlag parse(String value) { 138 String v = value.toUpperCase(Locale.ROOT); 139 return AppenderFlag.valueOf(v); 140 } 141 142 } 143 144 /** 145 * Creates a builder. 146 * @param name appender name. 147 * @return builder. 148 */ 149 public static Builder builder(String name) { 150 return new Builder(name); 151 } 152 153 /** 154 * Builder for creating standard appenders. 155 * <p> 156 * If the output is not set standard out will be used. If the encoder is not set a 157 * default encoder will be resolved from the output. 158 */ 159 public static final class Builder { 160 161 private @Nullable LogProvider<? extends LogOutput> output = null; 162 163 private @Nullable LogProvider<? extends LogEncoder> encoder = null; 164 165 private @Nullable EnumSet<AppenderFlag> flags = null; 166 167 private final String name; 168 169 private Builder(String name) { 170 this.name = name; 171 } 172 173 /** 174 * Name of the appender. 175 * @return name. 176 */ 177 public String name() { 178 return this.name; 179 } 180 181 /** 182 * Sets output. 183 * @param output output. 184 * @return builder. 185 */ 186 public Builder output(LogProvider<? extends LogOutput> output) { 187 this.output = output; 188 return this; 189 } 190 191 /** 192 * Sets output. 193 * @param output output. 194 * @return builder. 195 */ 196 public Builder output(LogOutput output) { 197 this.output = LogProvider.of(output); 198 return this; 199 } 200 201 /** 202 * Sets formatter as encoder. 203 * @param formatter formatter to be converted to encoder. 204 * @return builder. 205 * @see LogEncoder#of(LogFormatter) 206 */ 207 public Builder formatter(LogFormatter formatter) { 208 this.encoder = LogProvider.of(LogEncoder.of(formatter)); 209 return this; 210 } 211 212 /** 213 * Sets formatter as encoder. 214 * @param formatter formatter to be converted to encoder. 215 * @return builder. 216 * @see LogEncoder#of(LogFormatter) 217 */ 218 public Builder formatter(LogFormatter.EventFormatter formatter) { 219 this.encoder = LogProvider.of(LogEncoder.of(formatter)); 220 return this; 221 } 222 223 /** 224 * Sets encoder. 225 * @param encoder encoder not <code>null</code>. 226 * @return builder. 227 */ 228 public Builder encoder(LogProvider<? extends LogEncoder> encoder) { 229 this.encoder = encoder; 230 return this; 231 } 232 233 /** 234 * Sets encoder. 235 * @param encoder encoder not <code>null</code>. 236 * @return builder. 237 */ 238 public Builder encoder(LogEncoder encoder) { 239 this.encoder = LogProvider.of(encoder); 240 return this; 241 } 242 243 /** 244 * Sets appender flags. 245 * @param flags flags will replace all flags currently set. 246 * @return this. 247 */ 248 public Builder flags(Collection<AppenderFlag> flags) { 249 _flags().addAll(flags); 250 return this; 251 } 252 253 private EnumSet<AppenderFlag> _flags() { 254 EnumSet<AppenderFlag> flags = this.flags; 255 if (flags == null) { 256 this.flags = flags = EnumSet.noneOf(AppenderFlag.class); 257 } 258 return flags; 259 } 260 261 /** 262 * Adds a flag. 263 * @param flag flag. 264 * @return this. 265 */ 266 public Builder flag(AppenderFlag flag) { 267 _flags().add(flag); 268 return this; 269 } 270 271 /** 272 * Builds. 273 * @return an appender factory. 274 */ 275 public LogProvider<LogAppender> build() { 276 /* 277 * We need to capture parameters since appender creation needs to be lazy. 278 */ 279 var _name = name; 280 var _output = output; 281 var _encoder = encoder; 282 var _flags = flags; 283 /* 284 * TODO should we use the parent name for resolution? 285 */ 286 return (n, config) -> { 287 AppenderConfig a = new AppenderConfig(_name, LogProvider.provideOrNull(_output, _name, config), 288 LogProvider.provideOrNull(_encoder, _name, config), _flags); 289 return DefaultAppenderRegistry.appender(a, config); 290 }; 291 } 292 293 } 294 295 /** 296 * Provides appenders safely to the publisher. The providing calls of 297 * <code>asXXX</code> can only be called once as they register the appenders. 298 */ 299 class Appenders { 300 301 private final AtomicBoolean created = new AtomicBoolean(); 302 303 private final String name; 304 305 private final LogConfig config; 306 307 private final List<LogProvider<LogAppender>> appenders; 308 309 private Set<LogAppender.AppenderFlag> flags = EnumSet.noneOf(LogAppender.AppenderFlag.class); 310 311 Appenders(String name, LogConfig config, List<LogProvider<LogAppender>> appenders) { 312 super(); 313 this.name = name; 314 this.config = config; 315 this.appenders = appenders; 316 } 317 318 /** 319 * Sets flags for the appenders which should be done prior to <code>asXXX</code>. 320 * @param flags appender flags. 321 * @return this; 322 */ 323 public Appenders flags(Set<LogAppender.AppenderFlag> flags) { 324 this.flags = flags; 325 return this; 326 } 327 328 /** 329 * Return the appenders as a list. 330 * @return list of appenders. 331 * @throws IllegalStateException if appenders are already registered. 332 */ 333 public List<? extends LogAppender> asList() throws IllegalStateException { 334 if (created.compareAndSet(false, true)) { 335 var apps = appenders(); 336 List<LogAppender> appenders = new ArrayList<>(); 337 for (var a : apps) { 338 appenders.add(register(a)); 339 } 340 return appenders; 341 } 342 else { 343 throw new IllegalStateException("Appenders already provided."); 344 } 345 346 } 347 348 /** 349 * Consolidate the appenders as a single appender. The appenders will be appended 350 * synchronously and will share the same lock. 351 * @return single appender. 352 * @throws IllegalStateException if appenders are already registered. 353 */ 354 public LogAppender asSingle() throws IllegalStateException { 355 if (created.compareAndSet(false, true)) { 356 var apps = appenders(); 357 var appender = single(apps); 358 return register(appender); 359 } 360 else { 361 throw new IllegalStateException("Appenders already provided."); 362 } 363 } 364 365 private LogAppender register(LogAppender appender) { 366 return switch (appender) { 367 case DirectLogAppender ia -> { 368 var _a = ia.withFlags(flags); 369 config.serviceRegistry().put(LogAppender.class, name + "." + _a.name(), _a); 370 yield _a; 371 } 372 case CompositeLogAppender ca -> { 373 var _a = ca.withFlags(flags); 374 config.serviceRegistry().put(LogAppender.class, name, _a); 375 yield _a; 376 } 377 default -> { 378 throw new IllegalStateException(); 379 } 380 }; 381 } 382 383 private List<LogAppender> appenders() { 384 return LogProvider.flatten(appenders) 385 .describe(n -> "Appenders for route: '" + n + "'") 386 .provide(name, config); 387 } 388 389 /** 390 * Creates a composite log appender from many. The appenders will be appended 391 * synchronously and will share the same lock. 392 * @param appenders appenders. 393 * @return appender. 394 */ 395 private static LogAppender single(List<? extends LogAppender> appenders) { 396 if (appenders.isEmpty()) { 397 throw new IllegalArgumentException("A single appender is required"); 398 } 399 if (appenders.size() == 1) { 400 return Objects.requireNonNull(appenders.get(0)); 401 } 402 return CompositeLogAppender.of(appenders, Set.of()); 403 404 } 405 406 } 407 408 @Override 409 public void close(); 410 411} 412 413interface AppenderVisitor { 414 415 boolean consume(DirectLogAppender appender); 416 417} 418 419abstract class AppenderLock { 420 421 protected final ReentrantLock realLock; 422 static Function<Set<LogAppender.AppenderFlag>, AppenderLock> lockFactoryFunction = AppenderLock::_of; 423 424 private static AppenderLock _of(Set<LogAppender.AppenderFlag> flags) { 425 var lock = new ReentrantLock(); 426 if (flags.contains(LogAppender.AppenderFlag.REENTRY_LOG)) { 427 return new LogReentryAppenderLock(lock); 428 } 429 if (flags.contains(LogAppender.AppenderFlag.REENTRY_DROP)) { 430 return new DropReentryAppenderLock(lock); 431 } 432 return new RentryAppenderLock(lock); 433 } 434 435 static AppenderLock of(Set<LogAppender.AppenderFlag> flags) { 436 return lockFactoryFunction.apply(flags); 437 } 438 439 AppenderLock(ReentrantLock realLock) { 440 super(); 441 this.realLock = realLock; 442 } 443 444 boolean tryLock() { 445 realLock.lock(); 446 return true; 447 } 448 449 void lock() { 450 realLock.lock(); 451 } 452 453 void unlock() { 454 realLock.unlock(); 455 } 456 457 AppenderLock withAllowRentry() { 458 return new RentryAppenderLock(realLock); 459 } 460 461 static final class DropReentryAppenderLock extends AppenderLock { 462 463 DropReentryAppenderLock(ReentrantLock realLock) { 464 super(realLock); 465 } 466 467 @Override 468 public boolean tryLock() { 469 if (realLock.isHeldByCurrentThread()) { 470 return false; 471 } 472 realLock.lock(); 473 return true; 474 } 475 476 } 477 478 static final class LogReentryAppenderLock extends AppenderLock { 479 480 LogReentryAppenderLock(ReentrantLock realLock) { 481 super(realLock); 482 } 483 484 @Override 485 public boolean tryLock() { 486 if (realLock.isHeldByCurrentThread()) { 487 Exception exception = new Exception("reentrant appender"); 488 MetaLog.error(LogAppender.class, exception); 489 return false; 490 } 491 realLock.lock(); 492 return true; 493 } 494 495 } 496 497 static final class RentryAppenderLock extends AppenderLock { 498 499 RentryAppenderLock(ReentrantLock realLock) { 500 super(realLock); 501 } 502 503 @Override 504 AppenderLock withAllowRentry() { 505 return this; 506 } 507 508 } 509 510} 511 512/** 513 * This is a JAVADOC BUG 514 */ 515sealed interface InternalLogAppender extends LogAppender, Actor { 516 517 static InternalLogAppender of(LogAppender appender) { 518 // InternalLogAppender a = switch (appender) { 519 // case InternalLogAppender ia -> ia; 520 // }; 521 return Objects.requireNonNull((InternalLogAppender) appender); // TODO eclipse 522 // bug. 523 } 524 525 /** 526 * THIS IS A JAVADOC BUG. 527 * @param visitor ignore 528 * @return true if stop. 529 */ 530 boolean visit(AppenderVisitor visitor); 531 532 // InternalLogAppender changeLock(AppenderLock lock); 533 // 534 // InternalLogAppender withFlags(Set<LogAppender.AppenderFlag> flags); 535 536 /** 537 * An appender can act on actions. One of the key actions is reopening files. 538 * @param action action to run. 539 * @return responses. 540 */ 541 @Override 542 public List<LogResponse> act(LogAction action); 543 544} 545 546sealed interface DirectLogAppender extends InternalLogAppender { 547 548 String name(); 549 550 LogOutput output(); 551 552 LogEncoder encoder(); 553 554 default List<LogResponse> _request(LogAction action) { 555 List<LogResponse> r = switch (action) { 556 case LogAction.StandardAction a -> switch (a) { 557 case LogAction.StandardAction.REOPEN -> List.of(reopen()); 558 case LogAction.StandardAction.FLUSH -> List.of(flush()); 559 case LogAction.StandardAction.STATUS -> List.of(status()); 560 }; 561 default -> throw new IllegalArgumentException(); // TODO fucking eclipse 562 }; 563 return r; 564 } 565 566 default LogResponse reopen() { 567 var status = output().reopen(); 568 return new Response(LogOutput.class, name(), status); 569 } 570 571 default LogResponse flush() { 572 output().flush(); 573 return new Response(LogOutput.class, name(), LogResponse.Status.StandardStatus.OK); 574 } 575 576 default LogResponse status() { 577 Status status; 578 try { 579 status = output().status(); 580 } 581 catch (Exception e) { 582 status = LogResponse.Status.ofError(e); 583 } 584 return new Response(LogOutput.class, name(), status); 585 } 586 587 static List<DirectLogAppender> findAppenders(ServiceRegistry registry) { 588 List<DirectLogAppender> appenders = new ArrayList<>(); 589 for (var a : registry.find(LogAppender.class)) { 590 if (a instanceof InternalLogAppender internal) { 591 internal.visit(appenders::add); 592 } 593 } 594 return appenders; 595 } 596 597 static DirectLogAppender of(String name, LogOutput output, LogEncoder encoder, 598 Set<LogAppender.AppenderFlag> flags) { 599 var lock = AppenderLock.of(flags); 600 if (flags.contains(AppenderFlag.REUSE_BUFFER)) { 601 return new ReuseBufferLogAppender(name, output, encoder, flags, lock); 602 } 603 return new DefaultLogAppender(name, output, encoder, flags, lock); 604 } 605 606 // @Override 607 DirectLogAppender withFlags(Set<LogAppender.AppenderFlag> flags); 608 609 // @Override 610 DirectLogAppender changeLock(AppenderLock lock); 611 612} 613 614/** 615 * An abstract appender to help create custom appenders. 616 */ 617sealed abstract class AbstractLogAppender implements DirectLogAppender { 618 619 /** 620 * name. 621 */ 622 protected final String name; 623 624 /** 625 * output 626 */ 627 protected final LogOutput output; 628 629 /** 630 * encoder 631 */ 632 protected final LogEncoder encoder; 633 634 protected final Set<LogAppender.AppenderFlag> flags; 635 636 protected final boolean immediateFlush; 637 638 /** 639 * Creates an appender from an output and encoder. 640 * @param output set the output field and will be started and closed with the 641 * appender. 642 * @param encoder set the encoder field. 643 */ 644 protected AbstractLogAppender(String name, LogOutput output, LogEncoder encoder, 645 Set<LogAppender.AppenderFlag> flags) { 646 super(); 647 this.name = name; 648 this.output = output; 649 this.encoder = encoder; 650 this.flags = flags; 651 this.immediateFlush = flags.contains(LogAppender.AppenderFlag.IMMEDIATE_FLUSH); 652 } 653 654 @Override 655 public void start(LogConfig config) { 656 output.start(config); 657 } 658 659 @Override 660 public void close() { 661 output.close(); 662 } 663 664 @Override 665 public String toString() { 666 return getClass().getName() + "[name=" + name + " encoder=" + encoder + ", " + "output=" + output + ", flags=" 667 + flags + "]"; 668 } 669 670 @Override 671 public boolean visit(AppenderVisitor visitor) { 672 return visitor.consume(this); 673 } 674 675 @Override 676 public String name() { 677 return this.name; 678 } 679 680 @Override 681 public LogOutput output() { 682 return this.output; 683 } 684 685 @Override 686 public LogEncoder encoder() { 687 return this.encoder; 688 } 689 690} 691 692sealed interface BaseComposite<T extends InternalLogAppender> extends InternalLogAppender { 693 694 T[] components(); 695 696 AppenderLock lock(); 697 698 @Override 699 default void append(LogEvent[] event, int count) { 700 if (!lock().tryLock()) { 701 return; 702 } 703 try { 704 for (var appender : components()) { 705 appender.append(event, count); 706 } 707 } 708 finally { 709 lock().unlock(); 710 } 711 } 712 713 @Override 714 default void append(LogEvent event) { 715 if (!lock().tryLock()) { 716 return; 717 } 718 try { 719 for (var appender : components()) { 720 appender.append(event); 721 } 722 } 723 finally { 724 lock().unlock(); 725 } 726 } 727 728 @Override 729 default void close() { 730 lock().lock(); 731 try { 732 for (var appender : components()) { 733 appender.close(); 734 } 735 } 736 finally { 737 lock().unlock(); 738 } 739 } 740 741 @Override 742 default void start(LogConfig config) { 743 lock().lock(); 744 try { 745 for (var appender : components()) { 746 appender.start(config); 747 } 748 } 749 finally { 750 lock().unlock(); 751 } 752 753 } 754 755 @Override 756 default boolean visit(AppenderVisitor visitor) { 757 for (var appender : components()) { 758 if (appender.visit(visitor)) { 759 return true; 760 } 761 } 762 return false; 763 } 764 765 @Override 766 default List<LogResponse> act(LogAction action) { 767 lock().lock(); 768 try { 769 return Actor.act(components(), action); 770 } 771 finally { 772 lock().unlock(); 773 } 774 } 775 776} 777 778@SuppressWarnings("ArrayRecordComponent") 779record CompositeLogAppender(DirectLogAppender[] appenders, 780 AppenderLock lock) implements BaseComposite<DirectLogAppender>, InternalLogAppender { 781 782 public static CompositeLogAppender of(List<? extends LogAppender> appenders, Set<LogAppender.AppenderFlag> flags) { 783 AppenderLock lock = AppenderLock.of(flags); 784 AppenderLock directLock = lock.withAllowRentry(); 785 @SuppressWarnings("null") // TODO Eclipse issue here 786 DirectLogAppender @NonNull [] array = appenders.stream() 787 .map(CompositeLogAppender::cast) 788 .map(a -> a.withFlags(flags).changeLock(directLock)) 789 .toArray(i -> new DirectLogAppender[i]); 790 return new CompositeLogAppender(array, lock); 791 } 792 793 private static DirectLogAppender cast(LogAppender appender) { 794 return (DirectLogAppender) appender; 795 } 796 797 @Override 798 public DirectLogAppender[] components() { 799 return this.appenders; 800 } 801 802 // @Override 803 // public CompositeLogAppender changeLock(AppenderLock lock) { 804 // return of(List.of(appenders), lock, 805 // EnumSet.noneOf(LogAppender.AppenderFlag.class)); 806 // } 807 808 // @Override 809 public CompositeLogAppender withFlags(Set<LogAppender.AppenderFlag> flags) { 810 if (flags.isEmpty()) { 811 return this; 812 } 813 return of(List.of(appenders), flags); 814 } 815 816} 817 818sealed abstract class LockLogAppender extends AbstractLogAppender implements InternalLogAppender { 819 820 protected final AppenderLock lock; 821 822 public LockLogAppender(String name, LogOutput output, LogEncoder encoder, Set<LogAppender.AppenderFlag> flags, 823 AppenderLock lock) { 824 super(name, output, encoder, flags); 825 this.lock = lock; 826 } 827 828 @Override 829 public List<LogResponse> act(LogAction action) { 830 lock.lock(); 831 try { 832 return _request(action); 833 } 834 catch (UncheckedIOException ioe) { 835 return List.of(new Response(LogOutput.class, name, Status.ErrorStatus.of(ioe))); 836 } 837 finally { 838 lock.unlock(); 839 } 840 } 841 842 @Override 843 public void close() { 844 lock.lock(); 845 try { 846 super.close(); 847 } 848 finally { 849 lock.unlock(); 850 } 851 } 852 853 @Override 854 public DirectLogAppender withFlags(Set<LogAppender.AppenderFlag> flags) { 855 if (flags.isEmpty()) { 856 return this; 857 } 858 if (this.flags.containsAll(flags)) { 859 return this; 860 } 861 flags = EnumSet.copyOf(flags); 862 flags.addAll(this.flags); 863 if (flags.contains(LogAppender.AppenderFlag.REUSE_BUFFER)) { 864 return new ReuseBufferLogAppender(name, output, encoder, flags, lock); 865 } 866 return new DefaultLogAppender(name, output, encoder, flags, lock); 867 } 868 869} 870 871/* 872 * The idea here is to have the virtual thread do the formatting outside of the lock 873 */ 874final class DefaultLogAppender extends LockLogAppender implements InternalLogAppender { 875 876 DefaultLogAppender(String name, LogOutput output, LogEncoder encoder, Set<LogAppender.AppenderFlag> flags, 877 AppenderLock lock) { 878 super(name, output, encoder, flags, lock); 879 } 880 881 @Override 882 public final void append(LogEvent event) { 883 try (var buffer = encoder.buffer(output.bufferHints())) { 884 encoder.encode(event, buffer); 885 if (!lock.tryLock()) { 886 return; 887 } 888 try { 889 output.write(event, buffer); 890 if (immediateFlush) { 891 output.flush(); 892 } 893 } 894 finally { 895 lock.unlock(); 896 } 897 } 898 } 899 900 @Override 901 public void append(LogEvent[] events, int count) { 902 if (!lock.tryLock()) { 903 return; 904 } 905 try { 906 output.write(events, count, encoder); 907 if (immediateFlush) { 908 output.flush(); 909 } 910 } 911 finally { 912 lock.unlock(); 913 } 914 } 915 916 @Override 917 public DirectLogAppender changeLock(AppenderLock lock) { 918 return new DefaultLogAppender(name, output, encoder, flags, lock); 919 } 920 921} 922 923/* 924 * The idea here is to reuse the buffer trading lock contention for less GC. 925 */ 926final class ReuseBufferLogAppender extends LockLogAppender implements InternalLogAppender { 927 928 private final LogEncoder.Buffer buffer; 929 930 ReuseBufferLogAppender(String name, LogOutput output, LogEncoder encoder, Set<LogAppender.AppenderFlag> flags, 931 AppenderLock lock) { 932 super(name, output, encoder, flags, lock); 933 this.buffer = encoder.buffer(output.bufferHints()); 934 } 935 936 // ReuseBufferLogAppender(String name, LogOutput output, LogEncoder encoder) { 937 // this(name, output, encoder, EnumSet.noneOf(LogAppender.AppenderFlag.class), new 938 // ReentrantLock()); 939 // } 940 941 @Override 942 public final void append(LogEvent event) { 943 if (!lock.tryLock()) { 944 return; 945 } 946 try { 947 buffer.clear(); 948 encoder.encode(event, buffer); 949 output.write(event, buffer); 950 if (immediateFlush) { 951 output.flush(); 952 } 953 } 954 finally { 955 lock.unlock(); 956 } 957 } 958 959 @Override 960 public void append(LogEvent[] events, int count) { 961 if (!lock.tryLock()) { 962 return; 963 } 964 try { 965 output.write(events, count, encoder, buffer); 966 if (immediateFlush) { 967 output.flush(); 968 } 969 } 970 finally { 971 lock.unlock(); 972 } 973 } 974 975 @Override 976 public void close() { 977 lock.lock(); 978 try { 979 super.close(); 980 buffer.close(); 981 } 982 finally { 983 lock.unlock(); 984 } 985 } 986 987 @Override 988 public DirectLogAppender changeLock(AppenderLock lock) { 989 return new ReuseBufferLogAppender(name, output, encoder, flags, lock); 990 } 991 992}