8 n__log_8c
gullradriel edited this page 2026-10-30 03:26:30 +01:00

src/n_log.c

Generic logging system. More...

Structures

Name
struct LOG_LEVELS
internal struct to handle log types

Functions

Name
int vasprintf(char ** strp, const char * fmt, va_list ap)
snprintf from a va_list, helper for asprintf
int asprintf(char * strp[], const char * fmt, ... )
snprintf from a va_list
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', helper for vasprintf

Attributes

Name
int terminal_support_colors
char * proc_name
static proc name, for windows event log
LOG_LEVELS[] prioritynames
array of log levels
int log_file_owned
TRUE when log_file was opened by set_log_file() and must be closed by the library.
FILE * log_file
static FILE handling if logging to file is enabled
int LOG_TYPE
static global logging type ( STDERR, FILE, SYSJRNL )
int LOG_LEVEL
static global maximum wanted log level value

Defines

Name
COLOR_YELLOW
COLOR_WHITE
COLOR_RESET
COLOR_RED
COLOR_MAGENTA
COLOR_GREEN
COLOR_CYAN
COLOR_BLUE

Detailed Description

Generic logging system.

Author: Castagnier Mickael

Date: 2013-03-12

Functions Documentation

function vasprintf

int vasprintf(
    char ** strp,
    const char * fmt,
    va_list ap
)

snprintf from a va_list, helper for asprintf

Parameters:

  • strp string holding the result
  • fmt format to pass to printf
  • ap va_list to use as printf parameter

Return: -1 or the size of the string

function asprintf

int asprintf(
    char * strp[],
    const char * fmt,
    ... 
)

snprintf from a va_list

Parameters:

  • strp string holding the result
  • fmt format to pass to printf
  • ... list of format parameters

Return: -1 or the size of the string

function _vscprintf_so

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', helper for vasprintf

Parameters:

  • format format to pass to printf
  • pargs va_list to use as printf parameter

Return: -1 or the size of the string

Attributes Documentation

variable terminal_support_colors

static int terminal_support_colors = 0;

variable proc_name

static char * proc_name = NULL;

static proc name, for windows event log

variable prioritynames

static LOG_LEVELS[] prioritynames                             =
{
    {"EMERG", 0 , "ERROR"},
    {"ALERT", 1 , "ERROR"},
    {"CRITICAL", 2 , "ERROR"},
    {"ERR", 3 , "ERROR"},
    {"WARNING", 4 , "WARNING"},
    {"NOTICE", 5 , "SUCCESS"},
    {"INFO", 6 , "INFORMATION"},
    {"DEBUG", 7 , "INFORMATION"},
    {NULL, -1, NULL}};

array of log levels

variable log_file_owned

static int log_file_owned = (1 == 0);

TRUE when log_file was opened by set_log_file() and must be closed by the library.

variable log_file

static FILE * log_file = NULL;

static FILE handling if logging to file is enabled

variable LOG_TYPE

static int LOG_TYPE = -4;

static global logging type ( STDERR, FILE, SYSJRNL )

variable LOG_LEVEL

static int LOG_LEVEL = -1;

static global maximum wanted log level value

Macros Documentation

define COLOR_YELLOW

#define COLOR_YELLOW "\033[33m"

define COLOR_WHITE

#define COLOR_WHITE "\033[37m"

define COLOR_RESET

#define COLOR_RESET "\033[0m"

define COLOR_RED

#define COLOR_RED "\033[31m"

define COLOR_MAGENTA

#define COLOR_MAGENTA "\033[35m"

define COLOR_GREEN

#define COLOR_GREEN "\033[32m"

define COLOR_CYAN

#define COLOR_CYAN "\033[36m"

define COLOR_BLUE

#define COLOR_BLUE "\033[34m"

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include "nilorea/n_common.h"
#include "nilorea/n_log.h"

#include <pthread.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#ifndef __windows__
#include <syslog.h>
#else
#include <time.h>
#include "nilorea/n_windows.h"
#endif

static int terminal_support_colors = 0;

#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[31m"
#define COLOR_GREEN "\033[32m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_BLUE "\033[34m"
#define COLOR_MAGENTA "\033[35m"
#define COLOR_CYAN "\033[36m"
#define COLOR_WHITE "\033[37m"

