fixeria has uploaded this change for review. ( https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/39415?usp=email )
Change subject: WIP: add GSMTAP logging handler ......................................................................
WIP: add GSMTAP logging handler
Change-Id: Iea884e2ca146b852c64bd9f135d8c71f4c925f09 --- M config/sys.config A src/logger_gsmtap.erl A src/logger_gsmtap_h.erl 3 files changed, 258 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/erlang/osmo-s1gw refs/changes/15/39415/1
diff --git a/config/sys.config b/config/sys.config index d405db8..3bac4e4 100644 --- a/config/sys.config +++ b/config/sys.config @@ -26,7 +26,9 @@ {kernel, [{logger_level, info}, {logger, - [{handler, default, logger_std_h, + [{handler, gsmtap, logger_gsmtap_h, + #{level => debug}}, + {handler, default, logger_std_h, #{formatter => {logger_color_formatter, #{legacy_header => false, single_line => false, diff --git a/src/logger_gsmtap.erl b/src/logger_gsmtap.erl new file mode 100644 index 0000000..18998e6 --- /dev/null +++ b/src/logger_gsmtap.erl @@ -0,0 +1,178 @@ +%% Copyright (C) 2025 by sysmocom - s.f.m.c. GmbH info@sysmocom.de +%% Author: Vadim Yanitskiy vyanitskiy@sysmocom.de +%% +%% All Rights Reserved +%% +%% SPDX-License-Identifier: AGPL-3.0-or-later +%% +%% This program is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Affero General Public License as +%% published by the Free Software Foundation; either version 3 of the +%% License, or (at your option) any later version. +%% +%% This program is distributed in the hope that it will be useful, +%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%% GNU General Public License for more details. +%% +%% You should have received a copy of the GNU Affero General Public License +%% along with this program. If not, see https://www.gnu.org/licenses/. +%% +%% Additional Permission under GNU AGPL version 3 section 7: +%% +%% If you modify this Program, or any covered work, by linking or +%% combining it with runtime libraries of Erlang/OTP as released by +%% Ericsson on https://www.erlang.org (or a modified version of these +%% libraries), containing parts covered by the terms of the Erlang Public +%% License (https://www.erlang.org/EPLICENSE), the licensors of this +%% Program grant you additional permission to convey the resulting work +%% without the need to license the runtime libraries of Erlang/OTP under +%% the GNU Affero General Public License. Corresponding Source for a +%% non-source form of such a combination shall include the source code +%% for the parts of the runtime libraries of Erlang/OTP used as well as +%% that of the covered work. + +-module(logger_gsmtap). + +-behaviour(gen_server). + +%% public API +-export([start/1, + stop/1, + log/2]). +%% gen_server callbacks +-export([init/1, + handle_call/3, + handle_cast/2, + terminate/2]). + +-define(GSMTAP_PORT, 4729). +-define(GSMTAP_VERSION, 16#02). +-define(GSMTAP_HDR_LEN, 16#04). %% in number of 32bit words +-define(GSMTAP_TYPE_OSMOCORE_LOG, 16#10). + + +%% ------------------------------------------------------------------ +%% public API +%% ------------------------------------------------------------------ + +-spec start(map()) -> gen_server:start_ret(). +start(Config) -> + gen_server:start(?MODULE, [Config], []). + + +-spec log(pid(), logger:log_event()) -> ok. +log(Pid, LogEvent) -> + gen_server:cast(Pid, {?FUNCTION_NAME, LogEvent}). + + +-spec stop(pid()) -> ok. +stop(Pid) -> + gen_server:stop(Pid). + + +%% ------------------------------------------------------------------ +%% gen_server API +%% ------------------------------------------------------------------ + +init([RAddr]) -> + %% TODO: case gen_udp:open() of ... end, return an error + %% TODO: [{ip, Address}] -- bind IP + {ok, Sock} = gen_udp:open(0, [binary, + {reuseaddr, true}]), + %% ok = gen_udp:connect(Sock, RemAddr), + {ok, #{gsmtap_sock => Sock, + gsmtap_raddr => RAddr}}. + + +handle_call(_Request, _From, S) -> + {reply, {error, not_implemented}, S}. + + +handle_cast({log, LogEvent}, S) -> + PDU = gsmtap_pdu(LogEvent), + send_data(PDU, S), + {noreply, S}; + +handle_cast(_Request, S) -> + {noreply, S}. + + +terminate(_Reason, #{sock := Sock}) -> + gen_udp:close(Sock), + ok. + + +%% ------------------------------------------------------------------ +%% private API +%% ------------------------------------------------------------------ + +-spec send_data(binary(), map()) -> ok | {error, term()}. +send_data(Data, #{gsmtap_sock := Sock, + gsmtap_raddr := RemAddr}) -> + gen_udp:send(Sock, RemAddr, ?GSMTAP_PORT, Data). + + +-spec gsmtap_pdu(logger:log_event()) -> binary(). +gsmtap_pdu(#{msg := Msg, + level := Level, + meta := #{pid := Pid, + time := Time} = M}) -> + MsgStr = msg2str(Msg), + FileName = filename(M), + LineNr = maps:get(line, M, 0), + << ?GSMTAP_VERSION, + ?GSMTAP_HDR_LEN, + ?GSMTAP_TYPE_OSMOCORE_LOG, + 16#00:(128 - 3 * 8), %% padding + (Time div 1_000_000):32, %% seconds + (Time rem 1_000_000):32, %% microseconds + (charbuf("OsmoS1GW", 16))/bytes, %% XXX: hard-coded + 16#00:32, %% dummy, Pid goes to subsys + (log_level(Level)), + 16#00:24, %% padding + (charbuf(pid_to_list(Pid), 16))/bytes, + (charbuf(FileName, 32))/bytes, + LineNr:32, %% line number + (list_to_binary(MsgStr))/bytes + >>. + + +msg2str({string, Str}) -> + Str; + +msg2str({report, Report}) -> + io_lib:format("~p", [Report]); + +msg2str({FmtStr, Args}) -> + io_lib:format(FmtStr, Args). + + +filename(#{file := FileName}) -> + filename:basename(FileName); + +filename(#{}) -> "(none)". + + +-spec charbuf(Str0, Size) -> binary() + when Str0 :: string(), + Size :: non_neg_integer(). +charbuf(Str0, Size) -> + Str1 = string:slice(Str0, 0, Size - 1), %% truncate, if needed + Str2 = string:pad(Str1, Size, trailing, 16#00), %% pad, if needed + list_to_binary(Str2). + + +-spec log_level(atom()) -> 0..255. +log_level(debug) -> 1; +log_level(info) -> 3; +log_level(notice) -> 5; +log_level(warning) -> 6; %% XXX: non-standard +log_level(error) -> 7; +log_level(critical) -> 8; +log_level(alert) -> 9; %% XXX: non-standard +log_level(emergency) -> 11; %% XXX: non-standard +log_level(_) -> 255. %% XXX: non-standard + + +%% vim:set ts=4 sw=4 et: diff --git a/src/logger_gsmtap_h.erl b/src/logger_gsmtap_h.erl new file mode 100644 index 0000000..1ce40ba --- /dev/null +++ b/src/logger_gsmtap_h.erl @@ -0,0 +1,77 @@ +%% Copyright (C) 2025 by sysmocom - s.f.m.c. GmbH info@sysmocom.de +%% Author: Vadim Yanitskiy vyanitskiy@sysmocom.de +%% +%% All Rights Reserved +%% +%% SPDX-License-Identifier: AGPL-3.0-or-later +%% +%% This program is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Affero General Public License as +%% published by the Free Software Foundation; either version 3 of the +%% License, or (at your option) any later version. +%% +%% This program is distributed in the hope that it will be useful, +%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%% GNU General Public License for more details. +%% +%% You should have received a copy of the GNU Affero General Public License +%% along with this program. If not, see https://www.gnu.org/licenses/. +%% +%% Additional Permission under GNU AGPL version 3 section 7: +%% +%% If you modify this Program, or any covered work, by linking or +%% combining it with runtime libraries of Erlang/OTP as released by +%% Ericsson on https://www.erlang.org (or a modified version of these +%% libraries), containing parts covered by the terms of the Erlang Public +%% License (https://www.erlang.org/EPLICENSE), the licensors of this +%% Program grant you additional permission to convey the resulting work +%% without the need to license the runtime libraries of Erlang/OTP under +%% the GNU Affero General Public License. Corresponding Source for a +%% non-source form of such a combination shall include the source code +%% for the parts of the runtime libraries of Erlang/OTP used as well as +%% that of the covered work. + +-module(logger_gsmtap_h). + +-moduledoc """ +GSMTAP logging handler. +""". + +-behaviour(logger_handler). + +%% logger_handler callbacks +-export([adding_handler/1, + removing_handler/1, + filter_config/1, + log/2]). + + +%% ------------------------------------------------------------------ +%% logger_handler API +%% ------------------------------------------------------------------ + +-spec adding_handler(logger_handler:config()) -> {ok, logger_handler:config()} | + {error, term()}. +adding_handler(Config) -> + {ok, Pid} = logger_gsmtap:start({127,0,0,1}), %% XXX: hard-coded + {ok, Config#{pid => Pid}}. + + +-spec removing_handler(logger_handler:config()) -> ok. +removing_handler(#{pid := Pid}) -> + logger_gsmtap:stop(Pid), + ok. + + +-spec filter_config(logger_handler:config()) -> logger_handler:config(). +filter_config(Config) -> + maps:without([pid], Config). + + +-spec log(logger:log_event(), logger_handler:config()) -> term(). +log(LogEvent, #{pid := Pid}) -> + logger_gsmtap:log(Pid, LogEvent). + + +%% vim:set ts=4 sw=4 et: