View Javadoc

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 }