Line data Source code
1 : // Copyright (c) 2013-2025 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 Implementation of the various logger variable support.
22 : *
23 : * This file implements the few logger variables which retrieves their
24 : * value from the message variable.
25 : */
26 :
27 :
28 : // self
29 : //
30 : #include "snaplogger/exception.h"
31 : #include "snaplogger/format.h"
32 : #include "snaplogger/map_diagnostic.h"
33 : #include "snaplogger/nested_diagnostic.h"
34 : #include "snaplogger/syslog_appender.h"
35 : #include "snaplogger/trace_diagnostic.h"
36 : #include "snaplogger/variable.h"
37 :
38 :
39 : // C++
40 : //
41 : #include <iostream>
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 55 : DEFINE_LOGGER_VARIABLE(severity)
60 : {
61 : enum class format_t
62 : {
63 : FORMAT_ALPHA,
64 : FORMAT_NUMBER,
65 : FORMAT_SYSTEMD,
66 : };
67 :
68 34 : format_t format(format_t::FORMAT_ALPHA);
69 :
70 34 : auto params(get_params());
71 34 : if(!params.empty())
72 : {
73 26 : if(params[0]->get_name() == "format")
74 : {
75 26 : auto v(params[0]->get_value());
76 26 : if(v == "alpha")
77 : {
78 9 : format = format_t::FORMAT_ALPHA;
79 : }
80 17 : else if(v == "number")
81 : {
82 7 : format = format_t::FORMAT_NUMBER;
83 : }
84 10 : else if(v == "systemd")
85 : {
86 9 : format = format_t::FORMAT_SYSTEMD;
87 : }
88 : else
89 : {
90 : throw invalid_variable(
91 : "the ${severity:format=alpha|number|systemd} variable cannot be set to \""
92 2 : + v
93 3 : + "\".");
94 : }
95 26 : }
96 : }
97 :
98 33 : severity_t sev(msg.get_severity());
99 33 : switch(format)
100 : {
101 17 : case format_t::FORMAT_ALPHA:
102 : {
103 17 : severity::pointer_t severity(get_severity(msg, sev));
104 17 : if(severity != nullptr)
105 : {
106 17 : value += severity->get_description();
107 17 : break;
108 : }
109 17 : }
110 : [[fallthrough]];
111 : case format_t::FORMAT_NUMBER:
112 7 : value += std::to_string(static_cast<int>(sev));
113 7 : break;
114 :
115 9 : case format_t::FORMAT_SYSTEMD:
116 : // see https://www.freedesktop.org/software/systemd/man/sd-daemon.html
117 : //
118 9 : value += '<';
119 9 : value += std::to_string(syslog_appender::message_severity_to_syslog_priority(sev));
120 9 : value += '>';
121 9 : break;
122 :
123 : }
124 :
125 33 : variable::process_value(msg, value);
126 67 : }
127 :
128 :
129 :
130 41705 : DEFINE_LOGGER_VARIABLE(message)
131 : {
132 41648 : if(msg.get_recursive_message())
133 : {
134 : // do nothing if we find a ${message} inside the message itself
135 2 : return;
136 : }
137 :
138 41646 : std::string const m(msg.get_message());
139 41646 : if(m.find("${") == std::string::npos)
140 : {
141 41634 : value += m;
142 : }
143 : else
144 : {
145 12 : msg.set_recursive_message(true);
146 12 : format f(m);
147 12 : value += f.process_message(msg);
148 12 : msg.set_recursive_message(false);
149 12 : }
150 :
151 41646 : variable::process_value(msg, value);
152 41646 : }
153 :
154 :
155 :
156 12 : DEFINE_LOGGER_VARIABLE_IGNORED_ON_NO_REPEAT(field)
157 : {
158 2 : auto params(get_params());
159 2 : if(!params.empty())
160 : {
161 2 : if(params[0]->get_name() == "name")
162 : {
163 2 : auto name(params[0]->get_value());
164 2 : value += msg.get_field(name);
165 2 : }
166 : }
167 :
168 2 : variable::process_value(msg, value);
169 4 : }
170 :
171 :
172 :
173 10 : DEFINE_LOGGER_VARIABLE_IGNORED_ON_NO_REPEAT(fields)
174 : {
175 : enum class format_t
176 : {
177 : FORMAT_JSON,
178 : FORMAT_SHELL
179 : };
180 : enum class json_t
181 : {
182 : START_COMMA,
183 : END_COMMA,
184 : OBJECT
185 : };
186 :
187 1 : format_t format(format_t::FORMAT_JSON);
188 1 : json_t json(json_t::OBJECT);
189 :
190 1 : auto params(get_params());
191 1 : for(auto p : params)
192 : {
193 0 : auto const v(p->get_value());
194 0 : if(p->get_name() == "format")
195 : {
196 0 : if(v == "json")
197 : {
198 0 : format = format_t::FORMAT_JSON;
199 : }
200 0 : else if(v == "shell")
201 : {
202 0 : format = format_t::FORMAT_SHELL;
203 : }
204 : else
205 : {
206 : throw invalid_variable(
207 : "the ${fields:format=json|shell} variable cannot be set to \""
208 0 : + v
209 0 : + "\".");
210 : }
211 : }
212 0 : else if(p->get_name() == "json")
213 : {
214 0 : if(v == "start-comma")
215 : {
216 0 : json = json_t::START_COMMA;
217 : }
218 0 : else if(v == "end-comma")
219 : {
220 0 : json = json_t::END_COMMA;
221 : }
222 0 : else if(v == "object")
223 : {
224 0 : json = json_t::OBJECT;
225 : }
226 : else
227 : {
228 : throw invalid_variable(
229 : "the ${fields:json=start-comma|end-comma|object} variable cannot be set to \""
230 0 : + v
231 0 : + "\".");
232 : }
233 : }
234 0 : }
235 :
236 1 : int start_comma(0);
237 1 : switch(format)
238 : {
239 1 : case format_t::FORMAT_JSON:
240 1 : if(json == json_t::OBJECT)
241 : {
242 1 : value += "{";
243 1 : start_comma = 1;
244 : }
245 1 : break;
246 :
247 0 : case format_t::FORMAT_SHELL:
248 : // nothing for this one
249 0 : break;
250 :
251 : }
252 :
253 4 : for(auto f : msg.get_fields())
254 : {
255 3 : switch(format)
256 : {
257 3 : case format_t::FORMAT_JSON:
258 3 : if(json == json_t::START_COMMA || start_comma == 2)
259 : {
260 2 : value += ",";
261 : }
262 : // TODO: need to test for quotes inside "first" or "second"
263 3 : value += "\"" + f.first + "\":" + "\"" + f.second + "\"";
264 3 : if(json == json_t::END_COMMA)
265 : {
266 0 : value += ",";
267 : }
268 3 : else if(json == json_t::OBJECT)
269 : {
270 : // if more than one field, make sure to add commas
271 : // between each field
272 : //
273 3 : start_comma = 2;
274 : }
275 3 : break;
276 :
277 0 : case format_t::FORMAT_SHELL:
278 0 : value += f.first + "=" + f.second + "\n";
279 0 : break;
280 :
281 : }
282 4 : }
283 1 : switch(format)
284 : {
285 1 : case format_t::FORMAT_JSON:
286 1 : if(json == json_t::OBJECT)
287 : {
288 1 : value += "}";
289 : }
290 1 : break;
291 :
292 0 : case format_t::FORMAT_SHELL:
293 : // nothing for this one
294 0 : break;
295 :
296 : }
297 :
298 1 : variable::process_value(msg, value);
299 2 : }
300 :
301 :
302 :
303 25 : DEFINE_LOGGER_VARIABLE(project_name)
304 : {
305 : // when the advgetopt is properly connected to the logger, then the
306 : // logger will save the project name in its diagnostic map
307 : //
308 11 : map_diagnostics_t map(get_map_diagnostics(msg));
309 33 : auto it(map.find(DIAG_KEY_PROJECT_NAME));
310 11 : if(it != map.end())
311 : {
312 11 : value += it->second;
313 : }
314 :
315 11 : variable::process_value(msg, value);
316 22 : }
317 :
318 :
319 :
320 13 : DEFINE_LOGGER_VARIABLE(progname)
321 : {
322 : // when the advgetopt is properly connected to the logger, then the
323 : // logger will save the program name in its diagnostic map
324 : //
325 3 : map_diagnostics_t map(get_map_diagnostics(msg));
326 9 : auto it(map.find(DIAG_KEY_PROGNAME));
327 3 : if(it != map.end())
328 : {
329 3 : value += it->second;
330 : }
331 :
332 3 : variable::process_value(msg, value);
333 6 : }
334 :
335 :
336 :
337 29 : DEFINE_LOGGER_VARIABLE(version)
338 : {
339 : // when the advgetopt is properly connected to the logger, then the
340 : // logger will save the program version in its diagnostic map
341 : //
342 14 : map_diagnostics_t map(get_map_diagnostics(msg));
343 42 : auto it(map.find(DIAG_KEY_VERSION));
344 14 : if(it != map.end())
345 : {
346 12 : value += it->second;
347 : }
348 :
349 14 : variable::process_value(msg, value);
350 28 : }
351 :
352 :
353 :
354 8 : DEFINE_LOGGER_VARIABLE(build_date)
355 : {
356 0 : map_diagnostics_t map(get_map_diagnostics(msg));
357 0 : auto it(map.find(DIAG_KEY_BUILD_DATE));
358 0 : if(it != map.end())
359 : {
360 0 : value += it->second;
361 : }
362 :
363 0 : variable::process_value(msg, value);
364 0 : }
365 :
366 :
367 :
368 8 : DEFINE_LOGGER_VARIABLE(build_time)
369 : {
370 0 : map_diagnostics_t map(get_map_diagnostics(msg));
371 0 : auto it(map.find(DIAG_KEY_BUILD_TIME));
372 0 : if(it != map.end())
373 : {
374 0 : value += it->second;
375 : }
376 :
377 0 : variable::process_value(msg, value);
378 0 : }
379 :
380 :
381 :
382 8 : DEFINE_LOGGER_VARIABLE(filename)
383 : {
384 0 : value += msg.get_filename();
385 :
386 0 : variable::process_value(msg, value);
387 0 : }
388 :
389 :
390 :
391 9 : DEFINE_LOGGER_VARIABLE(basename)
392 : {
393 0 : std::string const filename(msg.get_filename());
394 0 : std::string::size_type const pos(filename.rfind('/'));
395 0 : if(pos == std::string::npos)
396 : {
397 0 : value += filename;
398 : }
399 : else
400 : {
401 0 : value += filename.substr(pos + 1);
402 : }
403 :
404 0 : variable::process_value(msg, value);
405 0 : }
406 :
407 :
408 :
409 8 : DEFINE_LOGGER_VARIABLE(path)
410 : {
411 0 : std::string const filename(msg.get_filename());
412 0 : std::string::size_type const pos(filename.rfind('/'));
413 0 : if(pos != std::string::npos)
414 : {
415 0 : value += filename.substr(0, pos);
416 : }
417 :
418 0 : variable::process_value(msg, value);
419 0 : }
420 :
421 :
422 :
423 9 : DEFINE_LOGGER_VARIABLE(function)
424 : {
425 0 : value += msg.get_function();
426 :
427 0 : variable::process_value(msg, value);
428 0 : }
429 :
430 :
431 :
432 11 : DEFINE_LOGGER_VARIABLE(line)
433 : {
434 1 : value += std::to_string(msg.get_line());
435 :
436 1 : variable::process_value(msg, value);
437 1 : }
438 :
439 :
440 :
441 26 : DEFINE_LOGGER_VARIABLE(diagnostic)
442 : {
443 9 : constexpr int FLAG_NESTED = 0x01;
444 9 : constexpr int FLAG_MAP = 0x02;
445 9 : constexpr int FLAG_TRACE = 0x04;
446 :
447 9 : std::int64_t nested_depth(-1);
448 9 : std::int64_t trace_count(-1);
449 9 : std::string key;
450 9 : int flags(0);
451 :
452 18 : for(auto const & p : get_params())
453 : {
454 9 : if(p->get_name() == "nested")
455 : {
456 4 : flags |= FLAG_NESTED;
457 :
458 : // the integer is optional
459 : //
460 4 : if(!p->empty())
461 : {
462 4 : nested_depth = p->get_integer();
463 : }
464 : }
465 5 : else if(p->get_name() == "map")
466 : {
467 5 : flags |= FLAG_MAP;
468 5 : key = p->get_value();
469 : }
470 0 : else if(p->get_name() == "trace")
471 : {
472 0 : flags |= FLAG_TRACE;
473 :
474 : // the integer is optional
475 : //
476 0 : if(!p->empty())
477 : {
478 0 : trace_count = p->get_integer();
479 : }
480 : }
481 9 : }
482 :
483 9 : if(flags == 0
484 9 : || (flags & FLAG_NESTED) != 0)
485 : {
486 4 : char sep('{');
487 4 : string_vector_t nested(get_nested_diagnostics(msg));
488 4 : size_t idx(0);
489 8 : if((flags & FLAG_NESTED) != 0
490 4 : && nested_depth != -1
491 8 : && static_cast<ssize_t>(nested.size()) > nested_depth)
492 : {
493 1 : idx = nested.size() - nested_depth;
494 1 : value += sep;
495 1 : value += "...";
496 1 : sep = '/';
497 : }
498 :
499 12 : for(; idx < nested.size(); ++idx)
500 : {
501 8 : value += sep;
502 8 : sep = '/';
503 8 : value += nested[idx];
504 : }
505 4 : value += "}";
506 4 : }
507 :
508 9 : if(flags == 0
509 9 : || (flags & FLAG_TRACE) != 0)
510 : {
511 0 : trace_diagnostics_t trace(get_trace_diagnostics());
512 0 : if(trace.empty())
513 : {
514 0 : value += "[<no trace>]";
515 : }
516 : else
517 : {
518 0 : auto it(trace.begin());
519 0 : size_t sz(trace.size());
520 0 : if((flags & FLAG_TRACE) != 0
521 0 : && trace_count > 0)
522 : {
523 0 : for(; static_cast<int64_t>(sz) > trace_count && it != trace.end(); ++it, --sz);
524 : }
525 :
526 0 : char sep('[');
527 0 : for(; it != trace.end(); ++it)
528 : {
529 0 : value += sep;
530 0 : sep = '/';
531 0 : value += *it;
532 : }
533 0 : value += "]";
534 : }
535 0 : }
536 :
537 9 : if(flags == 0
538 9 : || (flags & FLAG_MAP) != 0)
539 : {
540 5 : auto const diagnostics(get_map_diagnostics(msg));
541 5 : if(!diagnostics.empty())
542 : {
543 5 : if(key.empty())
544 : {
545 0 : char sep('<');
546 0 : for(auto d : diagnostics)
547 : {
548 0 : value += sep;
549 0 : sep = ':';
550 0 : value += d.first;
551 0 : value += '=';
552 0 : value += d.second;
553 0 : }
554 0 : value += '>';
555 : }
556 : else
557 : {
558 5 : auto it(diagnostics.find(key));
559 5 : if(it != diagnostics.end())
560 : {
561 5 : value += '<';
562 5 : value += key;
563 5 : value += '=';
564 5 : value += it->second;
565 5 : value += '>';
566 : }
567 : }
568 : }
569 5 : }
570 :
571 9 : variable::process_value(msg, value);
572 18 : }
573 :
574 :
575 12 : DEFINE_LOGGER_VARIABLE(components)
576 : {
577 3 : component::set_t const & components(msg.get_components());
578 3 : if(!components.empty())
579 : {
580 3 : char sep('[');
581 10 : for(auto c : components)
582 : {
583 7 : value += sep;
584 7 : sep = ',';
585 7 : value += c->get_name();
586 7 : }
587 3 : value += ']';
588 :
589 3 : variable::process_value(msg, value);
590 : }
591 3 : }
592 :
593 :
594 : }
595 : // no name namespace
596 :
597 :
598 : } // snaplogger namespace
599 : // vim: ts=4 sw=4 et
|