1+ -module (rabbit_log_tail ).
2+
3+ -export ([tail_n_lines /2 ]).
4+ -export ([init_tail_stream /4 ]).
5+
6+ -define (GUESS_OFFSET , 200 ).
7+
8+ init_tail_stream (Filename , Pid , Ref , Duration ) ->
9+ RPCProc = self (),
10+ Reader = spawn (fun () ->
11+ link (Pid ),
12+ case file :open (Filename , [read , binary ]) of
13+ {ok , File } ->
14+ TimeLimit = case Duration of
15+ infinity -> infinity ;
16+ _ -> erlang :system_time (second ) + Duration
17+ end ,
18+ {ok , _ } = file :position (File , eof ),
19+ RPCProc ! {Ref , opened },
20+ read_loop (File , Pid , Ref , TimeLimit );
21+ {error , _ } = Err ->
22+ RPCProc ! {Ref , Err }
23+ end
24+ end ),
25+ receive
26+ {Ref , opened } -> {ok , Ref };
27+ {Ref , {error , Err }} -> {error , Err }
28+ after 5000 ->
29+ exit (Reader , timeout ),
30+ {error , timeout }
31+ end .
32+
33+ read_loop (File , Pid , Ref , TimeLimit ) ->
34+ case is_integer (TimeLimit ) andalso erlang :system_time (second ) > TimeLimit of
35+ true -> Pid ! {Ref , <<>>, finished };
36+ false ->
37+ case file :read (File , ? GUESS_OFFSET ) of
38+ {ok , Data } ->
39+ Pid ! {Ref , Data , confinue },
40+ read_loop (File , Pid , Ref , TimeLimit );
41+ eof ->
42+ timer :sleep (1000 ),
43+ read_loop (File , Pid , Ref , TimeLimit );
44+ {error , _ } = Err ->
45+ Pid ! {Ref , Err , finished }
46+ end
47+ end .
48+
49+ tail_n_lines (Filename , N ) ->
50+ case file :open (Filename , [read , binary ]) of
51+ {ok , File } ->
52+ {ok , Eof } = file :position (File , eof ),
53+ % % Eof may move. Only read up to the current one.
54+ Result = reverse_read_n_lines (N , N , File , Eof , Eof ),
55+ file :close (File ),
56+ Result ;
57+ {error , _ } = Error -> Error
58+ end .
59+
60+ reverse_read_n_lines (N , OffsetN , File , Position , Eof ) ->
61+ GuessPosition = offset (Position , OffsetN ),
62+ case read_lines_from_position (File , GuessPosition , Eof ) of
63+ {ok , Lines } ->
64+ NLines = length (Lines ),
65+ case {NLines >= N , GuessPosition == 0 } of
66+ % % Take only N lines if there is more
67+ {true , _ } -> lists :nthtail (NLines - N , Lines );
68+ % % Safe to assume that NLines is less then N
69+ {_ , true } -> Lines ;
70+ % % Adjust position
71+ _ ->
72+ reverse_read_n_lines (N , N - NLines + 1 , File , GuessPosition , Eof )
73+ end ;
74+ {error , _ } = Error -> Error
75+ end .
76+
77+ read_from_position (File , GuessPosition , Eof ) ->
78+ file :pread (File , GuessPosition , max (0 , Eof - GuessPosition )).
79+
80+ read_lines_from_position (File , GuessPosition , Eof ) ->
81+ case read_from_position (File , GuessPosition , Eof ) of
82+ {ok , Data } ->
83+ Lines = binary :split (Data , <<" \n " >>, [global , trim ]),
84+ case {GuessPosition , Lines } of
85+ % % If position is 0 - there are no partial lines
86+ {0 , _ } -> {ok , Lines };
87+ % % Remove first line as it can be partial
88+ {_ , [_ | Rest ]} -> {ok , Rest };
89+ {_ , []} -> {ok , []}
90+ end ;
91+ {error , _ } = Error -> Error
92+ end .
93+
94+ offset (Base , N ) ->
95+ max (0 , Base - N * ? GUESS_OFFSET ).
0 commit comments