001package io.jstach.rainbowgum;
002
003import java.io.UncheckedIOException;
004import java.net.URI;
005
006import org.eclipse.jdt.annotation.Nullable;
007
008import io.jstach.rainbowgum.LogAppender.Appenders;
009
010/**
011 * Publishers push logs to appenders either synchronously or asynchronously.
012 * Implementations are required to be threadsafe and <strong>overlapping calls are
013 * expected!</strong> The publisher may report health issues such as metrics of dropped
014 * events with {@link #status()}.
015 */
016public sealed interface LogPublisher extends LogEventLogger, LogLifecycle, LogComponent {
017
018        /**
019         * If the publisher is synchronous.
020         * @return true if {@link LogPublisher.SyncLogPublisher}.
021         */
022        public boolean synchronous();
023
024        /**
025         * Requests the health of this publisher. If no exception is thrown the returned value
026         * is used. If an exception is thrown the status is considered to be error. The
027         * default implementation will return {@link LogResponse.Status.StandardStatus#OK}.
028         * which will check previous meta log error entries.
029         * @return status of this output.
030         * @throws Exception if status check fails which will be an error status.
031         */
032        default LogResponse.Status status() throws Exception {
033                return LogResponse.Status.StandardStatus.OK;
034        }
035
036        /**
037         * A factory for a publisher from config and appenders.
038         */
039        public interface PublisherFactory {
040
041                /**
042                 * Create the log publisher from config and appenders.
043                 * @param name used to identify where to pull config from.
044                 * @param config log config.
045                 * @param appenders appenders.
046                 * @return publisher.
047                 */
048                LogPublisher create(String name, LogConfig config, Appenders appenders);
049
050                /**
051                 * Provides a publisher factory by URI.
052                 * @param scheme scheme will be used to resolve publisher factory.
053                 * @return publisher factory.
054                 */
055                static PublisherFactory of(String scheme) {
056                        URI uri = URI.create(scheme + ":///");
057                        return of(uri);
058                }
059
060                /**
061                 * Provides the default publisher factory.
062                 * @return publisher factory.
063                 */
064                static PublisherFactory of() {
065                        return of(LogPublisherRegistry.DEFAULT_SCHEME);
066                }
067
068                /**
069                 * Provides a lazy loaded publisher from a URI.
070                 * @param ref uri.
071                 * @return provider of output.
072                 * @apiNote the provider may throw an {@link UncheckedIOException}.
073                 */
074                public static PublisherFactory of(LogProviderRef ref) {
075                        return (name, config, appenders) -> config.publisherRegistry().provide(ref).create(name, config, appenders);
076                }
077
078                /**
079                 * Provides a publisher factory by URI.
080                 * @param uri uri whose scheme will be used to resolve publisher factory.
081                 * @return publisher factory.
082                 */
083                static PublisherFactory of(URI uri) {
084                        return of(LogProviderRef.of(uri));
085                }
086
087                /**
088                 * Provides the default async publisher.
089                 * @param bufferSize maybe null provided as convenience as almost all async
090                 * publishers have some buffer.
091                 * @return async publisher.
092                 */
093                static PublisherFactory ofAsync(@Nullable Integer bufferSize) {
094                        String query = bufferSize == null ? "" : "?" + LogPublisherRegistry.BUFFER_SIZE_NAME + "=" + bufferSize;
095                        URI uri = URI.create(LogPublisherRegistry.ASYNC_SCHEME + ":///" + query);
096                        return of(uri);
097                }
098
099                /**
100                 * Provides the default registered sync publisher.
101                 * @return sync publisher.
102                 */
103                static PublisherFactory ofSync() {
104                        return of(LogPublisherRegistry.SYNC_SCHEME);
105                }
106
107                /**
108                 * Async builder.
109                 * @return async builder.
110                 */
111                public static AsyncLogPublisher.Builder async() {
112                        return AsyncLogPublisher.builder();
113                }
114
115                /**
116                 * Sync builder.
117                 * @return sync builder.
118                 */
119                public static SyncLogPublisher.Builder sync() {
120                        return SyncLogPublisher.builder();
121                }
122
123        }
124
125        /**
126         * SPI for custom publishers.
127         */
128        public interface PublisherProvider {
129
130                /**
131                 * Provides a publisher factory by properties and uri.
132                 * @param ref reference to provider usually just a uri
133                 * @return publisher factory.
134                 */
135                PublisherFactory provide(LogProviderRef ref);
136
137        }
138
139        /**
140         * Abstract publisher builder.
141         *
142         * @param <T> publisher builder type.
143         */
144        abstract class AbstractBuilder<T> {
145
146                /**
147                 * do nothing.
148                 */
149                protected AbstractBuilder() {
150                        super();
151                }
152
153                /**
154                 * This.
155                 * @return this.
156                 */
157                protected abstract T self();
158
159                /**
160                 * Creates publisher provider.
161                 * @return publisher provider
162                 */
163                public abstract PublisherFactory build();
164
165        }
166
167        /**
168         * Async publisher.
169         */
170        non-sealed interface AsyncLogPublisher extends LogPublisher {
171
172                @Override
173                default boolean synchronous() {
174                        return false;
175                }
176
177                /**
178                 * Async publisher builder.
179                 * @return builder.
180                 */
181                public static AsyncLogPublisher.Builder builder() {
182                        return new Builder();
183                }
184
185                /**
186                 * Async publisher builder.
187                 */
188                public static class Builder extends AbstractBuilder<AsyncLogPublisher.Builder> {
189
190                        /**
191                         * Buffer Size Property for Async publishers.
192                         */
193                        public static final String BUFFER_SIZE_PROPERTY = LogPublisherRegistry.BUFFER_SIZE_PROPERTY;
194
195                        private @Nullable Integer bufferSize;
196
197                        private Builder() {
198                        }
199
200                        /**
201                         * Sets buffer size. Typically means how many events can be queued up. Default
202                         * is usually 1024.
203                         * @param bufferSize buffer size.
204                         * @return this.
205                         */
206                        public AsyncLogPublisher.Builder bufferSize(int bufferSize) {
207                                this.bufferSize = bufferSize;
208                                return this;
209                        }
210
211                        @Override
212                        public PublisherFactory build() {
213                                Integer bufferSize = this.bufferSize;
214                                return PublisherFactory.ofAsync(bufferSize);
215                        }
216
217                        @Override
218                        protected AsyncLogPublisher.Builder self() {
219                                return this;
220                        }
221
222                }
223
224        }
225
226        /**
227         * Synchronous publisher.
228         */
229        non-sealed interface SyncLogPublisher extends LogPublisher {
230
231                /**
232                 * Sync publisher builder.
233                 * @return builder.
234                 */
235                public static SyncLogPublisher.Builder builder() {
236                        return new Builder();
237                }
238
239                @Override
240                default boolean synchronous() {
241                        return true;
242                }
243
244                /**
245                 * Synchronous publisher builder.
246                 */
247                public static final class Builder extends AbstractBuilder<SyncLogPublisher.Builder> {
248
249                        private Builder() {
250                        }
251
252                        /**
253                         * This builder.
254                         * @return this builder.
255                         */
256                        @Override
257                        protected SyncLogPublisher.Builder self() {
258                                return this;
259                        }
260
261                        /**
262                         * Build a publisher provider.
263                         * @return provider
264                         */
265                        @Override
266                        public PublisherFactory build() {
267                                return PublisherFactory.ofSync();
268                        }
269
270                }
271
272        }
273
274}
275
276final class DefaultSyncLogPublisher implements LogPublisher.SyncLogPublisher {
277
278        private final LogAppender appender;
279
280        public DefaultSyncLogPublisher(LogAppender appender) {
281                super();
282                this.appender = appender;
283        }
284
285        @Override
286        public void log(LogEvent event) {
287                appender.append(event);
288        }
289
290        @Override
291        public void start(LogConfig config) {
292                appender.start(config);
293        }
294
295        @Override
296        public void close() {
297                appender.close();
298        }
299
300        /*
301         * Exposed for unit test.
302         */
303        LogAppender appender() {
304                return this.appender;
305        }
306
307        @Override
308        public String toString() {
309                return "DefaultSyncLogPublisher[appender=" + appender + "]";
310        }
311
312}