-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
feat: implement log throttling #12520
feat: implement log throttling #12520
Conversation
apps/emqx/src/emqx_log_throttler.erl
Outdated
%% TODO: this still can flood the logs until throttle seq is created, | ||
%% Alternatively we can register all the throttle IDs in init/1, | ||
%% but this would require gathering all the throttle Ids somehow... | ||
create(EventId), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This make throttling not very strict: it can pass some log events before the gen_server creates the sequence.
Though it's probably acceptable I have some ideas how to avoid it completely:
- Change throttling API, instead of allow/1 implement
log/3,4
, e.g.:
log(EventId, Level, Data, Meta) when is_atom(EventId) ->
Seq = persistent_term:get(?SEQ_ID(EventId), undefined),
case Seq of
undefined ->
%% TODO: this still can flood the logs until throttle seq is created,
%% Alternatively we can register all the throttle IDs in init/1,
%% but this would require gathering all the throttle Ids somehow...
create_and_maybe_log(EventId, Level, Data, Meta),
SeqRef ->
maybe_log(?IS_ALLOWED(SeqRef), Level, Data, Meta)
end.
handle_cast({create_maybe_log, EventId, Level, Data, Meta}, State) ->
State1 =
case State of
#{EventId := _} ->
%% Must be allready logged: increment counter and silently drop this event
?INC_SEQ(SeqRef),
State;
_ ->
?SLOG(Level, Data, Meta),
SeqRef = ?NEW_SEQ,
%% One event has been already occurred
?INC_SEQ(SeqRef),
ok = ?NEW_THROTTLE(EventId, SeqRef),
TimerRef = schedule_refresh(?WINDOW_TIME_SEC, EventId),
State#{EventId => TimerRef}
end,
{noreply, State1};
- Create sequences in
init/1
, but then we need to make sure everyevent_id
used in ?LOG_THROTTLE somewhere in the code base has its sequence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe not a bad idea to maintain a static list of ids in a hidden config field's default value.
This makes it possible to configure the list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworked to use a static list.
emqx_schema:duration_s(), | ||
#{ | ||
default => <<"1m">>, | ||
desc => ?DESC("log_throttling_window_time") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing desc and label in rel/i18n/emqx_conf_schema.hocon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, thanks.
emqx_schema:duration_s(), | ||
#{ | ||
default => <<"1m">>, | ||
desc => ?DESC("log_throttling_window_time") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
importance => ?IMPORTANCE_MEDIUM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
apps/emqx/src/emqx_log_throttler.erl
Outdated
%% TODO: this still can flood the logs until throttle seq is created, | ||
%% Alternatively we can register all the throttle IDs in init/1, | ||
%% but this would require gathering all the throttle Ids somehow... | ||
create(EventId), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe not a bad idea to maintain a static list of ids in a hidden config field's default value.
This makes it possible to configure the list.
d4e908b
to
9daac34
Compare
9daac34
to
5714bd5
Compare
apps/emqx/include/logger.hrl
Outdated
?SLOG_THROTTLE(EventId, Level, Data, #{}) | ||
). | ||
|
||
-define(SLOG_THROTTLE(EventId, Level, Data, Meta), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just use the one-word msg
as EventId
?
Otherwise it's duplicated as msg
and as throttle_event_id
@@ -909,7 +909,12 @@ fields("log") -> | |||
aliases => [file_handlers], | |||
importance => ?IMPORTANCE_HIGH | |||
} | |||
)} | |||
)}, | |||
{"throttling", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{"throttling", | |
{throttling, |
apps/emqx/src/emqx_log_throttler.erl
Outdated
?SLOG(warning, #{ | ||
msg => "log_events_throttled_during_last_period", | ||
throttle_event_id => EventId, | ||
period_sec => Period, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duration => format_duration_string(Period),
apps/emqx/src/emqx_log_throttler.erl
Outdated
handle_info(refresh, State) -> | ||
PeriodSec = ?WINDOW_TIME_SEC, | ||
EventIds = ?EVENT_IDS_LIST, | ||
ok = lists:foreach( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe log a summary instead of one for each id?
e.g.
dropped => #{id1 => 12, id2 => 34}
)}, | ||
%% A static list of event ids used in ?SLOG_THROTTLE/3,4 macro. | ||
%% For internal (developer) use only. | ||
{"event_ids", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{"event_ids", | |
{msgs, |
@@ -1012,6 +1017,34 @@ fields("log_burst_limit") -> | |||
} | |||
)} | |||
]; | |||
fields("log_throttling") -> | |||
[ | |||
{"window_time", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use atoms for field names, also, should be time_window instead of window_time ?
{"window_time", | |
{time_window, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
time_window
also sounds more naturally to me, but there is window_time
in logger burst_limit
conf: https://github.com/emqx/emqx/blob/master/apps/emqx_conf/src/emqx_conf_schema.erl#L1006, so I decided to be consistent with it...
rel/i18n/emqx_conf_schema.hocon
Outdated
dropping all but the first event within a configured time window.""" | ||
|
||
log_throttling_window_time.desc: | ||
"""A time interval at which log throttling is applied. Defaults to 1 minute.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to mention default value in desc, the schema dump generates default along side it.
"""A time interval at which log throttling is applied. Defaults to 1 minute.""" | |
"""For throttled messages, only log 1 in each time window.""" |
rel/i18n/emqx_conf_schema.hocon
Outdated
"""A time interval at which log throttling is applied. Defaults to 1 minute.""" | ||
|
||
log_throttling_window_time.label: | ||
"""Log Throttling Window Time""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"""Log Throttling Window Time""" | |
"""Log Throttling Time Window""" |
0000b3c
to
5864e99
Compare
5864e99
to
7a1d173
Compare
7a1d173
to
9bd0d1b
Compare
[ | ||
{time_window, | ||
sc( | ||
emqx_schema:duration_s(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: since this is used by send_after
, might be safer to use timeout_duration_s
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also updated emqx#12520 to include newly added throttled log id
also updated emqx#12520 to include newly added throttled log id
also updated emqx#12520 to include newly added throttled log id
). | ||
|
||
-define(SLOG_THROTTLE(Level, Data, Meta), | ||
case emqx_log_throttler:allow(Level, maps:get(msg, Data)) of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't that make Data
- evaluated twice
- evaluated even if actual log level is above the passed
Level
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, fixed: #12927
Fixes EMQX-11530
Release version: v/e5.6.0
Short demo:
3000 subscribers to EMQX Enterprise with the default trial license:
emqtt_bench sub -t t/test -p 1883 -c 3000 -R 1000
Summary
PR Checklist
Please convert it to a draft if any of the following conditions are not met. Reviewers may skip over until all the items are checked:
changes/(ce|ee)/(feat|perf|fix|breaking)-<PR-id>.en.md
filesChecklist for CI (.github/workflows) changes
changes/
dir for user-facing artifacts update