Skip to content

Commit 8c4d0ef

Browse files
Merge pull request #2047 from rabbitmq/rabbitmq_cli_log_commands
Helpers for CLI logs command (cherry picked from commit df5952a)
1 parent a1a904a commit 8c4d0ef

File tree

1 file changed

+95
-0
lines changed

1 file changed

+95
-0
lines changed

src/rabbit_log_tail.erl

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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

Comments
 (0)