Line data Source code
1 : // Copyright (c) 2013-2021 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snaplogger
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Handle logger specific command line and other options.
22 : *
23 : * The logger supports a few options to override configuration files
24 : * and tweak settings from the command line. Since the user is in
25 : * control of the environment variable, we do not offer that option
26 : * here.
27 : */
28 :
29 :
30 : // self
31 : //
32 : #include "snaplogger/options.h"
33 :
34 : #include "snaplogger/logger.h"
35 : #include "snaplogger/map_diagnostic.h"
36 : #include "snaplogger/version.h"
37 :
38 :
39 : // boost lib
40 : //
41 : #include <boost/algorithm/string/replace.hpp>
42 :
43 :
44 : // advgetopt lib
45 : //
46 : #include <advgetopt/exception.h>
47 :
48 :
49 : // cppthread lib
50 : //
51 : #include <cppthread/log.h>
52 :
53 :
54 : // last include
55 : //
56 : #include <snapdev/poison.h>
57 :
58 :
59 :
60 : namespace snaplogger
61 : {
62 :
63 :
64 : namespace
65 : {
66 :
67 :
68 :
69 : /** \brief The command line options that the logger adds.
70 : *
71 : * This variable holds the list of options that the logger adds when you
72 : * call the add_logger_options() function. These allows us to have the
73 : * same command line options in all the tools we develop with the snaplogger.
74 : *
75 : * The options are:
76 : *
77 : * * no-log
78 : * * log-file
79 : * * log-config
80 : * * syslog
81 : * * console
82 : * * logger-show-banner
83 : * * logger-hide-banner
84 : * * log-config-path
85 : * * debug
86 : * * trace
87 : * * list-severities
88 : * * log-severity
89 : * * force-severity
90 : * * log-component
91 : * * logger-version
92 : * * logger-configuration-filenames
93 : */
94 : advgetopt::option const g_options[] =
95 : {
96 : // DIRECT SELECT
97 : //
98 : advgetopt::define_option(
99 : advgetopt::Name("no-log")
100 : , advgetopt::Flags(advgetopt::standalone_command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS>())
101 : , advgetopt::Help("do not log anything.")
102 : ),
103 : advgetopt::define_option(
104 : advgetopt::Name("log-file")
105 : , advgetopt::Flags(advgetopt::command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS
106 : , advgetopt::GETOPT_FLAG_REQUIRED>())
107 : , advgetopt::Help("log data to this specific log files")
108 : ),
109 : advgetopt::define_option(
110 : advgetopt::Name("log-config")
111 : , advgetopt::Flags(advgetopt::command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS
112 : , advgetopt::GETOPT_FLAG_REQUIRED>())
113 : , advgetopt::Help("only load this specific configuration file.")
114 : ),
115 : advgetopt::define_option(
116 : advgetopt::Name("syslog")
117 : , advgetopt::Flags(advgetopt::command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS>())
118 : , advgetopt::Help("send the logs to syslog only, the argument, if specified, is the name to use as the identity.")
119 : ),
120 : advgetopt::define_option(
121 : advgetopt::Name("console")
122 : , advgetopt::Flags(advgetopt::standalone_command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS>())
123 : , advgetopt::Help("print the logs out to the console.")
124 : ),
125 : advgetopt::define_option(
126 : advgetopt::Name("logger-show-banner")
127 : , advgetopt::Flags(advgetopt::standalone_command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS>())
128 : , advgetopt::Help("show a banner on startup with the tool name and version.")
129 : ),
130 : advgetopt::define_option(
131 : advgetopt::Name("logger-hide-banner")
132 : , advgetopt::Flags(advgetopt::standalone_command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS>())
133 : , advgetopt::Help("do not show the banner (--logger-show-banner has priority if specified).")
134 : ),
135 :
136 : // ALTERNATIVE CONFIG FILES
137 : //
138 : advgetopt::define_option(
139 : advgetopt::Name("log-config-path")
140 : , advgetopt::Flags(advgetopt::all_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS
141 : , advgetopt::GETOPT_FLAG_REQUIRED>())
142 : , advgetopt::Help("the path to the configuration folders.")
143 : ),
144 :
145 : // SEVERITY
146 : //
147 : advgetopt::define_option(
148 : advgetopt::Name("debug")
149 : , advgetopt::Flags(advgetopt::standalone_command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS>())
150 : , advgetopt::Help("change the logger severity level of each appender to DEBUG.")
151 : ),
152 : advgetopt::define_option(
153 : advgetopt::Name("trace")
154 : , advgetopt::Flags(advgetopt::standalone_command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS>())
155 : , advgetopt::Help("change the logger severity level of each appender to TRACE.")
156 : ),
157 : advgetopt::define_option(
158 : advgetopt::Name("log-severity")
159 : , advgetopt::Flags(advgetopt::command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS
160 : , advgetopt::GETOPT_FLAG_REQUIRED>())
161 : , advgetopt::Help("reduce the severity level of each appender to the specified level unless it is already lower.")
162 : ),
163 : advgetopt::define_option(
164 : advgetopt::Name("force-severity")
165 : , advgetopt::Flags(advgetopt::command_flags<advgetopt::GETOPT_FLAG_GROUP_OPTIONS
166 : , advgetopt::GETOPT_FLAG_REQUIRED>())
167 : , advgetopt::Help("change the logger severity level of each appender to the specified level.")
168 : ),
169 :
170 : // FILTERS
171 : //
172 : advgetopt::define_option(
173 : advgetopt::Name("log-component")
174 : , advgetopt::Flags(advgetopt::command_flags<
175 : advgetopt::GETOPT_FLAG_GROUP_OPTIONS
176 : , advgetopt::GETOPT_FLAG_MULTIPLE
177 : , advgetopt::GETOPT_FLAG_REQUIRED>())
178 : , advgetopt::Help("filter logs by component, use ! in front of a name to prevent those logs.")
179 : ),
180 :
181 : // COMMANDS
182 : //
183 : advgetopt::define_option(
184 : advgetopt::Name("list-severities")
185 : , advgetopt::Flags(advgetopt::standalone_command_flags<advgetopt::GETOPT_FLAG_GROUP_COMMANDS>())
186 : , advgetopt::Help("show the list of available log severities.")
187 : ),
188 : advgetopt::define_option(
189 : advgetopt::Name("logger-version")
190 : , advgetopt::Flags(advgetopt::standalone_command_flags<advgetopt::GETOPT_FLAG_GROUP_COMMANDS>())
191 : , advgetopt::Help("show the version of the logger library.")
192 : ),
193 : advgetopt::define_option(
194 : advgetopt::Name("logger-configuration-filenames")
195 : , advgetopt::Flags(advgetopt::standalone_command_flags<advgetopt::GETOPT_FLAG_GROUP_COMMANDS>())
196 : , advgetopt::Help("show the list of configuration filenames that would be loaded with the current options.")
197 : ),
198 :
199 : // END
200 : //
201 : advgetopt::end_options()
202 : };
203 :
204 :
205 :
206 :
207 : }
208 : // no name namespace
209 :
210 :
211 : /** \brief Add the logger command line options.
212 : *
213 : * This function automatically adds the logger specific command line options
214 : * to the \p opts object.
215 : *
216 : * The options allow for some logger specific tweaking in all your
217 : * applications without having to do that on a per application basis.
218 : * It also makes use of some command line options which you should not
219 : * overload.
220 : *
221 : * On top of the command line option additions, the function automatically
222 : * adds up to four new diagnostics:
223 : *
224 : * * DIAG_KEY_VERSION -- the version of the tool
225 : * * DIAG_KEY_BUILD_DATE -- the build date of the tool
226 : * * DIAG_KEY_BUILD_TIME -- the build time of the tool
227 : * * DIAG_KEY_PROJECT_NAME -- the name of the project
228 : *
229 : * The project name is always added. The other parameters are added only
230 : * if found in the \p opts environment variables.
231 : *
232 : * \param[in,out] opts The options to tweak with the logger options.
233 : */
234 0 : void add_logger_options(advgetopt::getopt & opts)
235 : {
236 0 : auto env(opts.get_options_environment());
237 0 : if(env.f_version != nullptr)
238 : {
239 0 : set_diagnostic(DIAG_KEY_VERSION, env.f_version);
240 : }
241 0 : if(env.f_build_date != nullptr)
242 : {
243 0 : set_diagnostic(DIAG_KEY_BUILD_DATE, env.f_build_date);
244 : }
245 0 : if(env.f_build_time != nullptr)
246 : {
247 0 : set_diagnostic(DIAG_KEY_BUILD_TIME, env.f_build_time);
248 : }
249 0 : set_diagnostic(DIAG_KEY_PROJECT_NAME, opts.get_project_name());
250 :
251 0 : opts.parse_options_info(g_options, true);
252 0 : }
253 :
254 :
255 :
256 :
257 : /** \brief Process the logger options.
258 : *
259 : * This function is expected to be called before you move forward with
260 : * the other work you want to do in your tool. It allows for processing
261 : * any and all the logger command line options.
262 : *
263 : * To use this command, you first want to call the add_logger_options()
264 : * function, process the arguments, and finally call this function as
265 : * follow:
266 : *
267 : * \code
268 : * my_app::my_app(int argc, char * argv[])
269 : * : f_opt(g_options_environment)
270 : * {
271 : * snaplogger::add_logger_options(f_opt);
272 : * f_opt.finish_parsing(argc, argv);
273 : * if(!snaplogger::process_logger_options(f_opt, "/etc/my-app/logger"))
274 : * {
275 : * // exit on any error
276 : * throw advgetopt::getopt_exit("logger options generated an error.", 0);
277 : * }
278 : *
279 : * ...
280 : * }
281 : * \endcode
282 : *
283 : * \exception advgetopt::getopt_exit
284 : * Some commands emit this exception which means that the process is done.
285 : * It is expected to quickly and silently exit after catching this
286 : * exception.
287 : *
288 : * \todo
289 : * See whether we can change the cppthread::log to a SNAP_LOG_... when
290 : * sending errors.
291 : *
292 : * \param[in] opts The option definitions and values.
293 : * \param[in] config_path A path where the logger configuration files are
294 : * to be searched. The logger also searches under `/usr/share/snaplogger/etc`
295 : * prior and `~/.config/<tool-name>/logger` after.
296 : * \param[in] out The output stream to use in case the command is to generate
297 : * output to the user. Defaults to `std::cout`.
298 : * \param[in] show_banner Whether to show the banner. For CLI and GUI tools,
299 : * you may want to hide this banner. For daemons, it is customary to leave
300 : * the banner as it just goes to a log file.
301 : *
302 : * \return true of success, false when an error occurs. You are expected to
303 : * stop your process immediately when this function returns false, but this
304 : * is not mandatory.
305 : */
306 0 : bool process_logger_options(advgetopt::getopt & opts
307 : , std::string const & config_path
308 : , std::basic_ostream<char> & out
309 : , bool show_banner)
310 : {
311 0 : constexpr int const OPTION_NO_LOG = 0x001;
312 0 : constexpr int const OPTION_LOG_FILE = 0x002;
313 0 : constexpr int const OPTION_LOG_CONFIG = 0x004;
314 0 : constexpr int const OPTION_SYSLOG = 0x008;
315 0 : constexpr int const OPTION_CONSOLE = 0x010;
316 :
317 0 : constexpr int const OPTION_TRACE_SEVERITY = 0x020;
318 0 : constexpr int const OPTION_DEBUG_SEVERITY = 0x040;
319 0 : constexpr int const OPTION_LOG_SEVERITY = 0x080;
320 0 : constexpr int const OPTION_FORCE_SEVERITY = 0x100;
321 :
322 0 : bool result(true);
323 :
324 0 : set_diagnostic(DIAG_KEY_PROGNAME, opts.get_program_name());
325 :
326 : // COMMANDS
327 : //
328 0 : if(opts.is_defined("logger-version"))
329 : {
330 0 : out << snaplogger::get_version_string() << std::endl;
331 0 : throw advgetopt::getopt_exit("--logger-version command processed.", 0);
332 : }
333 0 : if(opts.is_defined("list-severities"))
334 : {
335 0 : out << "List of the snaplogger known severities:\n";
336 :
337 0 : severity_by_severity_t const list(get_severities_by_severity());
338 0 : for(auto const & it : list)
339 : {
340 0 : string_vector_t const names(it.second->get_all_names());
341 0 : char const * sep = " . ";
342 0 : for(auto const & s : names)
343 : {
344 0 : out << sep << s;
345 0 : sep = ", ";
346 : }
347 :
348 0 : out << " [" << static_cast<int>(it.first);
349 0 : if(it.second->is_system())
350 : {
351 0 : out << "/system";
352 : }
353 0 : if(!it.second->get_styles().empty())
354 : {
355 0 : out << "/styles";
356 : }
357 0 : out << ']';
358 :
359 : //std::string const d(it.second->get_description());
360 : //if(!d.empty())
361 : //{
362 : // out << " -- " << d;
363 : //}
364 :
365 0 : out << '\n';
366 : }
367 0 : out << std::endl;
368 0 : throw advgetopt::getopt_exit("--list-severities command processed.", 0);
369 : }
370 :
371 : // LOG CONFIG
372 : //
373 0 : int log_config(0);
374 0 : if(opts.is_defined("no-log"))
375 : {
376 0 : log_config |= OPTION_NO_LOG;
377 : }
378 0 : if(opts.is_defined("log-file"))
379 : {
380 0 : log_config |= OPTION_LOG_FILE;
381 : }
382 0 : if(opts.is_defined("log-config"))
383 : {
384 0 : log_config |= OPTION_LOG_CONFIG;
385 : }
386 0 : if(opts.is_defined("syslog"))
387 : {
388 0 : log_config |= OPTION_SYSLOG;
389 : }
390 0 : if(opts.is_defined("console"))
391 : {
392 0 : log_config |= OPTION_CONSOLE;
393 : }
394 0 : if(opts.is_defined("logger-show-banner"))
395 : {
396 0 : show_banner = true;
397 : }
398 0 : else if(opts.is_defined("logger-hide-banner"))
399 : {
400 0 : show_banner = false;
401 : }
402 :
403 0 : bool const show_logger_configuration_files(opts.is_defined("logger-configuration-filenames"));
404 0 : switch(log_config)
405 : {
406 0 : case 0:
407 : // defaults apply as normal
408 :
409 : {
410 0 : advgetopt::options_environment opt_env;
411 :
412 0 : std::string user_config("~/.config/");
413 0 : user_config += opts.get_project_name();
414 0 : user_config += "/logger";
415 0 : char const * config_dirs[] =
416 : {
417 : "/usr/share/snaplogger/etc"
418 0 : , config_path.c_str()
419 0 : , user_config.c_str()
420 : , nullptr
421 0 : };
422 :
423 0 : if(opts.is_defined("log-config-path"))
424 : {
425 0 : config_dirs[0] = opts.get_string("log-config-path").c_str();
426 : }
427 :
428 0 : std::string const keep_project_name(opts.get_project_name());
429 0 : opt_env.f_project_name = keep_project_name.c_str();
430 0 : std::string const keep_group_name(opts.get_group_name());
431 0 : opt_env.f_group_name = keep_group_name.c_str();
432 0 : opt_env.f_environment_variable_name = "SNAPLOGGER";
433 : //opt_env.f_configuration_files = nullptr;
434 0 : opt_env.f_configuration_filename = "snaplogger.conf";
435 0 : opt_env.f_configuration_directories = config_dirs;
436 0 : opt_env.f_environment_flags = advgetopt::GETOPT_ENVIRONMENT_FLAG_DYNAMIC_PARAMETERS;
437 :
438 0 : advgetopt::getopt system_opts(opt_env);
439 :
440 0 : if(show_logger_configuration_files)
441 : {
442 0 : advgetopt::string_list_t list(system_opts.get_configuration_filenames(false, false));
443 0 : out << "Logger common configuration filenames:" << std::endl;
444 0 : for(auto n : list)
445 : {
446 0 : out << " . " << n << "\n";
447 : }
448 : }
449 :
450 : // load the system configuration file first
451 : //
452 0 : system_opts.parse_configuration_files();
453 0 : if(opts.get_program_fullname().empty())
454 : {
455 : // process environment variable now if no user filename
456 : // is going to be loaded
457 : //
458 0 : system_opts.parse_environment_variable();
459 : }
460 0 : logger::get_instance()->set_config(system_opts);
461 :
462 0 : if(!opts.get_program_fullname().empty())
463 : {
464 : // if we have a valid program name (non-empty) then try
465 : // to load these configuration files
466 : //
467 0 : std::string filename(opts.get_program_name());
468 0 : boost::replace_all(filename, "_", "-");
469 0 : filename += ".conf";
470 0 : opt_env.f_configuration_filename = filename.c_str();
471 0 : advgetopt::getopt config_opts(opt_env);
472 :
473 0 : if(show_logger_configuration_files)
474 : {
475 0 : advgetopt::string_list_t list(config_opts.get_configuration_filenames(false, false));
476 0 : out << "Logger application configuration filenames:" << std::endl;
477 0 : for(auto n : list)
478 : {
479 0 : out << " . " << n << "\n";
480 : }
481 : }
482 :
483 0 : config_opts.parse_configuration_files();
484 0 : config_opts.parse_environment_variable();
485 0 : logger::get_instance()->set_config(config_opts);
486 0 : }
487 : }
488 0 : break;
489 :
490 0 : case OPTION_NO_LOG:
491 : // do nothing
492 0 : break;
493 :
494 0 : case OPTION_LOG_FILE:
495 0 : configure_file(opts.get_string("log-file"));
496 0 : break;
497 :
498 0 : case OPTION_LOG_CONFIG:
499 0 : configure_config(opts.get_string("log-config"));
500 0 : break;
501 :
502 0 : case OPTION_SYSLOG:
503 0 : configure_syslog(opts.get_string("syslog"));
504 0 : break;
505 :
506 0 : case OPTION_CONSOLE:
507 0 : configure_console();
508 0 : break;
509 :
510 0 : default:
511 0 : cppthread::log << cppthread::log_level_t::error
512 0 : << "only one of --no-log, --log-file, --log-config, --syslog, --console can be used on your command line."
513 0 : << cppthread::end;
514 0 : result = false;
515 0 : break;
516 :
517 : }
518 :
519 0 : if(show_logger_configuration_files)
520 : {
521 0 : if(log_config != 0)
522 : {
523 0 : if(log_config == OPTION_LOG_CONFIG)
524 : {
525 0 : out << "Logger application configuration filename:" << std::endl
526 0 : << " . " << opts.get_string("log-config") << std::endl;
527 : }
528 : else
529 : {
530 0 : out << "No logger application configuration filenames available with the current command line options." << std::endl;
531 : }
532 : }
533 0 : throw advgetopt::getopt_exit("--logger-configuration-filenames command processed.", 0);
534 : }
535 :
536 : // SEVERITY
537 : //
538 0 : int severity_selection(0);
539 0 : if(opts.is_defined("trace"))
540 : {
541 0 : severity_selection |= OPTION_TRACE_SEVERITY;
542 : }
543 0 : if(opts.is_defined("debug"))
544 : {
545 0 : severity_selection |= OPTION_DEBUG_SEVERITY;
546 : }
547 0 : if(opts.is_defined("log-severity"))
548 : {
549 0 : severity_selection |= OPTION_LOG_SEVERITY;
550 : }
551 0 : if(opts.is_defined("force-severity"))
552 : {
553 0 : severity_selection |= OPTION_FORCE_SEVERITY;
554 : }
555 :
556 0 : switch(severity_selection)
557 : {
558 0 : case 0:
559 : // keep as is
560 0 : break;
561 :
562 0 : case OPTION_TRACE_SEVERITY:
563 0 : logger::get_instance()->reduce_severity(severity_t::SEVERITY_TRACE);
564 0 : configure_console(true);
565 0 : break;
566 :
567 0 : case OPTION_DEBUG_SEVERITY:
568 0 : logger::get_instance()->reduce_severity(severity_t::SEVERITY_DEBUG);
569 0 : configure_console(true);
570 0 : break;
571 :
572 0 : case OPTION_LOG_SEVERITY:
573 : {
574 0 : std::string const severity_name(opts.get_string("log-severity"));
575 0 : severity::pointer_t sev(get_severity(severity_name));
576 0 : if(sev == nullptr)
577 : {
578 0 : cppthread::log << cppthread::log_level_t::error
579 0 : << "unknown severity level \""
580 0 : << severity_name
581 0 : << "\"; please check your spelling."
582 0 : << cppthread::end;
583 0 : result = false;
584 : }
585 : else
586 : {
587 0 : logger::get_instance()->reduce_severity(sev->get_severity());
588 0 : }
589 : }
590 0 : break;
591 :
592 0 : case OPTION_FORCE_SEVERITY:
593 : {
594 0 : std::string const severity_name(opts.get_string("force-severity"));
595 0 : severity::pointer_t sev(get_severity(severity_name));
596 0 : if(sev == nullptr)
597 : {
598 0 : cppthread::log << cppthread::log_level_t::error
599 0 : << "unknown severity level \""
600 0 : << severity_name
601 0 : << "\"; please check your spelling against the --list-severities."
602 0 : << cppthread::end;
603 0 : result = false;
604 : }
605 : else
606 : {
607 0 : logger::get_instance()->set_severity(sev->get_severity());
608 0 : }
609 : }
610 0 : break;
611 :
612 0 : default:
613 0 : cppthread::log << cppthread::log_level_t::error
614 0 : << "only one of --debug, --log-severity, --force-severity can be used on your command line."
615 0 : << cppthread::end;
616 0 : return false;
617 :
618 : }
619 :
620 : // FILTERS
621 : //
622 0 : if(opts.is_defined("log-component"))
623 : {
624 0 : size_t const max(opts.size("log-component"));
625 0 : for(size_t idx(0); idx < max; ++idx)
626 : {
627 0 : std::string log_component(opts.get_string("log-component", idx));
628 0 : if(!log_component.empty())
629 : {
630 0 : if(log_component[0] == '!')
631 : {
632 0 : log_component = log_component.substr(1);
633 0 : if(!log_component.empty())
634 : {
635 0 : component::pointer_t comp(get_component(log_component));
636 0 : logger::get_instance()->add_component_to_ignore(comp);
637 : }
638 : }
639 : else
640 : {
641 0 : component::pointer_t comp(get_component(log_component));
642 0 : logger::get_instance()->add_component_to_include(comp);
643 : }
644 : }
645 : }
646 : }
647 :
648 0 : if(show_banner)
649 : {
650 0 : SNAP_LOG_INFO
651 0 : << section(g_normal_component)
652 0 : << section(g_self_component)
653 0 : << section(g_banner_component)
654 : << "--------------------------------------------------"
655 : << SNAP_LOG_SEND;
656 0 : SNAP_LOG_INFO
657 0 : << section(g_normal_component)
658 0 : << section(g_self_component)
659 0 : << section(g_banner_component)
660 0 : << opts.get_project_name()
661 : << " v"
662 0 : << opts.get_options_environment().f_version
663 : << " started."
664 : << SNAP_LOG_SEND;
665 : }
666 :
667 0 : return result;
668 : }
669 :
670 :
671 6 : } // snaplogger namespace
672 : // vim: ts=4 sw=4 et
|