1 |
|
%%% eturnal STUN/TURN server. |
2 |
|
%%% |
3 |
|
%%% Copyright (c) 2020-2022 Holger Weiss <holger@zedat.fu-berlin.de>. |
4 |
|
%%% Copyright (c) 2020-2022 ProcessOne, SARL. |
5 |
|
%%% All rights reserved. |
6 |
|
%%% |
7 |
|
%%% Licensed under the Apache License, Version 2.0 (the "License"); |
8 |
|
%%% you may not use this file except in compliance with the License. |
9 |
|
%%% You may obtain a copy of the License at |
10 |
|
%%% |
11 |
|
%%% http://www.apache.org/licenses/LICENSE-2.0 |
12 |
|
%%% |
13 |
|
%%% Unless required by applicable law or agreed to in writing, software |
14 |
|
%%% distributed under the License is distributed on an "AS IS" BASIS, |
15 |
|
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 |
|
%%% See the License for the specific language governing permissions and |
17 |
|
%%% limitations under the License. |
18 |
|
|
19 |
|
-module(eturnal_logger). |
20 |
|
-export([start/0, |
21 |
|
stop/0, |
22 |
|
progress_filter/2, |
23 |
|
reconfigure/0, |
24 |
|
is_valid_level/1, |
25 |
|
get_level/0, |
26 |
|
set_level/1, |
27 |
|
flush/0]). |
28 |
|
-export_type([level/0]). |
29 |
|
|
30 |
|
-include_lib("kernel/include/logger.hrl"). |
31 |
|
-define(ETURNAL_HANDLER, eturnal_log). |
32 |
|
-define(LOG_FILE_NAME, "eturnal.log"). |
33 |
|
-define(VALID_LEVELS, [critical, error, warning, notice, info, debug]). |
34 |
|
|
35 |
|
-type logger_config() :: #{file => file:filename(), |
36 |
|
file_check => non_neg_integer(), |
37 |
|
max_no_bytes => pos_integer() | infinity, |
38 |
|
max_no_files => non_neg_integer(), |
39 |
|
flush_qlen => pos_integer(), |
40 |
|
sync_mode_qlen => non_neg_integer(), |
41 |
|
drop_mode_qlen => pos_integer()}. |
42 |
|
|
43 |
|
% Subset of logger:level(): |
44 |
|
-type level() :: critical | error | warning | notice | info | debug. |
45 |
|
|
46 |
|
% Currently not exported by logger_formatter: |
47 |
|
-type metakey() :: atom() | [atom()]. |
48 |
|
-type template() :: [metakey() | {metakey(), template(), template()} | |
49 |
|
string()]. |
50 |
|
|
51 |
|
%% API. |
52 |
|
|
53 |
|
-spec start() -> ok. |
54 |
|
start() -> |
55 |
1 |
ok = init(get_config()), |
56 |
1 |
ok = configure_default_handler(). |
57 |
|
|
58 |
|
-spec stop() -> ok. |
59 |
|
stop() -> |
60 |
1 |
ok = terminate(). |
61 |
|
|
62 |
|
-spec progress_filter(logger:log_event(), any()) -> logger:filter_return(). |
63 |
|
progress_filter(#{level := info, |
64 |
|
msg := {report, #{label := {_, progress}}}} = Event, |
65 |
|
_Extra) -> |
66 |
25 |
case get_level() of |
67 |
|
debug -> |
68 |
25 |
logger_filters:progress(Event#{level => debug}, log); |
69 |
|
_ -> |
70 |
:-( |
stop |
71 |
|
end; |
72 |
|
progress_filter(_Event, _Extra) -> |
73 |
287 |
ignore. |
74 |
|
|
75 |
|
-spec reconfigure() -> ok. |
76 |
|
reconfigure() -> |
77 |
:-( |
Config = get_config(), |
78 |
:-( |
case logger:get_handler_config(?ETURNAL_HANDLER) of |
79 |
|
{ok, _OldConfig} -> |
80 |
:-( |
case logger:set_handler_config(?ETURNAL_HANDLER, config, Config) of |
81 |
|
ok -> |
82 |
:-( |
ok; |
83 |
|
{error, {illegal_config_change, _, _, _}} -> |
84 |
:-( |
?LOG_ERROR("New logging settings require restart") |
85 |
|
end, |
86 |
:-( |
ok = set_level(); |
87 |
|
{error, {not_found, _}} -> |
88 |
:-( |
ok = init(Config) |
89 |
|
end, |
90 |
:-( |
ok = configure_default_handler(). |
91 |
|
|
92 |
|
-spec is_valid_level(atom()) -> boolean(). |
93 |
|
is_valid_level(Level) -> |
94 |
2 |
lists:member(Level, ?VALID_LEVELS). |
95 |
|
|
96 |
|
-spec get_level() -> logger:level() | all | none. |
97 |
|
get_level() -> |
98 |
30 |
#{level := Level} = logger:get_primary_config(), |
99 |
30 |
Level. |
100 |
|
|
101 |
|
-spec set_level(level()) -> ok. |
102 |
|
set_level(Level) -> |
103 |
2 |
ok = logger:set_primary_config(level, Level), |
104 |
2 |
ok = logger:update_formatter_config( |
105 |
|
?ETURNAL_HANDLER, template, format_template()). |
106 |
|
|
107 |
|
%% Internal functions. |
108 |
|
|
109 |
|
-spec init(logger_config()) -> ok. |
110 |
|
init(Config) -> |
111 |
1 |
FmtConfig = #{time_designator => $\s, |
112 |
|
max_size => 100 * 1024, |
113 |
|
single_line => false}, |
114 |
1 |
case logger:add_primary_filter(progress_report, |
115 |
|
{fun ?MODULE:progress_filter/2, stop}) of |
116 |
|
ok -> |
117 |
1 |
ok; |
118 |
|
{error, {already_exist, _}} -> |
119 |
:-( |
ok |
120 |
|
end, |
121 |
1 |
case logger:add_handler(?ETURNAL_HANDLER, logger_std_h, |
122 |
|
#{level => all, |
123 |
|
config => Config, |
124 |
|
formatter => {logger_formatter, FmtConfig}}) of |
125 |
|
ok -> |
126 |
1 |
ok; |
127 |
|
{error, {already_exist, _}} -> |
128 |
:-( |
ok |
129 |
|
end, |
130 |
1 |
set_level(). |
131 |
|
|
132 |
|
-spec get_config() -> logger_config(). |
133 |
|
-ifdef(old_logger). % Erlang/OTP < 21.3. |
134 |
|
get_config() -> |
135 |
|
Config = #{sync_mode_qlen => 1000, |
136 |
|
drop_mode_qlen => 1000, % Never switch to synchronous mode. |
137 |
|
flush_qlen => 5000}, |
138 |
|
case get_log_file() of |
139 |
|
LogFile when is_list(LogFile) -> |
140 |
|
case eturnal:get_opt(log_rotate_size) of |
141 |
|
Size when is_integer(Size) -> |
142 |
|
?LOG_WARNING("Log rotation requires newer Erlang/OTP " |
143 |
|
"version, ignoring 'log_rotate_*' options"); |
144 |
|
infinity -> |
145 |
|
ok |
146 |
|
end, |
147 |
|
Config#{type => {file, LogFile}}; |
148 |
|
stdout -> |
149 |
|
Config |
150 |
|
end. |
151 |
|
-else. |
152 |
|
get_config() -> |
153 |
1 |
Config = #{sync_mode_qlen => 1000, |
154 |
|
drop_mode_qlen => 1000, % Never switch to synchronous mode. |
155 |
|
flush_qlen => 5000}, |
156 |
1 |
case get_log_file() of |
157 |
|
LogFile when is_list(LogFile) -> |
158 |
1 |
Config#{file => LogFile, |
159 |
|
file_check => 1000, |
160 |
|
max_no_bytes => eturnal:get_opt(log_rotate_size), |
161 |
|
max_no_files => eturnal:get_opt(log_rotate_count)}; |
162 |
|
stdout -> |
163 |
:-( |
Config |
164 |
|
end. |
165 |
|
-endif. |
166 |
|
|
167 |
|
-spec get_log_file() -> file:filename() | stdout. |
168 |
|
get_log_file() -> |
169 |
1 |
case eturnal:get_opt(log_dir) of |
170 |
|
LogDir when is_binary(LogDir) -> |
171 |
1 |
LogFile = filename:join(LogDir, <<?LOG_FILE_NAME>>), |
172 |
1 |
unicode:characters_to_list(LogFile); |
173 |
|
stdout -> |
174 |
:-( |
stdout |
175 |
|
end. |
176 |
|
|
177 |
|
-spec set_level() -> ok. |
178 |
|
set_level() -> |
179 |
1 |
ok = set_level(eturnal:get_opt(log_level)), |
180 |
1 |
ok = logger:update_formatter_config( |
181 |
|
?ETURNAL_HANDLER, template, format_template()). |
182 |
|
|
183 |
|
-spec logging_to_journal() -> boolean(). |
184 |
|
logging_to_journal() -> |
185 |
3 |
(eturnal:get_opt(log_dir) =:= stdout) and |
186 |
|
(os:getenv("JOURNAL_STREAM") =/= false). |
187 |
|
|
188 |
|
-spec format_template() -> template(). |
189 |
|
format_template() -> |
190 |
3 |
format_prefix() ++ format_message() ++ format_suffix(). |
191 |
|
|
192 |
|
-spec format_prefix() -> template(). |
193 |
|
format_prefix() -> |
194 |
3 |
case logging_to_journal() of |
195 |
|
true -> |
196 |
:-( |
["[", level, "] "]; |
197 |
|
false -> |
198 |
3 |
[time, " [", level, "] "] |
199 |
|
end. |
200 |
|
|
201 |
|
-spec format_message() -> template(). |
202 |
|
format_message() -> |
203 |
|
% For progress reports: |
204 |
3 |
[{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []}, |
205 |
|
% The actual log message: |
206 |
|
msg]. |
207 |
|
|
208 |
|
-spec format_suffix() -> template(). |
209 |
|
format_suffix() -> |
210 |
3 |
case get_level() of |
211 |
|
debug -> |
212 |
|
% Append (Module:Function/Arity and maybe :Line), if available: |
213 |
3 |
[{mfa, [" (", mfa, {line, [":", line], []}, ")"], []}, |
214 |
|
io_lib:nl()]; |
215 |
|
_Level -> |
216 |
:-( |
[io_lib:nl()] |
217 |
|
end. |
218 |
|
|
219 |
|
-spec configure_default_handler() -> ok. |
220 |
|
configure_default_handler() -> |
221 |
1 |
case eturnal:get_opt(log_dir) of |
222 |
|
LogDir when is_binary(LogDir) -> |
223 |
1 |
ok = logger:set_handler_config(default, level, warning); |
224 |
|
stdout -> |
225 |
:-( |
ok = logger:set_handler_config(default, level, none) |
226 |
|
end. |
227 |
|
|
228 |
|
-spec flush() -> ok. |
229 |
|
flush() -> |
230 |
1 |
lists:foreach( |
231 |
|
fun(#{id := HandlerID, module := logger_std_h}) -> |
232 |
3 |
logger_std_h:filesync(HandlerID); |
233 |
|
(_) -> |
234 |
2 |
ok |
235 |
|
end, logger:get_handler_config()). |
236 |
|
|
237 |
|
-spec terminate() -> ok. |
238 |
|
terminate() -> |
239 |
1 |
ok = flush(), |
240 |
1 |
ok = logger:set_handler_config(default, level, notice), |
241 |
1 |
ok = logger:remove_primary_filter(progress_report), |
242 |
1 |
ok = logger:remove_handler(?ETURNAL_HANDLER). |