typedef struct LOG_LEVELS {
    char* c_name;
    int c_val;     // cppcheck-suppress unusedStructMember ; used by downstream log consumers
    char* w_name;  // cppcheck-suppress unusedStructMember ; used by downstream log consumers

} LOG_LEVELS;

static LOG_LEVELS prioritynames[] =
    {
        {"EMERG", LOG_EMERG, "ERROR"},
        {"ALERT", LOG_ALERT, "ERROR"},
        {"CRITICAL", LOG_CRIT, "ERROR"},
        {"ERR", LOG_ERR, "ERROR"},
        {"WARNING", LOG_WARNING, "WARNING"},
        {"NOTICE", LOG_NOTICE, "SUCCESS"},
        {"INFO", LOG_INFO, "INFORMATION"},
        {"DEBUG", LOG_DEBUG, "INFORMATION"},
        {NULL, -1, NULL}};

static int LOG_LEVEL = LOG_NULL;

static int LOG_TYPE = LOG_STDERR;

static FILE* log_file = NULL;
static int log_file_owned = FALSE;

static char* proc_name = NULL;

char* open_sysjrnl(const char* identity) {
    __n_assert(identity, return NULL);
#ifndef __windows__
    /* LOG_CONS: log to console if no syslog available
     * LOG_PID: add pid of calling process to log
     * Local use 7 : compat with older logging systems
     */
    openlog(identity, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL7);
#endif
    proc_name = strdup(identity);
    return proc_name;
} /* open_sysjrnl */

void close_sysjrnl(void) {
#ifndef __windows__
    closelog();
#endif
    FreeNoLog(proc_name);
} /* close_sysjrnl */

void set_log_level(const int log_level) {
    if (log_level == LOG_FILE) {
        LOG_TYPE = LOG_FILE;
        return;
    }
    if (log_level == LOG_STDERR) {
        LOG_TYPE = LOG_STDERR;
        return;
    }
    if (log_level == LOG_SYSJRNL) {
        LOG_TYPE = LOG_SYSJRNL;
        return;
    }
    LOG_LEVEL = log_level;
#ifdef __windows__
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hOut != INVALID_HANDLE_VALUE) {
        DWORD dwMode = 0;
        if (GetConsoleMode(hOut, &dwMode)) {
            dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
            if (SetConsoleMode(hOut, dwMode)) {
                // color support is enabled
                terminal_support_colors = 1;
            }
        }
    }
#else
    // Probably a terminal, may support colors
    if (isatty(fileno(stdout))) {
        terminal_support_colors = 1;
    }
#endif
} /* set_log_level() */

int get_log_level(void) {
    return LOG_LEVEL;
} /* get_log_level() */

int set_log_file(char* file) {
    __n_assert(file, return FALSE);

    if (log_file) {
        if (log_file_owned)
            fclose(log_file);
        log_file = NULL;
        log_file_owned = FALSE;
    }

    int fd = open(file, O_CREAT | O_APPEND | O_WRONLY, 0600);  // rw------- permissions
    if (fd == -1) {
        n_log(LOG_ERR, "couldn't create %s with 0600 permission", _str(file));
        return FALSE;
    }

    log_file = fdopen(fd, "a");
    if (!log_file) {
        close(fd);
        n_log(LOG_ERR, "fdopen returned an invalid file descriptor pointer for %s:%d", _str(file), fd);
        return FALSE;
    }

    log_file_owned = TRUE;
    set_log_level(LOG_FILE);

    return TRUE;
} /* set_log_file */

int set_log_file_fd(FILE* fd) {
    __n_assert(fd, return FALSE);

    if (log_file && log_file != fd && log_file_owned)
        fclose(log_file);

    log_file = fd;
    log_file_owned = FALSE;
    set_log_level(LOG_FILE);

    return TRUE;
} /* set_log_file_fd */

FILE* get_log_file(void) {
    return log_file;
} /*get_log_level() */

