| 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 | 292 |     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). |