1 package com.github.triceo.splitlog.api;
2
3 import java.io.File;
4 import java.util.ServiceLoader;
5 import java.util.concurrent.TimeUnit;
6
7 /**
8 * Prepares an instance of {@link LogWatch} that will automatically
9 * {@link LogWatch#start()}. Unless overriden by the user, the instance will
10 * have the following properties:
11 *
12 * <dl>
13 * <dt>Reads file from beginning?</dt>
14 * <dd>Yes.</dd>
15 * <dt>Closes file when the reading is finished?</dt>
16 * <dd>No.</dd>
17 * <dt>The default interval between each read</dt>
18 * <dd>See {@link #DEFAULT_DELAY_BETWEEN_READS_IN_MILLISECONDS}.</dd>
19 * <dt>The buffer size for reading</dt>
20 * <dd>See {@link #DEFAULT_READ_BUFFER_SIZE_IN_BYTES}.</dd>
21 * <dt>Default message capacity</dt>
22 * <dd>{@link Integer#MAX_VALUE}, the maximum possible.</dd>
23 * <dt>Interval between two sweeps for unreachable messages.</dt>
24 * <dd>See {@link #DEFAULT_DELAY_BETWEEN_SWEEPS_IN_MILLISECONDS}.</dd>
25 * <dt>Interval between requesting tailing and the actual start of tailing.</dt>
26 * </dl>
27 *
28 * By default, the instance will store (and notify of) every message that has
29 * passed the {@link #getGateCondition()} and not do so for all others.
30 */
31 public abstract class LogWatchBuilder {
32
33 public static final long DEFAULT_DELAY_BETWEEN_READS_IN_MILLISECONDS = 1000;
34 public static final long DEFAULT_DELAY_BETWEEN_SWEEPS_IN_MILLISECONDS = 60 * 1000;
35 public static final int DEFAULT_READ_BUFFER_SIZE_IN_BYTES = 4096;
36
37 /**
38 * Used to construct a {@link LogWatch} for a particular log file.
39 *
40 * @return Builder that is used to configure the new log watch instance
41 * before using.
42 */
43 public static LogWatchBuilder getDefault() {
44 final ServiceLoader<LogWatchBuilder> ldr = ServiceLoader.load(LogWatchBuilder.class);
45 if (!ldr.iterator().hasNext()) {
46 throw new IllegalStateException("No LogWatchBuilder implementation registered.");
47 }
48 return ldr.iterator().next();
49 }
50
51 private static long getDelay(final int length, final TimeUnit unit) {
52 if (length < 1) {
53 throw new IllegalArgumentException("The length of time must be at least 1.");
54 }
55 switch (unit) {
56 case NANOSECONDS:
57 if (length < (1000 * 1000)) {
58 throw new IllegalArgumentException("The length of time must amount to at least 1 ms.");
59 }
60 break;
61 case MICROSECONDS:
62 if (length < 1000) {
63 throw new IllegalArgumentException("The length of time must amount to at least 1 ms.");
64 }
65 break;
66 default:
67 // every other unit is more than 1 ms by default
68 }
69 return unit.toMillis(length);
70 }
71
72 private boolean autoStarting = true;
73 private int bufferSize = LogWatchBuilder.DEFAULT_READ_BUFFER_SIZE_IN_BYTES;
74 private boolean closingBetweenReads;
75 private long delayBetweenReads = LogWatchBuilder.DEFAULT_DELAY_BETWEEN_READS_IN_MILLISECONDS;
76 private long delayBetweenSweeps = LogWatchBuilder.DEFAULT_DELAY_BETWEEN_SWEEPS_IN_MILLISECONDS;
77 private File fileToWatch;
78 private SimpleMessageCondition gateCondition;
79 private int limitCapacityTo = Integer.MAX_VALUE;
80 private boolean readingFromBeginning = true;
81 private SimpleMessageCondition storageCondition;
82
83 /**
84 * Build the log watch with previously defined properties, or defaults where
85 * not overriden. Such log watch will not start actually reading
86 * {@link #getWatchedFile()} until after {@link LogWatch#startFollowing()}
87 * or
88 * {@link LogWatch#startConsuming(com.github.triceo.splitlog.api.MessageListener)}
89 * .
90 *
91 * @return The newly built log watch.
92 */
93 public abstract LogWatch build();
94
95 /**
96 * Build the log watch with previously defined properties, or defaults where
97 * not overriden, and immediately start listening for {@link Message}s.
98 *
99 * @return The follower that will receive the initial messages. The actual
100 * {@link LogWatch} can be retrieved by
101 * {@link Follower#getFollowed()}.
102 */
103 public abstract Follower buildFollowing();
104
105 /**
106 * Build the log watch with previously defined properties, or defaults where
107 * not overriden, and immediately start listening for {@link Message}s
108 *
109 * @param splitter
110 * The splitter instance to use for the log watch instead of the
111 * default.
112 * @return The follower that will receive the initial messages. The actual
113 * {@link LogWatch} can be retrieved by
114 * {@link Follower#getFollowed()}.
115 */
116 public abstract Follower buildFollowingWith(final TailSplitter splitter);
117
118 /**
119 * Build the log watch with previously defined properties, or defaults where
120 * not overriden. Such log watch will not start actually reading
121 * {@link #getWatchedFile()} until after {@link LogWatch#startFollowing()}
122 * or
123 * {@link LogWatch#startConsuming(com.github.triceo.splitlog.api.MessageListener)}
124 * .
125 *
126 * @param splitter
127 * The splitter instance to use for the log watch instead of the
128 * default.
129 * @return The newly built log watch. This log watch will not start tailing
130 * until after {@link LogWatch#startFollowing()} or
131 * {@link LogWatch#startConsuming(com.github.triceo.splitlog.api.MessageListener)}
132 * .
133 */
134 public abstract LogWatch buildWith(final TailSplitter splitter);
135
136 /**
137 * Change the default behavior of the future log watch to close the watched
138 * file after each reading.
139 *
140 * @return This.
141 */
142 public LogWatchBuilder closingAfterReading() {
143 this.closingBetweenReads = true;
144 return this;
145 }
146
147 /**
148 * Do not run {@link LogWatch#start()} on the new instance.
149 *
150 * @return This.
151 */
152 public LogWatchBuilder doNotStart() {
153 this.autoStarting = false;
154 return this;
155 }
156
157 /**
158 * Get the capacity of the future log watch.
159 *
160 * @return Maximum capacity in messages.
161 */
162 public int getCapacityLimit() {
163 return this.limitCapacityTo;
164 }
165
166 /**
167 * Get the delay between attempts to read from the watched file.
168 *
169 * @return In milliseconds.
170 */
171 public long getDelayBetweenReads() {
172 return this.delayBetweenReads;
173 }
174
175 /**
176 * Get the delay between attempts to sweep unreachable messages from memory.
177 *
178 * @return In milliseconds.
179 */
180 public long getDelayBetweenSweeps() {
181 return this.delayBetweenSweeps;
182 }
183
184 /**
185 * Get the file that the log watch will be watching.
186 *
187 * @return The file that will be watched by the future log watch.
188 */
189 public File getFileToWatch() {
190 return this.fileToWatch;
191 }
192 /**
193 * The condition that will be used for accepting a {@link Message} into
194 * {@link LogWatch}.
195 *
196 * @return The condition.
197 */
198 public SimpleMessageCondition getGateCondition() {
199 return this.gateCondition;
200 }
201
202 /**
203 * Get the buffer size for the log watch.
204 *
205 * @return In bytes.
206 */
207 public int getReadingBufferSize() {
208 return this.bufferSize;
209 }
210
211 /**
212 * The condition that will be used for storing a {@link Message} within
213 * {@link LogWatch}.
214 *
215 * @return The condition.
216 */
217 public SimpleMessageCondition getStorageCondition() {
218 return this.storageCondition;
219 }
220
221 /**
222 * Change the default behavior of the future log watch so that the existing
223 * contents of the file is ignored and only the future additions to the file
224 * are reported.
225 *
226 * @return This.
227 */
228 public LogWatchBuilder ignoringPreexistingContent() {
229 this.readingFromBeginning = false;
230 return this;
231 }
232
233 /**
234 * @return Whether or not the file will be closed after it is read from.
235 */
236 public boolean isClosingBetweenReads() {
237 return this.closingBetweenReads;
238 }
239
240 /**
241 * @return True if the file will be read from the beginning, false if just
242 * the additions made post starting the log watch.
243 */
244 public boolean isReadingFromBeginning() {
245 return this.readingFromBeginning;
246 }
247
248 /**
249 * Limit capacity of the log watch to a given amount of messages.
250 *
251 * @param size
252 * Maximum amount of messages to store.
253 * @return This.
254 */
255 public LogWatchBuilder limitCapacityTo(final int size) {
256 if (size <= 0) {
257 throw new IllegalArgumentException("Size of the memory store must be larger than zero.");
258 }
259 this.limitCapacityTo = size;
260 return this;
261 }
262
263 @Override
264 public String toString() {
265 final StringBuilder builder = new StringBuilder();
266 builder.append("LogWatchBuilder [bufferSize=").append(this.bufferSize).append(", closingBetweenReads=")
267 .append(this.closingBetweenReads).append(", delayBetweenReads=").append(this.delayBetweenReads)
268 .append(", delayBetweenSweeps=").append(this.delayBetweenSweeps).append(", ");
269 if (this.fileToWatch != null) {
270 builder.append("fileToWatch=").append(this.fileToWatch).append(", ");
271 }
272 if (this.gateCondition != null) {
273 builder.append("gateCondition=").append(this.gateCondition).append(", ");
274 }
275 builder.append("limitCapacityTo=").append(this.limitCapacityTo).append(", readingFromBeginning=")
276 .append(this.readingFromBeginning).append(", ");
277 if (this.storageCondition != null) {
278 builder.append("storageCondition=").append(this.storageCondition);
279 }
280 builder.append("]");
281 return builder.toString();
282 }
283 /**
284 * Set the file that the future {@link LogWatch} will be tailing.
285 *
286 * @param f
287 * File to watch.
288 * @return This.
289 */
290 public LogWatchBuilder watchedFile(final File f) {
291 if (f == null) {
292 throw new IllegalArgumentException("File can not be null.");
293 }
294 this.fileToWatch = f;
295 return this;
296 }
297
298 /**
299 * Set the file that the future {@link LogWatch} will be tailing.
300 *
301 * @param f
302 * File to watch.
303 * @return This.
304 */
305 @Deprecated
306 public LogWatchBuilder watchingFile(final File f) {
307 return this.watchedFile(f);
308 }
309 /**
310 * @return True if the new logwatch will already be {@link LogWatch#start()}ed.
311 */
312 public boolean willBeStarted() {
313 return this.autoStarting;
314 }
315
316 /**
317 * Specify the delay between attempts to read the file.
318 *
319 * @param length
320 * Length of time.
321 * @param unit
322 * Unit of that length.
323 * @return This.
324 */
325 public LogWatchBuilder withDelayBetweenReads(final int length, final TimeUnit unit) {
326 this.delayBetweenReads = LogWatchBuilder.getDelay(length, unit);
327 return this;
328 }
329
330 /**
331 * Specify the delay between attempts to sweep the log watch from
332 * unreachable messages.
333 *
334 * @param length
335 * Length of time.
336 * @param unit
337 * Unit of that length.
338 * @return This.
339 */
340 public LogWatchBuilder withDelayBetweenSweeps(final int length, final TimeUnit unit) {
341 this.delayBetweenSweeps = LogWatchBuilder.getDelay(length, unit);
342 return this;
343 }
344
345 /**
346 * Only the {@link Message}s for which
347 * {@link SimpleMessageCondition#accept(Message)} is true will be passed to
348 * {@link Follower}s and other {@link MessageConsumer}s. Such
349 * {@link Message}s will also never be stored. For the purposes of Splitlog,
350 * if it fails this condition, it's as if it never existed.
351 *
352 * @param condition
353 * The condition.
354 * @return This.
355 */
356 public LogWatchBuilder withGateCondition(final SimpleMessageCondition condition) {
357 if (condition == null) {
358 throw new IllegalArgumentException("Gate condition must not be null.");
359 }
360 this.gateCondition = condition;
361 return this;
362 }
363
364 /**
365 * Specify the buffer size that will be used for reading changes made to the
366 * watched file.
367 *
368 * @param bufferSize
369 * In bytes.
370 * @return This.
371 */
372 public LogWatchBuilder withReadingBufferSize(final int bufferSize) {
373 if (bufferSize < 1) {
374 throw new IllegalArgumentException("Buffer size must be at least 1.");
375 }
376 this.bufferSize = bufferSize;
377 return this;
378 }
379
380 /**
381 * Only the messages for which
382 * {@link SimpleMessageCondition#accept(Message)} is true will be stored
383 * within the future {@link LogWatch}. However, {@link MessageConsumer}s
384 * will be notified of them either way.
385 *
386 * The condition in question will only ever be called on {@link Message}s
387 * that have already passed the {@link #getGateCondition()}.
388 *
389 * @param condition
390 * The condition.
391 * @return This.
392 */
393 public LogWatchBuilder withStorageCondition(final SimpleMessageCondition condition) {
394 if (condition == null) {
395 throw new IllegalArgumentException("Storage acceptance condition must not be null.");
396 }
397 this.storageCondition = condition;
398 return this;
399 }
400
401 }