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 278580 : logger::pointer_t logger::get_instance()
94 : {
95 557160 : guard g;
96 :
97 278580 : 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 557160 : 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 92586 : severity_t logger::get_lowest_severity() const
399 : {
400 185172 : guard g;
401 :
402 92586 : 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 92585 : 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 310 : void logger::severity_changed(severity_t severity_level)
436 : {
437 620 : guard g;
438 :
439 310 : if(severity_level < f_lowest_severity)
440 : {
441 16 : f_lowest_severity = severity_level;
442 : }
443 294 : 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 284 : auto const min(std::min_element(f_appenders.begin(), f_appenders.end()));
450 284 : f_lowest_severity = (*min)->get_severity();
451 : }
452 310 : }
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 : bool logger::is_asynchronous() const
517 : {
518 0 : guard g;
519 :
520 0 : return f_asynchronous;
521 : }
522 :
523 :
524 15 : void logger::set_asynchronous(bool status)
525 : {
526 15 : status = status != false;
527 :
528 15 : bool do_delete(false);
529 : {
530 30 : guard g;
531 :
532 15 : if(f_asynchronous != status)
533 : {
534 2 : f_asynchronous = status;
535 2 : if(!f_asynchronous)
536 : {
537 1 : do_delete = true;
538 : }
539 : }
540 : }
541 :
542 15 : if(do_delete)
543 : {
544 1 : private_logger * l(dynamic_cast<private_logger *>(this));
545 1 : l->delete_thread();
546 : }
547 15 : }
548 :
549 :
550 92585 : void logger::log_message(message const & msg)
551 : {
552 92585 : if(const_cast<message &>(msg).tellp() != 0)
553 : {
554 92585 : bool asynchronous(false);
555 : {
556 185170 : guard g;
557 :
558 92585 : if(f_asynchronous)
559 : {
560 2 : message::pointer_t m(std::make_shared<message>(msg, msg));
561 1 : private_logger * l(dynamic_cast<private_logger *>(this));
562 1 : l->send_message_to_thread(m);
563 1 : asynchronous = true;
564 : }
565 : }
566 :
567 92585 : if(!asynchronous)
568 : {
569 92584 : process_message(msg);
570 : }
571 : }
572 :
573 185158 : if(f_fatal_severity != severity_t::SEVERITY_OFF
574 92579 : && msg.get_severity() >= f_fatal_severity)
575 : {
576 0 : call_fatal_error_callback();
577 0 : throw fatal_error("A fatal error occurred.");
578 : }
579 92579 : }
580 :
581 :
582 92585 : void logger::process_message(message const & msg)
583 : {
584 185168 : appender::vector_t appenders;
585 :
586 : {
587 185168 : guard g;
588 :
589 92585 : bool include(f_components_to_include.empty());
590 92585 : component::set_t const & components(msg.get_components());
591 92585 : if(components.empty())
592 : {
593 92579 : if(f_components_to_ignore.find(f_normal_component) != f_components_to_ignore.end())
594 : {
595 0 : return;
596 : }
597 92579 : if(!include)
598 : {
599 0 : if(f_components_to_include.find(f_normal_component) != f_components_to_include.end())
600 : {
601 0 : include = true;
602 : }
603 : }
604 : }
605 : else
606 : {
607 12 : for(auto c : components)
608 : {
609 8 : if(f_components_to_ignore.find(c) != f_components_to_ignore.end())
610 : {
611 2 : return;
612 : }
613 6 : if(!include)
614 : {
615 0 : if(f_components_to_include.find(c) != f_components_to_include.end())
616 : {
617 0 : include = true;
618 : }
619 : }
620 : }
621 : }
622 92583 : if(!include)
623 : {
624 0 : return;
625 : }
626 :
627 92583 : if(f_appenders.empty())
628 : {
629 0 : if(isatty(fileno(stdout)))
630 : {
631 0 : add_console_appender();
632 : }
633 : else
634 : {
635 0 : add_syslog_appender(std::string());
636 : }
637 : }
638 :
639 92583 : ++f_severity_stats[static_cast<std::size_t>(msg.get_severity())];
640 :
641 92583 : appenders = f_appenders;
642 : }
643 :
644 185220 : for(auto a : appenders)
645 : {
646 92637 : a->send_message(msg);
647 : }
648 : }
649 :
650 :
651 0 : void logger::set_fatal_error_callback(std::function<void(void)> & f)
652 : {
653 0 : f_fatal_error_callback = f;
654 0 : }
655 :
656 :
657 0 : void logger::call_fatal_error_callback()
658 : {
659 0 : if(f_fatal_error_callback != nullptr)
660 : {
661 0 : f_fatal_error_callback();
662 : }
663 0 : }
664 :
665 :
666 : /** \brief Return statistics about log severities.
667 : *
668 : * This function returns the statistics counting each message sent per
669 : * severity.
670 : *
671 : * If you enabled the asynchronous functionality of the snaplogger,
672 : * then this statistics may not reflect the current state as the
673 : * logger thread may still not have processed all the messages.
674 : *
675 : * \note
676 : * The severity_stats_t type is a vector that includes all possible
677 : * severity levels (0 to 255), including severity levels that are not
678 : * currently declared. It is done that way so the access is as fast
679 : * as possible when we want to increment one of the stats. Using a
680 : * map would have a much greater impact on the process_message()
681 : * function.
682 : *
683 : * \return a copy of the severity statistics at the time of the call.
684 : */
685 0 : severity_stats_t logger::get_severity_stats() const
686 : {
687 0 : guard g;
688 :
689 0 : return f_severity_stats;
690 : }
691 :
692 :
693 0 : bool is_configured()
694 : {
695 0 : guard g;
696 :
697 0 : if(g_instance == nullptr)
698 : {
699 0 : return false;
700 : }
701 :
702 0 : return (*g_instance)->is_configured();
703 : }
704 :
705 :
706 0 : bool has_appender(std::string const & type)
707 : {
708 0 : guard g;
709 :
710 0 : if(g_instance == nullptr)
711 : {
712 0 : return false;
713 : }
714 :
715 0 : return (*g_instance)->has_appender(type);
716 : }
717 :
718 :
719 0 : void reopen()
720 : {
721 0 : guard g;
722 :
723 0 : if(g_instance == nullptr)
724 : {
725 0 : return;
726 : }
727 :
728 0 : (*g_instance)->reopen();
729 : }
730 :
731 :
732 0 : bool configure_console(bool force)
733 : {
734 0 : bool result(!is_configured() || (force && !has_appender("console")));
735 0 : if(result)
736 : {
737 0 : logger::get_instance()->add_console_appender();
738 : }
739 :
740 0 : return result;
741 : }
742 :
743 :
744 0 : bool configure_syslog(std::string const & identity)
745 : {
746 0 : bool result(!is_configured());
747 0 : if(result)
748 : {
749 0 : logger::get_instance()->add_syslog_appender(identity);
750 : }
751 :
752 0 : return result;
753 : }
754 :
755 :
756 0 : bool configure_file(std::string const & filename)
757 : {
758 0 : bool result(!is_configured());
759 0 : if(result)
760 : {
761 0 : logger::get_instance()->add_file_appender(filename);
762 : }
763 :
764 0 : return result;
765 : }
766 :
767 :
768 0 : bool configure_config(std::string const & config_filename)
769 : {
770 0 : bool result(!is_configured());
771 0 : if(result)
772 : {
773 0 : logger::get_instance()->add_config(config_filename);
774 : }
775 :
776 0 : return result;
777 : }
778 :
779 :
780 :
781 :
782 6 : } // snaplogger namespace
783 : // vim: ts=4 sw=4 et
|