Line data Source code
1 : // Copyright (c) 2013-2025 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 3 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
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : /** \file
20 : * \brief Severity levels for your log messages.
21 : *
22 : * The severity implementation loads the severity configuration file
23 : * and generates a set of severity levels that one can attach to
24 : * log messages.
25 : */
26 :
27 :
28 : // self
29 : //
30 : #include "snaplogger/severity.h"
31 :
32 : #include "snaplogger/exception.h"
33 : #include "snaplogger/guard.h"
34 : #include "snaplogger/private_logger.h"
35 :
36 :
37 : // advgetopt
38 : //
39 : #include <advgetopt/advgetopt.h>
40 : #include <advgetopt/options.h>
41 :
42 :
43 : // snapdev
44 : //
45 : #include <snapdev/stringize.h>
46 :
47 :
48 :
49 : // C++
50 : //
51 : #include <iostream>
52 : #include <map>
53 :
54 :
55 : // C
56 : //
57 : #include <sys/time.h>
58 :
59 :
60 : // last include
61 : //
62 : #include <snapdev/poison.h>
63 :
64 :
65 :
66 : namespace snaplogger
67 : {
68 :
69 :
70 :
71 : namespace
72 : {
73 :
74 :
75 : bool g_severity_auto_added = false;
76 :
77 :
78 : struct system_severity
79 : {
80 : severity_t f_severity = severity_t::SEVERITY_ALL;
81 : char const * f_name = nullptr;
82 : char const * f_alias = nullptr; // at most 1 alias for system severities
83 : char const * f_description = nullptr;
84 : char const * f_styles = nullptr;
85 : };
86 :
87 : constexpr system_severity g_system_severity[] =
88 : {
89 : {
90 : .f_severity = severity_t::SEVERITY_ALL,
91 : .f_name = "all",
92 : .f_alias = "everything",
93 : .f_description = "all",
94 : },
95 : {
96 : .f_severity = severity_t::SEVERITY_TRACE,
97 : .f_name = "trace",
98 : .f_alias = nullptr,
99 : .f_description = "trace",
100 : .f_styles = "italic"
101 : },
102 : {
103 : .f_severity = severity_t::SEVERITY_NOISY,
104 : .f_name = "noisy",
105 : .f_alias = "noise",
106 : .f_description = "noisy",
107 : .f_styles = "italic"
108 : },
109 : {
110 : .f_severity = severity_t::SEVERITY_DEBUG,
111 : .f_name = "debug",
112 : .f_alias = "dbg",
113 : .f_description = "debug",
114 : .f_styles = "italic"
115 : },
116 : {
117 : .f_severity = severity_t::SEVERITY_NOTICE,
118 : .f_name = "notice",
119 : .f_alias = "note",
120 : .f_description = "notice",
121 : .f_styles = nullptr
122 : },
123 : {
124 : .f_severity = severity_t::SEVERITY_UNIMPORTANT,
125 : .f_name = "unimportant",
126 : .f_alias = nullptr,
127 : .f_description = "unimportant",
128 : .f_styles = nullptr
129 : },
130 : {
131 : .f_severity = severity_t::SEVERITY_VERBOSE,
132 : .f_name = "verbose",
133 : .f_alias = "verb",
134 : .f_description = "verbose",
135 : .f_styles = nullptr
136 : },
137 : {
138 : .f_severity = severity_t::SEVERITY_CONFIGURATION,
139 : .f_name = "configuration",
140 : .f_alias = "config",
141 : .f_description = "configuration",
142 : .f_styles = nullptr
143 : },
144 : {
145 : .f_severity = severity_t::SEVERITY_CONFIGURATION_WARNING,
146 : .f_name = "configuration-warning",
147 : .f_alias = "config-warn",
148 : .f_description = "configuration warning",
149 : .f_styles = nullptr
150 : },
151 : {
152 : .f_severity = severity_t::SEVERITY_INFORMATION,
153 : .f_name = "information",
154 : .f_alias = "info",
155 : .f_description = "information",
156 : .f_styles = nullptr
157 : },
158 : {
159 : .f_severity = severity_t::SEVERITY_IMPORTANT,
160 : .f_name = "important",
161 : .f_alias = "significant",
162 : .f_description = "important",
163 : .f_styles = "green"
164 : },
165 : {
166 : .f_severity = severity_t::SEVERITY_MINOR,
167 : .f_name = "minor",
168 : .f_alias = nullptr,
169 : .f_description = "minor",
170 : .f_styles = "green"
171 : },
172 : {
173 : .f_severity = severity_t::SEVERITY_TODO,
174 : .f_name = "todo",
175 : .f_alias = nullptr,
176 : .f_description = "incomplete task",
177 : .f_styles = nullptr
178 : },
179 : {
180 : .f_severity = severity_t::SEVERITY_DEPRECATED,
181 : .f_name = "deprecated",
182 : .f_alias = nullptr,
183 : .f_description = "deprecated",
184 : .f_styles = "orange"
185 : },
186 : {
187 : .f_severity = severity_t::SEVERITY_WARNING,
188 : .f_name = "warning",
189 : .f_alias = "warn",
190 : .f_description = "warning",
191 : .f_styles = "orange"
192 : },
193 : {
194 : .f_severity = severity_t::SEVERITY_MAJOR,
195 : .f_name = "major",
196 : .f_alias = "paramount",
197 : .f_description = "major",
198 : .f_styles = "orange"
199 : },
200 : {
201 : .f_severity = severity_t::SEVERITY_RECOVERABLE_ERROR,
202 : .f_name = "recoverable-error",
203 : .f_alias = "recoverable",
204 : .f_description = "recoverable error",
205 : .f_styles = "red"
206 : },
207 : {
208 : .f_severity = severity_t::SEVERITY_ERROR,
209 : .f_name = "error",
210 : .f_alias = "err",
211 : .f_description = "error",
212 : .f_styles = "red"
213 : },
214 : {
215 : .f_severity = severity_t::SEVERITY_NOISY_ERROR,
216 : .f_name = "noisy-error",
217 : .f_alias = nullptr,
218 : .f_description = "noisy error",
219 : .f_styles = "red"
220 : },
221 : {
222 : .f_severity = severity_t::SEVERITY_SEVERE,
223 : .f_name = "severe",
224 : .f_alias = nullptr,
225 : .f_description = "severe error",
226 : .f_styles = "red"
227 : },
228 : {
229 : .f_severity = severity_t::SEVERITY_EXCEPTION,
230 : .f_name = "exception",
231 : .f_alias = nullptr,
232 : .f_description = "exception",
233 : .f_styles = "bold,red"
234 : },
235 : {
236 : .f_severity = severity_t::SEVERITY_CRITICAL,
237 : .f_name = "critical",
238 : .f_alias = "crit",
239 : .f_description = "critical",
240 : .f_styles = "bold,red"
241 : },
242 : {
243 : .f_severity = severity_t::SEVERITY_ALERT,
244 : .f_name = "alert",
245 : .f_alias = nullptr,
246 : .f_description = "alert",
247 : .f_styles = "bold,red"
248 : },
249 : {
250 : .f_severity = severity_t::SEVERITY_EMERGENCY,
251 : .f_name = "emergency",
252 : .f_alias = "emerg",
253 : .f_description = "emergency",
254 : .f_styles = "bold,red"
255 : },
256 : {
257 : .f_severity = severity_t::SEVERITY_FATAL,
258 : .f_name = "fatal",
259 : .f_alias = "fatal-error",
260 : .f_description = "fatal",
261 : .f_styles = "bold,red"
262 : },
263 : {
264 : .f_severity = severity_t::SEVERITY_OFF,
265 : .f_name = "off",
266 : .f_alias = "nothing",
267 : .f_description = "off",
268 : .f_styles = nullptr
269 : }
270 : };
271 :
272 :
273 : constexpr char const * const g_configuration_directories[] = {
274 : "/usr/share/snaplogger",
275 : "/etc/snaplogger",
276 : nullptr
277 : };
278 :
279 :
280 : advgetopt::options_environment g_config_option =
281 : {
282 : .f_project_name = "logger",
283 : .f_configuration_filename = "severity.ini",
284 : .f_configuration_directories = g_configuration_directories,
285 : .f_environment_flags = advgetopt::GETOPT_ENVIRONMENT_FLAG_DYNAMIC_PARAMETERS
286 : };
287 :
288 :
289 :
290 : /** \brief Add system severities.
291 : *
292 : * This function goes through the list of system securities and add them.
293 : * Then it loads the severity.ini files it files and updates the system
294 : * security levels and adds the user defined security levels.
295 : *
296 : * The severity.ini file format is as follow:
297 : *
298 : * * Section name, this is the name of the severity such as `[debug]`
299 : * * Section field: `severity`, the severity level of this severity
300 : * * Section field: `alias`, a list of other names for this severity (i.e.
301 : * the `information` severity is also named `info`)
302 : * * Section field: `description`, the description of the severity
303 : * * Section field: `styles`, the styles such as the color (red, orange...)
304 : *
305 : * For example:
306 : *
307 : * \code
308 : * [information]
309 : * level=50
310 : * aliases=info
311 : * description=info
312 : * default=true
313 : * styles=green
314 : * \endcode
315 : */
316 120 : void auto_add_severities()
317 : {
318 120 : guard g;
319 :
320 120 : if(g_severity_auto_added)
321 : {
322 119 : return;
323 : }
324 1 : g_severity_auto_added = true;
325 :
326 1 : private_logger::pointer_t l(get_private_logger());
327 :
328 27 : for(auto ss : g_system_severity)
329 : {
330 26 : severity::pointer_t sev(std::make_shared<severity>(ss.f_severity, ss.f_name, true));
331 26 : if(ss.f_alias != nullptr)
332 : {
333 : // at this time we have one at the most here
334 : //
335 51 : sev->add_alias(ss.f_alias);
336 : }
337 78 : sev->set_description(ss.f_description);
338 26 : if(ss.f_styles != nullptr)
339 : {
340 51 : sev->set_styles(ss.f_styles);
341 : }
342 :
343 26 : add_severity(sev);
344 26 : }
345 :
346 : // load user editable parameters
347 : //
348 1 : advgetopt::getopt::pointer_t config(std::make_shared<advgetopt::getopt>(g_config_option));
349 1 : config->parse_configuration_files();
350 3 : advgetopt::option_info::pointer_t const sections(config->get_option(advgetopt::CONFIGURATION_SECTIONS));
351 1 : if(sections != nullptr)
352 : {
353 0 : std::size_t const max(sections->size());
354 0 : for(std::size_t idx(0); idx < max; ++idx)
355 : {
356 0 : std::string const section_name(sections->get_value(idx));
357 :
358 : // we found a section name, this is the name of a severity,
359 : // gather the info and create the new severity
360 : //
361 0 : severity::pointer_t sev(get_severity(section_name));
362 0 : if(sev != nullptr)
363 : {
364 : // it already exists...
365 : //
366 : // we allow system severities to get updated in various ways
367 : // but user defined severities must be defined just once
368 : //
369 0 : if(!sev->is_system())
370 : {
371 : throw duplicate_error("we found two severity levels named \""
372 0 : + section_name
373 0 : + "\" in your severity.ini file.");
374 : }
375 :
376 : // although we allow most parameters to be changed,
377 : // we do not want the severity level to be changed
378 : //
379 0 : std::string const severity_field(section_name + "::severity");
380 0 : if(config->is_defined(severity_field))
381 : {
382 0 : long const level(config->get_long(severity_field));
383 0 : if(level < 0 || level > 255)
384 : {
385 0 : throw invalid_severity("severity level must be between 0 and 255.");
386 : }
387 0 : if(static_cast<severity_t>(level) != sev->get_severity())
388 : {
389 : throw invalid_severity("severity level of a system entry cannot be changed. \""
390 0 : + section_name
391 0 : + "\" is trying to do so with "
392 0 : + std::to_string(level)
393 0 : + " instead of "
394 0 : + std::to_string(static_cast<long>(sev->get_severity()))
395 0 : + " (but it's best not to define the severity level at all for system severities).");
396 : }
397 : }
398 :
399 0 : std::string const aliases_field(section_name + "::aliases");
400 0 : if(config->is_defined(aliases_field))
401 : {
402 0 : std::string const aliases(config->get_string(aliases_field));
403 0 : advgetopt::string_list_t names;
404 0 : advgetopt::split_string(aliases, names, {","});
405 0 : auto const & existing_names(sev->get_all_names());
406 0 : for(auto n : names)
407 : {
408 0 : auto const existing_alias(std::find(existing_names.begin(), existing_names.end(), n));
409 0 : if(existing_alias == existing_names.end())
410 : {
411 0 : sev->add_alias(n);
412 : }
413 0 : }
414 0 : }
415 0 : }
416 : else
417 : {
418 0 : std::string const severity_field(section_name + "::severity");
419 0 : if(!config->is_defined(severity_field))
420 : {
421 : throw invalid_severity("severity level must be defined for non-system severity entries. \""
422 0 : + section_name
423 0 : + "\" was not found.");
424 : }
425 0 : long const level(config->get_long(severity_field));
426 0 : if(level < 0 || level > 255)
427 : {
428 : throw invalid_severity("severity level must be between 0 and 255, "
429 0 : + std::to_string(level)
430 0 : + " is not valid.");
431 : }
432 :
433 0 : sev = get_severity(static_cast<severity_t>(level));
434 0 : if(sev != nullptr)
435 : {
436 : throw duplicate_error("there is another severity with level "
437 0 : + std::to_string(level)
438 0 : + ", try using aliases=... instead.");
439 : }
440 :
441 0 : sev = std::make_shared<severity>(static_cast<severity_t>(level), section_name);
442 :
443 0 : std::string const aliases_field(section_name + "::aliases");
444 0 : if(config->is_defined(aliases_field))
445 : {
446 0 : std::string const aliases(config->get_string(aliases_field));
447 0 : advgetopt::string_list_t names;
448 0 : advgetopt::split_string(aliases, names, {","});
449 0 : for(auto n : names)
450 : {
451 0 : sev->add_alias(n);
452 0 : }
453 0 : }
454 :
455 0 : add_severity(sev);
456 0 : }
457 :
458 0 : std::string const description_field(section_name + "::description");
459 0 : if(config->is_defined(description_field))
460 : {
461 0 : sev->set_description(config->get_string(description_field));
462 : }
463 :
464 0 : std::string const styles_field(section_name + "::styles");
465 0 : if(config->is_defined(styles_field))
466 : {
467 0 : sev->set_styles(config->get_string(styles_field));
468 : }
469 :
470 0 : std::string const default_field(section_name + "::default");
471 0 : if(config->is_defined(default_field))
472 : {
473 0 : if(advgetopt::is_true(config->get_string(default_field)))
474 : {
475 0 : l->set_default_severity(sev);
476 : }
477 : }
478 0 : }
479 : }
480 120 : }
481 :
482 :
483 :
484 : }
485 : // no name namespace
486 :
487 :
488 :
489 :
490 35 : severity::severity(severity_t sev, std::string const & name, bool system)
491 35 : : f_severity(sev)
492 175 : , f_names(string_vector_t({name}))
493 35 : , f_system(system)
494 : {
495 35 : if(sev < severity_t::SEVERITY_MIN || sev > severity_t::SEVERITY_MAX)
496 : {
497 : throw invalid_severity("the severity level cannot be "
498 4 : + std::to_string(static_cast<int>(sev))
499 8 : + ". The possible range is ["
500 8 : + std::to_string(static_cast<int>(severity_t::SEVERITY_MIN))
501 8 : + ".."
502 8 : + std::to_string(static_cast<int>(severity_t::SEVERITY_MAX))
503 6 : + "].");
504 : }
505 74 : }
506 :
507 :
508 : /** \brief Get the severity level.
509 : *
510 : * A severity object has a level from 0 to 255. This function returns that
511 : * level.
512 : *
513 : * Since a severity can have an alias, multiple severity objects can
514 : * have the exact same severity level.
515 : *
516 : * \return The severity level of this severity object.
517 : */
518 157 : severity_t severity::get_severity() const
519 : {
520 157 : return f_severity;
521 : }
522 :
523 :
524 : /** \brief Check whether this severity is a system defined severity.
525 : *
526 : * The snaplogger let you add severity levels at runtime or within
527 : * configuration files. This function lets you know whether that
528 : * severity was added by your software or was internally defined
529 : * by the library.
530 : *
531 : * A system severity cannot be deleted.
532 : *
533 : * \return true if the severity is considered a system severity.
534 : */
535 78 : bool severity::is_system() const
536 : {
537 78 : return f_system;
538 : }
539 :
540 :
541 : /** \brief Mark the severity as registered with the logger.
542 : *
543 : * \internal
544 : *
545 : * This function is considered internal. It is called by the private logger
546 : * implementation at the time the severity is added to the system.
547 : *
548 : * You should never have to call this function directly. You can use the
549 : * is_registered() function to check whether the severity was already
550 : * added or not.
551 : *
552 : * \sa private_logger::add_severity()
553 : */
554 29 : void severity::mark_as_registered()
555 : {
556 29 : f_registered = true;
557 29 : }
558 :
559 :
560 19 : bool severity::is_registered() const
561 : {
562 19 : return f_registered;
563 : }
564 :
565 :
566 77 : std::string severity::get_name() const
567 : {
568 77 : return f_names[0];
569 : }
570 :
571 :
572 : /** \brief All the aliases must be added before registering a severity.
573 : *
574 : * This function adds another alias to the severity.
575 : *
576 : * \exception duplicate_error
577 : * The function raises the duplicate error whenever the alias being added
578 : * is one that already exists in the list of this severity aliases.
579 : * It will not warn about adding the same alias to another severity as
580 : * long as that other severity is not a system defined severity.
581 : */
582 21 : void severity::add_alias(std::string const & name)
583 : {
584 21 : auto it(std::find(f_names.begin(), f_names.end(), name));
585 21 : if(it != f_names.end())
586 : {
587 : throw duplicate_error(
588 : "severity \""
589 4 : + f_names[0]
590 8 : + "\" already has an alias \""
591 8 : + name
592 6 : + "\".");
593 : }
594 :
595 19 : f_names.push_back(name);
596 :
597 : // TODO: we may be able to add a test here, but we allow
598 : // adding new aliases to system severity
599 :
600 19 : if(is_registered())
601 : {
602 2 : private_logger::pointer_t l(get_private_logger());
603 3 : l->add_alias(l->get_severity(f_severity), name);
604 2 : }
605 18 : }
606 :
607 :
608 65 : string_vector_t severity::get_all_names() const
609 : {
610 65 : return f_names;
611 : }
612 :
613 :
614 28 : void severity::set_description(std::string const & description)
615 : {
616 28 : f_description = description;
617 28 : }
618 :
619 :
620 45 : std::string severity::get_description() const
621 : {
622 45 : if(f_description.empty())
623 : {
624 2 : return get_name();
625 : }
626 43 : return f_description;
627 : }
628 :
629 :
630 18 : void severity::set_styles(std::string const & styles)
631 : {
632 18 : f_styles = styles;
633 18 : }
634 :
635 :
636 18 : std::string severity::get_styles() const
637 : {
638 18 : return f_styles;
639 : }
640 :
641 :
642 :
643 :
644 :
645 33 : void add_severity(severity::pointer_t sev)
646 : {
647 33 : auto_add_severities();
648 45 : get_private_logger()->add_severity(sev);
649 29 : }
650 :
651 :
652 40 : severity::pointer_t get_severity(std::string const & name)
653 : {
654 40 : auto_add_severities();
655 80 : return get_private_logger()->get_severity(name);
656 : }
657 :
658 :
659 2 : severity::pointer_t get_severity(message const & msg, std::string const & name)
660 : {
661 2 : auto_add_severities();
662 4 : return get_private_logger(msg)->get_severity(name);
663 : }
664 :
665 :
666 28 : severity::pointer_t get_severity(severity_t sev)
667 : {
668 28 : auto_add_severities();
669 56 : return get_private_logger()->get_severity(sev);
670 : }
671 :
672 :
673 17 : severity::pointer_t get_severity(message const & msg, severity_t sev)
674 : {
675 17 : auto_add_severities();
676 34 : return get_private_logger(msg)->get_severity(sev);
677 : }
678 :
679 :
680 2 : severity_by_name_t get_severities_by_name()
681 : {
682 4 : return get_private_logger()->get_severities_by_name();
683 : }
684 :
685 :
686 1 : severity_by_severity_t get_severities_by_severity()
687 : {
688 2 : return get_private_logger()->get_severities_by_severity();
689 : }
690 :
691 :
692 :
693 : } // snaplogger namespace
694 :
695 :
696 : #if SNAPDEV_CHECK_GCC_VERSION(7, 5, 0)
697 2 : snaplogger::severity::pointer_t operator ""_sev (char const * name, unsigned long size)
698 : {
699 8 : return snaplogger::get_severity(std::string(name, size));
700 : }
701 : #endif
702 :
703 :
704 : // vim: ts=4 sw=4 et
|