1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15- package exporter
15+ package tailer
1616
1717import (
1818 "container/list"
19- "github.com/fstab/grok_exporter/tailer"
2019 "github.com/prometheus/client_golang/prometheus"
2120 "log"
2221 "sync"
2322 "time"
2423)
2524
26- // implements tailer. Tailer
25+ // implements Tailer
2726type bufferedTailerWithMetrics struct {
2827 out chan string
29- orig tailer. Tailer
28+ orig Tailer
3029}
3130
3231func (b * bufferedTailerWithMetrics ) Lines () chan string {
3332 return b .out
3433}
3534
36- func (b * bufferedTailerWithMetrics ) Errors () chan tailer. Error {
35+ func (b * bufferedTailerWithMetrics ) Errors () chan Error {
3736 return b .orig .Errors ()
3837}
3938
@@ -46,7 +45,61 @@ func (b *bufferedTailerWithMetrics) Close() {
4645// and does not need to wait until the lines are processed.
4746// The number of buffered lines are exposed as a Prometheus metric, if lines are constantly
4847// produced faster than they are consumed, we will eventually run out of memory.
49- func BufferedTailerWithMetrics (orig tailer.Tailer ) tailer.Tailer {
48+ //
49+ // ---
50+ // The buffered tailer prevents the following error (this can be reproduced on Windows,
51+ // where we don't keep the logfile open):
52+ //
53+ // Example test actions
54+ // --------------------
55+ //
56+ // Sequence of actions simulated in fileTailer_test:
57+ //
58+ // 1) write line a
59+ // 2) write line b
60+ // 3) move the old logfile away and create a new logfile
61+ // 4) write line c
62+ //
63+ // Good case event processing
64+ // --------------------------
65+ //
66+ // How Events.Process() should process the file system events triggered by the actions above:
67+ //
68+ // 1) MODIFIED : Process() reads line a
69+ // 2) MODIFIED : Process() reads line b
70+ // 3) MOVED_FROM, CREATED : Process() resets the line reader and seeks the file to position 0
71+ // 4) MODIFIED : Process() reads line c
72+ //
73+ // Bad case event processing
74+ // -------------------------
75+ //
76+ // When Events.Process() receives a MODIFIED event, it does not know how many lines have been written.
77+ // Therefore, it reads all new lines until EOF is reached.
78+ // If line processing is slow (writing to the lines channel blocks until all grok patterns are processed),
79+ // we might read 'line b' while we are still processing the first MODIFIED event:
80+ //
81+ // 1) MODIFIED : Process() reads 'line a' and 'line b'
82+ //
83+ // Meanwhile, the test continues with steps 3 and 4, moving the logfile away, creating a new logfile,
84+ // and writing 'line c'. When the tailer receives the second MODIFIED event, it learns that the file
85+ // has been truncated, seeks to position 0, and reads 'line c'.
86+ //
87+ // 2) MODIFIED : Process() detects the truncated file, seeks to position 0, reads 'line c'
88+ //
89+ // The tailer now receives MOVED_FROM, which makes it close the logfile, CREATED, which makes
90+ // it open the logfile and start reading from position 0:
91+ //
92+ // 3) MOVED_FROM, CREATED : seek to position 0, read line c again !!!
93+ //
94+ // When the last MODIFIED event is processed, there are no more changes in the file:
95+ //
96+ // 4) MODIFIED : no changes in file
97+ //
98+ // As a result, we read 'line c' two times.
99+ //
100+ // To minimize the risk, use the buffered tailer to make sure file system events are handled
101+ // as quickly as possible without waiting for the grok patterns to be processed.
102+ func BufferedTailerWithMetrics (orig Tailer ) Tailer {
50103 buffer := list .New ()
51104 bufferSync := sync .NewCond (& sync.Mutex {}) // coordinate producer and consumer
52105 out := make (chan string )
0 commit comments