#ifndef _vscprintf
int _vscprintf_so(const char* format, va_list pargs) N_PRINTF_FORMAT(1, 0);
int _vscprintf_so(const char* format, va_list pargs) {
    int retval;
    va_list argcopy;
    va_copy(argcopy, pargs);
    retval = vsnprintf(NULL, 0, format, argcopy);
    va_end(argcopy);
    return retval;
}
#endif

/* mingw-w64 provides asprintf and vasprintf itself whenever _GNU_SOURCE is
 * defined: up to version 12 (Debian trixie's cross compiler) stdio.h defines
 * them inline when __USE_MINGW_ANSI_STDIO is set, so a definition here is a
 * redefinition error there, and from version 13 on it declares them as
 * aliases of __mingw_asprintf and __mingw_vasprintf. The #ifndef tests alone
 * never fire: these are functions, not macros. */
#if defined(__MINGW64_VERSION_MAJOR) && defined(_GNU_SOURCE)
#define N_LOG_CRT_ASPRINTF
#endif

#if !defined(vasprintf) && !defined(N_LOG_CRT_ASPRINTF)
int vasprintf(char** strp, const char* fmt, va_list ap) {
    long long int len = _vscprintf_so(fmt, ap);
    if (len == -1)
        return -1;
    char* str = NULL;
    Malloc(str, char, (size_t)len + 1 + sizeof(void*));  // len + EndOfString + padding
    if (!str)
        return -1;
    int r = vsnprintf(str, (size_t)(len + 1), fmt, ap); /* "secure" version of vsprintf */
    if (r == -1)
        return free(str), -1;
    *strp = str;
    return r;
}
#endif

#if !defined(asprintf) && !defined(N_LOG_CRT_ASPRINTF)
int asprintf(char* strp[], const char* fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int r = vasprintf(strp, fmt, ap);
    va_end(ap);
    return r;
}
#endif

void _n_log(int level, const char* file, const char* func, int line, const char* format, ...) {
    FILE* out = NULL;

    if (level == LOG_NULL)
        return;

    if (!log_file)
        out = stderr;
    else
        out = log_file;

    int log_level = get_log_level();

    if (level <= log_level) {
        va_list args;
        char* syslogbuffer = NULL;
        char* eventbuffer = NULL;
        char* syslogsafebuffer = NULL;
#ifdef __windows__
        size_t needed = 0;
        const char* name = "NULL";
        if (proc_name)
            name = proc_name;
#endif

        const char* color = "";
        const char* color_reset = "";

        if (terminal_support_colors) {
            color_reset = COLOR_RESET;
            switch (level) {
                case LOG_EMERG:
                    color = COLOR_RED;
                    break;
                case LOG_ALERT:
                    color = COLOR_MAGENTA;
                    break;
                case LOG_CRIT:
                    color = COLOR_RED;
                    break;
                case LOG_ERR:
                    color = COLOR_RED;
                    break;
                case LOG_WARNING:
                    color = COLOR_YELLOW;
                    break;
                case LOG_NOTICE:
                    color = COLOR_GREEN;
                    break;
                case LOG_INFO:
                    color = COLOR_CYAN;
                    break;
                case LOG_DEBUG:
                    color = COLOR_BLUE;
                    break;
                default:
                    color = COLOR_WHITE;
                    break;
            }
        }
        switch (LOG_TYPE) {
            case LOG_SYSJRNL:
                va_start(args, format);
                if (vasprintf(&syslogbuffer, format, args) == -1) {
                    syslogbuffer = NULL;
                }
                va_end(args);
                // Fallback if vasprintf is NULL */
                syslogsafebuffer = syslogbuffer ? syslogbuffer : "<log: vasprintf failed>";
#ifdef __windows__
                /* Sanitize syslog content to prevent command injection via system().
                 * Only mutate when syslogbuffer is non-NULL: syslogsafebuffer then
                 * points to heap memory.  When syslogbuffer is NULL it points to a
                 * read-only string literal and must not be written to. */
                if (syslogbuffer) {
                    for (char* p = syslogsafebuffer; *p; p++) {
                        if (*p == '"' || *p == '&' || *p == '|' || *p == '<' || *p == '>' || *p == '^') *p = '_';
                    }
                }
                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);
                if (snprintf_ret > 0) {
                    needed = (unsigned long long)snprintf_ret;
                    Malloc(eventbuffer, char, needed + 4);
                    if (eventbuffer) {
                        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);
                        system(eventbuffer);
                    }
                }
#else
                syslog(level, "%s->%s:%d %s", _str(file), _str(func), line, syslogsafebuffer);
#endif
                FreeNoLog(syslogbuffer);
                FreeNoLog(eventbuffer);
                break;
            default:
                fprintf(out, "%s%s:%jd:%s->%s:%d ", color, prioritynames[level].c_name, (intmax_t)time(NULL), _str(file), _str(func), line);
                if (format) {
                    va_start(args, format);
                    vfprintf(out, format, args);
                    va_end(args);
                }
                fprintf(out, "%s\n", color_reset);
                break;
        }
        fflush(out);
    }
} /* _n_log( ... ) */

