Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_log.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
26
27#include "nilorea/n_common.h"
28#include "nilorea/n_log.h"
29
30#include <pthread.h>
31#include <string.h>
32#include <inttypes.h>
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <fcntl.h>
36
37#ifndef __windows__
38#include <syslog.h>
39#else
40#include <time.h>
41#include "nilorea/n_windows.h"
42#endif
43
45
46#define COLOR_RESET "\033[0m"
47#define COLOR_RED "\033[31m"
48#define COLOR_GREEN "\033[32m"
49#define COLOR_YELLOW "\033[33m"
50#define COLOR_BLUE "\033[34m"
51#define COLOR_MAGENTA "\033[35m"
52#define COLOR_CYAN "\033[36m"
53#define COLOR_WHITE "\033[37m"
54
56typedef struct LOG_LEVELS {
58 char* c_name;
59 int c_val; // cppcheck-suppress unusedStructMember ; used by downstream log consumers
60 char* w_name; // cppcheck-suppress unusedStructMember ; used by downstream log consumers
61
63
66 {
67 {"EMERG", LOG_EMERG, "ERROR"},
68 {"ALERT", LOG_ALERT, "ERROR"},
69 {"CRITICAL", LOG_CRIT, "ERROR"},
70 {"ERR", LOG_ERR, "ERROR"},
71 {"WARNING", LOG_WARNING, "WARNING"},
72 {"NOTICE", LOG_NOTICE, "SUCCESS"},
73 {"INFO", LOG_INFO, "INFORMATION"},
74 {"DEBUG", LOG_DEBUG, "INFORMATION"},
75 {NULL, -1, NULL}};
76
78static int LOG_LEVEL = LOG_NULL;
79
81static int LOG_TYPE = LOG_STDERR;
82
84static FILE* log_file = NULL;
86static int log_file_owned = FALSE;
87
89static char* proc_name = NULL;
90
96char* open_sysjrnl(const char* identity) {
97 __n_assert(identity, return NULL);
98#ifndef __windows__
99 /* LOG_CONS: log to console if no syslog available
100 * LOG_PID: add pid of calling process to log
101 * Local use 7 : compat with older logging systems
102 */
103 openlog(identity, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL7);
104#endif
105 proc_name = strdup(identity);
106 return proc_name;
107} /* open_sysjrnl */
108
112void close_sysjrnl(void) {
113#ifndef __windows__
114 closelog();
115#endif
117} /* close_sysjrnl */
118
123void set_log_level(const int log_level) {
124 if (log_level == LOG_FILE) {
126 return;
127 }
128 if (log_level == LOG_STDERR) {
130 return;
131 }
132 if (log_level == LOG_SYSJRNL) {
134 return;
135 }
137#ifdef __windows__
138 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
139 if (hOut != INVALID_HANDLE_VALUE) {
140 DWORD dwMode = 0;
141 if (GetConsoleMode(hOut, &dwMode)) {
142 dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
143 if (SetConsoleMode(hOut, dwMode)) {
144 // color support is enabled
146 }
147 }
148 }
149#else
150 // Probably a terminal, may support colors
151 if (isatty(fileno(stdout))) {
153 }
154#endif
155} /* set_log_level() */
156
161int get_log_level(void) {
162 return LOG_LEVEL;
163} /* get_log_level() */
164
170int set_log_file(char* file) {
171 __n_assert(file, return FALSE);
172
173 if (log_file) {
174 if (log_file_owned)
175 fclose(log_file);
176 log_file = NULL;
177 log_file_owned = FALSE;
178 }
179
180 int fd = open(file, O_CREAT | O_APPEND | O_WRONLY, 0600); // rw------- permissions
181 if (fd == -1) {
182 n_log(LOG_ERR, "couldn't create %s with 0600 permission", _str(file));
183 return FALSE;
184 }
185
186 log_file = fdopen(fd, "a");
187 if (!log_file) {
188 close(fd);
189 n_log(LOG_ERR, "fdopen returned an invalid file descriptor pointer for %s:%d", _str(file), fd);
190 return FALSE;
191 }
192
193 log_file_owned = TRUE;
195
196 return TRUE;
197} /* set_log_file */
198
210int set_log_file_fd(FILE* fd) {
211 __n_assert(fd, return FALSE);
212
213 if (log_file && log_file != fd && log_file_owned)
214 fclose(log_file);
215
216 log_file = fd;
217 log_file_owned = FALSE;
219
220 return TRUE;
221} /* set_log_file_fd */
222
227FILE* get_log_file(void) {
228 return log_file;
229} /*get_log_level() */
230
231#ifndef _vscprintf
238int _vscprintf_so(const char* format, va_list pargs) N_PRINTF_FORMAT(1, 0);
239int _vscprintf_so(const char* format, va_list pargs) {
240 int retval;
241 va_list argcopy;
242 va_copy(argcopy, pargs);
243 retval = vsnprintf(NULL, 0, format, argcopy);
244 va_end(argcopy);
245 return retval;
246}
247#endif
248
249#ifndef vasprintf
257int vasprintf(char** strp, const char* fmt, va_list ap) {
258 long long int len = _vscprintf_so(fmt, ap);
259 if (len == -1)
260 return -1;
261 char* str = NULL;
262 Malloc(str, char, (size_t)len + 1 + sizeof(void*)); // len + EndOfString + padding
263 if (!str)
264 return -1;
265 int r = vsnprintf(str, (size_t)(len + 1), fmt, ap); /* "secure" version of vsprintf */
266 if (r == -1)
267 return free(str), -1;
268 *strp = str;
269 return r;
270}
271#endif
272
273#ifndef asprintf
281int asprintf(char* strp[], const char* fmt, ...) {
282 va_list ap;
283 va_start(ap, fmt);
284 int r = vasprintf(strp, fmt, ap);
285 va_end(ap);
286 return r;
287}
288#endif
289
298void _n_log(int level, const char* file, const char* func, int line, const char* format, ...) {
299 FILE* out = NULL;
300
301 if (level == LOG_NULL)
302 return;
303
304 if (!log_file)
305 out = stderr;
306 else
307 out = log_file;
308
309 int log_level = get_log_level();
310
311 if (level <= log_level) {
312 va_list args;
313 char* syslogbuffer = NULL;
314 char* eventbuffer = NULL;
315 char* syslogsafebuffer = NULL;
316#ifdef __windows__
317 size_t needed = 0;
318 const char* name = "NULL";
319 if (proc_name)
320 name = proc_name;
321#endif
322
323 const char* color = "";
324 const char* color_reset = "";
325
327 color_reset = COLOR_RESET;
328 switch (level) {
329 case LOG_EMERG:
330 color = COLOR_RED;
331 break;
332 case LOG_ALERT:
333 color = COLOR_MAGENTA;
334 break;
335 case LOG_CRIT:
336 color = COLOR_RED;
337 break;
338 case LOG_ERR:
339 color = COLOR_RED;
340 break;
341 case LOG_WARNING:
342 color = COLOR_YELLOW;
343 break;
344 case LOG_NOTICE:
345 color = COLOR_GREEN;
346 break;
347 case LOG_INFO:
348 color = COLOR_CYAN;
349 break;
350 case LOG_DEBUG:
351 color = COLOR_BLUE;
352 break;
353 default:
354 color = COLOR_WHITE;
355 break;
356 }
357 }
358 switch (LOG_TYPE) {
359 case LOG_SYSJRNL:
360 va_start(args, format);
361 if (vasprintf(&syslogbuffer, format, args) == -1) {
362 syslogbuffer = NULL;
363 }
364 va_end(args);
365 // Fallback if vasprintf is NULL */
366 syslogsafebuffer = syslogbuffer ? syslogbuffer : "<log: vasprintf failed>";
367#ifdef __windows__
368 /* Sanitize syslog content to prevent command injection via system().
369 * Only mutate when syslogbuffer is non-NULL: syslogsafebuffer then
370 * points to heap memory. When syslogbuffer is NULL it points to a
371 * read-only string literal and must not be written to. */
372 if (syslogbuffer) {
373 for (char* p = syslogsafebuffer; *p; p++) {
374 if (*p == '"' || *p == '&' || *p == '|' || *p == '<' || *p == '>' || *p == '^') *p = '_';
375 }
376 }
377 int snprintf_ret = snprintf(NULL, 0, "start /B EventCreate /t %s /id 666 /l APPLICATION /so %s /d \"%s\" > NUL 2>&1", prioritynames[level].w_name, name, syslogsafebuffer);
378 if (snprintf_ret > 0) {
379 needed = (unsigned long long)snprintf_ret;
380 Malloc(eventbuffer, char, needed + 4);
381 if (eventbuffer) {
382 snprintf(eventbuffer, needed + 4, "start /B EventCreate /t %s /id 666 /l APPLICATION /so %s /d \"%s\" > NUL 2>&1", prioritynames[level].w_name, name, syslogsafebuffer);
383 system(eventbuffer);
384 }
385 }
386#else
387 syslog(level, "%s->%s:%d %s", _str(file), _str(func), line, syslogsafebuffer);
388#endif
389 FreeNoLog(syslogbuffer);
390 FreeNoLog(eventbuffer);
391 break;
392 default:
393 fprintf(out, "%s%s:%jd:%s->%s:%d ", color, prioritynames[level].c_name, (intmax_t)time(NULL), _str(file), _str(func), line);
394 if (format) {
395 va_start(args, format);
396 vfprintf(out, format, args);
397 va_end(args);
398 }
399 fprintf(out, "%s\n", color_reset);
400 break;
401 }
402 fflush(out);
403 }
404} /* _n_log( ... ) */
405
413int open_safe_logging(TS_LOG** log, char* pathname, char* opt) {
414 if (!log) {
415 n_log(LOG_ERR, "Invalid log pointer (NULL)");
416 return FALSE;
417 }
418
419 if (*log) {
420 if ((*log)->file) {
421 n_log(LOG_ERR, "Log file '%s' already open", _str(pathname));
422 return -1000; // already open
423 }
424 n_log(LOG_ERR, "Log struct already allocated without file");
425 return FALSE;
426 }
427
428 if (!pathname || !opt) {
429 n_log(LOG_ERR, "Invalid pathname or mode (pathname=%s, opt=%s)", _str(pathname), _str(opt));
430 return FALSE;
431 }
432
433 Malloc((*log), TS_LOG, 1);
434 if (!(*log)) {
435 n_log(LOG_ERR, "Failed to allocate TS_LOG structure");
436 return FALSE;
437 }
438
439 pthread_mutex_init(&(*log)->LOG_MUTEX, NULL);
440
441 int flags = 0;
442 if (strcmp(opt, "a") == 0 || strcmp(opt, "a+") == 0) {
443 flags = O_CREAT | O_APPEND | O_WRONLY;
444 } else if (strcmp(opt, "w") == 0 || strcmp(opt, "w+") == 0) {
445 flags = O_CREAT | O_TRUNC | O_WRONLY;
446 } else {
447 n_log(LOG_ERR, "Unsupported open mode '%s' for pathname '%s'", _str(opt), _str(pathname));
448 pthread_mutex_destroy(&(*log)->LOG_MUTEX);
449 Free(*log);
450 *log = NULL;
451 return FALSE;
452 }
453
454 int fd = open(pathname, flags, 0600);
455 if (fd == -1) {
456 n_log(LOG_ERR, "Failed to open '%s' with 0600 permissions", _str(pathname));
457 pthread_mutex_destroy(&(*log)->LOG_MUTEX);
458 Free(*log);
459 *log = NULL;
460 return FALSE;
461 }
462
463 (*log)->file = fdopen(fd, opt);
464 if (!(*log)->file) {
465 n_log(LOG_ERR, "fdopen failed for '%s' (fd=%d, mode='%s')", _str(pathname), fd, _str(opt));
466 close(fd);
467 pthread_mutex_destroy(&(*log)->LOG_MUTEX);
468 Free(*log);
469 *log = NULL;
470 return FALSE;
471 }
472
473 return TRUE;
474} /* open_safe_logging */
475
482int write_safe_log(TS_LOG* log, char* pat, ...) {
483 /* argument list */
484 va_list arg;
485 char str[2048] = "";
486
487 if (!log)
488 return FALSE;
489
490 va_start(arg, pat);
491
492 vsnprintf(str, sizeof(str), pat, arg);
493
494 va_end(arg);
495
496 pthread_mutex_lock(&log->LOG_MUTEX);
497 fprintf(log->file, "%s", str);
498 fflush(log->file);
499 pthread_mutex_unlock(&log->LOG_MUTEX);
500
501 return TRUE;
502
503} /* write_safe_log( ... ) */
504
511 if (!log)
512 return FALSE;
513
514 pthread_mutex_lock(&log->LOG_MUTEX);
515 fflush(log->file);
516 pthread_mutex_unlock(&log->LOG_MUTEX);
517
518 pthread_mutex_destroy(&log->LOG_MUTEX);
519
520 fclose(log->file);
521
522 Free(log);
523
524 return TRUE;
525
526} /* close_safe_logging( ...) */
static int log_level
void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color)
draw a line
#define FreeNoLog(__ptr)
Free Handler without log.
Definition n_common.h:272
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
Definition n_common.h:204
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:279
#define _str(__PTR)
define true
Definition n_common.h:193
#define Free(__ptr)
Free Handler to get errors.
Definition n_common.h:263
pthread_mutex_t LOG_MUTEX
mutex for thread-safe writting
Definition n_log.h:121
FILE * file
File handler.
Definition n_log.h:123
#define LOG_ALERT
action must be taken immediately
Definition n_log.h:96
#define LOG_SYSJRNL
to sysjrnl
Definition n_log.h:76
FILE * get_log_file(void)
return the current log_file
Definition n_log.c:227
int write_safe_log(TS_LOG *log, char *pat,...)
write to a thread-safe logging file
Definition n_log.c:482
int open_safe_logging(TS_LOG **log, char *pathname, char *opt)
Open a thread-safe logging file.
Definition n_log.c:413
#define LOG_EMERG
system is unusable
Definition n_log.h:94
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_FILE
internal, logging to file
Definition n_log.h:72
void close_sysjrnl(void)
Close syslog connection or clean internals for event log.
Definition n_log.c:112
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#define LOG_ERR
error conditions
Definition n_log.h:100
char * open_sysjrnl(const char *identity)
Open connection to syslog or create internals for event log.
Definition n_log.c:96
int set_log_file_fd(FILE *fd)
Set the logging to an already open stream instead of stderr.
Definition n_log.c:210
#define N_PRINTF_FORMAT(fmt, args)
declare a function printf-like; nothing on a compiler without attributes
Definition n_log.h:63
#define LOG_STDERR
internal, default LOG_TYPE
Definition n_log.h:74
#define LOG_CRIT
critical conditions
Definition n_log.h:98
int close_safe_logging(TS_LOG *log)
close a thread-safe logging file
Definition n_log.c:510
int set_log_file(char *file)
Set the logging to a file instead of stderr.
Definition n_log.c:170
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_WARNING
warning conditions
Definition n_log.h:102
#define LOG_NULL
no log output
Definition n_log.h:70
#define LOG_INFO
informational
Definition n_log.h:106
void _n_log(int level, const char *file, const char *func, int line, const char *format,...)
Logging function.
Definition n_log.c:298
int get_log_level(void)
Get the global log level value.
Definition n_log.c:161
ThreadSafe LOGging structure.
Definition n_log.h:119
Common headers and low-level functions & define.
#define COLOR_RESET
Definition n_log.c:46
static LOG_LEVELS prioritynames[]
array of log levels
Definition n_log.c:65
#define COLOR_BLUE
Definition n_log.c:50
static char * proc_name
static proc name, for windows event log
Definition n_log.c:89
int c_val
Definition n_log.c:59
static int LOG_LEVEL
static global maximum wanted log level value
Definition n_log.c:78
#define COLOR_YELLOW
Definition n_log.c:49
static int LOG_TYPE
static global logging type ( STDERR, FILE, SYSJRNL )
Definition n_log.c:81
int vasprintf(char **strp, const char *fmt, va_list ap)
snprintf from a va_list, helper for asprintf
Definition n_log.c:257
int asprintf(char *strp[], const char *fmt,...)
snprintf from a va_list
Definition n_log.c:281
static int terminal_support_colors
Definition n_log.c:44
#define COLOR_CYAN
Definition n_log.c:52
#define COLOR_MAGENTA
Definition n_log.c:51
#define COLOR_WHITE
Definition n_log.c:53
static FILE * log_file
static FILE handling if logging to file is enabled
Definition n_log.c:84
char * w_name
Definition n_log.c:60
char * c_name
string of log type
Definition n_log.c:58
#define COLOR_RED
Definition n_log.c:47
static int log_file_owned
TRUE when log_file was opened by set_log_file() and must be closed by the library.
Definition n_log.c:86
#define COLOR_GREEN
Definition n_log.c:48
int _vscprintf_so(const char *format, va_list pargs)
compute the size of a string made with format 'fmt' and arguments in the va_list 'ap',...
Definition n_log.c:239
internal struct to handle log types
Definition n_log.c:56
Generic log system.