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