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:
commit
a49750049f
|
@ -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,
|
||||||
|
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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">>},
|
||||||
|
|
|
@ -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}.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue