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 : // last include
48 : //
49 : #include <snapdev/poison.h>
50 :
51 :
52 :
53 : namespace snaplogger
54 : {
55 :
56 :
57 : namespace
58 : {
59 :
60 :
61 :
62 : // if we want to be able to reference such we need to create it TBD
63 : // (and it should probably be in the null_appender.cpp file instead)
64 : //APPENDER_FACTORY(null);
65 :
66 :
67 : }
68 :
69 :
70 18 : appender::appender(std::string const & name, std::string const & type)
71 : : f_type(type)
72 : , f_name(name)
73 18 : , f_normal_component(get_component(COMPONENT_NORMAL))
74 : {
75 36 : guard g;
76 :
77 18 : f_format = get_private_logger()->get_default_format();
78 18 : }
79 :
80 :
81 18 : appender::~appender()
82 : {
83 18 : }
84 :
85 :
86 3 : std::string const & appender::get_type() const
87 : {
88 3 : return f_type;
89 : }
90 :
91 :
92 0 : void appender::set_name(std::string const & name)
93 : {
94 0 : if(f_name != "console"
95 0 : && f_name != "syslog")
96 : {
97 : throw invalid_parameter(
98 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).");
99 : }
100 :
101 0 : f_name = name;
102 0 : }
103 :
104 :
105 1 : std::string const & appender::get_name() const
106 : {
107 1 : return f_name;
108 : }
109 :
110 :
111 89038 : bool appender::is_enabled() const
112 : {
113 89038 : return f_enabled;
114 : }
115 :
116 :
117 0 : void appender::set_enabled(bool status)
118 : {
119 0 : guard g;
120 :
121 0 : f_enabled = status;
122 0 : }
123 :
124 :
125 18 : bool appender::unique() const
126 : {
127 18 : return false;
128 : }
129 :
130 :
131 297 : severity_t appender::get_severity() const
132 : {
133 594 : guard g;
134 :
135 594 : return f_severity;
136 : }
137 :
138 :
139 281 : void appender::set_severity(severity_t severity_level)
140 : {
141 562 : guard g;
142 :
143 281 : f_severity = severity_level;
144 281 : logger::get_instance()->severity_changed(severity_level);
145 281 : }
146 :
147 :
148 0 : void appender::reduce_severity(severity_t severity_level)
149 : {
150 0 : guard g;
151 :
152 0 : if(severity_level < f_severity)
153 : {
154 0 : set_severity(severity_level);
155 : }
156 0 : }
157 :
158 :
159 0 : bool appender::operator < (appender const & rhs) const
160 : {
161 0 : return f_severity < rhs.f_severity;
162 : }
163 :
164 :
165 18 : void appender::set_config(advgetopt::getopt const & opts)
166 : {
167 36 : guard g;
168 :
169 : // ENABLE
170 : //
171 : {
172 36 : std::string const specialized_enabled(f_name + "::enabled");
173 18 : if(opts.is_defined(specialized_enabled))
174 : {
175 0 : f_enabled = opts.get_string(specialized_enabled) != "false";
176 : }
177 18 : else if(opts.is_defined("enabled"))
178 : {
179 0 : f_enabled = opts.get_string("enabled") != "false";
180 : }
181 : else
182 : {
183 18 : f_enabled = true;
184 : }
185 : }
186 :
187 : // FORMAT
188 : //
189 : {
190 36 : std::string const specialized_format(f_name + "::format");
191 18 : if(opts.is_defined(specialized_format))
192 : {
193 0 : f_format = std::make_shared<format>(opts.get_string(specialized_format));
194 : }
195 18 : else if(opts.is_defined("format"))
196 : {
197 0 : f_format = std::make_shared<format>(opts.get_string("format"));
198 : }
199 : }
200 :
201 : // SEVERITY
202 : //
203 36 : std::string const specialized_severity(f_name + "::severity");
204 18 : if(opts.is_defined(specialized_severity))
205 : {
206 0 : std::string const severity_name(opts.get_string(specialized_severity));
207 0 : severity::pointer_t sev(snaplogger::get_severity(severity_name));
208 0 : if(sev != nullptr)
209 : {
210 0 : set_severity(sev->get_severity());
211 : }
212 : else
213 : {
214 : throw invalid_severity(
215 : "severity level named \""
216 0 : + severity_name
217 0 : + "\" not found.");
218 : }
219 : }
220 18 : else if(opts.is_defined("severity"))
221 : {
222 0 : std::string const severity_name(opts.get_string("severity"));
223 0 : severity::pointer_t sev(snaplogger::get_severity(severity_name));
224 0 : if(sev != nullptr)
225 : {
226 0 : set_severity(sev->get_severity());
227 : }
228 : else
229 : {
230 : throw invalid_severity(
231 : "severity level named \""
232 0 : + severity_name
233 0 : + "\" not found.");
234 : }
235 : }
236 :
237 : // COMPONENTS
238 : //
239 36 : std::string comp;
240 36 : std::string const components(f_name + "::components");
241 18 : if(opts.is_defined(components))
242 : {
243 0 : comp = opts.get_string(components);
244 : }
245 18 : else if(opts.is_defined("components"))
246 : {
247 0 : comp = opts.get_string("components");
248 : }
249 18 : if(comp.empty())
250 : {
251 18 : add_component(f_normal_component);
252 : }
253 : else
254 : {
255 0 : advgetopt::string_list_t component_names;
256 0 : advgetopt::split_string(comp, component_names, {","});
257 0 : for(auto name : component_names)
258 : {
259 0 : add_component(get_component(name));
260 : }
261 : }
262 :
263 : // FILTER
264 : //
265 : {
266 36 : std::string filter;
267 36 : std::string const specialized_filter(f_name + "::filter");
268 18 : if(opts.is_defined(specialized_filter))
269 : {
270 0 : filter = opts.get_string(specialized_filter);
271 : }
272 18 : else if(opts.is_defined("filter"))
273 : {
274 0 : filter = opts.get_string("filter");
275 : }
276 18 : if(!filter.empty())
277 : {
278 0 : std::regex_constants::syntax_option_type flags(std::regex::nosubs | std::regex::optimize);
279 0 : std::regex_constants::syntax_option_type type(std::regex::extended);
280 0 : if(filter[0] == '/')
281 : {
282 0 : std::string::size_type pos(filter.rfind('/'));
283 0 : if(pos == 0)
284 : {
285 : throw invalid_variable(
286 : "invalid filter \""
287 0 : + filter
288 0 : + "\"; missing ending '/'.");
289 : }
290 0 : std::string const flag_list(filter.substr(pos + 1));
291 0 : filter = filter.substr(1, pos - 2);
292 0 : if(filter.empty())
293 : {
294 : throw invalid_variable(
295 : "invalid filter \""
296 0 : + filter
297 0 : + "\"; the regular expression is empty.");
298 : }
299 : // TODO: for errors we would need to iterate using the libutf8
300 : // (since we could have a Unicode character after the /)
301 : //
302 : // TODO: if two type flags are found, err too
303 : //
304 0 : int count(0);
305 0 : for(auto f : flag_list)
306 : {
307 0 : switch(f)
308 : {
309 0 : case 'i':
310 0 : flags |= std::regex::icase;
311 0 : break;
312 :
313 0 : case 'c':
314 0 : flags |= std::regex::collate;
315 0 : break;
316 :
317 0 : case 'j':
318 0 : type = std::regex::ECMAScript;
319 0 : ++count;
320 0 : break;
321 :
322 0 : case 'b':
323 0 : type = std::regex::basic;
324 0 : ++count;
325 0 : break;
326 :
327 0 : case 'x':
328 0 : type = std::regex::extended;
329 0 : ++count;
330 0 : break;
331 :
332 0 : case 'a':
333 0 : type = std::regex::awk;
334 0 : ++count;
335 0 : break;
336 :
337 0 : case 'g':
338 0 : type = std::regex::grep;
339 0 : ++count;
340 0 : break;
341 :
342 0 : case 'e':
343 0 : type = std::regex::egrep;
344 0 : ++count;
345 0 : break;
346 :
347 0 : default:
348 : throw invalid_variable(
349 : "in \""
350 0 : + filter
351 0 : + "\", found invalid flag '"
352 0 : + f
353 0 : + "'.");
354 :
355 : }
356 0 : if(count > 1)
357 : {
358 : throw invalid_variable(
359 : "found multiple types in \""
360 0 : + filter
361 0 : + "\".");
362 : }
363 : }
364 : }
365 0 : f_filter = std::make_shared<std::regex>(filter, flags | type);
366 : }
367 : }
368 :
369 : // REPEAT
370 : //
371 : {
372 36 : std::string no_repeat(f_name + "::no-repeat");
373 18 : if(!opts.is_defined(no_repeat))
374 : {
375 18 : if(opts.is_defined("no-repeat"))
376 : {
377 0 : no_repeat = "no-repeat";
378 : }
379 : else
380 : {
381 18 : no_repeat.clear();
382 : }
383 : }
384 18 : if(!no_repeat.empty())
385 : {
386 0 : std::string const value(opts.get_string(no_repeat));
387 0 : if(value != "off")
388 : {
389 0 : if(value == "max"
390 0 : || value == "maximum")
391 : {
392 0 : f_no_repeat_size = NO_REPEAT_MAXIMUM;
393 : }
394 0 : else if(value == "default")
395 : {
396 0 : f_no_repeat_size = NO_REPEAT_DEFAULT;
397 : }
398 : else
399 : {
400 0 : f_no_repeat_size = opts.get_long("no-repeat", 0, 0, NO_REPEAT_MAXIMUM);
401 : }
402 : }
403 : }
404 : }
405 18 : }
406 :
407 :
408 0 : void appender::reopen()
409 : {
410 0 : }
411 :
412 :
413 19 : void appender::add_component(component::pointer_t comp)
414 : {
415 19 : f_components.insert(comp);
416 19 : }
417 :
418 :
419 40 : format::pointer_t appender::set_format(format::pointer_t new_format)
420 : {
421 80 : guard g;
422 :
423 40 : format::pointer_t old(f_format);
424 40 : f_format = new_format;
425 80 : return old;
426 : }
427 :
428 :
429 89038 : void appender::send_message(message const & msg)
430 : {
431 178076 : if(!is_enabled()
432 89038 : || msg.get_severity() < f_severity)
433 : {
434 91690 : return;
435 : }
436 :
437 45374 : component::set_t const & components(msg.get_components());
438 45374 : if(components.empty())
439 : {
440 : // user did not supply any component in 'msg', check for
441 : // the normal component
442 : //
443 90740 : if(!f_components.empty()
444 45370 : && f_components.find(f_normal_component) == f_components.end())
445 : {
446 0 : return;
447 : }
448 : }
449 : else
450 : {
451 4 : if(snap::empty_set_intersection(f_components, components))
452 : {
453 2 : return;
454 : }
455 : }
456 :
457 86379 : std::string formatted_message(f_format->process_message(msg));
458 45367 : if(formatted_message.empty())
459 : {
460 4360 : return;
461 : }
462 :
463 82014 : if(f_filter != nullptr
464 41007 : && !std::regex_match(formatted_message, *f_filter))
465 : {
466 0 : return;
467 : }
468 :
469 82014 : if(formatted_message.back() != '\n'
470 41007 : && formatted_message.back() != '\r')
471 : {
472 : // TODO: add support to define line terminator (cr, nl, cr nl)
473 : //
474 41007 : formatted_message += '\n';
475 : }
476 :
477 41007 : if(f_no_repeat_size > 0)
478 : {
479 0 : std::string const non_changing_message(f_format->process_message(msg, true));
480 0 : auto it(std::find(f_last_messages.rbegin(), f_last_messages.rend(), non_changing_message));
481 0 : if(it != f_last_messages.rend())
482 : {
483 : // TODO: look into a way to count said messages and print out
484 : // the total number or something of the sort...
485 : // (maybe we store those messages in a buffer and once
486 : // we are to replace a message, that's when we forward
487 : // it and that can include the count? also that extra
488 : // write can be based on time and/or count)
489 : //
490 0 : f_last_messages.erase(std::next(it).base()); // erase() expects an iterator, not a reverse iterator
491 : }
492 0 : f_last_messages.push_back(non_changing_message);
493 0 : if(f_last_messages.size() > f_no_repeat_size)
494 : {
495 0 : f_last_messages.pop_front();
496 : }
497 : }
498 :
499 41007 : process_message(msg, formatted_message);
500 : }
501 :
502 :
503 0 : void appender::process_message(message const & msg, std::string const & formatted_message)
504 : {
505 : // the default is a "null appender" -- do nothing
506 0 : snap::NOTUSED(msg);
507 0 : snap::NOTUSED(formatted_message);
508 0 : }
509 :
510 :
511 :
512 :
513 :
514 8 : appender_factory::appender_factory(std::string const & type)
515 8 : : f_type(type)
516 : {
517 8 : }
518 :
519 :
520 8 : appender_factory::~appender_factory()
521 : {
522 8 : }
523 :
524 :
525 16 : std::string const & appender_factory::get_type() const
526 : {
527 16 : return f_type;
528 : }
529 :
530 :
531 :
532 :
533 8 : void register_appender_factory(appender_factory::pointer_t factory)
534 : {
535 8 : get_private_logger()->register_appender_factory(factory);
536 8 : }
537 :
538 :
539 2 : appender::pointer_t create_appender(std::string const & type, std::string const & name)
540 : {
541 2 : return get_private_logger()->create_appender(type, name);
542 : }
543 :
544 :
545 :
546 :
547 :
548 :
549 0 : safe_format::safe_format(appender::pointer_t a, format::pointer_t new_format)
550 : : f_appender(a)
551 0 : , f_old_format(a->set_format(new_format))
552 : {
553 0 : }
554 :
555 :
556 0 : safe_format::~safe_format()
557 : {
558 0 : snap::NOTUSED(f_appender->set_format(f_old_format));
559 0 : }
560 :
561 :
562 :
563 :
564 :
565 :
566 :
567 6 : } // snaplogger namespace
568 : // vim: ts=4 sw=4 et
|