Branch data Line data Source code
1 : : #include "rational.h"
2 : : #include <limits.h>
3 : : #include <stdint.h>
4 : : #include <stdlib.h>
5 : : #include <stdio.h>
6 : : #include <string.h>
7 : :
8 : : // Global flag to manage output of all logging messages
9 : : // in an application and its default value
10 : : _Atomic LOGMODES rational_logger_mode = REGULAR;
11 : : _Atomic Return global_return_status = SUCCESS;
12 : :
13 : : /**
14 : : * @brief Converts LOGMODES bit flags to their string representation
15 : : *
16 : : * @details This function takes a combination of LOGMODES flags and converts them
17 : : * into a human-readable string representation where individual flags
18 : : * are separated by " | ". For example, (VERBOSE | SILENT) will be
19 : : * converted to "VERBOSE | SILENT"
20 : : *
21 : : * @param mode Combination of LOGMODES flags
22 : : * @return char* Pointer to static string containing flag names
23 : : *
24 : : * @note The function uses a static buffer which means:
25 : : * 1. No memory allocation/deallocation is needed
26 : : * 2. The buffer contents will be overwritten on next function call
27 : : * 3. The function is not thread-safe
28 : : * 4. The returned pointer should not be freed
29 : : *
30 : : * @warning Maximum resulting string length is limited to 256 characters
31 : : */
32 : 1520 : char *rational_reconvert(LOGMODES mode)
33 : : {
34 : : /* Static buffer to store the resulting string */
35 : : static char buffer[MAX_CHARACTERS];
36 : 1520 : buffer[0] = '\0'; /* Initialize buffer as empty string */
37 : :
38 : : /* Flag to track if we're adding the first item (for | separator) */
39 : 1520 : int first = 1;
40 : :
41 : : /* Define mapping between flag values and their string representations
42 : : * The array is terminated with {0, NULL} for easy iteration
43 : : */
44 : : static const struct {
45 : : LOGMODES flag; /* Flag value from LOGMODES constants */
46 : : const char *name; /* String representation of the flag */
47 : : } mapping[] = {
48 : : {REGULAR,"REGULAR"},
49 : : {VERBOSE,"VERBOSE"},
50 : : {TESTING,"TESTING"},
51 : : {ERROR,"ERROR"},
52 : : {SILENT,"SILENT"},
53 : : {UNDECOR,"UNDECOR"},
54 : : {REMEMBER,"REMEMBER"},
55 : : {VISIBLE_IN_SILENT,"VISIBLE_IN_SILENT"},
56 : : {0,NULL} /* Terminator element */
57 : : };
58 : :
59 : : /* Iterate through all possible flags */
60 [ + + ]: 13680 : for(int i = 0; mapping[i].name != NULL; i++)
61 : : {
62 : : /* Check if current flag is set in mode using bitwise AND */
63 [ + + ]: 12160 : if(mode & mapping[i].flag)
64 : : {
65 : : /* Add separator before all elements except the first one */
66 [ + + ]: 1536 : if(!first)
67 : : {
68 : 17 : strcat(buffer," | ");
69 : : }
70 : :
71 : : /* Add flag name to the result string */
72 : 1536 : strcat(buffer,mapping[i].name);
73 : :
74 : : /* Clear first flag as we've added an element */
75 : 1536 : first = 0;
76 : : }
77 : : }
78 : :
79 : 1520 : return buffer;
80 : : }
81 : :
82 : : /**
83 : : *
84 : : * @brief Format current date and time in ISO format
85 : : * @param time_string Pointer to the destination buffer that receives the timestamp.
86 : : * @param buffer_size Size of the destination buffer in bytes.
87 : : * @return Return SUCCESS on success, FAILURE on error (buffer contents will be empty on failure).
88 : : *
89 : : */
90 : 325 : static Return logger_show_time(
91 : : char *time_string,
92 : : size_t buffer_size)
93 : : {
94 : : /* Status returned by this function through provide()
95 : : Default value assumes successful completion */
96 : 325 : Return status = SUCCESS;
97 : :
98 : : struct timeval current_time;
99 : : struct tm local_time;
100 : :
101 [ + + ]: 325 : if(gettimeofday(¤t_time,NULL) != 0)
102 : : {
103 : 1 : time_string[0] = '\0';
104 : 1 : status = FAILURE;
105 : : }
106 : :
107 [ + + ]: 325 : if(SUCCESS == status)
108 : : {
109 [ + + ]: 324 : if(localtime_r(¤t_time.tv_sec,&local_time) == NULL)
110 : : {
111 : 1 : time_string[0] = '\0';
112 : 1 : status = FAILURE;
113 : : }
114 : : }
115 : :
116 [ + + ]: 325 : if(SUCCESS == status)
117 : : {
118 : 323 : const int milliseconds = (int)(current_time.tv_usec / 1000);
119 : :
120 : 323 : if(snprintf(time_string,
121 : : buffer_size,
122 : : "%04d-%02d-%02d %02d:%02d:%02d:%03d",
123 : 323 : local_time.tm_year + 1900,
124 [ + + ]: 323 : local_time.tm_mon + 1,
125 : : local_time.tm_mday,
126 : : local_time.tm_hour,
127 : : local_time.tm_min,
128 : : local_time.tm_sec,
129 : : milliseconds) < 0)
130 : : {
131 : 1 : time_string[0] = '\0';
132 : 1 : status = FAILURE;
133 : : }
134 : : }
135 : :
136 : 325 : return(status);
137 : : }
138 : :
139 : : __attribute__((format(printf,3,0)))
140 : 46907 : static void logger_line_append_va(
141 : : char **line,
142 : : int *line_len,
143 : : const char *fmt,
144 : : va_list args)
145 : : {
146 : : va_list args_copy;
147 : 46907 : va_copy(args_copy,args);
148 : 46907 : const int needed = vsnprintf(NULL,0,fmt,args_copy);
149 : 46907 : va_end(args_copy);
150 : :
151 [ + + ]: 46907 : if(needed < 0)
152 : : {
153 : 3 : return;
154 : : }
155 : :
156 : 46905 : const size_t new_len = (size_t)(*line_len) + (size_t)needed;
157 : 46905 : char *tmp = realloc(*line,new_len + 1);
158 : :
159 [ + + ]: 46905 : if(tmp == NULL)
160 : : {
161 : 1 : return;
162 : : }
163 : :
164 : 46904 : *line = tmp;
165 : :
166 : : va_list args_copy2;
167 : 46904 : va_copy(args_copy2,args);
168 : 46904 : vsnprintf(*line + *line_len,(size_t)needed + 1,fmt,args_copy2);
169 : 46904 : va_end(args_copy2);
170 : :
171 : 46904 : *line_len = (int)new_len;
172 : : }
173 : :
174 : : __attribute__((format(printf,3,4)))
175 : 21002 : static void logger_line_append(
176 : : char **line,
177 : : int *line_len,
178 : : const char *fmt,
179 : : ...)
180 : : {
181 : : va_list args;
182 : 21002 : va_start(args,fmt);
183 : 21002 : logger_line_append_va(line,line_len,fmt,args);
184 : 21002 : va_end(args);
185 : 21002 : }
186 : :
187 : : /**
188 : : * @brief Append one byte as a visible hexadecimal escape
189 : : *
190 : : * @param[in,out] line Destination buffer with enough free space
191 : : * @param[in,out] line_len Current destination length in bytes
192 : : * @param[in] byte Byte to append as \xNN
193 : : */
194 : 8 : static void logger_line_append_hex_escape(
195 : : char *line,
196 : : size_t *line_len,
197 : : unsigned char byte)
198 : : {
199 : : /*
200 : : * Use an explicit digit table instead of sprintf().
201 : : * This helper is used from the logger cleanup path, so it should not depend
202 : : * on formatting functions or temporary buffers just to render one byte
203 : : */
204 : : static const char hex_digits[] = "0123456789ABCDEF";
205 : :
206 : : /*
207 : : * Write the escape directly into the caller-owned output buffer.
208 : : * The sanitizer allocates enough room before calling this helper, and
209 : : * line_len is advanced after every written character so the next append can
210 : : * continue from the correct position
211 : : */
212 : 8 : line[(*line_len)++] = '\\';
213 : 8 : line[(*line_len)++] = 'x';
214 : 8 : line[(*line_len)++] = hex_digits[byte >> 4U];
215 : 8 : line[(*line_len)++] = hex_digits[byte & 0x0FU];
216 : 8 : }
217 : :
218 : : /**
219 : : * @brief Decode one UTF-8 sequence from a bounded byte range
220 : : *
221 : : * @details
222 : : * The logger uses this small decoder instead of locale-dependent multibyte
223 : : * conversion because the process locale is not guaranteed to be initialized
224 : : * before a log line is printed. The function accepts only shortest-form UTF-8,
225 : : * rejects surrogate code points, and rejects values outside the Unicode range
226 : : *
227 : : * @param[in] line Source byte range
228 : : * @param[in] line_len Number of bytes available at @p line
229 : : * @param[out] codepoint Decoded Unicode code point
230 : : * @return Number of bytes consumed, or 0 when the input is not valid UTF-8
231 : : */
232 : 6264 : static size_t logger_line_decode_utf8(
233 : : const char *line,
234 : : size_t line_len,
235 : : uint32_t *codepoint)
236 : : {
237 : : /*
238 : : * Refuse invalid input before looking at the first byte.
239 : : * Returning 0 tells the caller to treat the current byte as unsafe and
240 : : * print it as a visible escape instead of trusting it as text
241 : : */
242 [ + - + - : 6264 : if(line == NULL || codepoint == NULL || line_len == 0U)
- + ]
243 : : {
244 : 0 : return(0U);
245 : : }
246 : :
247 : 6264 : const unsigned char first_byte = (unsigned char)line[0];
248 : :
249 : : /*
250 : : * ASCII is a single-byte subset of UTF-8.
251 : : * Decoding it here keeps the rest of the function focused on multibyte
252 : : * sequences and lets the caller apply its own ASCII control-character policy
253 : : */
254 [ - + ]: 6264 : if(first_byte < 0x80U)
255 : : {
256 : 0 : *codepoint = (uint32_t)first_byte;
257 : 0 : return(1U);
258 : : }
259 : :
260 : : /*
261 : : * Classify the leading byte and prepare the partial code point.
262 : : * The first byte tells us how many continuation bytes must follow and also
263 : : * provides the high bits of the decoded Unicode value
264 : : */
265 : 6264 : size_t expected_len = 0U;
266 : 6264 : uint32_t decoded_codepoint = 0U;
267 : 6264 : uint32_t lowest_codepoint = 0U;
268 : :
269 [ + + + + ]: 6264 : if(first_byte >= 0xC2U && first_byte <= 0xDFU)
270 : : {
271 : 5401 : expected_len = 2U;
272 : 5401 : decoded_codepoint = (uint32_t)(first_byte & 0x1FU);
273 : 5401 : lowest_codepoint = 0x80U;
274 [ + + + - ]: 863 : } else if(first_byte >= 0xE0U && first_byte <= 0xEFU){
275 : 861 : expected_len = 3U;
276 : 861 : decoded_codepoint = (uint32_t)(first_byte & 0x0FU);
277 : 861 : lowest_codepoint = 0x800U;
278 [ - + - - ]: 2 : } else if(first_byte >= 0xF0U && first_byte <= 0xF4U){
279 : 0 : expected_len = 4U;
280 : 0 : decoded_codepoint = (uint32_t)(first_byte & 0x07U);
281 : 0 : lowest_codepoint = 0x10000U;
282 : : } else {
283 : : /*
284 : : * Bytes outside these leading-byte ranges cannot start a valid UTF-8
285 : : * sequence. This includes continuation bytes seen without a starter,
286 : : * obsolete overlong starters, and values beyond the Unicode limit
287 : : */
288 : 2 : return(0U);
289 : : }
290 : :
291 : : /*
292 : : * A valid sequence must be complete inside the provided byte range.
293 : : * The logger works with bounded buffers, so an incomplete trailing sequence
294 : : * is escaped byte by byte rather than reading past the formatted line
295 : : */
296 [ - + ]: 6262 : if(line_len < expected_len)
297 : : {
298 : 0 : return(0U);
299 : : }
300 : :
301 : : /*
302 : : * Every byte after the leading byte must have the UTF-8 continuation shape
303 : : * 10xxxxxx. While checking that shape, assemble the final code point by
304 : : * shifting in the six payload bits carried by each continuation byte
305 : : */
306 [ + + ]: 13383 : for(size_t i = 1U; i < expected_len; i++)
307 : : {
308 : 7123 : const unsigned char continuation_byte = (unsigned char)line[i];
309 : :
310 [ + + ]: 7123 : if((continuation_byte & 0xC0U) != 0x80U)
311 : : {
312 : 2 : return(0U);
313 : : }
314 : :
315 : 7121 : decoded_codepoint = (decoded_codepoint << 6U) | (uint32_t)(continuation_byte & 0x3FU);
316 : : }
317 : :
318 : : /*
319 : : * Reject overlong encodings.
320 : : * UTF-8 has exactly one shortest byte representation for each code point,
321 : : * and accepting longer aliases would let unsafe bytes hide behind another
322 : : * spelling of the same character
323 : : */
324 [ - + ]: 6260 : if(decoded_codepoint < lowest_codepoint)
325 : : {
326 : 0 : return(0U);
327 : : }
328 : :
329 : : /*
330 : : * UTF-16 surrogate values are not Unicode scalar values.
331 : : * They are invalid in UTF-8 text, so the logger escapes their original bytes
332 : : * instead of copying them into terminal output
333 : : */
334 [ - + - - ]: 6260 : if(decoded_codepoint >= 0xD800U && decoded_codepoint <= 0xDFFFU)
335 : : {
336 : 0 : return(0U);
337 : : }
338 : :
339 : : /*
340 : : * Unicode ends at U+10FFFF.
341 : : * Anything above that value is invalid input and must be shown as escaped
342 : : * bytes, not as trusted text
343 : : */
344 [ - + ]: 6260 : if(decoded_codepoint > 0x10FFFFU)
345 : : {
346 : 0 : return(0U);
347 : : }
348 : :
349 : : /*
350 : : * At this point the byte sequence is well-formed UTF-8.
351 : : * Return both the decoded code point and the number of bytes consumed so the
352 : : * sanitizer can decide whether the character is safe to copy
353 : : */
354 : 6260 : *codepoint = decoded_codepoint;
355 : 6260 : return(expected_len);
356 : : }
357 : :
358 : : /**
359 : : * @brief Escape bytes that can disrupt terminal output
360 : : *
361 : : * @details
362 : : * The logger receives an already formatted line, so this layer cannot tell
363 : : * whether bytes came from a file name, a database path, an error message, or
364 : : * fixed application text. The filter therefore treats the complete log line as
365 : : * terminal output and escapes only byte patterns that are unsafe to write as
366 : : * text. Plain printable ASCII, newline, carriage return, tab, raw ESC, and
367 : : * valid non-C1 UTF-8 multibyte sequences are preserved. Invalid multibyte input
368 : : * is escaped byte by byte so malformed file names remain visible without being
369 : : * interpreted by the terminal. Unicode C1 control characters such as U+0090
370 : : * are also escaped, even though their UTF-8 byte sequence is formally valid,
371 : : * because terminals may interpret them as control strings and hide later output.
372 : : *
373 : : * This first pass deliberately leaves raw ESC bytes unchanged. The project uses
374 : : * ESC-based decorations such as bold and colors, and distinguishing those
375 : : * trusted decorations from path bytes requires a separate whitelist policy. That
376 : : * whitelist can be added later without changing slog() call sites
377 : : *
378 : : * @param[in,out] line Pointer to the allocated line buffer
379 : : * @param[in,out] line_len Current line length in bytes, updated on success
380 : : */
381 : 37114 : static void logger_line_sanitize_for_terminal(
382 : : char **line,
383 : : int *line_len)
384 : : {
385 : : /*
386 : : * Nothing useful can be sanitized without an existing allocated buffer and a
387 : : * positive byte count. Returning quietly preserves the logger's current
388 : : * best-effort behavior for allocation and formatting failure paths
389 : : */
390 [ + - + + : 37114 : if(line == NULL || *line == NULL || line_len == NULL || *line_len <= 0)
+ - + + ]
391 : : {
392 : 11213 : return;
393 : : }
394 : :
395 : 25901 : const size_t input_len = (size_t)*line_len;
396 : :
397 : : /*
398 : : * Escaping one input byte as \xNN needs four output bytes.
399 : : * This guard keeps the worst-case allocation and the final int length update
400 : : * inside representable bounds
401 : : */
402 [ - + ]: 25901 : if(input_len > (size_t)INT_MAX / 4U)
403 : : {
404 : 0 : return;
405 : : }
406 : :
407 : : /*
408 : : * Allocate for the worst case where every input byte becomes \xNN.
409 : : * The extra byte is for a terminator because the logger stores text in a C
410 : : * string buffer even though fwrite() uses the explicit byte length
411 : : */
412 : 25901 : char *sanitized_line = malloc((input_len * 4U) + 1U);
413 : :
414 [ - + ]: 25901 : if(sanitized_line == NULL)
415 : : {
416 : 0 : return;
417 : : }
418 : :
419 : : /*
420 : : * input_position walks through the original formatted line.
421 : : * output_len tracks the next free position in the sanitized replacement
422 : : * buffer, which may grow faster than the input when bytes are escaped
423 : : */
424 : 25901 : size_t input_position = 0U;
425 : 25901 : size_t output_len = 0U;
426 : :
427 : : /*
428 : : * Consume one ASCII byte or one complete UTF-8 sequence per loop.
429 : : * The loop never trusts a multibyte sequence until it has been decoded and
430 : : * checked, so damaged input cannot leak raw terminal controls into output
431 : : */
432 [ + + ]: 1331908 : while(input_position < input_len)
433 : : {
434 : 1306007 : const unsigned char byte = (unsigned char)(*line)[input_position];
435 : :
436 [ + + ]: 1306007 : if(byte < 0x80U)
437 : : {
438 : : /*
439 : : * ASCII printable characters are safe to copy.
440 : : * Newline, carriage return, and tab are preserved because log lines
441 : : * legitimately use them for layout. ESC is also preserved for now so
442 : : * existing color and bold decorations keep working until an explicit
443 : : * decoration whitelist is added
444 : : */
445 [ + + + + : 1299743 : if(byte == '\n' || byte == '\r' || byte == '\t' || byte == '\033' || (byte >= 0x20U && byte != 0x7FU))
+ + + + +
+ + + ]
446 : : {
447 : 1299741 : sanitized_line[output_len++] = (char)byte;
448 : : } else {
449 : : /*
450 : : * Other ASCII control bytes and DEL are not safe terminal text.
451 : : * Showing them as \xNN makes the byte visible to the user while
452 : : * preventing the terminal from treating it as an action
453 : : */
454 : 2 : logger_line_append_hex_escape(sanitized_line,&output_len,byte);
455 : : }
456 : :
457 : 1299743 : input_position++;
458 : 1299747 : continue;
459 : : }
460 : :
461 : : /*
462 : : * Non-ASCII input must first prove that it is valid UTF-8.
463 : : * The decoder is intentionally local and locale-independent so logger
464 : : * safety does not depend on whether setlocale() has already run
465 : : */
466 : 6264 : uint32_t codepoint = 0U;
467 : 6264 : const size_t decoded_len = logger_line_decode_utf8(*line + input_position,
468 : : input_len - input_position,
469 : : &codepoint);
470 : :
471 : : /*
472 : : * Invalid UTF-8 is escaped one byte at a time.
473 : : * This preserves every original byte in a readable form and then retries
474 : : * from the next byte, which helps recover cleanly after a malformed prefix
475 : : */
476 [ + + ]: 6264 : if(decoded_len == 0U)
477 : : {
478 : 4 : logger_line_append_hex_escape(sanitized_line,&output_len,byte);
479 : 4 : input_position++;
480 : 4 : continue;
481 : : }
482 : :
483 : : /*
484 : : * C1 controls are dangerous even when encoded as valid UTF-8.
485 : : * U+0090, for example, is a terminal control-string introducer on some
486 : : * terminals, so the original bytes are escaped instead of being copied
487 : : */
488 [ + - + + ]: 6260 : if(codepoint >= 0x80U && codepoint <= 0x9FU)
489 : : {
490 [ + + ]: 3 : for(size_t i = 0U; i < decoded_len; i++)
491 : : {
492 : 2 : const unsigned char control_byte = (unsigned char)(*line)[input_position + i];
493 : 2 : logger_line_append_hex_escape(sanitized_line,&output_len,control_byte);
494 : : }
495 : : } else {
496 : : /*
497 : : * Valid non-C1 UTF-8 is copied unchanged.
498 : : * This keeps ordinary international file names and messages readable
499 : : * instead of turning every non-ASCII character into escapes
500 : : */
501 : 6259 : memcpy(sanitized_line + output_len,*line + input_position,decoded_len);
502 : 6259 : output_len += decoded_len;
503 : : }
504 : :
505 : 6260 : input_position += decoded_len;
506 : : }
507 : :
508 : : /*
509 : : * Replace the original formatted line with the sanitized version.
510 : : * The caller will pass the same buffer to REMEMBER and fwrite(), so both
511 : : * delayed warnings and immediate terminal output see identical safe text
512 : : */
513 : 25901 : sanitized_line[output_len] = '\0';
514 : 25901 : free(*line);
515 : 25901 : *line = sanitized_line;
516 : 25901 : *line_len = (int)output_len;
517 : : }
518 : :
519 : : __attribute__((format(printf,7,0)))
520 : 37114 : static void logger_line(
521 : : char **line,
522 : : int *line_len,
523 : : const LOGMODES level,
524 : : const char *const filename,
525 : : size_t line_number,
526 : : const char *const funcname,
527 : : const char *fmt,
528 : : va_list args)
529 : : {
530 [ + + ]: 37114 : if(rational_logger_mode & SILENT)
531 : : {
532 [ + + ]: 4408 : if(level & VISIBLE_IN_SILENT)
533 : : {
534 : 28 : logger_line_append_va(line,line_len,fmt,args);
535 : : }
536 : :
537 : 4408 : return;
538 : : }
539 : :
540 [ + + + + : 32706 : if(!(level & UNDECOR) && (level & TESTING) && (rational_logger_mode & TESTING))
+ + ]
541 : : {
542 : : // Print out the word "TESTING:"
543 : 19441 : logger_line_append(line,line_len,"TESTING:");
544 : : }
545 : :
546 [ + + + + : 32706 : if(!(level & UNDECOR) && (level & (VERBOSE|ERROR)) && (rational_logger_mode & VERBOSE))
+ + ]
547 : : {
548 : : char time_string[sizeof "2011-10-18 07:07:09:000"];
549 : 325 : (void)logger_show_time(time_string,sizeof(time_string));
550 : :
551 : : // Print out current time
552 : 325 : logger_line_append(line,line_len,"%s ",time_string);
553 : :
554 : : // Print out the source file name
555 : 325 : logger_line_append(line,line_len,"%s:",filename);
556 : :
557 : : // Print out line number in source file
558 : 325 : logger_line_append(line,line_len,"%03zu:",line_number);
559 : :
560 : : // Print out name of the function itself
561 : 325 : logger_line_append(line,line_len,"%s:",funcname);
562 : : }
563 : :
564 [ + + + + : 32706 : if(!(level & UNDECOR) && (level & ERROR) && (rational_logger_mode & (REGULAR | ERROR)))
+ + ]
565 : : {
566 : : // Print out error prefix
567 : 26 : logger_line_append(line,line_len,"ERROR: ");
568 : :
569 [ + + + + : 32680 : } else if(!(level & UNDECOR) && (level & ERROR) && (rational_logger_mode & (TESTING | VERBOSE))){
+ + ]
570 : : // Print out the word "ERROR:"
571 : 235 : logger_line_append(line,line_len,"ERROR:");
572 : : }
573 : :
574 [ + + + + ]: 32706 : if(level & ERROR && rational_logger_mode & ERROR)
575 : : {
576 : : // Print out other arguments
577 : 2 : logger_line_append_va(line,line_len,fmt,args);
578 : :
579 [ + + + + ]: 32704 : } else if(level & (REGULAR|ERROR) && rational_logger_mode & REGULAR){
580 : : // Print out other arguments
581 : 1132 : logger_line_append_va(line,line_len,fmt,args);
582 : :
583 [ + + + + ]: 31572 : } else if(level & (VERBOSE|ERROR) && rational_logger_mode & VERBOSE){
584 : : // Print out other arguments
585 : 640 : logger_line_append_va(line,line_len,fmt,args);
586 : :
587 [ + + + + ]: 30932 : } else if(level & (TESTING|ERROR) && rational_logger_mode & TESTING){
588 : : // Print out other arguments
589 : 24103 : logger_line_append_va(line,line_len,fmt,args);
590 : : }
591 : : }
592 : :
593 : : /**
594 : : *
595 : : * @brief Build and print a formatted log line with file, line, and function metadata
596 : : *
597 : : * @details When REMEMBER is set and the weak rational_remember() symbol is defined,
598 : : * the formatted line (without a trailing newline) and its length are
599 : : * passed to that callback.
600 : : *
601 : : */
602 : : __attribute__((format(printf,5,6))) // Without this we will get warning
603 : 37114 : void rational_logger(
604 : : const LOGMODES level,
605 : : const char *const filename,
606 : : size_t line,
607 : : const char *const funcname,
608 : : const char *fmt,
609 : : ...)
610 : : {
611 : :
612 : 37114 : char *logger_line_text = NULL;
613 : 37114 : int line_len = 0;
614 : :
615 : : va_list args;
616 : 37114 : va_start(args,fmt);
617 : 37114 : logger_line(&logger_line_text,&line_len,level,filename,line,funcname,fmt,args);
618 : 37114 : va_end(args);
619 : :
620 : : /*
621 : : * Sanitize the final formatted line before any consumer sees it.
622 : : * This keeps immediate terminal output and delayed REMEMBER output identical,
623 : : * and prevents path bytes from being interpreted as terminal controls
624 : : */
625 : 37114 : logger_line_sanitize_for_terminal(&logger_line_text,&line_len);
626 : :
627 [ + + + - : 37114 : if((level & REMEMBER) && rational_remember && logger_line_text != NULL && line_len > 0)
+ + + + ]
628 : : {
629 : 143 : rational_remember(logger_line_text,line_len);
630 : : }
631 : :
632 [ + + ]: 37114 : if(logger_line_text != NULL)
633 : : {
634 : 25902 : fwrite(logger_line_text,sizeof(char),(size_t)line_len,stdout);
635 : : }
636 : :
637 : 37114 : free(logger_line_text);
638 : 37114 : }
639 : :
640 : : #ifdef TEST
641 : : /**
642 : : * @file test_slog.c
643 : : * @brief Complete test suite for log functionality
644 : : */
645 : : int main(void)
646 : : {
647 : : printf("All available combinations:\n");
648 : : printf("%s\n",rational_convert(REGULAR));
649 : : printf("%s\n",rational_convert(VERBOSE));
650 : : printf("%s\n",rational_convert(TESTING));
651 : : printf("%s\n",rational_convert(SILENT));
652 : : printf("%s\n",rational_convert(REGULAR|VERBOSE));
653 : : printf("%s\n",rational_convert(REGULAR|TESTING));
654 : : printf("%s\n",rational_convert(VERBOSE|TESTING));
655 : : printf("%s\n",rational_convert(REGULAR|VERBOSE|TESTING));
656 : : printf("%s\n",rational_convert(ERROR));
657 : : printf("%s\n",rational_convert(UNDECOR));
658 : : printf("%s\n",rational_convert(EVERY|UNDECOR));
659 : : printf("%s\n",rational_convert(ERROR|UNDECOR));
660 : : printf("%s\n",rational_convert(VISIBLE_IN_SILENT));
661 : :
662 : : /* Test REGULAR mode combinations */
663 : : rational_logger_mode = REGULAR;
664 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
665 : : printf("1. Must print:"); slog(REGULAR,"true"); printf("\n");
666 : : printf("2. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
667 : : printf("3. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
668 : : printf("4. Must print:"); slog(ERROR,"true"); printf("\n");
669 : :
670 : : /* Test VERBOSE mode combinations */
671 : : rational_logger_mode = VERBOSE;
672 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
673 : : printf("5. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
674 : : printf("6. Must print:"); slog(VERBOSE,"true"); printf("\n");
675 : : printf("7. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
676 : : printf("8. Must print:"); slog(ERROR,"true"); printf("\n");
677 : :
678 : : /* Test TESTING mode combinations */
679 : : rational_logger_mode = TESTING;
680 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
681 : : printf("9. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
682 : : printf("10. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
683 : : printf("11. Must print:"); slog(TESTING,"true"); printf("\n");
684 : : printf("12. Must print:"); slog(ERROR,"true"); printf("\n");
685 : :
686 : : /* Test SILENT mode combinations */
687 : : rational_logger_mode = SILENT;
688 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
689 : : printf("13. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
690 : : printf("14. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
691 : : printf("15. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
692 : : printf("16. Won't print:"); slog(ERROR,"but printed!"); printf("\n");
693 : :
694 : : /* Test REGULAR|VERBOSE combinations */
695 : : rational_logger_mode = REGULAR|VERBOSE;
696 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
697 : : printf("17. Must print:"); slog(REGULAR,"true"); printf("\n");
698 : : printf("18. Must print:"); slog(VERBOSE,"true"); printf("\n");
699 : : printf("19. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
700 : : printf("20. Must print:"); slog(ERROR,"true"); printf("\n");
701 : :
702 : : /* Test REGULAR|TESTING combinations */
703 : : rational_logger_mode = REGULAR|TESTING;
704 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
705 : : printf("21. Must print:"); slog(REGULAR,"true"); printf("\n");
706 : : printf("22. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
707 : : printf("23. Must print:"); slog(TESTING,"true"); printf("\n");
708 : : printf("24. Must print:"); slog(ERROR,"true"); printf("\n");
709 : :
710 : : /* Test VERBOSE|TESTING combinations */
711 : : rational_logger_mode = VERBOSE|TESTING;
712 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
713 : : printf("25. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
714 : : printf("26. Must print:"); slog(VERBOSE,"true"); printf("\n");
715 : : printf("27. Must print:"); slog(TESTING,"true"); printf("\n");
716 : : printf("28. Must print:"); slog(ERROR,"true"); printf("\n");
717 : :
718 : : /* Test REGULAR|VERBOSE|TESTING combinations */
719 : : rational_logger_mode = REGULAR|VERBOSE|TESTING;
720 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
721 : : printf("29. Must print:"); slog(REGULAR,"true"); printf("\n");
722 : : printf("30. Must print:"); slog(VERBOSE,"true"); printf("\n");
723 : : printf("31. Must print:"); slog(TESTING,"true"); printf("\n");
724 : : printf("32. Must print:"); slog(ERROR,"true"); printf("\n");
725 : :
726 : : /* Test ERROR mode combinations */
727 : : rational_logger_mode = ERROR;
728 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
729 : : printf("33. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
730 : : printf("34. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
731 : : printf("35. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
732 : : printf("36. Must print:"); slog(ERROR,"true"); printf("\n");
733 : :
734 : : /*
735 : : * Test UNDECOR flag: suppress logger prefixes (TESTING:, time/file/line/func, ERROR:)
736 : : * The output between the '|' markers should contain only the message payload.
737 : : */
738 : :
739 : : rational_logger_mode = EVERY|ERROR;
740 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
741 : : printf("37. Must print no prefixes:|"); slog(EVERY|UNDECOR,"true"); printf("|\n");
742 : : printf("38. Must print no ERROR prefix:|"); slog(ERROR|UNDECOR,"true"); printf("|\n");
743 : :
744 : : rational_logger_mode = VERBOSE;
745 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
746 : : printf("39. Must print no time/file/line/func:|"); slog(VERBOSE|UNDECOR,"true"); printf("|\n");
747 : :
748 : : rational_logger_mode = TESTING;
749 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
750 : : printf("40. Must print no TESTING prefix:|"); slog(TESTING|UNDECOR,"true"); printf("|\n");
751 : :
752 : : rational_logger_mode = REGULAR;
753 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
754 : : printf("41. Must not print (VERBOSE not enabled):|"); slog(VERBOSE|UNDECOR,"but printed!"); printf("|\n");
755 : :
756 : : rational_logger_mode = SILENT;
757 : : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
758 : : printf("42. Must print in SILENT without prefixes:|"); slog(EVERY|VISIBLE_IN_SILENT,"true"); printf("|\n");
759 : : printf("43. Must print no ERROR prefix in SILENT:|"); slog(ERROR|VISIBLE_IN_SILENT,"true"); printf("|\n");
760 : :
761 : : return 0;
762 : : }
763 : : #endif
|