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}