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/private_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/logger.h"
35 : #include "snaplogger/syslog_appender.h"
36 :
37 :
38 : // cppthread lib
39 : //
40 : #include <cppthread/log.h>
41 : #include <cppthread/runner.h>
42 :
43 :
44 : // last include
45 : //
46 : #include <snapdev/poison.h>
47 :
48 :
49 :
50 : namespace snaplogger
51 : {
52 :
53 :
54 : namespace
55 : {
56 :
57 :
58 :
59 2 : void getopt_logs(cppthread::log_level_t l, std::string const & m)
60 : {
61 2 : severity_t sev(severity_t::SEVERITY_ERROR);
62 2 : switch(l)
63 : {
64 0 : case cppthread::log_level_t::debug:
65 0 : sev = severity_t::SEVERITY_DEBUG;
66 0 : break;
67 :
68 2 : case cppthread::log_level_t::info:
69 2 : sev = severity_t::SEVERITY_INFORMATION;
70 2 : break;
71 :
72 0 : case cppthread::log_level_t::warning:
73 0 : sev = severity_t::SEVERITY_WARNING;
74 0 : break;
75 :
76 0 : case cppthread::log_level_t::fatal:
77 0 : sev = severity_t::SEVERITY_FATAL;
78 0 : break;
79 :
80 : //case cppthread::log_level_t::error:
81 0 : default:
82 : // anything else, keep SEVERITY_ERROR
83 0 : break;
84 :
85 : }
86 :
87 4 : message msg(sev, __FILE__, __func__, __LINE__);
88 :
89 : // we do not use the g_... names in case they were not yet allocated
90 : //
91 2 : msg.add_component(get_component(COMPONENT_NORMAL));
92 2 : msg.add_component(get_component(COMPONENT_CPPTHREAD));
93 :
94 2 : msg << m;
95 :
96 : // this call cannot create a loop, if the creation of the logger
97 : // generates an cppthread log, then the second call will generate
98 : // an exception (see get_instance() in snaplogger/logger.cpp)
99 : //
100 4 : logger::pointer_t lg(logger::get_instance());
101 :
102 2 : lg->log_message(msg);
103 2 : }
104 :
105 :
106 :
107 : }
108 : // no name namespace
109 :
110 :
111 : namespace detail
112 : {
113 :
114 :
115 :
116 :
117 1 : class asynchronous_logger
118 : : public cppthread::runner
119 : {
120 : public:
121 1 : asynchronous_logger(message_fifo_t::pointer_t fifo)
122 1 : : runner("logger asynchronous thread")
123 2 : , f_logger(logger::get_instance())
124 3 : , f_fifo(fifo)
125 : {
126 1 : }
127 :
128 2 : virtual void run()
129 : {
130 : // loop until the FIFO is marked as being done
131 : //
132 : for(;;)
133 : {
134 3 : message::pointer_t msg;
135 2 : if(!f_fifo->pop_front(msg, -1))
136 : {
137 1 : break;
138 : }
139 2 : logger::pointer_t l(f_logger.lock());
140 1 : if(l != nullptr)
141 : {
142 1 : l->process_message(*msg);
143 : }
144 1 : }
145 1 : }
146 :
147 : private:
148 : logger::weak_pointer_t f_logger = logger::pointer_t();
149 : message_fifo_t::pointer_t f_fifo = message_fifo_t::pointer_t();
150 : };
151 :
152 :
153 :
154 : }
155 : // detail namespace
156 :
157 :
158 :
159 2 : private_logger::private_logger()
160 : {
161 : // if a call arrives really early, this is defined in the logger
162 : //
163 2 : f_normal_component = get_component(COMPONENT_NORMAL);
164 :
165 2 : cppthread::set_log_callback(getopt_logs);
166 2 : }
167 :
168 :
169 6 : private_logger::~private_logger()
170 : {
171 2 : delete_thread();
172 4 : }
173 :
174 :
175 2 : void private_logger::shutdown()
176 : {
177 2 : delete_thread();
178 2 : }
179 :
180 :
181 8 : void private_logger::register_appender_factory(appender_factory::pointer_t factory)
182 : {
183 8 : if(factory == nullptr)
184 : {
185 : throw logger_logic_error( // LCOV_EXCL_LINE
186 : "register_appender_factory() called with a nullptr."); // LCOV_EXCL_LINE
187 : }
188 :
189 16 : guard g;
190 :
191 8 : if(f_appender_factories.find(factory->get_type()) != f_appender_factories.end())
192 : {
193 : throw duplicate_error( // LCOV_EXCL_LINE
194 : "trying to register appender type \"" // LCOV_EXCL_LINE
195 : + factory->get_type() // LCOV_EXCL_LINE
196 : + "\" twice won't work."); // LCOV_EXCL_LINE
197 : }
198 :
199 8 : f_appender_factories[factory->get_type()] = factory;
200 8 : }
201 :
202 :
203 2 : appender::pointer_t private_logger::create_appender(std::string const & type, std::string const & name)
204 : {
205 4 : guard g;
206 :
207 2 : auto it(f_appender_factories.find(type));
208 2 : if(it != f_appender_factories.end())
209 : {
210 1 : return it->second->create(name);
211 : }
212 :
213 1 : return appender::pointer_t();
214 : }
215 :
216 :
217 : /** \brief Get a component by name.
218 : *
219 : * All components are stored in the f_components set managed by the
220 : * private_logger object instance. This way each component is unique.
221 : *
222 : * This function searches the list of existing components. If one with
223 : * the same name already exists, then that one is picked and returned.
224 : * If it doesn't exist yet, then a new component is created and that
225 : * new component's pointer is saved in the f_components list and
226 : * returned.
227 : *
228 : * The name of the components must be composed of letters (a-z),
229 : * underscores (_), and digits (0-9). Any other character is considered
230 : * invalid. The function will force uppercase characters (A-Z) to lowercase
231 : * and dashes (-) to underscores (_). Finally, a component name can't start
232 : * with a digit (0-9).
233 : *
234 : * \exception invalid_parameter
235 : * This function raises an invalid_parameter exception when it find an
236 : * invalid character in the input name.
237 : *
238 : * \param[in] name The name of the component to retrieve.
239 : *
240 : * \return The pointer to the component named \p name.
241 : */
242 54 : component::pointer_t private_logger::get_component(std::string const & name)
243 : {
244 108 : guard g;
245 :
246 108 : std::string n;
247 54 : n.reserve(name.length());
248 466 : for(char const * s(name.c_str()); *s != '\0'; ++s)
249 : {
250 412 : if(*s >= 'a' && *s <= 'z')
251 : {
252 390 : n += *s;
253 : }
254 22 : else if(*s >= 'A' && *s <= 'Z')
255 : {
256 : // force to lowercase
257 : //
258 0 : n += *s | 0x20;
259 : }
260 22 : else if(*s == '-' || *s == '_')
261 : {
262 0 : n += '_';
263 : }
264 22 : else if(*s >= '0' && *s <= '9')
265 : {
266 22 : if(n.empty())
267 : {
268 : throw invalid_parameter(
269 : "a component name cannot start with a digits ("
270 0 : + name
271 0 : + ")");
272 : }
273 22 : n += *s;
274 : }
275 : else
276 : {
277 : throw invalid_parameter(
278 : "a component name cannot include a '"
279 0 : + *s
280 0 : + ("' character ("
281 0 : + name
282 0 : + ")"));
283 : }
284 : }
285 :
286 54 : auto it(f_components.find(n));
287 54 : if(it != f_components.end())
288 : {
289 34 : return it->second;
290 : }
291 :
292 40 : auto comp(std::make_shared<component>(n));
293 20 : f_components[n] = comp;
294 :
295 20 : return comp;
296 : }
297 :
298 :
299 18 : format::pointer_t private_logger::get_default_format()
300 : {
301 36 : guard g;
302 :
303 18 : if(f_default_format == nullptr)
304 : {
305 2 : f_default_format = std::make_shared<format>(
306 : //"${env:name=HOME:padding='-':align=center:exact_width=6} "
307 : "${date} ${time} ${hostname}"
308 : " ${progname}[${pid}]: ${severity}:"
309 : " ${message:escape:max_width=1000}"
310 : " (in function \"${function}()\")"
311 : " (${basename}:${line})"
312 1 : );
313 : }
314 :
315 36 : return f_default_format;
316 : }
317 :
318 :
319 91736 : environment::pointer_t private_logger::create_environment()
320 : {
321 91736 : pid_t const tid(cppthread::gettid());
322 :
323 183472 : guard g;
324 :
325 91736 : auto it(f_environment.find(tid));
326 91736 : if(it == f_environment.end())
327 : {
328 4 : auto result(std::make_shared<environment>(tid));
329 2 : f_environment[tid] = result;
330 2 : return result;
331 : }
332 :
333 91734 : return it->second;
334 : }
335 :
336 :
337 : /** \brief Add a severity.
338 : *
339 : * This function adds a severity to the private logger object.
340 : *
341 : * Remember that a severity can be given aliases so this function may
342 : * add quite a few entries, not just one.
343 : *
344 : * To add an alias after creation, make sure to use the add_alias() instead.
345 : * This makes sure you link the same severity to several names.
346 : *
347 : * \warning
348 : * You should not be calling this function directly. Please see the
349 : * direct snaplogger::add_severity() function instead.
350 : *
351 : * \exception duplicate_error
352 : * The function verifies that the new severity is not a duplicate of
353 : * an existing system severity. The verification process checks the
354 : * severity by severity level and by name. You can, however, have
355 : * _duplicates_ of user defined severity levels. However, the last
356 : * user defined severity of a given name & level sticks, the others
357 : * get deleted.
358 : *
359 : * \param[in] sev The severity object to be added.
360 : */
361 25 : void private_logger::add_severity(severity::pointer_t sev)
362 : {
363 50 : guard g;
364 :
365 25 : auto it(f_severity_by_severity.find(sev->get_severity()));
366 25 : if(it != f_severity_by_severity.end())
367 : {
368 2 : if(it->second->is_system())
369 : {
370 : throw duplicate_error("a system severity ("
371 4 : + std::to_string(static_cast<long>(it->first))
372 4 : + ") cannot be replaced (same severity level: "
373 8 : + std::to_string(static_cast<long>(sev->get_severity()))
374 6 : + ").");
375 : }
376 : }
377 :
378 60 : for(auto n : sev->get_all_names())
379 : {
380 37 : auto s(f_severity_by_name.find(n));
381 37 : if(s != f_severity_by_name.end())
382 : {
383 2 : if(s->second->is_system())
384 : {
385 : // note that any severity can be partially edited, just not
386 : // added more than once
387 : //
388 : throw duplicate_error("a system severity ("
389 4 : + n
390 6 : + ") cannot be replaced (same name).");
391 : }
392 : }
393 : }
394 :
395 21 : sev->mark_as_registered();
396 :
397 21 : f_severity_by_severity[sev->get_severity()] = sev;
398 :
399 56 : for(auto const n : sev->get_all_names())
400 : {
401 35 : f_severity_by_name[n] = sev;
402 : }
403 21 : }
404 :
405 :
406 : /** \brief Add yet another alias.
407 : *
408 : * This function is used when the system aliases get assigned additional
409 : * aliases. The add_severity() was already called with system definitions,
410 : * so this is the only way to add additional aliases to them trhough the .ini
411 : * files.
412 : *
413 : * \param[in] sev A pointer to the severity to be added.
414 : * \param[in] name The name of the alias.
415 : */
416 2 : void private_logger::add_alias(severity::pointer_t sev, std::string const & name)
417 : {
418 4 : guard g;
419 :
420 2 : auto it(f_severity_by_severity.find(sev->get_severity()));
421 2 : if(it == f_severity_by_severity.end())
422 : {
423 : throw duplicate_error("to register an alias the corresponding main severity must already be registered. We could not find a severity with level "
424 0 : + std::to_string(static_cast<long>(sev->get_severity()))
425 0 : + ".");
426 : }
427 :
428 2 : auto s(f_severity_by_name.find(name));
429 2 : if(s != f_severity_by_name.end())
430 : {
431 0 : if(s->second->is_system())
432 : {
433 : // note that any severity can be partially edited, just not
434 : // added more than once
435 : //
436 : throw duplicate_error("a system severity ("
437 0 : + name
438 0 : + ") cannot be replaced (same name).");
439 : }
440 : }
441 :
442 2 : f_severity_by_name[name] = sev;
443 2 : }
444 :
445 :
446 30 : severity::pointer_t private_logger::get_severity(std::string const & name) const
447 : {
448 60 : guard g;
449 :
450 60 : std::string n(name);
451 30 : std::transform(n.begin(), n.end(), n.begin(), std::towlower);
452 30 : auto it(f_severity_by_name.find(n));
453 30 : if(it == f_severity_by_name.end())
454 : {
455 5 : return severity::pointer_t();
456 : }
457 :
458 25 : return it->second;
459 : }
460 :
461 :
462 18 : severity::pointer_t private_logger::get_severity(severity_t sev) const
463 : {
464 36 : guard g;
465 :
466 18 : auto it(f_severity_by_severity.find(sev));
467 18 : if(it == f_severity_by_severity.end())
468 : {
469 2 : return severity::pointer_t();
470 : }
471 :
472 16 : return it->second;
473 : }
474 :
475 :
476 0 : severity::pointer_t private_logger::get_default_severity() const
477 : {
478 0 : return f_default_severity;
479 : }
480 :
481 :
482 1 : void private_logger::set_default_severity(severity::pointer_t sev)
483 : {
484 1 : f_default_severity = sev;
485 1 : }
486 :
487 :
488 26 : void private_logger::set_diagnostic(std::string const & key, std::string const & diagnostic)
489 : {
490 52 : guard g;
491 :
492 26 : f_map_diagnostics[key] = diagnostic;
493 26 : }
494 :
495 :
496 1 : void private_logger::unset_diagnostic(std::string const & key)
497 : {
498 2 : guard g;
499 :
500 1 : auto it(f_map_diagnostics.find(key));
501 1 : if(it != f_map_diagnostics.end())
502 : {
503 1 : f_map_diagnostics.erase(it);
504 : }
505 1 : }
506 :
507 :
508 46 : map_diagnostics_t private_logger::get_map_diagnostics()
509 : {
510 92 : guard g;
511 :
512 92 : return f_map_diagnostics;
513 : }
514 :
515 :
516 0 : void private_logger::set_maximum_trace_diagnostics(size_t max)
517 : {
518 0 : f_maximum_trace_diagnostics = max;
519 0 : }
520 :
521 :
522 0 : size_t private_logger::get_maximum_trace_diagnostics() const
523 : {
524 0 : return f_maximum_trace_diagnostics;
525 : }
526 :
527 :
528 0 : void private_logger::add_trace_diagnostic(std::string const & diagnostic)
529 : {
530 0 : guard g;
531 :
532 0 : f_trace_diagnostics.push_back(diagnostic);
533 0 : if(f_trace_diagnostics.size() > f_maximum_trace_diagnostics)
534 : {
535 0 : f_trace_diagnostics.pop_front();
536 : }
537 0 : }
538 :
539 :
540 0 : void private_logger::clear_trace_diagnostics()
541 : {
542 0 : guard g;
543 :
544 0 : f_trace_diagnostics.clear();
545 0 : }
546 :
547 :
548 0 : trace_diagnostics_t private_logger::get_trace_diagnostics()
549 : {
550 0 : guard g;
551 :
552 0 : return f_trace_diagnostics;
553 : }
554 :
555 :
556 3 : void private_logger::push_nested_diagnostic(std::string const & diagnostic)
557 : {
558 6 : guard g;
559 :
560 3 : f_nested_diagnostics.push_back(diagnostic);
561 3 : }
562 :
563 :
564 3 : void private_logger::pop_nested_diagnostic()
565 : {
566 6 : guard g;
567 :
568 3 : f_nested_diagnostics.pop_back();
569 3 : }
570 :
571 :
572 8 : string_vector_t private_logger::get_nested_diagnostics() const
573 : {
574 16 : guard g;
575 :
576 16 : return f_nested_diagnostics;
577 : }
578 :
579 :
580 61 : void private_logger::register_variable_factory(variable_factory::pointer_t factory)
581 : {
582 122 : guard g;
583 :
584 61 : auto it(f_variable_factories.find(factory->get_type()));
585 61 : if(it != f_variable_factories.end())
586 : {
587 : throw duplicate_error(
588 : "trying to add two variable factories of type \""
589 2 : + factory->get_type()
590 3 : + "\".");
591 : }
592 :
593 60 : f_variable_factories[factory->get_type()] = factory;
594 60 : }
595 :
596 :
597 218 : variable::pointer_t private_logger::get_variable(std::string const & type)
598 : {
599 436 : guard g;
600 :
601 218 : if(f_variable_factories.empty())
602 : {
603 : throw invalid_variable("No variable factories were registered yet; you can't create a variable with type \"" // LCOV_EXCL_LINE
604 : + type // LCOV_EXCL_LINE
605 : + "\" at this point."); // LCOV_EXCL_LINE
606 : }
607 :
608 218 : auto it(f_variable_factories.find(type));
609 218 : if(it == f_variable_factories.end())
610 : {
611 : // TBD: should we instead return a null var.?
612 : throw invalid_variable("You can't create variable with type \""
613 2 : + type
614 3 : + "\", no such variable type was registered.");
615 : }
616 :
617 434 : return it->second->create_variable();
618 : }
619 :
620 :
621 45816 : bool private_logger::has_functions() const
622 : {
623 91632 : guard g;
624 :
625 91632 : return !f_functions.empty();
626 : }
627 :
628 :
629 23 : void private_logger::register_function(function::pointer_t func)
630 : {
631 46 : guard g;
632 :
633 23 : auto it(f_functions.find(func->get_name()));
634 23 : if(it != f_functions.end())
635 : {
636 : throw duplicate_error(
637 : "trying to add two functions named \""
638 2 : + func->get_name()
639 3 : + "\".");
640 : }
641 22 : f_functions[func->get_name()] = func;
642 22 : }
643 :
644 :
645 96 : function::pointer_t private_logger::get_function(std::string const & name) const
646 : {
647 192 : guard g;
648 :
649 96 : auto it(f_functions.find(name));
650 96 : if(it != f_functions.end())
651 : {
652 73 : return it->second;
653 : }
654 :
655 23 : return function::pointer_t();
656 : }
657 :
658 :
659 1 : void private_logger::create_thread()
660 : {
661 2 : guard g;
662 :
663 : try
664 : {
665 1 : f_fifo = std::make_shared<message_fifo_t>();
666 1 : f_asynchronous_logger = std::make_shared<detail::asynchronous_logger>(f_fifo);
667 1 : f_thread = std::make_shared<cppthread::thread>("asynchronous logger thread", f_asynchronous_logger.get());
668 1 : f_thread->start();
669 : }
670 : catch(...) // LCOV_EXCL_LINE
671 : {
672 : if(f_fifo != nullptr) // LCOV_EXCL_LINE
673 : {
674 : f_fifo->done(false); // LCOV_EXCL_LINE
675 : }
676 :
677 : f_thread.reset(); // LCOV_EXCL_LINE
678 : f_asynchronous_logger.reset(); // LCOV_EXCL_LINE
679 : f_fifo.reset(); // LCOV_EXCL_LINE
680 : throw; // LCOV_EXCL_LINE
681 : }
682 1 : }
683 :
684 :
685 5 : void private_logger::delete_thread()
686 : {
687 : // WARNING: we can't call fifo::done() while our guard is locked
688 : // we also have to make sure it's not a null pointer
689 : //
690 10 : message_fifo_t::pointer_t fifo = message_fifo_t::pointer_t();
691 10 : asynchronous_logger_pointer_t asynchronous_logger = asynchronous_logger_pointer_t();
692 10 : cppthread::thread::pointer_t thread = cppthread::thread::pointer_t();
693 :
694 : {
695 10 : guard g;
696 :
697 5 : swap(thread, f_thread);
698 5 : swap(asynchronous_logger, f_asynchronous_logger);
699 5 : swap(fifo, f_fifo);
700 : }
701 :
702 5 : if(fifo != nullptr)
703 : {
704 1 : fifo->done(false);
705 : }
706 :
707 : try
708 : {
709 5 : thread.reset();
710 : }
711 : catch(std::exception const & e)
712 : {
713 : // in most cases this one happens when quitting when one of your
714 : // functions attempts to get an instance of the logger, which is
715 : // forbidden once you return from your main() function
716 : //
717 : std::cerr << "got exception \""
718 : << e.what()
719 : << "\" while deleting the asynchronous thread."
720 : << std::endl;
721 : }
722 5 : }
723 :
724 :
725 1 : void private_logger::send_message_to_thread(message::pointer_t msg)
726 : {
727 : {
728 2 : guard g;
729 :
730 1 : if(f_fifo == nullptr)
731 : {
732 1 : create_thread();
733 : }
734 : }
735 :
736 1 : f_fifo->push_back(msg);
737 1 : }
738 :
739 :
740 :
741 :
742 92213 : private_logger::pointer_t get_private_logger()
743 : {
744 92213 : return std::dynamic_pointer_cast<private_logger>(logger::get_instance());
745 : }
746 :
747 :
748 45882 : private_logger::pointer_t get_private_logger(message const & msg)
749 : {
750 45882 : return std::dynamic_pointer_cast<private_logger>(msg.get_logger());
751 : }
752 :
753 :
754 :
755 6 : } // snaplogger namespace
756 : // vim: ts=4 sw=4 et
|