Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automated logging towards GRiSP.io #18

Merged
merged 22 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ Such client is disabled by default (`{ntp, false}`), and is not required to auth
Accepts an integer that represents time in milliseconds, default value is `5_000`.
Allows to tweak the timeout of each API request going through the websocket.

### ws_logs_interval
### logs_interval

Accepts an integer that represents time in milliseconds, default value is `2_000`.
Sets the intervall between each log batch dispatch to grisp.io.
ziopio marked this conversation as resolved.
Show resolved Hide resolved

### ws_logs_batch_size
### logs_batch_size

Accepts an integer that represents the maximum number of logs that can be batched togheder, default value is `100`.
Accepts an integer that represents the maximum number of logs that can be batched together, default value is `100`.

## API Usage example

Expand Down
4 changes: 2 additions & 2 deletions src/grisp_io.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
{connect, true}, % keeps a constant connection with grisp.io
{ntp, false}, % if set to true, starts the NTP client
{ws_requests_timeout, 5_000},
{ws_logs_interval, 2_000},
{ws_logs_batch_size, 100},
{logs_interval, 2_000},
{logs_batch_size, 100},
{logger, [
% Enable our own default handler,
% which will receive all events from boot
Expand Down
6 changes: 6 additions & 0 deletions src/grisp_io_binlog.erl
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
%% @doc Ring buffer for storing logger logs
%%
%% This module extends a standard queue into a ring buffer.
%% It can be truncated once a batch of logs has been sent out
%% and is not needed anymore.
%% @end
-module(grisp_io_binlog).
ziopio marked this conversation as resolved.
Show resolved Hide resolved

% API
Expand Down
41 changes: 4 additions & 37 deletions src/grisp_io_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
% Internal API
-export([disconnected/0]).
-export([handle_message/1]).
-export([trigger_logs/0]).

-behaviour(gen_statem).
-export([init/1, terminate/3, code_change/4, callback_mode/0, handle_event/4]).
Expand Down Expand Up @@ -47,9 +46,6 @@ disconnected() ->
handle_message(Payload) ->
gen_statem:cast(?MODULE, {?FUNCTION_NAME, Payload}).

trigger_logs() ->
gen_statem:cast(?MODULE, ?FUNCTION_NAME).

% gen_statem CALLBACKS ---------------------------------------------------------

init([]) ->
Expand Down Expand Up @@ -148,25 +144,18 @@ handle_event(state_timeout, retry, connecting, Data) ->
?LOG_NOTICE(#{event => connected}),
{next_state, connected, Data};
false ->
?LOG_NOTICE(#{event => waiting_ws_connection}),
?LOG_INFO(#{event => waiting_ws_connection}),
{keep_state_and_data, [{state_timeout, ?STD_TIMEOUT, retry}]}
end;

% CONNECTED
handle_event(enter, _OldState, connected, _Data) ->
trigger_logs(),
grisp_io_log_server:start(),
keep_state_and_data;
handle_event(cast, trigger_logs, connected, _Data) ->
{ok, Interval} = application:get_env(grisp_io, ws_logs_interval),
TriggerLogs = {state_timeout, Interval, send_logs},
{keep_state_and_data, [TriggerLogs]};
handle_event(cast, disconnected, connected, Data) ->
?LOG_WARNING(#{event => disconnected}),
StopLogs = {state_timeout, infinity, send_logs},
{next_state, waiting_ip, Data, [StopLogs]};
handle_event(state_timeout, send_logs, connected, _) ->
async_send_logs(),
keep_state_and_data;
grisp_io_log_server:stop(),
{next_state, waiting_ip, Data};

handle_event(E, OldS, NewS, Data) ->
?LOG_ERROR(#{event => unhandled_gen_statem_event,
Expand All @@ -192,28 +181,6 @@ request_timeout() ->
{ok, V} = application:get_env(grisp_io, ws_requests_timeout),
V.

async_send_logs() ->
spawn(fun () ->
{ok, Size} = application:get_env(grisp_io, ws_logs_batch_size),
case grisp_io_logger_bin:chunk(Size) of
{[], _Dropped} -> ok;
Chunk -> send_logs_chunk(Chunk)
end,
trigger_logs()
end).

send_logs_chunk({Events, Dropped}) ->
LogUpdate = #{
events => [[Seq, E] || {Seq, E} <- Events],
dropped => Dropped
},
case request(post, logs, LogUpdate) of
{ok, #{seq := Seq, dropped := ServerDropped}} ->
grisp_io_logger_bin:sync(Seq, ServerDropped);
E ->
io:format("Error sending logs = ~p\n",[E])
end.

% IP check functions

check_inet_ipv4() ->
Expand Down
64 changes: 64 additions & 0 deletions src/grisp_io_log_server.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
%% @doc gen_server that sends logs to GRiSP.io on a timed intervall.
%%
%% This process is controlled by grisp_io_client.
%% @end
-module(grisp_io_log_server).

% API
-export([start_link/0]).
-export([start/0]).
-export([stop/0]).

-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).

-include_lib("kernel/include/logger.hrl").

-record(state, {
active = false :: boolean(),
timer :: undefined | timer:tref()
}).

% API

start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

start() -> gen_server:cast(?MODULE, ?FUNCTION_NAME).
ziopio marked this conversation as resolved.
Show resolved Hide resolved

stop() -> gen_server:cast(?MODULE, ?FUNCTION_NAME).

% gen_server CALLBACKS ---------------------------------------------------------

init([]) -> {ok, #state{}}.

handle_call(_, _, _) ->
error(?FUNCTION_NAME).

handle_cast(start, #state{active = false, timer = undefined}) ->
{ok, Interval} = application:get_env(grisp_io, logs_interval),
{ok, Tref} = timer:send_interval(Interval, send_logs),
{noreply, #state{active = true, timer = Tref}};
handle_cast(stop, State) ->
timer:cancel(State#state.timer),
{noreply, #state{}}.

handle_info(send_logs, #state{active = true} = State) ->
{ok, Size} = application:get_env(grisp_io, logs_batch_size),
case grisp_io_logger_bin:chunk(Size) of
{[], _Dropped} -> ok;
Chunk -> send_logs_chunk(Chunk)
end,
{noreply, State}.

send_logs_chunk({Events, Dropped}) ->
LogUpdate = #{
events => [[Seq, E] || {Seq, E} <- Events],
dropped => Dropped
},
case grisp_io_client:request(post, logs, LogUpdate) of
{ok, #{seq := Seq, dropped := ServerDropped}} ->
grisp_io_logger_bin:sync(Seq, ServerDropped);
E ->
?LOG_ERROR(#{event => send_logs, data => E})
end.
8 changes: 8 additions & 0 deletions src/grisp_io_logger_bin.erl
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
%% @doc Log handler and formatter for the logger app
%%
%% It is optimized to use a fixed size ring buffer and
%% return chucks of older logs first while storing new ones.
%% It can be synched to discard old logs if they are not needed anymore.
%% If the buffer is filled, oldest logs are dropped
%% and a fake logger event is inserted to inform the user.
%% @end
-module(grisp_io_logger_bin).
ziopio marked this conversation as resolved.
Show resolved Hide resolved

-include_lib("kernel/include/logger.hrl").
Expand Down
1 change: 1 addition & 0 deletions src/grisp_io_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ init([]) ->
end,
ChildSpecs = NTP ++ [
worker(grisp_io_ws, []),
worker(grisp_io_log_server, []),
worker(grisp_io_client, [])
],
{ok, {SupFlags, ChildSpecs}}.
Expand Down
Loading