Merge pull request #12172 from zhongwencool/audit-max-size-microsecond

fix: use microsecond precision as the primary key for audit logs
This commit is contained in:
zhongwencool 2023-12-15 10:52:21 +08:00 committed by GitHub
commit a49750049f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 66 additions and 12 deletions

View File

@ -516,7 +516,7 @@ do_t_session_expiration(_Config, Opts) ->
t_session_gc(Config) -> t_session_gc(Config) ->
GCInterval = ?config(gc_interval, Config), GCInterval = ?config(gc_interval, Config),
[Node1, Node2, Node3] = Nodes = ?config(nodes, Config), [Node1, Node2, _Node3] = Nodes = ?config(nodes, Config),
CoreNodes = [Node1, Node2], CoreNodes = [Node1, Node2],
[ [
Port1, Port1,

View File

@ -119,7 +119,7 @@ log_to_db(Log) ->
Audit0 = to_audit(Log), Audit0 = to_audit(Log),
Audit = Audit0#?AUDIT{ Audit = Audit0#?AUDIT{
node = node(), node = node(),
created_at = erlang:system_time(millisecond) created_at = erlang:system_time(microsecond)
}, },
mria:dirty_write(?AUDIT, Audit). mria:dirty_write(?AUDIT, Audit).

View File

