Line data Source code
1 : // Copyright (c) 2013-2022 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 implements the base appender class.
24 : */
25 :
26 :
27 : // self
28 : //
29 : #include "snaplogger/appender.h"
30 :
31 : #include "snaplogger/exception.h"
32 : #include "snaplogger/guard.h"
33 : #include "snaplogger/private_logger.h"
34 :
35 :
36 : // snapdev
37 : //
38 : #include <snapdev/empty_set_intersection.h>
39 : #include <snapdev/not_used.h>
40 :
41 :
42 : // C++
43 : //
44 : #include <iostream>
45 :
46 :
47 : // C lib
48 : //
49 : #include <math.h>
50 :
51 :
52 : // last include
53 : //
54 : #include <snapdev/poison.h>
55 :
56 :
57 :
58 : namespace snaplogger
59 : {
60 :
61 :
62 : namespace
63 : {
64 :
65 :
66 :
67 : // if we want to be able to reference such we need to create it TBD
68 : // (and it should probably be in the null_appender.cpp file instead)
69 : //APPENDER_FACTORY(null);
70 :
71 :
72 : }
73 :
74 :
75 23 : appender::appender(std::string const & name, std::string const & type)
76 : : f_type(type)
77 : , f_name(name)
78 23 : , f_normal_component(get_component(COMPONENT_NORMAL))
79 : {
80 46 : guard g;
81 :
82 23 : f_format = get_private_logger()->get_default_format();
83 23 : }
84 :
85 :
86 17 : appender::~appender()
87 : {
88 17 : }
89 :
90 :
91 3 : std::string const & appender::get_type() const
92 : {
93 : // we do not need to guard this one because we set it up on creation
94 : // and it can't be modified later
95 : //
96 3 : return f_type;
97 : }
98 :
99 :
100 0 : void appender::set_name(std::string const & name)
101 : {
102 0 : guard g;
103 :
104 0 : if(f_name != "console"
105 0 : && f_name != "syslog")
106 : {
107 : throw invalid_parameter(
108 0 : "the appender set_name() can only be used for the console & syslog appenders to rename them to your own appender name (and done internally only).");
109 : }
110 :
111 0 : f_name = name;
112 0 : }
113 :
114 :
115 1 : std::string const & appender::get_name() const
116 : {
117 2 : guard g;
118 :
119 2 : return f_name;
120 : }
121 :
122 :
123 93437 : bool appender::is_enabled() const
124 : {
125 186874 : guard g;
126 :
127 186874 : return f_enabled;
128 : }
129 :
130 :
131 0 : void appender::set_enabled(bool status)
132 : {
133 0 : guard g;
134 :
135 0 : f_enabled = status;
136 0 : }
137 :
138 :
139 23 : bool appender::unique() const
140 : {
141 23 : return false;
142 : }
143 :
144 :
145 307 : severity_t appender::get_severity() const
146 : {
147 614 : guard g;
148 :
149 614 : return f_severity;
150 : }
151 :
152 :
153 292 : void appender::set_severity(severity_t severity_level)
154 : {
155 584 : guard g;
156 :
157 292 : f_severity = severity_level;
158 292 : logger::get_instance()->severity_changed(severity_level);
159 292 : }
160 :
161 :
162 2 : void appender::reduce_severity(severity_t severity_level)
163 : {
164 4 : guard g;
165 :
166 2 : if(severity_level < f_severity)
167 : {
168 2 : set_severity(severity_level);
169 : }
170 2 : }
171 :
172 :
173 0 : void appender::increase_severity(severity_t severity_level)
174 : {
175 0 : guard g;
176 :
177 0 : if(severity_level > f_severity)
178 : {
179 0 : set_severity(severity_level);
180 : }
181 0 : }
182 :
183 :
184 0 : bool appender::operator < (appender const & rhs) const
185 : {
186 0 : guard g;
187 :
188 0 : return f_severity < rhs.f_severity;
189 : }
190 :
191 :
192 27 : void appender::set_config(advgetopt::getopt const & opts)
193 : {
194 54 : guard g;
195 :
196 : // ENABLE
197 : //
198 : {
199 54 : std::string const specialized_enabled(f_name + "::enabled");
200 27 : if(opts.is_defined(specialized_enabled))
201 : {
202 0 : f_enabled = opts.get_string(specialized_enabled) != "false";
203 : }
204 27 : else if(opts.is_defined("enabled"))
205 : {
206 0 : f_enabled = opts.get_string("enabled") != "false";
207 : }
208 : else
209 : {
210 27 : f_enabled = true;
211 : }
212 : }
213 :
214 : // FORMAT
215 : //
216 : {
217 54 : std::string const specialized_format(f_name + "::format");
218 27 : if(opts.is_defined(specialized_format))
219 : {
220 0 : f_format = std::make_shared<format>(opts.get_string(specialized_format));
221 : }
222 27 : else if(opts.is_defined("format"))
223 : {
224 0 : f_format = std::make_shared<format>(opts.get_string("format"));
225 : }
226 : }
227 :
228 : // BITRATE
229 : //
230 : {
231 : // the input is considered to be in mbps
232 : //
233 54 : std::string bitrate("0");
234 54 : std::string const specialized_bitrate(f_name + "::bitrate");
235 27 : if(opts.is_defined(specialized_bitrate))
236 : {
237 0 : bitrate = opts.get_string(specialized_bitrate);
238 : }
239 27 : else if(opts.is_defined("bitrate"))
240 : {
241 0 : bitrate = opts.get_string("bitrate");
242 : }
243 27 : char * end(nullptr);
244 27 : errno = 0;
245 27 : double rate(strtod(bitrate.c_str(), &end));
246 27 : if(rate < 0.0
247 27 : || end == nullptr
248 27 : || *end != '\0'
249 27 : || errno == ERANGE)
250 : {
251 0 : rate = 0.0;
252 : }
253 27 : if(rate > 0.0)
254 : {
255 : // transform the rate to bytes per minute
256 : //
257 0 : rate = rate * (60.0 * 1'000'000.0 / 8.0);
258 : }
259 27 : f_bytes_per_minute = static_cast<decltype(f_bytes_per_minute)>(floor(rate));
260 : }
261 :
262 : // SEVERITY
263 : //
264 54 : std::string const specialized_severity(f_name + "::severity");
265 27 : if(opts.is_defined(specialized_severity))
266 : {
267 0 : std::string const severity_name(opts.get_string(specialized_severity));
268 0 : severity::pointer_t sev(snaplogger::get_severity(severity_name));
269 0 : if(sev != nullptr)
270 : {
271 0 : set_severity(sev->get_severity());
272 : }
273 : else
274 : {
275 : throw invalid_severity(
276 : "severity level named \""
277 0 : + severity_name
278 0 : + "\" not found.");
279 : }
280 : }
281 27 : else if(opts.is_defined("severity"))
282 : {
283 0 : std::string const severity_name(opts.get_string("severity"));
284 0 : severity::pointer_t sev(snaplogger::get_severity(severity_name));
285 0 : if(sev != nullptr)
286 : {
287 0 : set_severity(sev->get_severity());
288 : }
289 : else
290 : {
291 : throw invalid_severity(
292 : "severity level named \""
293 0 : + severity_name
294 0 : + "\" not found.");
295 : }
296 : }
297 :
298 : // COMPONENTS
299 : //
300 54 : std::string comp;
301 54 : std::string const components(f_name + "::components");
302 27 : if(opts.is_defined(components))
303 : {
304 0 : comp = opts.get_string(components);
305 : }
306 27 : else if(opts.is_defined("components"))
307 : {
308 0 : comp = opts.get_string("components");
309 : }
310 27 : if(comp.empty())
311 : {
312 27 : add_component(f_normal_component);
313 : }
314 : else
315 : {
316 0 : advgetopt::string_list_t component_names;
317 0 : advgetopt::split_string(comp, component_names, {","});
318 0 : for(auto name : component_names)
319 : {
320 0 : add_component(get_component(name));
321 : }
322 : }
323 :
324 : // FILTER
325 : //
326 : {
327 54 : std::string filter;
328 54 : std::string const specialized_filter(f_name + "::filter");
329 27 : if(opts.is_defined(specialized_filter))
330 : {
331 0 : filter = opts.get_string(specialized_filter);
332 : }
333 27 : else if(opts.is_defined("filter"))
334 : {
335 0 : filter = opts.get_string("filter");
336 : }
337 27 : if(!filter.empty())
338 : {
339 0 : std::regex_constants::syntax_option_type flags(std::regex::nosubs | std::regex::optimize);
340 0 : std::regex_constants::syntax_option_type type(std::regex::extended);
341 0 : if(filter[0] == '/')
342 : {
343 0 : std::string::size_type pos(filter.rfind('/'));
344 0 : if(pos == 0)
345 : {
346 : throw invalid_variable(
347 : "invalid filter \""
348 0 : + filter
349 0 : + "\"; missing ending '/'.");
350 : }
351 0 : std::string const flag_list(filter.substr(pos + 1));
352 0 : filter = filter.substr(1, pos - 2);
353 0 : if(filter.empty())
354 : {
355 : throw invalid_variable(
356 : "invalid filter \""
357 0 : + filter
358 0 : + "\"; the regular expression is empty.");
359 : }
360 : // TODO: for errors we would need to iterate using the libutf8
361 : // (since we could have a Unicode character after the /)
362 : //
363 : // TODO: if two type flags are found, err too
364 : //
365 0 : int count(0);
366 0 : for(auto f : flag_list)
367 : {
368 0 : switch(f)
369 : {
370 0 : case 'i':
371 0 : flags |= std::regex::icase;
372 0 : break;
373 :
374 0 : case 'c':
375 0 : flags |= std::regex::collate;
376 0 : break;
377 :
378 0 : case 'j':
379 0 : type = std::regex::ECMAScript;
380 0 : ++count;
381 0 : break;
382 :
383 0 : case 'b':
384 0 : type = std::regex::basic;
385 0 : ++count;
386 0 : break;
387 :
388 0 : case 'x':
389 0 : type = std::regex::extended;
390 0 : ++count;
391 0 : break;
392 :
393 0 : case 'a':
394 0 : type = std::regex::awk;
395 0 : ++count;
396 0 : break;
397 :
398 0 : case 'g':
399 0 : type = std::regex::grep;
400 0 : ++count;
401 0 : break;
402 :
403 0 : case 'e':
404 0 : type = std::regex::egrep;
405 0 : ++count;
406 0 : break;
407 :
408 0 : default:
409 : throw invalid_variable(
410 : "in \""
411 0 : + filter
412 0 : + "\", found invalid flag '"
413 0 : + f
414 0 : + "'.");
415 :
416 : }
417 0 : if(count > 1)
418 : {
419 : throw invalid_variable(
420 : "found multiple types in \""
421 0 : + filter
422 0 : + "\".");
423 : }
424 : }
425 : }
426 0 : f_filter = std::make_shared<std::regex>(filter, flags | type);
427 : }
428 : }
429 :
430 : // NO REPEAT
431 : //
432 : {
433 54 : std::string no_repeat(f_name + "::no-repeat");
434 27 : if(!opts.is_defined(no_repeat))
435 : {
436 27 : if(opts.is_defined("no-repeat"))
437 : {
438 0 : no_repeat = "no-repeat";
439 : }
440 : else
441 : {
442 27 : no_repeat.clear();
443 : }
444 : }
445 27 : if(!no_repeat.empty())
446 : {
447 0 : std::string const value(opts.get_string(no_repeat));
448 0 : if(value != "off")
449 : {
450 0 : if(value == "max"
451 0 : || value == "maximum")
452 : {
453 0 : f_no_repeat_size = NO_REPEAT_MAXIMUM;
454 : }
455 0 : else if(value == "default")
456 : {
457 0 : f_no_repeat_size = NO_REPEAT_DEFAULT;
458 : }
459 : else
460 : {
461 0 : f_no_repeat_size = opts.get_long(no_repeat, 0, 0, NO_REPEAT_MAXIMUM);
462 : }
463 : }
464 : }
465 : }
466 27 : }
467 :
468 :
469 0 : void appender::reopen()
470 : {
471 0 : }
472 :
473 :
474 29 : void appender::add_component(component::pointer_t comp)
475 : {
476 58 : guard g;
477 :
478 29 : f_components.insert(comp);
479 29 : }
480 :
481 :
482 45 : format::pointer_t appender::set_format(format::pointer_t new_format)
483 : {
484 90 : guard g;
485 :
486 45 : format::pointer_t old(f_format);
487 45 : f_format = new_format;
488 90 : return old;
489 : }
490 :
491 :
492 0 : long appender::get_bytes_per_minute() const
493 : {
494 0 : return f_bytes_per_minute;
495 : }
496 :
497 :
498 : /** \brief Return the number of dropped message due to bitrate restrictions.
499 : *
500 : * It is possible to set the number of bits per second that an appender
501 : * will accept. Anything over that amount will be dropped.
502 : *
503 : * The logger converts the bits per second amount to a bytes per minute.
504 : * Each time a message gets sent, the number of bytes in that message
505 : * string is added to a counter. If that counter reaches a number of bytes
506 : * per minute larger than the allowed bytes per minutes, then the messages
507 : * get dropped until 1 minute elapses.
508 : *
509 : * This function returns any message that was sent and was dropped because
510 : * the bytes per minute limit was reached.
511 : *
512 : * \note
513 : * This counter doesn't get reset so reading it always returns a grand
514 : * total of all the messages that were dropped so far. This counter is
515 : * per appender.
516 : *
517 : * \return The number of messages that were dropped.
518 : */
519 0 : std::size_t appender::get_bytes_dropped_messages() const
520 : {
521 0 : return f_bytes_dropped_messages;
522 : }
523 :
524 :
525 93437 : void appender::send_message(message const & msg)
526 : {
527 134837 : guard g;
528 :
529 186874 : if(!is_enabled()
530 93437 : || msg.get_severity() < f_severity)
531 : {
532 46831 : return;
533 : }
534 :
535 46606 : component::set_t const & components(msg.get_components());
536 46606 : if(components.empty())
537 : {
538 : // user did not supply any component in 'msg', check for
539 : // the normal component
540 : //
541 93192 : if(!f_components.empty()
542 139788 : && f_components.find(f_normal_component) == f_components.end())
543 : {
544 0 : return;
545 : }
546 : }
547 : else
548 : {
549 10 : if(snapdev::empty_set_intersection(f_components, components))
550 : {
551 3 : return;
552 : }
553 : }
554 :
555 87997 : std::string formatted_message(f_format->process_message(msg));
556 46597 : if(formatted_message.empty())
557 : {
558 5203 : return;
559 : }
560 :
561 82788 : if(f_filter != nullptr
562 41394 : && !std::regex_match(formatted_message, *f_filter))
563 : {
564 0 : return;
565 : }
566 :
567 82788 : if(formatted_message.back() != '\n'
568 41394 : && formatted_message.back() != '\r')
569 : {
570 : // TODO: add support to define line terminator (cr, nl, cr nl)
571 : //
572 41394 : formatted_message += '\n';
573 : }
574 :
575 : // TBD: should we use the time of the message rather than 'now'?
576 : //
577 41394 : if(f_bytes_per_minute != 0)
578 : {
579 0 : time_t const now(time(0));
580 0 : if(f_bytes_minute - now >= 60)
581 : {
582 0 : f_bytes_minute = now;
583 0 : f_bytes_received = 0;
584 : }
585 0 : else if(f_bytes_received + static_cast<long>(formatted_message.length())
586 0 : >= f_bytes_per_minute)
587 : {
588 : // overflow
589 : //
590 : // IMPORTANT NOTE: this algorithm may kick out a very long log
591 : // message and then accept a smaller one which still fits in the
592 : // `f_bytes_per_minute` bitrate
593 : //
594 0 : ++f_bytes_dropped_messages;
595 0 : return;
596 : }
597 0 : f_bytes_received += formatted_message.length();
598 : }
599 :
600 41394 : if(f_no_repeat_size > NO_REPEAT_OFF)
601 : {
602 0 : std::string const non_changing_message(f_format->process_message(msg, true));
603 0 : auto it(std::find(f_last_messages.rbegin(), f_last_messages.rend(), non_changing_message));
604 0 : if(it != f_last_messages.rend())
605 : {
606 : // TODO: count the number of times this message occurred and
607 : // at a certain number, show it again -- add an option to
608 : // defined that certain number
609 : //
610 : // check the timestamp and if the message happened more
611 : // than X seconds prior, repeat it anyway (this can be
612 : // a timeout of our cache as far as implementation is
613 : // concerned) -- add option to define time and this check
614 : // could go "the other way around" where we cumulate messages
615 : // and if not repeat within X seconds, then send them out
616 : // so here we would just push messages on a "stack" and
617 : // the thread would take care of send messages and if
618 : // repeated then we can show a "count" in the message
619 : // (i.e. something like "(message repeated N times in
620 : // the last X seconds)") -- I think syslog sends the
621 : // first message and then records the repeats for
622 : // X seconds and finally sends the results to the screen
623 : //
624 : // the current implementation helps, but it's weak in
625 : // its results... (the results look weird)
626 : //
627 0 : return;
628 : }
629 0 : f_last_messages.push_back(non_changing_message);
630 0 : if(f_last_messages.size() > f_no_repeat_size)
631 : {
632 0 : f_last_messages.pop_front();
633 : }
634 : }
635 :
636 41394 : process_message(msg, formatted_message);
637 : }
638 :
639 :
640 0 : void appender::process_message(message const & msg, std::string const & formatted_message)
641 : {
642 : // the default is a "null appender" -- do nothing
643 0 : snapdev::NOT_USED(msg, formatted_message);
644 0 : }
645 :
646 :
647 :
648 :
649 :
650 8 : appender_factory::appender_factory(std::string const & type)
651 8 : : f_type(type)
652 : {
653 8 : char const * appender_factory_debug(getenv("APPENDER_FACTORY_DEBUG"));
654 8 : if(appender_factory_debug != nullptr
655 0 : && *appender_factory_debug != '\0')
656 : {
657 0 : std::cerr << "appender_factor:debug: adding appender factory \"" << type << "\".\n";
658 : }
659 8 : }
660 :
661 :
662 4 : appender_factory::~appender_factory()
663 : {
664 4 : }
665 :
666 :
667 16 : std::string const & appender_factory::get_type() const
668 : {
669 16 : return f_type;
670 : }
671 :
672 :
673 :
674 :
675 8 : void register_appender_factory(appender_factory::pointer_t factory)
676 : {
677 8 : get_private_logger()->register_appender_factory(factory);
678 8 : }
679 :
680 :
681 2 : appender::pointer_t create_appender(std::string const & type, std::string const & name)
682 : {
683 2 : return get_private_logger()->create_appender(type, name);
684 : }
685 :
686 :
687 :
688 :
689 :
690 :
691 0 : safe_format::safe_format(appender::pointer_t a, format::pointer_t new_format)
692 : : f_appender(a)
693 0 : , f_old_format(a->set_format(new_format))
694 : {
695 0 : }
696 :
697 :
698 0 : safe_format::~safe_format()
699 : {
700 0 : snapdev::NOT_USED(f_appender->set_format(f_old_format));
701 0 : }
702 :
703 :
704 :
705 :
706 :
707 :
708 :
709 6 : } // snaplogger namespace
710 : // vim: ts=4 sw=4 et
|