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 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 lib
37 : //
38 : #include <snapdev/empty_set_intersection.h>
39 : #include <snapdev/not_used.h>
40 :
41 :
42 : // C++ lib
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 20 : appender::appender(std::string const & name, std::string const & type)
76 : : f_type(type)
77 : , f_name(name)
78 20 : , f_normal_component(get_component(COMPONENT_NORMAL))
79 : {
80 40 : guard g;
81 :
82 20 : f_format = get_private_logger()->get_default_format();
83 20 : }
84 :
85 :
86 20 : appender::~appender()
87 : {
88 20 : }
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 92637 : bool appender::is_enabled() const
124 : {
125 185274 : guard g;
126 :
127 185274 : 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 20 : bool appender::unique() const
140 : {
141 20 : return false;
142 : }
143 :
144 :
145 304 : severity_t appender::get_severity() const
146 : {
147 608 : guard g;
148 :
149 608 : return f_severity;
150 : }
151 :
152 :
153 290 : void appender::set_severity(severity_t severity_level)
154 : {
155 580 : guard g;
156 :
157 290 : f_severity = severity_level;
158 290 : logger::get_instance()->severity_changed(severity_level);
159 290 : }
160 :
161 :
162 0 : void appender::reduce_severity(severity_t severity_level)
163 : {
164 0 : guard g;
165 :
166 0 : if(severity_level < f_severity)
167 : {
168 0 : set_severity(severity_level);
169 : }
170 0 : }
171 :
172 :
173 0 : bool appender::operator < (appender const & rhs) const
174 : {
175 0 : guard g;
176 :
177 0 : return f_severity < rhs.f_severity;
178 : }
179 :
180 :
181 20 : void appender::set_config(advgetopt::getopt const & opts)
182 : {
183 40 : guard g;
184 :
185 : // ENABLE
186 : //
187 : {
188 40 : std::string const specialized_enabled(f_name + "::enabled");
189 20 : if(opts.is_defined(specialized_enabled))
190 : {
191 0 : f_enabled = opts.get_string(specialized_enabled) != "false";
192 : }
193 20 : else if(opts.is_defined("enabled"))
194 : {
195 0 : f_enabled = opts.get_string("enabled") != "false";
196 : }
197 : else
198 : {
199 20 : f_enabled = true;
200 : }
201 : }
202 :
203 : // FORMAT
204 : //
205 : {
206 40 : std::string const specialized_format(f_name + "::format");
207 20 : if(opts.is_defined(specialized_format))
208 : {
209 0 : f_format = std::make_shared<format>(opts.get_string(specialized_format));
210 : }
211 20 : else if(opts.is_defined("format"))
212 : {
213 0 : f_format = std::make_shared<format>(opts.get_string("format"));
214 : }
215 : }
216 :
217 : // BITRATE
218 : //
219 : {
220 : // the input is considered to be in mbps
221 : //
222 40 : std::string bitrate("0");
223 40 : std::string const specialized_bitrate(f_name + "::bitrate");
224 20 : if(opts.is_defined(specialized_bitrate))
225 : {
226 0 : bitrate = opts.get_string(specialized_bitrate);
227 : }
228 20 : else if(opts.is_defined("bitrate"))
229 : {
230 0 : bitrate = opts.get_string("bitrate");
231 : }
232 20 : char * end(nullptr);
233 20 : errno = 0;
234 20 : double rate(strtod(bitrate.c_str(), &end));
235 20 : if(rate < 0.0
236 20 : || end == nullptr
237 20 : || *end != '\0'
238 20 : || errno == ERANGE)
239 : {
240 0 : rate = 0.0;
241 : }
242 20 : if(rate > 0.0)
243 : {
244 : // transform the rate to bytes per minute
245 : //
246 0 : rate = rate * (60.0 * 1'000'000.0 / 8.0);
247 : }
248 20 : f_bytes_per_minute = static_cast<decltype(f_bytes_per_minute)>(floor(rate));
249 : }
250 :
251 : // SEVERITY
252 : //
253 40 : std::string const specialized_severity(f_name + "::severity");
254 20 : if(opts.is_defined(specialized_severity))
255 : {
256 0 : std::string const severity_name(opts.get_string(specialized_severity));
257 0 : severity::pointer_t sev(snaplogger::get_severity(severity_name));
258 0 : if(sev != nullptr)
259 : {
260 0 : set_severity(sev->get_severity());
261 : }
262 : else
263 : {
264 : throw invalid_severity(
265 : "severity level named \""
266 0 : + severity_name
267 0 : + "\" not found.");
268 : }
269 : }
270 20 : else if(opts.is_defined("severity"))
271 : {
272 0 : std::string const severity_name(opts.get_string("severity"));
273 0 : severity::pointer_t sev(snaplogger::get_severity(severity_name));
274 0 : if(sev != nullptr)
275 : {
276 0 : set_severity(sev->get_severity());
277 : }
278 : else
279 : {
280 : throw invalid_severity(
281 : "severity level named \""
282 0 : + severity_name
283 0 : + "\" not found.");
284 : }
285 : }
286 :
287 : // COMPONENTS
288 : //
289 40 : std::string comp;
290 40 : std::string const components(f_name + "::components");
291 20 : if(opts.is_defined(components))
292 : {
293 0 : comp = opts.get_string(components);
294 : }
295 20 : else if(opts.is_defined("components"))
296 : {
297 0 : comp = opts.get_string("components");
298 : }
299 20 : if(comp.empty())
300 : {
301 20 : add_component(f_normal_component);
302 : }
303 : else
304 : {
305 0 : advgetopt::string_list_t component_names;
306 0 : advgetopt::split_string(comp, component_names, {","});
307 0 : for(auto name : component_names)
308 : {
309 0 : add_component(get_component(name));
310 : }
311 : }
312 :
313 : // FILTER
314 : //
315 : {
316 40 : std::string filter;
317 40 : std::string const specialized_filter(f_name + "::filter");
318 20 : if(opts.is_defined(specialized_filter))
319 : {
320 0 : filter = opts.get_string(specialized_filter);
321 : }
322 20 : else if(opts.is_defined("filter"))
323 : {
324 0 : filter = opts.get_string("filter");
325 : }
326 20 : if(!filter.empty())
327 : {
328 0 : std::regex_constants::syntax_option_type flags(std::regex::nosubs | std::regex::optimize);
329 0 : std::regex_constants::syntax_option_type type(std::regex::extended);
330 0 : if(filter[0] == '/')
331 : {
332 0 : std::string::size_type pos(filter.rfind('/'));
333 0 : if(pos == 0)
334 : {
335 : throw invalid_variable(
336 : "invalid filter \""
337 0 : + filter
338 0 : + "\"; missing ending '/'.");
339 : }
340 0 : std::string const flag_list(filter.substr(pos + 1));
341 0 : filter = filter.substr(1, pos - 2);
342 0 : if(filter.empty())
343 : {
344 : throw invalid_variable(
345 : "invalid filter \""
346 0 : + filter
347 0 : + "\"; the regular expression is empty.");
348 : }
349 : // TODO: for errors we would need to iterate using the libutf8
350 : // (since we could have a Unicode character after the /)
351 : //
352 : // TODO: if two type flags are found, err too
353 : //
354 0 : int count(0);
355 0 : for(auto f : flag_list)
356 : {
357 0 : switch(f)
358 : {
359 0 : case 'i':
360 0 : flags |= std::regex::icase;
361 0 : break;
362 :
363 0 : case 'c':
364 0 : flags |= std::regex::collate;
365 0 : break;
366 :
367 0 : case 'j':
368 0 : type = std::regex::ECMAScript;
369 0 : ++count;
370 0 : break;
371 :
372 0 : case 'b':
373 0 : type = std::regex::basic;
374 0 : ++count;
375 0 : break;
376 :
377 0 : case 'x':
378 0 : type = std::regex::extended;
379 0 : ++count;
380 0 : break;
381 :
382 0 : case 'a':
383 0 : type = std::regex::awk;
384 0 : ++count;
385 0 : break;
386 :
387 0 : case 'g':
388 0 : type = std::regex::grep;
389 0 : ++count;
390 0 : break;
391 :
392 0 : case 'e':
393 0 : type = std::regex::egrep;
394 0 : ++count;
395 0 : break;
396 :
397 0 : default:
398 : throw invalid_variable(
399 : "in \""
400 0 : + filter
401 0 : + "\", found invalid flag '"
402 0 : + f
403 0 : + "'.");
404 :
405 : }
406 0 : if(count > 1)
407 : {
408 : throw invalid_variable(
409 : "found multiple types in \""
410 0 : + filter
411 0 : + "\".");
412 : }
413 : }
414 : }
415 0 : f_filter = std::make_shared<std::regex>(filter, flags | type);
416 : }
417 : }
418 :
419 : // REPEAT
420 : //
421 : {
422 40 : std::string no_repeat(f_name + "::no-repeat");
423 20 : if(!opts.is_defined(no_repeat))
424 : {
425 20 : if(opts.is_defined("no-repeat"))
426 : {
427 0 : no_repeat = "no-repeat";
428 : }
429 : else
430 : {
431 20 : no_repeat.clear();
432 : }
433 : }
434 20 : if(!no_repeat.empty())
435 : {
436 0 : std::string const value(opts.get_string(no_repeat));
437 0 : if(value != "off")
438 : {
439 0 : if(value == "max"
440 0 : || value == "maximum")
441 : {
442 0 : f_no_repeat_size = NO_REPEAT_MAXIMUM;
443 : }
444 0 : else if(value == "default")
445 : {
446 0 : f_no_repeat_size = NO_REPEAT_DEFAULT;
447 : }
448 : else
449 : {
450 0 : f_no_repeat_size = opts.get_long("no-repeat", 0, 0, NO_REPEAT_MAXIMUM);
451 : }
452 : }
453 : }
454 : }
455 20 : }
456 :
457 :
458 0 : void appender::reopen()
459 : {
460 0 : }
461 :
462 :
463 21 : void appender::add_component(component::pointer_t comp)
464 : {
465 42 : guard g;
466 :
467 21 : f_components.insert(comp);
468 21 : }
469 :
470 :
471 42 : format::pointer_t appender::set_format(format::pointer_t new_format)
472 : {
473 84 : guard g;
474 :
475 42 : format::pointer_t old(f_format);
476 42 : f_format = new_format;
477 84 : return old;
478 : }
479 :
480 :
481 0 : long appender::get_bytes_per_minute() const
482 : {
483 0 : return f_bytes_per_minute;
484 : }
485 :
486 :
487 : /** \brief Return the number of dropped message due to bitrate restrictions.
488 : *
489 : * It is possible to set the number of bits per second that an appender
490 : * will accept. Anything over that amount will be dropped.
491 : *
492 : * The logger converts the bits per second amount to a bytes per minute.
493 : * Each time a message gets sent, the number of bytes in that message
494 : * string is added to a counter. If that counter reaches a number of bytes
495 : * per minute larger than the allowed bytes per minutes, then the messages
496 : * get dropped until 1 minute elapses.
497 : *
498 : * This function returns any message that was sent and was dropped because
499 : * the bytes per minute limit was reached.
500 : *
501 : * \note
502 : * This counter doesn't get reset so reading it always returns a grand
503 : * total of all the messages that were dropped so far. This counter is
504 : * per appender.
505 : *
506 : * \return The number of messages that were dropped.
507 : */
508 0 : std::size_t appender::get_bytes_dropped_messages() const
509 : {
510 0 : return f_bytes_dropped_messages;
511 : }
512 :
513 :
514 92637 : void appender::send_message(message const & msg)
515 : {
516 134601 : guard g;
517 :
518 185274 : if(!is_enabled()
519 92637 : || msg.get_severity() < f_severity)
520 : {
521 45773 : return;
522 : }
523 :
524 46864 : component::set_t const & components(msg.get_components());
525 46864 : if(components.empty())
526 : {
527 : // user did not supply any component in 'msg', check for
528 : // the normal component
529 : //
530 93720 : if(!f_components.empty()
531 140580 : && f_components.find(f_normal_component) == f_components.end())
532 : {
533 0 : return;
534 : }
535 : }
536 : else
537 : {
538 4 : if(snap::empty_set_intersection(f_components, components))
539 : {
540 2 : return;
541 : }
542 : }
543 :
544 88820 : std::string formatted_message(f_format->process_message(msg));
545 46856 : if(formatted_message.empty())
546 : {
547 4898 : return;
548 : }
549 :
550 83916 : if(f_filter != nullptr
551 41958 : && !std::regex_match(formatted_message, *f_filter))
552 : {
553 0 : return;
554 : }
555 :
556 83916 : if(formatted_message.back() != '\n'
557 41958 : && formatted_message.back() != '\r')
558 : {
559 : // TODO: add support to define line terminator (cr, nl, cr nl)
560 : //
561 41958 : formatted_message += '\n';
562 : }
563 :
564 : // TBD: should we use the time of the message rather than 'now'?
565 : //
566 41958 : if(f_bytes_per_minute != 0)
567 : {
568 0 : time_t const now(time(0));
569 0 : if(f_bytes_minute - now >= 60)
570 : {
571 0 : f_bytes_minute = now;
572 0 : f_bytes_received = 0;
573 : }
574 0 : else if(f_bytes_received + static_cast<long>(formatted_message.length())
575 0 : >= f_bytes_per_minute)
576 : {
577 : // overflow
578 : //
579 : // IMPORTANT NOTE: this algorithm may kick out a very long log
580 : // message and then accept a smaller one which still fits in the
581 : // `f_bytes_per_minute` bitrate
582 : //
583 0 : ++f_bytes_dropped_messages;
584 0 : return;
585 : }
586 0 : f_bytes_received += formatted_message.length();
587 : }
588 :
589 41958 : if(f_no_repeat_size > NO_REPEAT_OFF)
590 : {
591 0 : std::string const non_changing_message(f_format->process_message(msg, true));
592 0 : auto it(std::find(f_last_messages.rbegin(), f_last_messages.rend(), non_changing_message));
593 0 : if(it != f_last_messages.rend())
594 : {
595 : // TODO: look into a way to count said messages and print out
596 : // the total number or something of the sort...
597 : // (maybe we store those messages in a buffer and once
598 : // we are to replace a message, that's when we forward
599 : // it and that can include the count? also that extra
600 : // write can be based on time and/or count)
601 : //
602 0 : f_last_messages.erase(std::next(it).base()); // erase() expects an iterator, not a reverse iterator
603 : }
604 0 : f_last_messages.push_back(non_changing_message);
605 0 : if(f_last_messages.size() > f_no_repeat_size)
606 : {
607 0 : f_last_messages.pop_front();
608 : }
609 : }
610 :
611 41958 : process_message(msg, formatted_message);
612 : }
613 :
614 :
615 0 : void appender::process_message(message const & msg, std::string const & formatted_message)
616 : {
617 : // the default is a "null appender" -- do nothing
618 0 : snap::NOT_USED(msg, formatted_message);
619 0 : }
620 :
621 :
622 :
623 :
624 :
625 8 : appender_factory::appender_factory(std::string const & type)
626 8 : : f_type(type)
627 : {
628 8 : }
629 :
630 :
631 8 : appender_factory::~appender_factory()
632 : {
633 8 : }
634 :
635 :
636 16 : std::string const & appender_factory::get_type() const
637 : {
638 16 : return f_type;
639 : }
640 :
641 :
642 :
643 :
644 8 : void register_appender_factory(appender_factory::pointer_t factory)
645 : {
646 8 : get_private_logger()->register_appender_factory(factory);
647 8 : }
648 :
649 :
650 2 : appender::pointer_t create_appender(std::string const & type, std::string const & name)
651 : {
652 2 : return get_private_logger()->create_appender(type, name);
653 : }
654 :
655 :
656 :
657 :
658 :
659 :
660 0 : safe_format::safe_format(appender::pointer_t a, format::pointer_t new_format)
661 : : f_appender(a)
662 0 : , f_old_format(a->set_format(new_format))
663 : {
664 0 : }
665 :
666 :
667 0 : safe_format::~safe_format()
668 : {
669 0 : snap::NOT_USED(f_appender->set_format(f_old_format));
670 0 : }
671 :
672 :
673 :
674 :
675 :
676 :
677 :
678 6 : } // snaplogger namespace
679 : // vim: ts=4 sw=4 et
|