@ -32,7 +32,7 @@
{<<"http_method">>, atom}, {<<"http_method">>, atom},
{<<"gte_created_at">>, timestamp}, {<<"gte_created_at">>, timestamp},
{<<"lte_created_at">>, timestamp}, {<<"lte_created_at">>, timestamp},
{<<"gte_duration_ms">>, timestamp}, {<<"gte_duration_ms">>, integer},
{<<"lte_duration_ms">>, integer} {<<"lte_duration_ms">>, integer}
]). ]).
-define(DISABLE_MSG, <<"Audit is disabled">>). -define(DISABLE_MSG, <<"Audit is disabled">>).
@ -130,14 +130,14 @@ schema("/audit") ->
desc => ?DESC(filter_lte_duration_ms) desc => ?DESC(filter_lte_duration_ms)
})}, })},
{gte_created_at, {gte_created_at,
?HOCON(emqx_utils_calendar:epoch_millisecond(), #{ ?HOCON(emqx_utils_calendar:epoch_microsecond(), #{
in => query, in => query,
required => false, required => false,
example => <<"2023-10-15T00:00:00.820384+08:00">>, example => <<"2023-10-15T00:00:00.820384+08:00">>,
desc => ?DESC(filter_gte_created_at) desc => ?DESC(filter_gte_created_at)
})}, })},
{lte_created_at, {lte_created_at,
?HOCON(emqx_utils_calendar:epoch_millisecond(), #{ ?HOCON(emqx_utils_calendar:epoch_microsecond(), #{
in => query, in => query,
example => <<"2023-10-16T00:00:00.820384+08:00">>, example => <<"2023-10-16T00:00:00.820384+08:00">>,
required => false, required => false,
@ -170,7 +170,7 @@ fields(audit) ->
[ [
{created_at, {created_at,
?HOCON( ?HOCON(
emqx_utils_calendar:epoch_millisecond(), emqx_utils_calendar:epoch_microsecond(),
#{ #{
desc => "The time when the log is created" desc => "The time when the log is created"
} }

View File

@ -140,9 +140,9 @@ t_disabled(_) ->
t_cli(_Config) -> t_cli(_Config) ->
Size = mnesia:table_info(emqx_audit, size), Size = mnesia:table_info(emqx_audit, size),
TimeInt = erlang:system_time(millisecond) - 10, TimeInt = erlang:system_time(microsecond) - 1000,
Time = integer_to_list(TimeInt), Time = integer_to_list(TimeInt),
DateStr = calendar:system_time_to_rfc3339(TimeInt, [{unit, millisecond}]), DateStr = calendar:system_time_to_rfc3339(TimeInt, [{unit, microsecond}]),
Date = emqx_http_lib:uri_encode(DateStr), Date = emqx_http_lib:uri_encode(DateStr),
ok = emqx_ctl:run_command(["conf", "show", "log"]), ok = emqx_ctl:run_command(["conf", "show", "log"]),
AuditPath = emqx_mgmt_api_test_util:api_path(["audit"]), AuditPath = emqx_mgmt_api_test_util:api_path(["audit"]),
@ -164,7 +164,11 @@ t_cli(_Config) ->
], ],
Data Data
), ),
%% check create at is valid
[#{<<"created_at">> := CreateAtRaw}] = Data,
CreateAt = calendar:rfc3339_to_system_time(binary_to_list(CreateAtRaw), [{unit, microsecond}]),
?assert(CreateAt > TimeInt, CreateAtRaw),
?assert(CreateAt < TimeInt + 5000000, CreateAtRaw),
%% check cli filter %% check cli filter
{ok, Res1} = emqx_mgmt_api_test_util:request_api(get, AuditPath, "from=cli", AuthHeader), {ok, Res1} = emqx_mgmt_api_test_util:request_api(get, AuditPath, "from=cli", AuthHeader),
#{<<"data">> := Data1} = emqx_utils_json:decode(Res1, [return_maps]), #{<<"data">> := Data1} = emqx_utils_json:decode(Res1, [return_maps]),
@ -174,25 +178,41 @@ t_cli(_Config) ->
), ),
?assertMatch(#{<<"data">> := []}, emqx_utils_json:decode(Res2, [return_maps])), ?assertMatch(#{<<"data">> := []}, emqx_utils_json:decode(Res2, [return_maps])),
%% check created_at filter %% check created_at filter microsecond
{ok, Res3} = emqx_mgmt_api_test_util:request_api( {ok, Res3} = emqx_mgmt_api_test_util:request_api(
get, AuditPath, "gte_created_at=" ++ Time, AuthHeader get, AuditPath, "gte_created_at=" ++ Time, AuthHeader
), ),
#{<<"data">> := Data3} = emqx_utils_json:decode(Res3, [return_maps]), #{<<"data">> := Data3} = emqx_utils_json:decode(Res3, [return_maps]),
?assertEqual(1, erlang:length(Data3)), ?assertEqual(1, erlang:length(Data3)),
%% check created_at filter rfc3339
{ok, Res31} = emqx_mgmt_api_test_util:request_api( {ok, Res31} = emqx_mgmt_api_test_util:request_api(
get, AuditPath, "gte_created_at=" ++ Date, AuthHeader get, AuditPath, "gte_created_at=" ++ Date, AuthHeader
), ),
?assertEqual(Res3, Res31), ?assertEqual(Res3, Res31),
%% check created_at filter millisecond
TimeMs = integer_to_list(TimeInt div 1000),
{ok, Res32} = emqx_mgmt_api_test_util:request_api(
get, AuditPath, "gte_created_at=" ++ TimeMs, AuthHeader
),
?assertEqual(Res3, Res32),
%% check created_at filter microsecond
{ok, Res4} = emqx_mgmt_api_test_util:request_api( {ok, Res4} = emqx_mgmt_api_test_util:request_api(
get, AuditPath, "lte_created_at=" ++ Time, AuthHeader get, AuditPath, "lte_created_at=" ++ Time, AuthHeader
), ),
#{<<"data">> := Data4} = emqx_utils_json:decode(Res4, [return_maps]), #{<<"data">> := Data4} = emqx_utils_json:decode(Res4, [return_maps]),
?assertEqual(Size, erlang:length(Data4)), ?assertEqual(Size, erlang:length(Data4)),
%% check created_at filter rfc3339
{ok, Res41} = emqx_mgmt_api_test_util:request_api( {ok, Res41} = emqx_mgmt_api_test_util:request_api(
get, AuditPath, "lte_created_at=" ++ Date, AuthHeader get, AuditPath, "lte_created_at=" ++ Date, AuthHeader
), ),
?assertEqual(Res4, Res41), ?assertEqual(Res4, Res41),
%% check created_at filter millisecond
{ok, Res42} = emqx_mgmt_api_test_util:request_api(
get, AuditPath, "lte_created_at=" ++ TimeMs, AuthHeader
),
?assertEqual(Res4, Res42),
%% check duration_ms filter %% check duration_ms filter
{ok, Res5} = emqx_mgmt_api_test_util:request_api( {ok, Res5} = emqx_mgmt_api_test_util:request_api(
@ -224,7 +244,7 @@ t_max_size(_Config) ->
fun(_) -> fun(_) ->
ok = emqx_ctl:run_command(["conf", "show", "log"]) ok = emqx_ctl:run_command(["conf", "show", "log"])
end, end,
lists:duplicate(110, 1) lists:duplicate(100, 1)
), ),
_ = mnesia:dump_log(), _ = mnesia:dump_log(),
LogCount = wait_for_dirty_write_log_done(1500), LogCount = wait_for_dirty_write_log_done(1500),

View File

@ -143,6 +143,24 @@ readable("epoch_millisecond()") ->
] ]
} }
}; };
readable("epoch_microsecond()") ->
%% only for swagger
#{
swagger => #{
<<"oneOf">> => [
#{
type => integer,
example => 1640995200000000,
description => <<"epoch-microsecond">>
},
#{
type => string,
example => <<"2022-01-01T00:00:00.000000Z">>,
format => <<"date-time">>
}
]
}
};
readable("duration()") -> readable("duration()") ->
#{ #{
swagger => #{type => string, example => <<"12m">>}, swagger => #{type => string, example => <<"12m">>},

