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 267857 : logger::pointer_t logger::get_instance()
94 : {
95 535714 : guard g;
96 :
97 267857 : 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 535714 : 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 : 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 18 : void logger::add_appender(appender::pointer_t a)
242 : {
243 36 : guard g;
244 :
245 18 : if(a->unique())
246 : {
247 0 : std::string const type(a->get_type());
248 : 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 18 : f_appenders.push_back(a);
287 :
288 18 : 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 : advgetopt::getopt opts(opt_env);
323 0 : a->set_config(opts);
324 :
325 0 : guard g;
326 :
327 0 : add_appender(a);
328 :
329 0 : return a;
330 : }
331 :
332 :
333 0 : appender::pointer_t logger::add_syslog_appender(std::string const & identity)
334 : {
335 0 : appender::pointer_t a(std::make_shared<syslog_appender>("syslog"));
336 :
337 0 : advgetopt::option options[] =
338 : {
339 : advgetopt::define_option(
340 : advgetopt::Name("syslog::identity")
341 : , advgetopt::Flags(advgetopt::command_flags<advgetopt::GETOPT_FLAG_REQUIRED>())
342 : ),
343 : advgetopt::end_options()
344 : };
345 :
346 0 : advgetopt::options_environment opt_env;
347 0 : opt_env.f_project_name = "snaplogger";
348 0 : opt_env.f_options = options;
349 0 : advgetopt::getopt opts(opt_env);
350 0 : if(!identity.empty())
351 : {
352 0 : opts.get_option("syslog::identity")->set_value(0, identity);
353 : }
354 0 : a->set_config(opts);
355 :
356 0 : guard g;
357 :
358 0 : add_appender(a);
359 :
360 0 : return a;
361 : }
362 :
363 :
364 0 : appender::pointer_t logger::add_file_appender(std::string const & filename)
365 : {
366 0 : file_appender::pointer_t a(std::make_shared<file_appender>("file"));
367 :
368 0 : advgetopt::option options[] =
369 : {
370 : advgetopt::define_option(
371 : advgetopt::Name("file::filename")
372 : , advgetopt::Flags(advgetopt::command_flags<advgetopt::GETOPT_FLAG_REQUIRED>())
373 : ),
374 : advgetopt::end_options()
375 : };
376 :
377 0 : advgetopt::options_environment opt_env;
378 0 : opt_env.f_project_name = "snaplogger";
379 0 : opt_env.f_options = options;
380 0 : advgetopt::getopt opts(opt_env);
381 0 : if(!filename.empty())
382 : {
383 0 : opts.get_option("file::filename")->set_value(0, filename);
384 : }
385 0 : a->set_config(opts);
386 :
387 0 : guard g;
388 :
389 0 : add_appender(a);
390 :
391 0 : return a;
392 : }
393 :
394 :
395 89028 : severity_t logger::get_lowest_severity() const
396 : {
397 178056 : guard g;
398 :
399 89028 : if(f_appenders.empty())
400 : {
401 : // we do not know the level yet, we do not have the appenders
402 : // yet... so accept anything at this point
403 : //
404 1 : return severity_t::SEVERITY_ALL;
405 : }
406 :
407 89027 : return f_lowest_severity;
408 : }
409 :
410 :
411 0 : void logger::set_severity(severity_t severity_level)
412 : {
413 0 : guard g;
414 :
415 0 : f_lowest_severity = severity_level;
416 0 : for(auto a : f_appenders)
417 : {
418 0 : a->set_severity(severity_level);
419 : }
420 0 : }
421 :
422 :
423 0 : void logger::reduce_severity(severity_t severity_level)
424 : {
425 0 : for(auto a : f_appenders)
426 : {
427 0 : a->reduce_severity(severity_level);
428 : }
429 0 : }
430 :
431 :
432 299 : void logger::severity_changed(severity_t severity_level)
433 : {
434 598 : guard g;
435 :
436 299 : if(severity_level < f_lowest_severity)
437 : {
438 16 : f_lowest_severity = severity_level;
439 : }
440 283 : else if(severity_level > f_lowest_severity)
441 : {
442 : // if the severity level grew we have to search for the new lowest;
443 : // this happens very rarely while running, it's likely to happen
444 : // up to once per appender on initialization.
445 : //
446 279 : auto const min(std::min_element(f_appenders.begin(), f_appenders.end()));
447 279 : f_lowest_severity = (*min)->get_severity();
448 : }
449 299 : }
450 :
451 :
452 0 : severity_t logger::get_default_severity() const
453 : {
454 0 : private_logger const * l(dynamic_cast<private_logger const *>(this));
455 0 : severity::pointer_t sev(l->get_default_severity());
456 0 : return sev == nullptr ? severity_t::SEVERITY_DEFAULT : sev->get_severity();
457 : }
458 :
459 :
460 0 : bool logger::set_default_severity(severity_t severity_level)
461 : {
462 0 : private_logger * l(dynamic_cast<private_logger *>(this));
463 0 : if(severity_level == severity_t::SEVERITY_ALL)
464 : {
465 : // reset to default
466 0 : l->set_default_severity(severity::pointer_t());
467 : }
468 : else
469 : {
470 0 : severity::pointer_t sev(l->get_severity(severity_level));
471 0 : if(sev == nullptr)
472 : {
473 0 : return false;
474 : }
475 0 : l->set_default_severity(sev);
476 : }
477 0 : return true;
478 : }
479 :
480 :
481 0 : void logger::add_component_to_include(component::pointer_t comp)
482 : {
483 0 : guard g;
484 :
485 0 : f_components_to_include.insert(comp);
486 0 : }
487 :
488 :
489 0 : void logger::remove_component_to_include(component::pointer_t comp)
490 : {
491 0 : guard g;
492 :
493 0 : f_components_to_include.erase(comp);
494 0 : }
495 :
496 :
497 1 : void logger::add_component_to_ignore(component::pointer_t comp)
498 : {
499 2 : guard g;
500 :
501 1 : f_components_to_ignore.insert(comp);
502 1 : }
503 :
504 :
505 0 : void logger::remove_component_to_ignore(component::pointer_t comp)
506 : {
507 0 : guard g;
508 :
509 0 : f_components_to_ignore.erase(comp);
510 0 : }
511 :
512 :
513 0 : bool logger::is_asynchronous() const
514 : {
515 0 : guard g;
516 :
517 0 : return f_asynchronous;
518 : }
519 :
520 :
521 15 : void logger::set_asynchronous(bool status)
522 : {
523 15 : status = status != false;
524 :
525 15 : bool do_delete(false);
526 : {
527 30 : guard g;
528 :
529 15 : if(f_asynchronous != status)
530 : {
531 2 : f_asynchronous = status;
532 2 : if(!f_asynchronous)
533 : {
534 1 : do_delete = true;
535 : }
536 : }
537 : }
538 :
539 15 : if(do_delete)
540 : {
541 1 : private_logger * l(dynamic_cast<private_logger *>(this));
542 1 : l->delete_thread();
543 : }
544 15 : }
545 :
546 :
547 89027 : void logger::log_message(message const & msg)
548 : {
549 89027 : if(const_cast<message &>(msg).tellp() != 0)
550 : {
551 89027 : bool asynchronous(false);
552 : {
553 178054 : guard g;
554 :
555 89027 : if(f_asynchronous)
556 : {
557 2 : message::pointer_t m(std::make_shared<message>(msg, msg));
558 1 : private_logger * l(dynamic_cast<private_logger *>(this));
559 1 : l->send_message_to_thread(m);
560 1 : asynchronous = true;
561 : }
562 : }
563 :
564 89027 : if(!asynchronous)
565 : {
566 89026 : process_message(msg);
567 : }
568 : }
569 :
570 178044 : if(f_fatal_severity != severity_t::SEVERITY_OFF
571 89022 : && msg.get_severity() >= f_fatal_severity)
572 : {
573 0 : call_fatal_error_callback();
574 0 : throw fatal_error("A fatal error occurred.");
575 : }
576 89022 : }
577 :
578 :
579 89027 : void logger::process_message(message const & msg)
580 : {
581 178052 : appender::vector_t appenders;
582 :
583 : {
584 178052 : guard g;
585 :
586 89027 : bool include(f_components_to_include.empty());
587 89027 : component::set_t const & components(msg.get_components());
588 89027 : if(components.empty())
589 : {
590 89021 : if(f_components_to_ignore.find(f_normal_component) != f_components_to_ignore.end())
591 : {
592 0 : return;
593 : }
594 89021 : if(!include)
595 : {
596 0 : if(f_components_to_include.find(f_normal_component) != f_components_to_include.end())
597 : {
598 0 : include = true;
599 : }
600 : }
601 : }
602 : else
603 : {
604 12 : for(auto c : components)
605 : {
606 8 : if(f_components_to_ignore.find(c) != f_components_to_ignore.end())
607 : {
608 2 : return;
609 : }
610 6 : if(!include)
611 : {
612 0 : if(f_components_to_include.find(c) != f_components_to_include.end())
613 : {
614 0 : include = true;
615 : }
616 : }
617 : }
618 : }
619 89025 : if(!include)
620 : {
621 0 : return;
622 : }
623 :
624 89025 : if(f_appenders.empty())
625 : {
626 0 : if(isatty(fileno(stdout)))
627 : {
628 0 : add_console_appender();
629 : }
630 : else
631 : {
632 0 : add_syslog_appender(std::string());
633 : }
634 : }
635 :
636 89025 : ++f_severity_stats[static_cast<std::size_t>(msg.get_severity())];
637 :
638 89025 : appenders = f_appenders;
639 : }
640 :
641 178063 : for(auto a : appenders)
642 : {
643 89038 : a->send_message(msg);
644 : }
645 : }
646 :
647 :
648 0 : void logger::set_fatal_error_callback(std::function<void(void)> & f)
649 : {
650 0 : f_fatal_error_callback = f;
651 0 : }
652 :
653 :
654 0 : void logger::call_fatal_error_callback()
655 : {
656 0 : if(f_fatal_error_callback != nullptr)
657 : {
658 0 : f_fatal_error_callback();
659 : }
660 0 : }
661 :
662 :
663 : /** \brief Return statistics about log severities.
664 : *
665 : * This function returns the statistics counting each message sent per
666 : * severity.
667 : *
668 : * If you enabled the asynchronous functionality of the snaplogger,
669 : * then this statistics may not reflect the current state as the
670 : * logger thread may still not have processed all the messages.
671 : *
672 : * \note
673 : * The severity_stats_t type is a vector that includes all possible
674 : * severity levels (0 to 255), including severity levels that are not
675 : * currently declared. It is done that way so the access is as fast
676 : * as possible when we want to increment one of the stats. Using a
677 : * map would have a much greater impact on the process_message()
678 : * function.
679 : *
680 : * \return a copy of the severity statistics at the time of the call.
681 : */
682 0 : severity_stats_t logger::get_severity_stats() const
683 : {
684 0 : guard g;
685 :
686 0 : return f_severity_stats;
687 : }
688 :
689 :
690 0 : bool is_configured()
691 : {
692 0 : guard g;
693 :
694 0 : if(g_instance == nullptr)
695 : {
696 0 : return false;
697 : }
698 :
699 0 : return (*g_instance)->is_configured();
700 : }
701 :
702 :
703 0 : bool has_appender(std::string const & type)
704 : {
705 0 : guard g;
706 :
707 0 : if(g_instance == nullptr)
708 : {
709 0 : return false;
710 : }
711 :
712 0 : return (*g_instance)->has_appender(type);
713 : }
714 :
715 :
716 0 : void reopen()
717 : {
718 0 : guard g;
719 :
720 0 : if(g_instance == nullptr)
721 : {
722 0 : return;
723 : }
724 :
725 0 : (*g_instance)->reopen();
726 : }
727 :
728 :
729 0 : bool configure_console(bool force)
730 : {
731 0 : bool result(!is_configured() || (force && !has_appender("console")));
732 0 : if(result)
733 : {
734 0 : logger::get_instance()->add_console_appender();
735 : }
736 :
737 0 : return result;
738 : }
739 :
740 :
741 0 : bool configure_syslog(std::string const & identity)
742 : {
743 0 : bool result(!is_configured());
744 0 : if(result)
745 : {
746 0 : logger::get_instance()->add_syslog_appender(identity);
747 : }
748 :
749 0 : return result;
750 : }
751 :
752 :
753 0 : bool configure_file(std::string const & filename)
754 : {
755 0 : bool result(!is_configured());
756 0 : if(result)
757 : {
758 0 : logger::get_instance()->add_file_appender(filename);
759 : }
760 :
761 0 : return result;
762 : }
763 :
764 :
765 0 : bool configure_config(std::string const & config_filename)
766 : {
767 0 : bool result(!is_configured());
768 0 : if(result)
769 : {
770 0 : logger::get_instance()->add_config(config_filename);
771 : }
772 :
773 0 : return result;
774 : }
775 :
776 :
777 :
778 :
779 6 : } // snaplogger namespace
780 : // vim: ts=4 sw=4 et
|