int open_safe_logging(TS_LOG** log, char* pathname, char* opt) {
    if (!log) {
        n_log(LOG_ERR, "Invalid log pointer (NULL)");
        return FALSE;
    }

    if (*log) {
        if ((*log)->file) {
            n_log(LOG_ERR, "Log file '%s' already open", _str(pathname));
            return -1000;  // already open
        }
        n_log(LOG_ERR, "Log struct already allocated without file");
        return FALSE;
    }

    if (!pathname || !opt) {
        n_log(LOG_ERR, "Invalid pathname or mode (pathname=%s, opt=%s)", _str(pathname), _str(opt));
        return FALSE;
    }

    Malloc((*log), TS_LOG, 1);
    if (!(*log)) {
        n_log(LOG_ERR, "Failed to allocate TS_LOG structure");
        return FALSE;
    }

    pthread_mutex_init(&(*log)->LOG_MUTEX, NULL);

    int flags = 0;
    if (strcmp(opt, "a") == 0 || strcmp(opt, "a+") == 0) {
        flags = O_CREAT | O_APPEND | O_WRONLY;
    } else if (strcmp(opt, "w") == 0 || strcmp(opt, "w+") == 0) {
        flags = O_CREAT | O_TRUNC | O_WRONLY;
    } else {
        n_log(LOG_ERR, "Unsupported open mode '%s' for pathname '%s'", _str(opt), _str(pathname));
        pthread_mutex_destroy(&(*log)->LOG_MUTEX);
        Free(*log);
        *log = NULL;
        return FALSE;
    }

    int fd = open(pathname, flags, 0600);
    if (fd == -1) {
        n_log(LOG_ERR, "Failed to open '%s' with 0600 permissions", _str(pathname));
        pthread_mutex_destroy(&(*log)->LOG_MUTEX);
        Free(*log);
        *log = NULL;
        return FALSE;
    }

    (*log)->file = fdopen(fd, opt);
    if (!(*log)->file) {
        n_log(LOG_ERR, "fdopen failed for '%s' (fd=%d, mode='%s')", _str(pathname), fd, _str(opt));
        close(fd);
        pthread_mutex_destroy(&(*log)->LOG_MUTEX);
        Free(*log);
        *log = NULL;
        return FALSE;
    }

    return TRUE;
} /* open_safe_logging */

int write_safe_log(TS_LOG* log, char* pat, ...) {
    /* argument list */
    va_list arg;
    char str[2048] = "";

    if (!log)
        return FALSE;

    va_start(arg, pat);

    vsnprintf(str, sizeof(str), pat, arg);

    va_end(arg);

    pthread_mutex_lock(&log->LOG_MUTEX);
    fprintf(log->file, "%s", str);
    fflush(log->file);
    pthread_mutex_unlock(&log->LOG_MUTEX);

    return TRUE;

} /* write_safe_log( ... ) */

int close_safe_logging(TS_LOG* log) {
    if (!log)
        return FALSE;

    pthread_mutex_lock(&log->LOG_MUTEX);
    fflush(log->file);
    pthread_mutex_unlock(&log->LOG_MUTEX);

    pthread_mutex_destroy(&log->LOG_MUTEX);

    fclose(log->file);

    Free(log);

    return TRUE;

} /* close_safe_logging( ...) */