View File

@ -29,6 +29,7 @@
%% API %% API
-export([ -export([
to_epoch_millisecond/1, to_epoch_millisecond/1,
to_epoch_microsecond/1,
to_epoch_second/1, to_epoch_second/1,
human_readable_duration_string/1 human_readable_duration_string/1
]). ]).
@ -54,6 +55,7 @@
%% so the maximum date can reach 9999-12-31 which is ample. %% so the maximum date can reach 9999-12-31 which is ample.
-define(MAXIMUM_EPOCH, 253402214400). -define(MAXIMUM_EPOCH, 253402214400).
-define(MAXIMUM_EPOCH_MILLI, 253402214400_000). -define(MAXIMUM_EPOCH_MILLI, 253402214400_000).
-define(MAXIMUM_EPOCH_MICROS, 253402214400_000_000).
-define(DATE_PART, [ -define(DATE_PART, [
year, year,
@ -75,13 +77,16 @@
-reflect_type([ -reflect_type([
epoch_millisecond/0, epoch_millisecond/0,
epoch_second/0 epoch_second/0,
epoch_microsecond/0
]). ]).
-type epoch_second() :: non_neg_integer(). -type epoch_second() :: non_neg_integer().
-type epoch_millisecond() :: non_neg_integer(). -type epoch_millisecond() :: non_neg_integer().
-type epoch_microsecond() :: non_neg_integer().
-typerefl_from_string({epoch_second/0, ?MODULE, to_epoch_second}). -typerefl_from_string({epoch_second/0, ?MODULE, to_epoch_second}).
-typerefl_from_string({epoch_millisecond/0, ?MODULE, to_epoch_millisecond}). -typerefl_from_string({epoch_millisecond/0, ?MODULE, to_epoch_millisecond}).
-typerefl_from_string({epoch_microsecond/0, ?MODULE, to_epoch_microsecond}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Epoch <-> RFC 3339 %% Epoch <-> RFC 3339
@ -93,6 +98,9 @@ to_epoch_second(DateTime) ->
to_epoch_millisecond(DateTime) -> to_epoch_millisecond(DateTime) ->
to_epoch(DateTime, millisecond). to_epoch(DateTime, millisecond).
to_epoch_microsecond(DateTime) ->
to_epoch(DateTime, microsecond).
to_epoch(DateTime, Unit) -> to_epoch(DateTime, Unit) ->
try try
case string:to_integer(DateTime) of case string:to_integer(DateTime) of
@ -131,6 +139,14 @@ validate_epoch(Epoch, second) when Epoch =< ?MAXIMUM_EPOCH ->
{ok, Epoch}; {ok, Epoch};
validate_epoch(Epoch, millisecond) when Epoch =< ?MAXIMUM_EPOCH_MILLI -> validate_epoch(Epoch, millisecond) when Epoch =< ?MAXIMUM_EPOCH_MILLI ->
{ok, Epoch}; {ok, Epoch};
%% http api use millisecond but we should transform to microsecond
validate_epoch(Epoch, microsecond) when
Epoch >= ?MAXIMUM_EPOCH andalso
Epoch =< ?MAXIMUM_EPOCH_MILLI
->
{ok, Epoch * 1000};
validate_epoch(Epoch, microsecond) when Epoch =< ?MAXIMUM_EPOCH_MICROS ->
{ok, Epoch};
validate_epoch(_Epoch, _Unit) -> validate_epoch(_Epoch, _Unit) ->
{error, bad_epoch}. {error, bad_epoch}.