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 Appenders are used to append data to somewhere.
22 : *
23 : * This file declares the base appender class.
24 : */
25 :
26 : // self
27 : //
28 : #include "snaplogger/logger.h"
29 :
30 : #include "snaplogger/console_appender.h"
31 : #include "snaplogger/exception.h"
32 : #include "snaplogger/file_appender.h"
33 : #include "snaplogger/guard.h"
34 : #include "snaplogger/private_logger.h"
35 : #include "snaplogger/syslog_appender.h"
36 :
37 :
38 : // last include
39 : //
40 : #include <snapdev/poison.h>
41 :
42 :
43 :
44 : namespace snaplogger
45 : {
46 :
47 :
48 : namespace
49 : {
50 :
51 :
52 :
53 : bool g_first_instance = true;
54 : logger::pointer_t * g_instance = nullptr;
55 :
56 :
57 : struct auto_delete_logger
58 : {
59 2 : ~auto_delete_logger()
60 : {
61 2 : logger::pointer_t * ptr(nullptr);
62 : {
63 4 : guard g;
64 2 : swap(ptr, g_instance);
65 : }
66 2 : if(ptr != nullptr)
67 : {
68 2 : (*ptr)->shutdown();
69 2 : delete ptr;
70 : }
71 2 : }
72 : };
73 :
74 2 : auto_delete_logger g_logger_deleter = auto_delete_logger();
75 :
76 :
77 :
78 : }
79 : // no name namespace
80 :
81 :
82 :
83 2 : logger::logger()
84 : {
85 2 : }
86 :
87 :
88 2 : logger::~logger()
89 : {
90 2 : }
91 :
92 :
93 285380 : logger::pointer_t logger::get_instance()
94 : {
95 570760 : guard g;
96 :
97 285380 : if(g_instance == nullptr)
98 : {
99 2 : if(!g_first_instance)
100 : {
101 0 : throw duplicate_error("preventing an attempt of re-creating the snap logger.");
102 : }
103 :
104 2 : g_first_instance = false;
105 :
106 : // note that we create a `private_logger` object
107 : //
108 2 : g_instance = new logger::pointer_t();
109 2 : g_instance->reset(new private_logger());
110 : }
111 :
112 570760 : return *g_instance;
113 : }
114 :
115 :
116 : /** \brief Reset the logger to its startup state.
117 : *
118 : * This function resets the logger to non-asynchronous and no appenders.
119 : *
120 : * This is mainly used in our unit tests so we do not have to run the
121 : * tests one at a time. It should nearly never be useful in your environment
122 : * except if you do a fork() and wanted the child to have its own special
123 : * log environment.
124 : */
125 13 : void logger::reset()
126 : {
127 26 : guard g;
128 :
129 13 : set_asynchronous(false);
130 13 : f_appenders.clear();
131 13 : f_lowest_severity = severity_t::SEVERITY_OFF;
132 13 : }
133 :
134 :
135 0 : void logger::shutdown()
136 : {
137 0 : }
138 :
139 :
140 0 : bool logger::is_configured() const
141 : {
142 0 : guard g;
143 :
144 0 : return !f_appenders.empty();
145 : }
146 :
147 :
148 0 : bool logger::has_appender(std::string const & type) const
149 : {
150 0 : return std::find_if(
151 : f_appenders.begin()
152 : , f_appenders.end()
153 0 : , [&type](auto a)
154 0 : {
155 0 : return type == a->get_type();
156 0 : }) != f_appenders.end();
157 : }
158 :
159 :
160 2 : appender::pointer_t logger::get_appender(std::string const & name) const
161 : {
162 2 : auto it(std::find_if(
163 : f_appenders.begin()
164 : , f_appenders.end()
165 1 : , [&name](auto a)
166 1 : {
167 1 : return name == a->get_name();
168 3 : }));
169 2 : if(it == f_appenders.end())
170 : {
171 1 : return appender::pointer_t();
172 : }
173 :
174 1 : return *it;
175 : }
176 :
177 :
178 0 : void logger::set_config(advgetopt::getopt const & params)
179 : {
180 : // The asynchronous flag can cause problems unless the programmer
181 : // specifically planned for it so we do not allow it in configuration
182 : // file at the moment. Later we may have two flags. If both are true
183 : // then we will allow asynchronous logging.
184 : //
185 : //if(params.is_defined("asynchronous"))
186 : //{
187 : // set_asynchronous(params.get_string("asynchronous") == "true");
188 : //}
189 :
190 0 : auto const & sections(params.get_option(advgetopt::CONFIGURATION_SECTIONS));
191 0 : if(sections != nullptr)
192 : {
193 0 : size_t const max(sections->size());
194 0 : for(size_t idx(0); idx < max; ++idx)
195 : {
196 0 : std::string const section_name(sections->get_value(idx));
197 0 : std::string const section_type(section_name + "::type");
198 0 : std::string type;
199 0 : if(params.is_defined(section_type))
200 : {
201 0 : type = params.get_string(section_type);
202 : }
203 : else
204 : {
205 : // try with the name of the section if no type is defined
206 : //
207 0 : type = section_name;
208 : }
209 0 : if(!type.empty())
210 : {
211 0 : appender::pointer_t a(create_appender(type, section_name));
212 0 : if(a != nullptr)
213 : {
214 0 : add_appender(a);
215 : }
216 : // else -- this may be a section which does not represent an appender
217 : }
218 : }
219 : }
220 :
221 0 : guard g;
222 :
223 0 : for(auto a : f_appenders)
224 : {
225 0 : a->set_config(params);
226 : }
227 0 : }
228 :
229 :
230 0 : void logger::reopen()
231 : {
232 0 : guard g;
233 :
234 0 : for(auto a : f_appenders)
235 : {
236 0 : a->reopen();
237 : }
238 0 : }
239 :
240 :
241 20 : void logger::add_appender(appender::pointer_t a)
242 : {
243 40 : guard g;
244 :
245 20 : if(a->unique())
246 : {
247 0 : std::string const type(a->get_type());
248 0 : auto it(std::find_if(
249 : f_appenders.begin()
250 : , f_appenders.end()
251 0 : , [&type](auto app)
252 0 : {
253 0 : return type == app->get_type();
254 0 : }));
255 0 : if(it != f_appenders.end())
256 : {
257 : // the console is a pretty special type because it can't be
258 : // added twice but it may get added early because an error
259 : // occurs and forces initialization of the logger "too soon"
260 : //
261 0 : if(type == "console")
262 : {
263 0 : if(a->get_name() != "console"
264 0 : && (*it)->get_name() == "console")
265 : {
266 0 : (*it)->set_name(a->get_name());
267 : }
268 0 : return;
269 : }
270 0 : if(type == "syslog")
271 : {
272 0 : if(a->get_name() != "syslog"
273 0 : && (*it)->get_name() == "syslog")
274 : {
275 0 : (*it)->set_name(a->get_name());
276 : }
277 0 : return;
278 : }
279 : throw duplicate_error(
280 : "an appender of type \""
281 0 : + type
282 0 : + "\" can only be added once.");
283 : }
284 : }
285 :
286 20 : f_appenders.push_back(a);
287 :
288 20 : severity_changed(a->get_severity());
289 : }
290 :
291 :
292 0 : void logger::add_config(std::string const & config_filename)
293 : {
294 0 : advgetopt::options_environment opt_env;
295 :
296 0 : char const * configuration_files[] =
297 : {
298 0 : config_filename.c_str()
299 : , nullptr
300 0 : };
301 :
302 0 : opt_env.f_project_name = "snaplogger";
303 0 : opt_env.f_environment_variable_name = "SNAPLOGGER";
304 0 : opt_env.f_configuration_files = configuration_files;
305 0 : opt_env.f_environment_flags = advgetopt::GETOPT_ENVIRONMENT_FLAG_DYNAMIC_PARAMETERS;
306 :
307 0 : advgetopt::getopt opts(opt_env);
308 :
309 0 : opts.parse_configuration_files();
310 0 : opts.parse_environment_variable();
311 :
312 0 : set_config(opts);
313 0 : }
314 :
315 :
316 0 : appender::pointer_t logger::add_console_appender()
317 : {
318 0 : appender::pointer_t a(std::make_shared<console_appender>("console"));
319 :
320 0 : advgetopt::options_environment opt_env;
321 0 : opt_env.f_project_name = "snaplogger";
322 0 : opt_env.f_environment_flags = advgetopt::GETOPT_ENVIRONMENT_FLAG_AUTO_DONE;
323 0 : advgetopt::getopt opts(opt_env);
324 0 : a->set_config(opts);
325 :
326 0 : guard g;
327 :
328 0 : add_appender(a);
329 :
330 0 : return a;
331 : }
332 :
333 :
334 0 : appender::pointer_t logger::add_syslog_appender(std::string const & identity)
335 : {
336 0 : appender::pointer_t a(std::make_shared<syslog_appender>("syslog"));
337 :
338 0 : advgetopt::option options[] =
339 : {
340 : advgetopt::define_option(
341 : advgetopt::Name("syslog::identity")
342 : , advgetopt::Flags(advgetopt::command_flags<advgetopt::GETOPT_FLAG_REQUIRED>())
343 : ),
344 : advgetopt::end_options()
345 : };
346 :
347 0 : advgetopt::options_environment opt_env;
348 0 : opt_env.f_project_name = "snaplogger";
349 0 : opt_env.f_environment_flags = advgetopt::GETOPT_ENVIRONMENT_FLAG_AUTO_DONE;
350 0 : opt_env.f_options = options;
351 0 : advgetopt::getopt opts(opt_env);
352 0 : if(!identity.empty())
353 : {
354 0 : opts.get_option("syslog::identity")->set_value(0, identity);
355 : }
356 0 : a->set_config(opts);
357 :
358 0 : guard g;
359 :
360 0 : add_appender(a);
361 :
362 0 : return a;
363 : }
364 :
365 :
366 0 : appender::pointer_t logger::add_file_appender(std::string const & filename)
367 : {
368 0 : file_appender::pointer_t a(std::make_shared<file_appender>("file"));
369 :
370 0 : advgetopt::option options[] =
371 : {
372 : advgetopt::define_option(
373 : advgetopt::Name("file::filename")
374 : , advgetopt::Flags(advgetopt::command_flags<advgetopt::GETOPT_FLAG_REQUIRED>())
375 : ),
376 : advgetopt::end_options()
377 : };
378 :
379 0 : advgetopt::options_environment opt_env;
380 0 : opt_env.f_project_name = "snaplogger";
381 0 : opt_env.f_environment_flags = advgetopt::GETOPT_ENVIRONMENT_FLAG_AUTO_DONE;
382 0 : opt_env.f_options = options;
383 0 : advgetopt::getopt opts(opt_env);
384 0 : if(!filename.empty())
385 : {
386 0 : opts.get_option("file::filename")->set_value(0, filename);
387 : }
388 0 : a->set_config(opts);
389 :
390 0 : guard g;
391 :
392 0 : add_appender(a);
393 :
394 0 : return a;
395 : }
396 :
397 :
398 94852 : severity_t logger::get_lowest_severity() const
399 : {
400 189704 : guard g;
401 :
402 94852 : if(f_appenders.empty())
403 : {
404 : // we do not know the level yet, we do not have the appenders
405 : // yet... so accept anything at this point
406 : //
407 1 : return severity_t::SEVERITY_ALL;
408 : }
409 :
410 94851 : return f_lowest_severity;
411 : }
412 :
413 :
414 1 : void logger::set_severity(severity_t severity_level)
415 : {
416 2 : guard g;
417 :
418 1 : f_lowest_severity = severity_level;
419 6 : for(auto a : f_appenders)
420 : {
421 5 : a->set_severity(severity_level);
422 : }
423 1 : }
424 :
425 :
426 0 : void logger::reduce_severity(severity_t severity_level)
427 : {
428 0 : for(auto a : f_appenders)
429 : {
430 0 : a->reduce_severity(severity_level);
431 : }
432 0 : }
433 :
434 :
435 312 : void logger::severity_changed(severity_t severity_level)
436 : {
437 624 : guard g;
438 :
439 312 : if(severity_level < f_lowest_severity)
440 : {
441 16 : f_lowest_severity = severity_level;
442 : }
443 296 : else if(severity_level > f_lowest_severity)
444 : {
445 : // if the severity level grew we have to search for the new lowest;
446 : // this happens very rarely while running, it's likely to happen
447 : // up to once per appender on initialization.
448 : //
449 286 : auto const min(std::min_element(f_appenders.begin(), f_appenders.end()));
450 286 : f_lowest_severity = (*min)->get_severity();
451 : }
452 312 : }
453 :
454 :
455 0 : severity_t logger::get_default_severity() const
456 : {
457 0 : private_logger const * l(dynamic_cast<private_logger const *>(this));
458 0 : severity::pointer_t sev(l->get_default_severity());
459 0 : return sev == nullptr ? severity_t::SEVERITY_DEFAULT : sev->get_severity();
460 : }
461 :
462 :
463 0 : bool logger::set_default_severity(severity_t severity_level)
464 : {
465 0 : private_logger * l(dynamic_cast<private_logger *>(this));
466 0 : if(severity_level == severity_t::SEVERITY_ALL)
467 : {
468 : // reset to default
469 0 : l->set_default_severity(severity::pointer_t());
470 : }
471 : else
472 : {
473 0 : severity::pointer_t sev(l->get_severity(severity_level));
474 0 : if(sev == nullptr)
475 : {
476 0 : return false;
477 : }
478 0 : l->set_default_severity(sev);
479 : }
480 0 : return true;
481 : }
482 :
483 :
484 0 : void logger::add_component_to_include(component::pointer_t comp)
485 : {
486 0 : guard g;
487 :
488 0 : f_components_to_include.insert(comp);
489 0 : }
490 :
491 :
492 0 : void logger::remove_component_to_include(component::pointer_t comp)
493 : {
494 0 : guard g;
495 :
496 0 : f_components_to_include.erase(comp);
497 0 : }
498 :
499 :
500 1 : void logger::add_component_to_ignore(component::pointer_t comp)
501 : {
502 2 : guard g;
503 :
504 1 : f_components_to_ignore.insert(comp);
505 1 : }
506 :
507 :
508 0 : void logger::remove_component_to_ignore(component::pointer_t comp)
509 : {
510 0 : guard g;
511 :
512 0 : f_components_to_ignore.erase(comp);
513 0 : }
514 :
515 :
516 0 : void logger::add_default_field(std::string const & name, std::string const & value)
517 : {
518 0 : if(!name.empty())
519 : {
520 0 : if(name[0] == '_')
521 : {
522 : throw invalid_parameter(
523 : "field name \""
524 0 : + name
525 0 : + "\" is a system name (whether reserved or already defined) and as such is read-only."
526 0 : " Do not start your field names with an underscore (_).");
527 : }
528 0 : if(name == "id")
529 : {
530 : throw invalid_parameter(
531 : "field name \"id\" is automatically set by the message class,"
532 0 : " it cannot be set as a default field.");
533 : }
534 :
535 0 : guard g;
536 :
537 0 : f_default_fields[name] = value;
538 : }
539 0 : }
540 :
541 :
542 0 : std::string logger::get_default_field(std::string const & name) const
543 : {
544 0 : guard g;
545 :
546 0 : auto it(f_default_fields.find(name));
547 0 : if(it != f_default_fields.end())
548 : {
549 0 : return it->second;
550 : }
551 0 : return std::string();
552 : }
553 :
554 :
555 94852 : field_map_t logger::get_default_fields() const
556 : {
557 189704 : guard g;
558 :
559 189704 : return f_default_fields;
560 : }
561 :
562 :
563 0 : void logger::remove_default_field(std::string const & name)
564 : {
565 0 : guard g;
566 :
567 0 : auto it(f_default_fields.find(name));
568 0 : if(it != f_default_fields.end())
569 : {
570 0 : f_default_fields.erase(it);
571 : }
572 0 : }
573 :
574 :
575 0 : bool logger::is_asynchronous() const
576 : {
577 0 : guard g;
578 :
579 0 : return f_asynchronous;
580 : }
581 :
582 :
583 15 : void logger::set_asynchronous(bool status)
584 : {
585 15 : status = status != false;
586 :
587 15 : bool do_delete(false);
588 : {
589 30 : guard g;
590 :
591 15 : if(f_asynchronous != status)
592 : {
593 2 : f_asynchronous = status;
594 2 : if(!f_asynchronous)
595 : {
596 1 : do_delete = true;
597 : }
598 : }
599 : }
600 :
601 15 : if(do_delete)
602 : {
603 1 : private_logger * l(dynamic_cast<private_logger *>(this));
604 1 : l->delete_thread();
605 : }
606 15 : }
607 :
608 :
609 94851 : void logger::log_message(message const & msg)
610 : {
611 94851 : if(const_cast<message &>(msg).tellp() != 0)
612 : {
613 94851 : bool asynchronous(false);
614 : {
615 189702 : guard g;
616 :
617 94851 : if(f_asynchronous)
618 : {
619 2 : message::pointer_t m(std::make_shared<message>(msg, msg));
620 1 : private_logger * l(dynamic_cast<private_logger *>(this));
621 1 : l->send_message_to_thread(m);
622 1 : asynchronous = true;
623 : }
624 : }
625 :
626 94851 : if(!asynchronous)
627 : {
628 94850 : process_message(msg);
629 : }
630 : }
631 :
632 189690 : if(f_fatal_severity != severity_t::SEVERITY_OFF
633 94845 : && msg.get_severity() >= f_fatal_severity)
634 : {
635 0 : call_fatal_error_callback();
636 0 : throw fatal_error("A fatal error occurred.");
637 : }
638 94845 : }
639 :
640 :
641 94851 : void logger::process_message(message const & msg)
642 : {
643 189700 : appender::vector_t appenders;
644 :
645 : {
646 189700 : guard g;
647 :
648 94851 : bool include(f_components_to_include.empty());
649 94851 : component::set_t const & components(msg.get_components());
650 94851 : if(components.empty())
651 : {
652 94845 : if(f_components_to_ignore.find(f_normal_component) != f_components_to_ignore.end())
653 : {
654 0 : return;
655 : }
656 94845 : if(!include)
657 : {
658 0 : if(f_components_to_include.find(f_normal_component) != f_components_to_include.end())
659 : {
660 0 : include = true;
661 : }
662 : }
663 : }
664 : else
665 : {
666 12 : for(auto c : components)
667 : {
668 8 : if(f_components_to_ignore.find(c) != f_components_to_ignore.end())
669 : {
670 2 : return;
671 : }
672 6 : if(!include)
673 : {
674 0 : if(f_components_to_include.find(c) != f_components_to_include.end())
675 : {
676 0 : include = true;
677 : }
678 : }
679 : }
680 : }
681 94849 : if(!include)
682 : {
683 0 : return;
684 : }
685 :
686 94849 : if(f_appenders.empty())
687 : {
688 0 : if(isatty(fileno(stdout)))
689 : {
690 0 : add_console_appender();
691 : }
692 : else
693 : {
694 0 : add_syslog_appender(std::string());
695 : }
696 : }
697 :
698 94849 : ++f_severity_stats[static_cast<std::size_t>(msg.get_severity())];
699 :
700 94849 : appenders = f_appenders;
701 : }
702 :
703 189752 : for(auto a : appenders)
704 : {
705 94903 : a->send_message(msg);
706 : }
707 : }
708 :
709 :
710 0 : void logger::set_fatal_error_callback(std::function<void(void)> & f)
711 : {
712 0 : f_fatal_error_callback = f;
713 0 : }
714 :
715 :
716 0 : void logger::call_fatal_error_callback()
717 : {
718 0 : if(f_fatal_error_callback != nullptr)
719 : {
720 0 : f_fatal_error_callback();
721 : }
722 0 : }
723 :
724 :
725 : /** \brief Return statistics about log severities.
726 : *
727 : * This function returns the statistics counting each message sent per
728 : * severity.
729 : *
730 : * If you enabled the asynchronous functionality of the snaplogger,
731 : * then this statistics may not reflect the current state as the
732 : * logger thread may still not have processed all the messages.
733 : *
734 : * \note
735 : * The severity_stats_t type is a vector that includes all possible
736 : * severity levels (0 to 255), including severity levels that are not
737 : * currently declared. It is done that way so the access is as fast
738 : * as possible when we want to increment one of the stats. Using a
739 : * map would have a much greater impact on the process_message()
740 : * function.
741 : *
742 : * \return a copy of the severity statistics at the time of the call.
743 : */
744 0 : severity_stats_t logger::get_severity_stats() const
745 : {
746 0 : guard g;
747 :
748 0 : return f_severity_stats;
749 : }
750 :
751 :
752 0 : bool is_configured()
753 : {
754 0 : guard g;
755 :
756 0 : if(g_instance == nullptr)
757 : {
758 0 : return false;
759 : }
760 :
761 0 : return (*g_instance)->is_configured();
762 : }
763 :
764 :
765 0 : bool has_appender(std::string const & type)
766 : {
767 0 : guard g;
768 :
769 0 : if(g_instance == nullptr)
770 : {
771 0 : return false;
772 : }
773 :
774 0 : return (*g_instance)->has_appender(type);
775 : }
776 :
777 :
778 0 : void reopen()
779 : {
780 0 : guard g;
781 :
782 0 : if(g_instance == nullptr)
783 : {
784 0 : return;
785 : }
786 :
787 0 : (*g_instance)->reopen();
788 : }
789 :
790 :
791 0 : bool configure_console(bool force)
792 : {
793 0 : bool result(!is_configured() || (force && !has_appender("console")));
794 0 : if(result)
795 : {
796 0 : logger::get_instance()->add_console_appender();
797 : }
798 :
799 0 : return result;
800 : }
801 :
802 :
803 0 : bool configure_syslog(std::string const & identity)
804 : {
805 0 : bool result(!is_configured());
806 0 : if(result)
807 : {
808 0 : logger::get_instance()->add_syslog_appender(identity);
809 : }
810 :
811 0 : return result;
812 : }
813 :
814 :
815 0 : bool configure_file(std::string const & filename)
816 : {
817 0 : bool result(!is_configured());
818 0 : if(result)
819 : {
820 0 : logger::get_instance()->add_file_appender(filename);
821 : }
822 :
823 0 : return result;
824 : }
825 :
826 :
827 0 : bool configure_config(std::string const & config_filename)
828 : {
829 0 : bool result(!is_configured());
830 0 : if(result)
831 : {
832 0 : logger::get_instance()->add_config(config_filename);
833 : }
834 :
835 0 : return result;
836 : }
837 :
838 :
839 :
840 :
841 6 : } // snaplogger namespace
842 : // vim: ts=4 sw=4 et
|