001package io.jstach.rainbowgum; 002 003import java.io.UncheckedIOException; 004import java.net.URI; 005 006import io.jstach.rainbowgum.LogEncoder.AbstractEncoder; 007import io.jstach.rainbowgum.LogEncoder.Buffer.StringBuilderBuffer; 008import io.jstach.rainbowgum.LogOutput.WriteMethod; 009 010/** 011 * Encodes a {@link LogEvent} into a buffer of its choosing. While the {@link Buffer} does 012 * not need to be thread-safe the encoder itself should be. 013 * <p> 014 * An appender typically calls an encoder by first {@linkplain #buffer(BufferHints) 015 * creating a buffer} that the encoder knows about or reusing an existing {@link Buffer} 016 * the encoder knows about. 017 * <p> 018 * The {@linkplain #encode(LogEvent, Buffer) encoding into a buffer} typically happens 019 * outside of lock to minimize lock contention. Thus the appender promises not to share a 020 * buffer at the same time with other threads as well as only use a buffer the log encoder 021 * created at some point. 022 * <p> 023 * Once encoding is done the appender than typically enters into a lock (appenders 024 * attached to an async publisher may not need to use a lock) where the appender will ask 025 * the buffer to {@linkplain Buffer#drain(LogOutput, LogEvent) drain} its contents into 026 * the output. 027 * <p> 028 * Because {@link Buffer} is not a specific implementation the Encoder typically casts the 029 * buffer to the expected concrete implementation. {@link AbstractEncoder} can make this 030 * logic easier and is recommended to extend it. 031 * <p> 032 * Given the complexity of encoders it is recommend use the much easier to implement 033 * interface of {@link LogFormatter} and convert it to an encoder with 034 * {@link #of(LogFormatter)}. 035 * 036 * @see LogFormatter 037 * @see Buffer 038 * @see LogAppender 039 * @see StringBuilderBuffer 040 */ 041public interface LogEncoder { 042 043 /** 044 * Creates a <strong>new</strong> buffer. The encoder should not try to reuse buffers 045 * as that is the responsibility of the {@linkplain LogAppender appender} (and 046 * possibly {@link LogOutput} but usually not). Hints can be retrieved by call 047 * {@link LogOutput#bufferHints()}. 048 * @param hints hints are like size and storage type etc. 049 * @return a new buffer. 050 * @apiNote hints can be retrieved by calling {@link LogOutput#bufferHints()} the 051 * reason the output itself is not passed is to prevent the buffer from using the 052 * output directly at an inappropriate time as well as the rare possibility of the 053 * buffer being used by multiple outputs. 054 */ 055 public Buffer buffer(BufferHints hints); 056 057 /** 058 * Encodes an event to the buffer. It is recommended that the encoder call 059 * {@link Buffer#clear()} before using. 060 * @param event log event. 061 * @param buffer buffer created from {@link #buffer(BufferHints)}. 062 */ 063 public void encode(LogEvent event, Buffer buffer); 064 065 /** 066 * Creates an encoder from a formatter. 067 * @param formatter formatter. 068 * @return encoder. 069 */ 070 public static LogEncoder of(LogFormatter formatter) { 071 return new FormatterEncoder(formatter); 072 } 073 074 /** 075 * Provides a lazy loaded encoder from a URI. 076 * @param uri uri. 077 * @return provider of encoder. 078 */ 079 public static LogProvider<LogEncoder> of(URI uri) { 080 return of(LogProviderRef.of(uri)); 081 } 082 083 /** 084 * Provides a lazy loaded encoder from a provider ref. 085 * @param ref uri. 086 * @return provider of output. 087 * @apiNote the provider may throw an {@link UncheckedIOException}. 088 */ 089 public static LogProvider<LogEncoder> of(LogProviderRef ref) { 090 return (s, c) -> { 091 return c.encoderRegistry().provide(ref).provide(s, c); 092 }; 093 } 094 095 /** 096 * Finds output based on URI. 097 */ 098 public interface EncoderProvider { 099 100 /** 101 * Loads an encoder from a URI. 102 * @param ref reference to provider usually just a uri. 103 * @return output. 104 * @throws LogProviderRef.NotFoundException if there is no registered provider. 105 */ 106 LogProvider<LogEncoder> provide(LogProviderRef ref) throws LogProviderRef.NotFoundException; 107 108 } 109 110 /** 111 * Encoders buffer. 112 */ 113 public interface Buffer extends AutoCloseable { 114 115 /** 116 * The appender will call this usually within a lock to transfer content from the 117 * buffer to the output. 118 * @param output output to receive content. 119 * @param event log event. 120 */ 121 public void drain(LogOutput output, LogEvent event); 122 123 /** 124 * Prepare the buffer for reuse. 125 * <p> 126 * An appender may not call clear before being passed to the encoder so the 127 * encoder should do its own clearing. 128 */ 129 public void clear(); 130 131 /** 132 * Convenience that will call clear. 133 */ 134 @Override 135 default void close() { 136 clear(); 137 } 138 139 /** 140 * A buffer that simply wraps a {@link StringBuilder}. Direct access to the 141 * {@link StringBuilder} is available as the field {@link #stringBuilder}. 142 * 143 * @see AbstractEncoder 144 */ 145 public final class StringBuilderBuffer implements Buffer { 146 147 /** 148 * Underlying StringBuilder. 149 */ 150 public final StringBuilder stringBuilder; 151 152 /** 153 * Creates a StringBuilder based buffer. 154 * @param sb string builder. 155 * @return buffer. 156 */ 157 public static StringBuilderBuffer of(StringBuilder sb) { 158 return new StringBuilderBuffer(sb); 159 } 160 161 private StringBuilderBuffer(StringBuilder stringBuilder) { 162 super(); 163 this.stringBuilder = stringBuilder; 164 } 165 166 @Override 167 public void drain(LogOutput output, LogEvent event) { 168 output.write(event, stringBuilder.toString()); 169 } 170 171 @Override 172 public void clear() { 173 stringBuilder.setLength(0); 174 } 175 176 } 177 178 } 179 180 /** 181 * Hints the output can pass to the encoder for creating buffers like max size and 182 * storage style of the buffer etc. 183 * 184 * @apiNote There is no guarantees the encoder/buffer will honor these hints. 185 */ 186 public interface BufferHints { 187 188 /* 189 * TODO should we seal this? 190 */ 191 192 /** 193 * The preferred write style of the output. 194 * @return write method. 195 * @apiNote {@link WriteMethod} implements this interface for convenience. 196 */ 197 LogOutput.WriteMethod writeMethod(); 198 199 /** 200 * Maximum size of the buffer. This is a way for the encoder to say it can only 201 * handle so much data per event. 202 * @return a negative number indicates size is not important. 203 */ 204 default int maximumSize() { 205 return -1; 206 } 207 208 } 209 210 /** 211 * Abstract encoder that will cast the buffer to the desired implementation. Extend to 212 * make creating encoders easier. 213 * 214 * @param <T> buffer type. 215 */ 216 abstract class AbstractEncoder<T extends Buffer> implements LogEncoder { 217 218 /** 219 * Do nothing constructor. 220 */ 221 protected AbstractEncoder() { 222 223 } 224 225 /** 226 * Create a specific buffer implementation. 227 * @param hints buffer creation hints. 228 * @return buffer 229 */ 230 protected abstract T doBuffer(BufferHints hints); 231 232 /** 233 * A type safe version of {@link #encode(LogEvent, Buffer)}. 234 * @param event event. 235 * @param buffer casted buffer. 236 */ 237 protected abstract void doEncode(LogEvent event, T buffer); 238 239 @Override 240 public final Buffer buffer(BufferHints hints) { 241 return doBuffer(hints); 242 } 243 244 @SuppressWarnings("unchecked") 245 @Override 246 public final void encode(LogEvent event, Buffer buffer) { 247 doEncode(event, (T) buffer); 248 249 } 250 251 } 252 253} 254 255final class FormatterEncoder extends AbstractEncoder<StringBuilderBuffer> { 256 257 private final LogFormatter formatter; 258 259 public FormatterEncoder(LogFormatter formatter) { 260 super(); 261 this.formatter = formatter; 262 } 263 264 @Override 265 protected void doEncode(LogEvent event, StringBuilderBuffer buffer) { 266 buffer.clear(); 267 StringBuilder sb = buffer.stringBuilder; 268 formatter.format(sb, event); 269 } 270 271 @Override 272 protected StringBuilderBuffer doBuffer(BufferHints hints) { 273 return StringBuilderBuffer.of(new StringBuilder()); 274 } 275 276}