/* Copyright (C) 2007-2021 Open Information Security Foundation
 *
 * You can copy, redistribute or modify this Program under the terms of
 * the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/**
 * \file
 *
 * \author Anoop Saldanha <anoopsaldanha@gmail.com>
 *
 * Debug utility functions
 */

#include "suricata-common.h"
#include "util-debug.h"

#include "output.h"

#include "suricata.h"

#include "util-conf.h"
#include "util-enum.h"
#include "util-path.h"
#include "util-syslog.h"
#include "util-time.h"

// clang-format off
/* holds the string-enum mapping for the enums held in the table SCLogLevel */
SCEnumCharMap sc_log_level_map[] = {
    { "Not set",        SC_LOG_NOTSET },
    { "None",           SC_LOG_NONE },
    { "Error",          SC_LOG_ERROR },
    { "Warning",        SC_LOG_WARNING },
    { "Notice",         SC_LOG_NOTICE },
    { "Info",           SC_LOG_INFO },
    { "Perf",           SC_LOG_PERF },
    { "Config",         SC_LOG_CONFIG },
    { "Debug",          SC_LOG_DEBUG },
    { NULL,             -1 }
};

SCEnumCharMap sc_log_slevel_map[] = {
    { "Not set",        SC_LOG_NOTSET },
    { "None",           SC_LOG_NONE },
    { "E",              SC_LOG_ERROR },
    { "W",              SC_LOG_WARNING },
    { "i",              SC_LOG_NOTICE },
    { "i",              SC_LOG_INFO },
    { "i",              SC_LOG_PERF },
    { "i",              SC_LOG_CONFIG },
    { "d",              SC_LOG_DEBUG },
    { NULL,             -1 }
};

/* holds the string-enum mapping for the enums held in the table SCLogOPIface */
SCEnumCharMap sc_log_op_iface_map[ ] = {
    { "Console",        SC_LOG_OP_IFACE_CONSOLE },
    { "File",           SC_LOG_OP_IFACE_FILE },
    { "Syslog",         SC_LOG_OP_IFACE_SYSLOG },
    { NULL,             -1 }
};
// clang-format on

#if defined (OS_WIN32)
/**
 * \brief Used for synchronous output on WIN32
 */
static SCMutex sc_log_stream_lock;
#endif /* OS_WIN32 */

/**
 * \brief Transform the module name into display module name for logging
 */
static const char *SCTransformModule(const char *module_name, int *dn_len);

/**
 * \brief Holds the config state for the logging module
 */
static SCLogConfig *sc_log_config = NULL;

/**
 * \brief Returns the full path given a file and configured log dir
 */
static char *SCLogGetLogFilename(const char *);

/**
 * \brief Holds the global log level.  Is the same as sc_log_config->log_level
 */
SCLogLevel sc_log_global_log_level;

/**
 * \brief Used to indicate whether the logging module has been init or not
 */
int sc_log_module_initialized = 0;

/**
 * \brief Used to indicate whether the logging module has been cleaned or not
 */
int sc_log_module_cleaned = 0;

/**
 * \brief Maps the SC logging level to the syslog logging level
 *
 * \param The SC logging level that has to be mapped to the syslog_log_level
 *
 * \retval syslog_log_level The mapped syslog_api_log_level, for the logging
 *                          module api's internal log_level
 */
static inline int SCLogMapLogLevelToSyslogLevel(int log_level)
{
    int syslog_log_level = 0;

    switch (log_level) {
        case SC_LOG_ERROR:
            syslog_log_level = LOG_ERR;
            break;
        case SC_LOG_WARNING:
            syslog_log_level = LOG_WARNING;
            break;
        case SC_LOG_NOTICE:
            syslog_log_level = LOG_NOTICE;
            break;
        case SC_LOG_INFO:
            syslog_log_level = LOG_INFO;
            break;
        case SC_LOG_CONFIG:
        case SC_LOG_DEBUG:
        case SC_LOG_PERF:
            syslog_log_level = LOG_DEBUG;
            break;
        default:
            syslog_log_level = LOG_EMERG;
            break;
    }

    return syslog_log_level;
}

/**
 * \brief Output function that logs a character string out to a file descriptor
 *
 * \param fd  Pointer to the file descriptor
 * \param msg Pointer to the character string that should be logged
 */
static inline void SCLogPrintToStream(FILE *fd, char *msg)
{
    /* Would only happen if the log file failed to re-open during rotation. */
    if (fd == NULL) {
        return;
    }

#if defined (OS_WIN32)
	SCMutexLock(&sc_log_stream_lock);
#endif /* OS_WIN32 */

    if (fprintf(fd, "%s\n", msg) < 0)
        printf("Error writing to stream using fprintf\n");

    fflush(fd);

#if defined (OS_WIN32)
	SCMutexUnlock(&sc_log_stream_lock);
#endif /* OS_WIN32 */

    return;
}

/**
 * \brief Output function that logs a character string through the syslog iface
 *
 * \param syslog_log_level Holds the syslog_log_level that the message should be
 *                         logged as
 * \param msg              Pointer to the char string, that should be logged
 *
 * \todo syslog is thread-safe according to POSIX manual and glibc code, but we
 *       we will have to look into non POSIX compliant boxes like freeBSD
 */
static inline void SCLogPrintToSyslog(int syslog_log_level, const char *msg)
{
    //static struct syslog_data data = SYSLOG_DATA_INIT;
    //syslog_r(syslog_log_level, NULL, "%s", msg);

    syslog(syslog_log_level, "%s", msg);

    return;
}

/**
 */
static int SCLogMessageJSON(SCTime_t tval, char *buffer, size_t buffer_size, SCLogLevel log_level,
        const char *file, unsigned line, const char *function, const char *module,
        const char *message)
{
    JsonBuilder *js = jb_new_object();
    if (unlikely(js == NULL))
        goto error;

    char timebuf[64];
    CreateIsoTimeString(tval, timebuf, sizeof(timebuf));
    jb_set_string(js, "timestamp", timebuf);

    const char *s = SCMapEnumValueToName(log_level, sc_log_level_map);
    if (s != NULL) {
        jb_set_string(js, "log_level", s);
    } else {
        JB_SET_STRING(js, "log_level", "INVALID");
    }

    JB_SET_STRING(js, "event_type", "engine");
    jb_open_object(js, "engine");

    if (message)
        jb_set_string(js, "message", message);

    if (t_thread_name[0] != '\0') {
        jb_set_string(js, "thread_name", t_thread_name);
    }

    if (module) {
        /* Determine how much of module name to display */
        int dn_len = 0;
        const char *dn_name;
        dn_name = SCTransformModule(module, &dn_len);
        jb_set_string(js, "module", dn_name);
    }

    if (log_level >= SC_LOG_DEBUG) {
        if (function)
            jb_set_string(js, "function", function);

        if (file)
            jb_set_string(js, "file", file);

        if (line > 0)
            jb_set_uint(js, "line", line);
    }
    jb_close(js); // engine

    jb_close(js);
    memcpy(buffer, jb_ptr(js), MIN(buffer_size, jb_len(js)));

    jb_free(js);

    return 0;

error:
    return -1;
}

static const int transform_max_segs = 2; /* The maximum segment count to display */
/*
 * \brief Return a display name for the given module name for logging.
 *
 * The transformation is dependent upon the source code module names
 * that use the dash character to separate incremental refinements of
 * the subsystem.
 *
 * The transformation uses the local constant "transform_max_segs" to determine
 * how many segments to display; the transformed name will never consist
 * of more than this many segments.
 *
 * E.g., "detect-http-content-len" ==> "detect-http" when the max is 2
 *
 * \param module_name The source code module name to be transformed.
 * \param dn_len The number of characters in the display name to print.
 *
 * \retval Pointer to the display name
 */
static const char *SCTransformModule(const char *module_name, int *dn_len)
{
    /*
     * special case for source code module names beginning with:
     *    Prefixes skipped
     *        tm-*
     *        util-*
     *        source-*
     *    No transformation
     *        app-layer-*
     */
    if (strncmp("tm-", module_name, 3) == 0) {
        *dn_len = strlen(module_name) - 3;
        return module_name + 3;
    } else if (strncmp("util-", module_name, 5) == 0) {
        *dn_len = strlen(module_name) - 5;
        return module_name + 5;
    } else if (strncmp("source-pcap-file", module_name, 16) == 0) {
        *dn_len = strlen("pcap");
        return "pcap";
    } else if (strncmp("source-", module_name, 7) == 0) {
        *dn_len = strlen(module_name) - 7;
        return module_name + 7;
    } else if (strncmp("runmode-", module_name, 8) == 0) {
        *dn_len = strlen(module_name) - 8;
        return module_name + 8;
    } else if (strncmp("app-layer-", module_name, 10) == 0) {
        *dn_len = strlen(module_name);
        return module_name;
    } else if (strncmp("detect-engine", module_name, 13) == 0) {
        *dn_len = strlen("detect");
        return "detect";
    }

    int seg_cnt = 0;

    char *last;
    char *w = (char *)module_name;
    while (w && (w = strchr(w, '-')) != NULL && seg_cnt < transform_max_segs) {
        seg_cnt++;
        last = w;
        w++; /* skip past '-' */
    }

    if (seg_cnt < transform_max_segs)
        *dn_len = strlen(module_name);
    else
        *dn_len = last - module_name;

    return module_name;
}

/**
 * \brief Adds the global log_format to the outgoing buffer
 *
 * \param log_level log_level of the message that has to be logged
 * \param msg       Buffer containing the outgoing message
 * \param file      File_name from where the message originated
 * \param function  Function_name from where the message originated
 * \param line      Line_no from where the messaged originated
 *
 * \retval 0 on success; else a negative value on error
 */
static SCError SCLogMessageGetBuffer(SCTime_t tval, int color, SCLogOPType type, char *buffer,
        size_t buffer_size, const char *log_format, const SCLogLevel log_level, const char *file,
        const unsigned int line, const char *function, const char *module, const char *message)
{
    if (type == SC_LOG_OP_TYPE_JSON)
        return SCLogMessageJSON(
                tval, buffer, buffer_size, log_level, file, line, function, module, message);

    char *temp = buffer;
    const char *s = NULL;
    struct tm *tms = NULL;

    const char *redb = "";
    const char *red = "";
    const char *yellowb = "";
    const char *yellow = "";
    const char *green = "";
    const char *blue = "";
    const char *reset = "";
    if (color) {
        redb = "\x1b[1;31m";
        red = "\x1b[31m";
        yellowb = "\x1b[1;33m";
        yellow = "\x1b[33m";
        green = "\x1b[32m";
        blue = "\x1b[34m";
        reset = "\x1b[0m";
    }
    /* no of characters_written(cw) by snprintf */
    int cw = 0;

    BUG_ON(sc_log_module_initialized != 1);

    /* make a copy of the format string as it will be modified below */
    const int add_M = strstr(log_format, "%M") == NULL;
    char local_format[strlen(log_format) + add_M * 2 + 1];
    strlcpy(local_format, log_format, sizeof(local_format));
    if (add_M)
        strlcat(local_format, "%M", sizeof(local_format));
    char *temp_fmt = local_format;
    char *substr = temp_fmt;
    struct tm local_tm;

    while ((temp_fmt = strchr(temp_fmt, SC_LOG_FMT_PREFIX))) {
        if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) {
            return 0;
        }
        switch(temp_fmt[1]) {
            case SC_LOG_FMT_TIME:
                temp_fmt[0] = '\0';

                tms = SCLocalTime(SCTIME_SECS(tval), &local_tm);

                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                        "%s%s%04d-%02d-%02d %02d:%02d:%02d%s", substr, green, tms->tm_year + 1900,
                        tms->tm_mon + 1, tms->tm_mday, tms->tm_hour, tms->tm_min, tms->tm_sec,
                        reset);
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_TIME_LEGACY:
                temp_fmt[0] = '\0';

                tms = SCLocalTime(SCTIME_SECS(tval), &local_tm);

                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                              "%s%s%d/%d/%04d -- %02d:%02d:%02d%s",
                              substr, green, tms->tm_mday, tms->tm_mon + 1,
                              tms->tm_year + 1900, tms->tm_hour, tms->tm_min,
                              tms->tm_sec, reset);
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_PID:
                temp_fmt[0] = '\0';
                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                              "%s%s%u%s", substr, yellow, getpid(), reset);
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_TID:
                temp_fmt[0] = '\0';
                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                              "%s%s%lu%s", substr, yellow, SCGetThreadIdLong(), reset);
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_THREAD_NAME:
            case SC_LOG_FMT_TM:
                temp_fmt[0] = '\0';
                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s", substr,
                        yellow, t_thread_name, reset);
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_LOG_LEVEL:
                temp_fmt[0] = '\0';
                s = SCMapEnumValueToName(log_level, sc_log_level_map);
                if (s != NULL) {
                    if (log_level <= SC_LOG_ERROR)
                        cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                                  "%s%s%s%s", substr, redb, s, reset);
                    else if (log_level == SC_LOG_WARNING)
                        cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                                  "%s%s%s%s", substr, red, s, reset);
                    else if (log_level == SC_LOG_NOTICE)
                        cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                                  "%s%s%s%s", substr, yellowb, s, reset);
                    else
                        cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s",
                                substr, yellow, s, reset);
                } else {
                    cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s", substr,
                            "INVALID");
                }
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_LOG_SLEVEL:
                temp_fmt[0] = '\0';
                s = SCMapEnumValueToName(log_level, sc_log_slevel_map);
                if (s != NULL) {
                    if (log_level <= SC_LOG_ERROR)
                        cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s",
                                substr, redb, s, reset);
                    else if (log_level == SC_LOG_WARNING)
                        cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s",
                                substr, red, s, reset);
                    else if (log_level == SC_LOG_NOTICE)
                        cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s",
                                substr, yellowb, s, reset);
                    else
                        cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                                  "%s%s%s%s", substr, yellow, s, reset);
                } else {
                    cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                                  "%s%s", substr, "INVALID");
                }
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_FILE_NAME:
                temp_fmt[0] = '\0';
                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                              "%s%s%s%s", substr, blue, file, reset);
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_LINE:
                temp_fmt[0] = '\0';
                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                              "%s%s%u%s", substr, green, line, reset);
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_SUBSYSTEM:
                temp_fmt[0] = '\0';

                /* Determine how much of module name to display */
                int dn_len = 0;
                const char *dn_name = "unknown";
                if (module) {
                    dn_name = SCTransformModule(module, &dn_len);
                }

                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s", substr,
                        green, dn_name, reset);
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_FUNCTION:
                temp_fmt[0] = '\0';
                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
                              "%s%s%s%s", substr, green, function, reset);
                if (cw < 0)
                    return -1;
                temp += cw;
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;

            case SC_LOG_FMT_MESSAGE: {
                temp_fmt[0] = '\0';
                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s", substr);
                if (cw < 0) {
                    return -1;
                }
                temp += cw;
                if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) {
                    return 0;
                }
                const char *hi = "";
                if (log_level <= SC_LOG_ERROR)
                    hi = red;
                else if (log_level <= SC_LOG_NOTICE)
                    hi = yellow;
                cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s", hi, message,
                        reset);
                if (cw < 0) {
                    return -1;
                }
                temp += cw;
                if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) {
                    return 0;
                }
                temp_fmt++;
                substr = temp_fmt;
                substr++;
                break;
            }
        }
        temp_fmt++;
    }
    if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) {
        return 0;
    }
    cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s", substr);
    if (cw < 0) {
        return -1;
    }
    if (sc_log_config->op_filter_regex != NULL) {
        if (pcre2_match(sc_log_config->op_filter_regex, (PCRE2_SPTR8)buffer, strlen(buffer), 0, 0,
                    sc_log_config->op_filter_regex_match, NULL) < 0) {
            return -1; // bit hacky, but just return !0
        }
    }

    return 0;
}

/** \internal
 *  \brief try to reopen file
 *  \note no error reporting here, as we're called by SCLogMessage
 *  \retval status 0 ok, -1 error */
static int SCLogReopen(SCLogOPIfaceCtx *op_iface_ctx)
{
    if (op_iface_ctx->file == NULL) {
        return 0;
    }

    if (op_iface_ctx->file_d != NULL) {
        fclose(op_iface_ctx->file_d);
    }
    op_iface_ctx->file_d = fopen(op_iface_ctx->file, "a");
    if (op_iface_ctx->file_d == NULL) {
        return -1;
    }
    return 0;
}

/**
 * \brief Adds the global log_format to the outgoing buffer
 *
 * \param log_level log_level of the message that has to be logged
 * \param msg       Buffer containing the outgoing message
 * \param file      File_name from where the message originated
 * \param function  Function_name from where the message originated
 * \param line      Line_no from where the messaged originated
 *
 * \retval SC_OK on success; else an error code
 */
SCError SCLogMessage(const SCLogLevel log_level, const char *file, const unsigned int line,
        const char *function, const char *module, const char *message)
{
    char buffer[SC_LOG_MAX_LOG_MSG_LEN] = "";
    SCLogOPIfaceCtx *op_iface_ctx = NULL;

    if (sc_log_module_initialized != 1) {
        printf("Logging module not initialized.  Call SCLogInitLogModule() "
               "first before using the debug API\n");
        return SC_OK;
    }

    /* get ts here so we log the same ts to each output */
    struct timeval tval;
    gettimeofday(&tval, NULL);
    SCTime_t ts = SCTIME_FROM_TIMEVAL(&tval);

    op_iface_ctx = sc_log_config->op_ifaces;
    while (op_iface_ctx != NULL) {
        if (log_level != SC_LOG_NOTSET && log_level > op_iface_ctx->log_level) {
            op_iface_ctx = op_iface_ctx->next;
            continue;
        }

        switch (op_iface_ctx->iface) {
            case SC_LOG_OP_IFACE_CONSOLE:
                if (SCLogMessageGetBuffer(ts, op_iface_ctx->use_color, op_iface_ctx->type, buffer,
                            sizeof(buffer),
                            op_iface_ctx->log_format ? op_iface_ctx->log_format
                                                     : sc_log_config->log_format,
                            log_level, file, line, function, module, message) == 0) {
                    SCLogPrintToStream((log_level == SC_LOG_ERROR)? stderr: stdout, buffer);
                }
                break;
            case SC_LOG_OP_IFACE_FILE:
                if (SCLogMessageGetBuffer(ts, 0, op_iface_ctx->type, buffer, sizeof(buffer),
                            op_iface_ctx->log_format ? op_iface_ctx->log_format
                                                     : sc_log_config->log_format,
                            log_level, file, line, function, module, message) == 0) {
                    int r = 0;
                    SCMutexLock(&op_iface_ctx->fp_mutex);
                    if (op_iface_ctx->rotation_flag) {
                        r = SCLogReopen(op_iface_ctx);
                        op_iface_ctx->rotation_flag = 0;
                    }
                    SCLogPrintToStream(op_iface_ctx->file_d, buffer);
                    SCMutexUnlock(&op_iface_ctx->fp_mutex);

                    /* report error outside of lock to avoid recursion */
                    if (r == -1) {
                        SCLogError("re-opening file \"%s\" failed: %s", op_iface_ctx->file,
                                strerror(errno));
                    }
                }
                break;
            case SC_LOG_OP_IFACE_SYSLOG:
                if (SCLogMessageGetBuffer(ts, 0, op_iface_ctx->type, buffer, sizeof(buffer),
                            op_iface_ctx->log_format ? op_iface_ctx->log_format
                                                     : sc_log_config->log_format,
                            log_level, file, line, function, module, message) == 0) {
                    SCLogPrintToSyslog(SCLogMapLogLevelToSyslogLevel(log_level), buffer);
                }
                break;
            default:
                break;
        }
        op_iface_ctx = op_iface_ctx->next;
    }
    return SC_OK;
}

void SCLog(int x, const char *file, const char *func, const int line, const char *module,
        const char *fmt, ...)
{
    if (sc_log_global_log_level >= x &&
            (sc_log_fg_filters_present == 0 ||
             SCLogMatchFGFilterWL(file, func, line) == 1 ||
             SCLogMatchFGFilterBL(file, func, line) == 1) &&
            (sc_log_fd_filters_present == 0 ||
             SCLogMatchFDFilter(func) == 1))
    {
        char msg[SC_LOG_MAX_LOG_MSG_LEN];
        va_list ap;
        va_start(ap, fmt);
        vsnprintf(msg, sizeof(msg), fmt, ap);
        va_end(ap);
        SCLogMessage(x, file, line, func, module, msg);
    }
}

void SCLogErr(int x, const char *file, const char *func, const int line, const char *module,
        const char *fmt, ...)
{
    if (sc_log_global_log_level >= x &&
            (sc_log_fg_filters_present == 0 ||
             SCLogMatchFGFilterWL(file, func, line) == 1 ||
             SCLogMatchFGFilterBL(file, func, line) == 1) &&
            (sc_log_fd_filters_present == 0 ||
             SCLogMatchFDFilter(func) == 1))
    {
        char msg[SC_LOG_MAX_LOG_MSG_LEN];
        va_list ap;
        va_start(ap, fmt);
        vsnprintf(msg, sizeof(msg), fmt, ap);
        va_end(ap);
        SCLogMessage(x, file, line, func, module, msg);
    }
}

/**
 * \brief Returns whether debug messages are enabled to be logged or not
 *
 * \retval 1 if debug messages are enabled to be logged
 * \retval 0 if debug messages are not enabled to be logged
 */
int SCLogDebugEnabled(void)
{
#ifdef DEBUG
    if (sc_log_global_log_level == SC_LOG_DEBUG)
        return 1;
    else
        return 0;
#else
    return 0;
#endif
}

/**
 * \brief Allocates an output buffer for an output interface.  Used when we
 *        want the op_interface log_format to override the global_log_format.
 *        Currently not used.
 *
 * \retval buffer Pointer to the newly created output_buffer
 */
SCLogOPBuffer *SCLogAllocLogOPBuffer(void)
{
    SCLogOPBuffer *buffer = NULL;

    if ( (buffer = SCMalloc(sc_log_config->op_ifaces_cnt *
                          sizeof(SCLogOPBuffer))) == NULL) {
        FatalError("Fatal error encountered in SCLogAllocLogOPBuffer. Exiting...");
    }

    SCLogOPIfaceCtx *op_iface_ctx = sc_log_config->op_ifaces;
    for (int i = 0; i < sc_log_config->op_ifaces_cnt; i++, op_iface_ctx = op_iface_ctx->next) {
        buffer[i].log_format = op_iface_ctx->log_format;
        buffer[i].temp = buffer[i].msg;
    }

    return buffer;
}

/*----------------------The logging module initialization code--------------- */

/**
 * \brief Returns a new output_interface_context
 *
 * \retval iface_ctx Pointer to a newly allocated output_interface_context
 * \initonly
 */
static inline SCLogOPIfaceCtx *SCLogAllocLogOPIfaceCtx(void)
{
    SCLogOPIfaceCtx *iface_ctx = NULL;

    if ((iface_ctx = SCCalloc(1, sizeof(SCLogOPIfaceCtx))) == NULL) {
        FatalError("Fatal error encountered in SCLogallocLogOPIfaceCtx. Exiting...");
    }

    return iface_ctx;
}

/**
 * \brief Initializes the file output interface
 *
 * \param file       Path to the file used for logging purposes
 * \param log_format Pointer to the log_format for this op interface, that
 *                   overrides the global_log_format
 * \param log_level  Override of the global_log_level by this interface
 *
 * \retval iface_ctx Pointer to the file output interface context created
 * \initonly
 */
static inline SCLogOPIfaceCtx *SCLogInitFileOPIface(const char *file, uint32_t userid,
        uint32_t groupid, const char *log_format, int log_level, SCLogOPType type)
{
    SCLogOPIfaceCtx *iface_ctx = SCLogAllocLogOPIfaceCtx();
    if (iface_ctx == NULL) {
        FatalError("Fatal error encountered in SCLogInitFileOPIface. Exiting...");
    }

    if (file == NULL) {
        goto error;
    }

    iface_ctx->iface = SC_LOG_OP_IFACE_FILE;
    iface_ctx->type = type;

    if ( (iface_ctx->file_d = fopen(file, "a")) == NULL) {
        SCLogWarning("error opening file %s: %s", file, strerror(errno));
        goto error;
    }

#ifndef OS_WIN32
    if (userid != 0 || groupid != 0) {
        if (fchown(fileno(iface_ctx->file_d), userid, groupid) == -1) {
            SCLogWarning("Failed to change ownership of file %s: %s", file, strerror(errno));
        }
    }
#endif

    if ((iface_ctx->file = SCStrdup(file)) == NULL) {
        goto error;
    }

    if (log_format != NULL && (iface_ctx->log_format = SCStrdup(log_format)) == NULL) {
        goto error;
    }

    SCMutexInit(&iface_ctx->fp_mutex, NULL);
    OutputRegisterFileRotationFlag(&iface_ctx->rotation_flag);

    iface_ctx->log_level = log_level;

    return iface_ctx;

error:
    if (iface_ctx->file != NULL) {
        SCFree((char *)iface_ctx->file);
        iface_ctx->file = NULL;
    }
    if (iface_ctx->log_format != NULL) {
        SCFree((char *)iface_ctx->log_format);
        iface_ctx->log_format = NULL;
    }
    if (iface_ctx->file_d != NULL) {
        fclose(iface_ctx->file_d);
        iface_ctx->file_d = NULL;
    }
    SCFree(iface_ctx);
    return NULL;
}

/**
 * \brief Initializes the console output interface and deals with possible
 *        env var overrides.
 *
 * \param log_format Pointer to the log_format for this op interface, that
 *                   overrides the global_log_format
 * \param log_level  Override of the global_log_level by this interface
 *
 * \retval iface_ctx Pointer to the console output interface context created
 * \initonly
 */
static inline SCLogOPIfaceCtx *SCLogInitConsoleOPIface(const char *log_format,
                                                       SCLogLevel log_level, SCLogOPType type)
{
    SCLogOPIfaceCtx *iface_ctx = SCLogAllocLogOPIfaceCtx();

    if (iface_ctx == NULL) {
        FatalError("Fatal error encountered in SCLogInitConsoleOPIface. Exiting...");
    }

    iface_ctx->iface = SC_LOG_OP_IFACE_CONSOLE;
    iface_ctx->type = type;

    /* console log format is overridden by envvars */
    const char *tmp_log_format = log_format;
    const char *s = getenv(SC_LOG_ENV_LOG_FORMAT);
    if (s != NULL) {
#if 0
        printf("Overriding setting for \"console.format\" because of env "
                "var SC_LOG_FORMAT=\"%s\".\n", s);
#endif
        tmp_log_format = s;
    }

    if (tmp_log_format != NULL &&
        (iface_ctx->log_format = SCStrdup(tmp_log_format)) == NULL) {
        printf("Error allocating memory\n");
        exit(EXIT_FAILURE);
    }

    /* console log level is overridden by envvars */
    SCLogLevel tmp_log_level = log_level;
    s = getenv(SC_LOG_ENV_LOG_LEVEL);
    if (s != NULL) {
        SCLogLevel l = SCMapEnumNameToValue(s, sc_log_level_map);
        if (l > SC_LOG_NOTSET && l < SC_LOG_LEVEL_MAX) {
#if 0
            printf("Overriding setting for \"console.level\" because of env "
                    "var SC_LOG_LEVEL=\"%s\".\n", s);
#endif
            tmp_log_level = l;
        }
    }
    iface_ctx->log_level = tmp_log_level;

#ifndef OS_WIN32
    if (isatty(fileno(stdout)) && isatty(fileno(stderr))) {
        iface_ctx->use_color = TRUE;
    }
#endif

    return iface_ctx;
}

/**
 * \brief Initializes the syslog output interface
 *
 * \param facility   The facility code for syslog
 * \param log_format Pointer to the log_format for this op interface, that
 *                   overrides the global_log_format
 * \param log_level  Override of the global_log_level by this interface
 *
 * \retval iface_ctx Pointer to the syslog output interface context created
 */
static inline SCLogOPIfaceCtx *SCLogInitSyslogOPIface(int facility,
                                                      const char *log_format,
                                                      SCLogLevel log_level,
                                                      SCLogOPType type)
{
    SCLogOPIfaceCtx *iface_ctx = SCLogAllocLogOPIfaceCtx();

    if ( iface_ctx == NULL) {
        FatalError("Fatal error encountered in SCLogInitSyslogOPIface. Exiting...");
    }

    iface_ctx->iface = SC_LOG_OP_IFACE_SYSLOG;
    iface_ctx->type = type;

    if (facility == -1)
        facility = SC_LOG_DEF_SYSLOG_FACILITY;
    iface_ctx->facility = facility;

    if (log_format != NULL &&
        (iface_ctx->log_format = SCStrdup(log_format)) == NULL) {
        printf("Error allocating memory\n");
        exit(EXIT_FAILURE);
    }

    iface_ctx->log_level = log_level;

    openlog(NULL, LOG_NDELAY, iface_ctx->facility);

    return iface_ctx;
}

/**
 * \brief Frees the output_interface context supplied as an argument
 *
 * \param iface_ctx Pointer to the op_interface_context to be freed
 */
static inline void SCLogFreeLogOPIfaceCtx(SCLogOPIfaceCtx *iface_ctx)
{
    SCLogOPIfaceCtx *temp = NULL;

    while (iface_ctx != NULL) {
        temp = iface_ctx;

        if (iface_ctx->file_d != NULL) {
            fclose(iface_ctx->file_d);
            SCMutexDestroy(&iface_ctx->fp_mutex);
        }

        if (iface_ctx->file != NULL)
            SCFree((void *)iface_ctx->file);

        if (iface_ctx->log_format != NULL)
            SCFree((void *)iface_ctx->log_format);

        if (iface_ctx->iface == SC_LOG_OP_IFACE_SYSLOG) {
            closelog();
        }

        iface_ctx = iface_ctx->next;

        SCFree(temp);
    }

    return;
}

/**
 * \brief Internal function used to set the logging module global_log_level
 *        during the initialization phase
 *
 * \param sc_lid The initialization data supplied.
 * \param sc_lc  The logging module context which has to be updated.
 */
static inline void SCLogSetLogLevel(SCLogInitData *sc_lid, SCLogConfig *sc_lc)
{
    SCLogLevel log_level = SC_LOG_NOTSET;
    const char *s = NULL;

    /* envvar overrides config */
    s = getenv(SC_LOG_ENV_LOG_LEVEL);
    if (s != NULL) {
        log_level = SCMapEnumNameToValue(s, sc_log_level_map);
    } else if (sc_lid != NULL) {
        log_level = sc_lid->global_log_level;
    }

    /* deal with the global_log_level to be used */
    if (log_level > SC_LOG_NOTSET && log_level < SC_LOG_LEVEL_MAX)
        sc_lc->log_level = log_level;
    else {
        sc_lc->log_level = SC_LOG_DEF_LOG_LEVEL;
#ifndef UNITTESTS
        if (sc_lid != NULL) {
            printf("Warning: Invalid/No global_log_level assigned by user.  Falling "
                   "back on the default_log_level \"%s\"\n",
                   SCMapEnumValueToName(sc_lc->log_level, sc_log_level_map));
        }
#endif
    }

    /* we also set it to a global var, as it is easier to access it */
    sc_log_global_log_level = sc_lc->log_level;

    return;
}

SCLogLevel SCLogGetLogLevel(void)
{
    return sc_log_global_log_level;
}

static inline const char *SCLogGetDefaultLogFormat(const SCLogLevel lvl)
{
    const char *prog_ver = GetProgramVersion();
    if (strstr(prog_ver, "RELEASE") != NULL) {
        if (lvl <= SC_LOG_NOTICE)
            return SC_LOG_DEF_LOG_FORMAT_REL_NOTICE;
        else if (lvl <= SC_LOG_INFO)
            return SC_LOG_DEF_LOG_FORMAT_REL_INFO;
        else if (lvl <= SC_LOG_CONFIG)
            return SC_LOG_DEF_LOG_FORMAT_REL_CONFIG;
    }
    return SC_LOG_DEF_LOG_FORMAT_DEBUG;
}

/**
 * \brief Internal function used to set the logging module global_log_format
 *        during the initialization phase
 *
 * \param sc_lid The initialization data supplied.
 * \param sc_lc  The logging module context which has to be updated.
 */
static inline void SCLogSetLogFormat(SCLogInitData *sc_lid, SCLogConfig *sc_lc)
{
    const char *format = NULL;

    /* envvar overrides config */
    format = getenv(SC_LOG_ENV_LOG_FORMAT);
    if (format == NULL) {
        if (sc_lid != NULL) {
            format = sc_lid->global_log_format;
        }
    }

    /* deal with the global log format to be used */
    if (format == NULL || strlen(format) > SC_LOG_MAX_LOG_FORMAT_LEN) {
        format = SCLogGetDefaultLogFormat(sc_lc->log_level);
#ifndef UNITTESTS
        if (sc_lid != NULL) {
            printf("Warning: Invalid/No global_log_format supplied by user or format "
                   "length exceeded limit of \"%d\" characters.  Falling back on "
                   "default log_format \"%s\"\n", SC_LOG_MAX_LOG_FORMAT_LEN,
                   format);
        }
#endif
    }

    if (format != NULL && (sc_lc->log_format = SCStrdup(format)) == NULL) {
        printf("Error allocating memory\n");
        exit(EXIT_FAILURE);
    }

    return;
}

/**
 * \brief Internal function used to set the logging module global_op_ifaces
 *        during the initialization phase
 *
 * \param sc_lid The initialization data supplied.
 * \param sc_lc  The logging module context which has to be updated.
 */
static inline void SCLogSetOPIface(SCLogInitData *sc_lid, SCLogConfig *sc_lc)
{
    SCLogOPIfaceCtx *op_ifaces_ctx = NULL;
    int op_iface = 0;
    const char *s = NULL;

    if (sc_lid != NULL && sc_lid->op_ifaces != NULL) {
        sc_lc->op_ifaces = sc_lid->op_ifaces;
        sc_lid->op_ifaces = NULL;
        sc_lc->op_ifaces_cnt = sc_lid->op_ifaces_cnt;
    } else {
        s = getenv(SC_LOG_ENV_LOG_OP_IFACE);
        if (s != NULL) {
            op_iface = SCMapEnumNameToValue(s, sc_log_op_iface_map);

            if(op_iface < 0 || op_iface >= SC_LOG_OP_IFACE_MAX) {
                op_iface = SC_LOG_DEF_LOG_OP_IFACE;
#ifndef UNITTESTS
                printf("Warning: Invalid output interface supplied by user.  "
                       "Falling back on default_output_interface \"%s\"\n",
                       SCMapEnumValueToName(op_iface, sc_log_op_iface_map));
#endif
            }
        }
        else {
            op_iface = SC_LOG_DEF_LOG_OP_IFACE;
#ifndef UNITTESTS
            if (sc_lid != NULL) {
                printf("Warning: Output_interface not supplied by user.  Falling "
                       "back on default_output_interface \"%s\"\n",
                       SCMapEnumValueToName(op_iface, sc_log_op_iface_map));
            }
#endif
        }

        switch (op_iface) {
            case SC_LOG_OP_IFACE_CONSOLE:
                op_ifaces_ctx = SCLogInitConsoleOPIface(NULL, SC_LOG_LEVEL_MAX,0);
                break;
            case SC_LOG_OP_IFACE_FILE:
                s = getenv(SC_LOG_ENV_LOG_FILE);
                if (s == NULL) {
                    char *str = SCLogGetLogFilename(SC_LOG_DEF_LOG_FILE);
                    if (str != NULL) {
                        op_ifaces_ctx = SCLogInitFileOPIface(str, 0, 0, NULL, SC_LOG_LEVEL_MAX, 0);
                        SCFree(str);
                    }
                } else {
                    op_ifaces_ctx = SCLogInitFileOPIface(s, 0, 0, NULL, SC_LOG_LEVEL_MAX, 0);
                }
                break;
            case SC_LOG_OP_IFACE_SYSLOG:
                s = getenv(SC_LOG_ENV_LOG_FACILITY);
                if (s == NULL)
                    s = SC_LOG_DEF_SYSLOG_FACILITY_STR;

                op_ifaces_ctx = SCLogInitSyslogOPIface(SCMapEnumNameToValue(s, SCSyslogGetFacilityMap()), NULL, -1,0);
                break;
        }
        sc_lc->op_ifaces = op_ifaces_ctx;
        sc_lc->op_ifaces_cnt++;
    }
    return;
}

/**
 * \brief Internal function used to set the logging module op_filter
 *        during the initialization phase
 *
 * \param sc_lid The initialization data supplied.
 * \param sc_lc  The logging module context which has to be updated.
 */
static inline void SCLogSetOPFilter(SCLogInitData *sc_lid, SCLogConfig *sc_lc)
{
    const char *filter = NULL;

    int opts = 0;
    int en;
    PCRE2_SIZE eo = 0;

    /* envvar overrides */
    filter = getenv(SC_LOG_ENV_LOG_OP_FILTER);
    if (filter == NULL) {
        if (sc_lid != NULL) {
            filter = sc_lid->op_filter;
        }
    }

    if (filter != NULL && strcmp(filter, "") != 0) {
        sc_lc->op_filter = SCStrdup(filter);
        if (sc_lc->op_filter == NULL) {
            printf("pcre filter alloc failed\n");
            return;
        }
        sc_lc->op_filter_regex =
                pcre2_compile((PCRE2_SPTR8)filter, PCRE2_ZERO_TERMINATED, opts, &en, &eo, NULL);
        if (sc_lc->op_filter_regex == NULL) {
            SCFree(sc_lc->op_filter);
            PCRE2_UCHAR errbuffer[256];
            pcre2_get_error_message(en, errbuffer, sizeof(errbuffer));
            printf("pcre2 compile of \"%s\" failed at offset %d : %s\n", filter, (int)eo,
                    errbuffer);
            return;
        }
        sc_lc->op_filter_regex_match =
                pcre2_match_data_create_from_pattern(sc_lc->op_filter_regex, NULL);
    }

    return;
}

/**
 * \brief Returns a pointer to a new SCLogInitData.  This is a public interface
 *        intended to be used after the logging parameters are read from the
 *        conf file
 *
 * \retval sc_lid Pointer to the newly created SCLogInitData
 * \initonly
 */
SCLogInitData *SCLogAllocLogInitData(void)
{
    SCLogInitData *sc_lid = NULL;

    if ((sc_lid = SCCalloc(1, sizeof(SCLogInitData))) == NULL)
        return NULL;

    return sc_lid;
}

#ifdef UNITTESTS
#ifndef OS_WIN32
/**
 * \brief Frees a SCLogInitData
 *
 * \param sc_lid Pointer to the SCLogInitData to be freed
 */
static void SCLogFreeLogInitData(SCLogInitData *sc_lid)
{
    if (sc_lid != NULL) {
        SCLogFreeLogOPIfaceCtx(sc_lid->op_ifaces);
        SCFree(sc_lid);
    }

    return;
}
#endif
#endif

/**
 * \brief Frees the logging module context
 */
static inline void SCLogFreeLogConfig(SCLogConfig *sc_lc)
{
    if (sc_lc != NULL) {
        if (sc_lc->startup_message != NULL)
            SCFree(sc_lc->startup_message);
        if (sc_lc->log_format != NULL)
            SCFree(sc_lc->log_format);
        if (sc_lc->op_filter != NULL)
            SCFree(sc_lc->op_filter);

        if (sc_lc->op_filter_regex != NULL)
            pcre2_code_free(sc_lc->op_filter_regex);
        if (sc_lc->op_filter_regex_match)
            pcre2_match_data_free(sc_lc->op_filter_regex_match);

        SCLogFreeLogOPIfaceCtx(sc_lc->op_ifaces);
        SCFree(sc_lc);
    }

    return;
}

/**
 * \brief Appends an output_interface to the output_interface list sent in head
 *
 * \param iface_ctx Pointer to the output_interface that has to be added to head
 * \param head      Pointer to the output_interface list
 */
void SCLogAppendOPIfaceCtx(SCLogOPIfaceCtx *iface_ctx, SCLogInitData *sc_lid)
{
    SCLogOPIfaceCtx *temp = NULL, *prev = NULL;
    SCLogOPIfaceCtx **head = &sc_lid->op_ifaces;

    if (iface_ctx == NULL) {
#ifdef DEBUG
        printf("Argument(s) to SCLogAppendOPIfaceCtx() NULL\n");
#endif
        return;
    }

    temp = *head;
    while (temp != NULL) {
        prev = temp;
        temp = temp->next;
    }

    if (prev == NULL)
        *head = iface_ctx;
    else
        prev->next = iface_ctx;

    sc_lid->op_ifaces_cnt++;

    return;
}

#ifdef UNITTESTS
#ifndef OS_WIN32
/**
 * \internal
 * \brief Creates a new output interface based on the arguments sent.  The kind
 *        of output interface to be created is decided by the iface_name arg.
 *        If iface_name is "file", the arg argument will hold the filename to be
 *        used for logging purposes.  If iface_name is "syslog", the arg
 *        argument holds the facility code.  If iface_name is "console", arg is
 *        NULL.
 *
 * \param iface_name Interface name.  Can be "console", "file" or "syslog"
 * \param log_format Override for the global_log_format
 * \param log_level  Override for the global_log_level
 * \param log_level  Parameter required by a particular interface.  Explained in
 *                   the function description
 *
 * \retval iface_ctx Pointer to the newly created output interface
 */
static SCLogOPIfaceCtx *SCLogInitOPIfaceCtx(
        const char *iface_name, const char *log_format, int log_level, const char *arg)
{
    int iface = SCMapEnumNameToValue(iface_name, sc_log_op_iface_map);

    if (log_level < SC_LOG_NONE || log_level > SC_LOG_DEBUG) {
        printf("Warning: Supplied log_level_override for op_interface \"%s\" "
               "is invalid.  Defaulting to not specifying an override\n",
                iface_name);
        log_level = SC_LOG_NOTSET;
    }

    switch (iface) {
        case SC_LOG_OP_IFACE_CONSOLE:
            return SCLogInitConsoleOPIface(log_format, log_level, SC_LOG_OP_TYPE_REGULAR);
        case SC_LOG_OP_IFACE_FILE:
            return SCLogInitFileOPIface(arg, 0, 0, log_format, log_level, SC_LOG_OP_TYPE_REGULAR);
        case SC_LOG_OP_IFACE_SYSLOG:
            return SCLogInitSyslogOPIface(SCMapEnumNameToValue(arg, SCSyslogGetFacilityMap()),
                    log_format, log_level, SC_LOG_OP_TYPE_REGULAR);
        default:
#ifdef DEBUG
            printf("Output Interface \"%s\" not supported by the logging module",
                   iface_name);
#endif
            return NULL;
    }
}
#endif
#endif

/**
 * \brief Initializes the logging module.
 *
 * \param sc_lid The initialization data for the logging module.  If sc_lid is
 *               NULL, we would stick to the default configuration for the
 *               logging subsystem.
 * \initonly
 */
void SCLogInitLogModule(SCLogInitData *sc_lid)
{
    /* De-initialize the logging context, if it has already init by the
     * environment variables at the start of the engine */
    SCLogDeInitLogModule();

#if defined (OS_WIN32)
    if (SCMutexInit(&sc_log_stream_lock, NULL) != 0) {
        FatalError("Failed to initialize log mutex.");
    }
#endif /* OS_WIN32 */

    /* sc_log_config is a global variable */
    if ((sc_log_config = SCCalloc(1, sizeof(SCLogConfig))) == NULL) {
        FatalError("Fatal error encountered in SCLogInitLogModule. Exiting...");
    }

    SCLogSetLogLevel(sc_lid, sc_log_config);
    SCLogSetLogFormat(sc_lid, sc_log_config);
    SCLogSetOPIface(sc_lid, sc_log_config);
    SCLogSetOPFilter(sc_lid, sc_log_config);

    sc_log_module_initialized = 1;
    sc_log_module_cleaned = 0;

    //SCOutputPrint(sc_did->startup_message);

    rs_log_set_level(sc_log_global_log_level);
    return;
}

void SCLogLoadConfig(int daemon, int verbose, uint32_t userid, uint32_t groupid)
{
    ConfNode *outputs;
    SCLogInitData *sc_lid;
    int have_logging = 0;
    int max_level = 0;
    SCLogLevel min_level = 0;

    /* If verbose logging was requested, set the minimum as
     * SC_LOG_NOTICE plus the extra verbosity. */
    if (verbose) {
        min_level = SC_LOG_NOTICE + verbose;
    }

    outputs = ConfGetNode("logging.outputs");
    if (outputs == NULL) {
        SCLogDebug("No logging.output configuration section found.");
        return;
    }

    sc_lid = SCLogAllocLogInitData();
    if (sc_lid == NULL) {
        SCLogDebug("Could not allocate memory for log init data");
        return;
    }

    /* Get default log level and format. */
    const char *default_log_level_s = NULL;
    if (ConfGet("logging.default-log-level", &default_log_level_s) == 1) {
        SCLogLevel default_log_level =
            SCMapEnumNameToValue(default_log_level_s, sc_log_level_map);
        if (default_log_level == -1) {
            SCLogError("Invalid default log level: %s", default_log_level_s);
            exit(EXIT_FAILURE);
        }
        sc_lid->global_log_level = MAX(min_level, default_log_level);
    }
    else {
        sc_lid->global_log_level = MAX(min_level, SC_LOG_NOTICE);
    }

    if (ConfGet("logging.default-log-format", &sc_lid->global_log_format) != 1)
        sc_lid->global_log_format = SCLogGetDefaultLogFormat(sc_lid->global_log_level);

    (void)ConfGet("logging.default-output-filter", &sc_lid->op_filter);

    ConfNode *seq_node, *output;
    TAILQ_FOREACH(seq_node, &outputs->head, next) {
        SCLogLevel level = sc_lid->global_log_level;
        SCLogOPIfaceCtx *op_iface_ctx = NULL;
        const char *format;
        const char *level_s;

        output = ConfNodeLookupChild(seq_node, seq_node->val);
        if (output == NULL)
            continue;

        /* By default an output is enabled. */
        const char *enabled = ConfNodeLookupChildValue(output, "enabled");
        if (enabled != NULL && ConfValIsFalse(enabled))
            continue;

        SCLogOPType type = SC_LOG_OP_TYPE_REGULAR;
        const char *type_s = ConfNodeLookupChildValue(output, "type");
        if (type_s != NULL) {
            if (strcmp(type_s, "regular") == 0)
                type = SC_LOG_OP_TYPE_REGULAR;
            else if (strcmp(type_s, "json") == 0) {
                type = SC_LOG_OP_TYPE_JSON;
            }
        }

        format = ConfNodeLookupChildValue(output, "format");

        level_s = ConfNodeLookupChildValue(output, "level");
        if (level_s != NULL) {
            level = SCMapEnumNameToValue(level_s, sc_log_level_map);
            if (level == -1) {
                SCLogError("Invalid log level: %s", level_s);
                exit(EXIT_FAILURE);
            }
            max_level = MAX(max_level, level);
        }

        /* Increase the level of extra verbosity was requested. */
        level = MAX(min_level, level);

        if (strcmp(output->name, "console") == 0) {
            op_iface_ctx = SCLogInitConsoleOPIface(format, level, type);
        }
        else if (strcmp(output->name, "file") == 0) {
            if (format == NULL) {
                format = SC_LOG_DEF_FILE_FORMAT;
            }

            const char *filename = ConfNodeLookupChildValue(output, "filename");
            if (filename == NULL) {
                FatalError("Logging to file requires a filename");
            }
            char *path = NULL;
            if (!(PathIsAbsolute(filename))) {
                path = SCLogGetLogFilename(filename);
            } else {
                path = SCStrdup(filename);
            }
            if (path == NULL)
                FatalError("failed to setup output to file");
            have_logging = 1;
            op_iface_ctx = SCLogInitFileOPIface(path, userid, groupid, format, level, type);
            SCFree(path);
        }
        else if (strcmp(output->name, "syslog") == 0) {
            int facility = SC_LOG_DEF_SYSLOG_FACILITY;
            const char *facility_s = ConfNodeLookupChildValue(output,
                "facility");
            if (facility_s != NULL) {
                facility = SCMapEnumNameToValue(facility_s, SCSyslogGetFacilityMap());
                if (facility == -1) {
                    SCLogWarning("Invalid syslog "
                                 "facility: \"%s\", now using \"%s\" as syslog "
                                 "facility",
                            facility_s, SC_LOG_DEF_SYSLOG_FACILITY_STR);
                    facility = SC_LOG_DEF_SYSLOG_FACILITY;
                }
            }
            SCLogDebug("Initializing syslog logging with format \"%s\"", format);
            have_logging = 1;
            op_iface_ctx = SCLogInitSyslogOPIface(facility, format, level, type);
        }
        else {
            SCLogWarning("invalid logging method: %s, ignoring", output->name);
        }
        if (op_iface_ctx != NULL) {
            SCLogAppendOPIfaceCtx(op_iface_ctx, sc_lid);
        }
    }

    if (daemon && (have_logging == 0)) {
        SCLogWarning("no logging compatible with daemon mode selected,"
                     " suricata won't be able to log. Please update "
                     " 'logging.outputs' in the YAML.");
    }

    /* Set the global log level to that of the max level used. */
    sc_lid->global_log_level = MAX(sc_lid->global_log_level, max_level);
    SCLogInitLogModule(sc_lid);

    SCLogDebug("sc_log_global_log_level: %d", sc_log_global_log_level);
    SCLogDebug("sc_lc->log_format: %s", sc_log_config->log_format);
    SCLogDebug("SCLogSetOPFilter: filter: %s", sc_log_config->op_filter);

    if (sc_lid != NULL)
        SCFree(sc_lid);
}

/**
 * \brief Returns a full file path given a filename uses log dir specified in
 *        conf or DEFAULT_LOG_DIR
 *
 * \param filearg The relative filename for which we want a full path include
 *                log directory
 *
 * \retval log_filename The fullpath of the logfile to open
 */
static char *SCLogGetLogFilename(const char *filearg)
{
    const char *log_dir = ConfigGetLogDirectory();
    char *log_filename = SCMalloc(PATH_MAX);
    if (unlikely(log_filename == NULL))
        return NULL;
    snprintf(log_filename, PATH_MAX, "%s/%s", log_dir, filearg);
    return log_filename;
}

/**
 * \brief De-Initializes the logging module
 */
void SCLogDeInitLogModule(void)
{
    SCLogFreeLogConfig(sc_log_config);

    /* reset the global logging_module variables */
    sc_log_global_log_level = 0;
    sc_log_module_initialized = 0;
    sc_log_module_cleaned = 1;
    sc_log_config = NULL;

    /* de-init the FD filters */
    SCLogReleaseFDFilters();
    /* de-init the FG filters */
    SCLogReleaseFGFilters();

#if defined (OS_WIN32)
    SCMutexDestroy(&sc_log_stream_lock);
#endif /* OS_WIN32 */

    return;
}

//------------------------------------Unit_Tests--------------------------------

/* The logging engine should be tested to the maximum extent possible, since
 * logging code would be used throughout the codebase, and hence we can't afford
 * to have a single bug here(not that you can afford to have a bug
 * elsewhere ;) ). Please report a bug, if you get a slightest hint of a bug
 * from the logging module.
 */

#ifdef UNITTESTS

static int SCLogTestInit01(void)
{
#ifndef OS_WIN32
    /* unset any environment variables set for the logging module */
    unsetenv(SC_LOG_ENV_LOG_LEVEL);
    unsetenv(SC_LOG_ENV_LOG_OP_IFACE);
    unsetenv(SC_LOG_ENV_LOG_FORMAT);

    SCLogInitLogModule(NULL);

    FAIL_IF_NULL(sc_log_config);

    FAIL_IF_NOT(SC_LOG_DEF_LOG_LEVEL == sc_log_config->log_level);
    FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
               SC_LOG_DEF_LOG_OP_IFACE == sc_log_config->op_ifaces->iface);
    FAIL_IF_NOT(sc_log_config->log_format != NULL &&
                strcmp(SCLogGetDefaultLogFormat(sc_log_config->log_level),
                        sc_log_config->log_format) == 0);

    SCLogDeInitLogModule();

    setenv(SC_LOG_ENV_LOG_LEVEL, "Debug", 1);
    setenv(SC_LOG_ENV_LOG_OP_IFACE, "Console", 1);
    setenv(SC_LOG_ENV_LOG_FORMAT, "%n- %l", 1);

    SCLogInitLogModule(NULL);

    FAIL_IF_NOT(SC_LOG_DEBUG == sc_log_config->log_level);
    FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
               SC_LOG_OP_IFACE_CONSOLE == sc_log_config->op_ifaces->iface);
    FAIL_IF_NOT(sc_log_config->log_format != NULL &&
               !strcmp("%n- %l", sc_log_config->log_format));

    unsetenv(SC_LOG_ENV_LOG_LEVEL);
    unsetenv(SC_LOG_ENV_LOG_OP_IFACE);
    unsetenv(SC_LOG_ENV_LOG_FORMAT);

    SCLogDeInitLogModule();
#endif
    PASS;
}

static int SCLogTestInit02(void)
{
#ifndef OS_WIN32
    SCLogInitData *sc_lid = NULL;
    SCLogOPIfaceCtx *sc_iface_ctx = NULL;
    char *logfile = SCLogGetLogFilename("boo.txt");
    sc_lid = SCLogAllocLogInitData();
    FAIL_IF_NULL(sc_lid);
    sc_lid->startup_message = "Test02";
    sc_lid->global_log_level = SC_LOG_DEBUG;
    sc_lid->op_filter = "boo";
    sc_iface_ctx = SCLogInitOPIfaceCtx("file", "%m - %d", SC_LOG_WARNING, logfile);
    SCLogAppendOPIfaceCtx(sc_iface_ctx, sc_lid);
    sc_iface_ctx = SCLogInitOPIfaceCtx("console", NULL, SC_LOG_ERROR,
                                       NULL);
    SCLogAppendOPIfaceCtx(sc_iface_ctx, sc_lid);

    SCLogInitLogModule(sc_lid);

    FAIL_IF_NULL(sc_log_config);

    FAIL_IF_NOT(SC_LOG_DEBUG == sc_log_config->log_level);
    FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
               SC_LOG_OP_IFACE_FILE == sc_log_config->op_ifaces->iface);
    FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
               sc_log_config->op_ifaces->next != NULL &&
               SC_LOG_OP_IFACE_CONSOLE == sc_log_config->op_ifaces->next->iface);
    FAIL_IF_NOT(sc_log_config->log_format != NULL &&
                strcmp(SCLogGetDefaultLogFormat(sc_log_config->log_level),
                        sc_log_config->log_format) == 0);
    FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
               sc_log_config->op_ifaces->log_format != NULL &&
               strcmp("%m - %d", sc_log_config->op_ifaces->log_format) == 0);
    FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
               sc_log_config->op_ifaces->next != NULL &&
               sc_log_config->op_ifaces->next->log_format == NULL);

    SCLogFreeLogInitData(sc_lid);
    SCLogDeInitLogModule();

    sc_lid = SCLogAllocLogInitData();
    FAIL_IF_NULL(sc_lid);
    sc_lid->startup_message = "Test02";
    sc_lid->global_log_level = SC_LOG_DEBUG;
    sc_lid->op_filter = "boo";
    sc_lid->global_log_format = "kaboo";

    SCLogInitLogModule(sc_lid);

    FAIL_IF_NULL(sc_log_config);

    FAIL_IF_NOT(SC_LOG_DEBUG == sc_log_config->log_level);
    FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
               SC_LOG_OP_IFACE_CONSOLE == sc_log_config->op_ifaces->iface);
    FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
               sc_log_config->op_ifaces->next == NULL);
    FAIL_IF_NOT(sc_log_config->log_format != NULL &&
               strcmp("kaboo", sc_log_config->log_format) == 0);
    FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
               sc_log_config->op_ifaces->log_format == NULL);
    FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
               sc_log_config->op_ifaces->next == NULL);

    SCLogFreeLogInitData(sc_lid);
    SCLogDeInitLogModule();
    SCFree(logfile);
#endif
    PASS;
}

static int SCLogTestInit03(void)
{
    SCLogInitLogModule(NULL);

    SCLogAddFGFilterBL(NULL, "bamboo", -1);
    SCLogAddFGFilterBL(NULL, "soo", -1);
    SCLogAddFGFilterBL(NULL, "dummy", -1);

    FAIL_IF_NOT(SCLogPrintFGFilters() == 3);

    SCLogAddFGFilterBL(NULL, "dummy1", -1);
    SCLogAddFGFilterBL(NULL, "dummy2", -1);

    FAIL_IF_NOT(SCLogPrintFGFilters() == 5);

    SCLogDeInitLogModule();

    PASS;
}

static int SCLogTestInit04(void)
{
    SCLogInitLogModule(NULL);

    SCLogAddFDFilter("bamboo");
    SCLogAddFDFilter("soo");
    SCLogAddFDFilter("foo");
    SCLogAddFDFilter("roo");

    FAIL_IF_NOT(SCLogPrintFDFilters() == 4);

    SCLogAddFDFilter("loo");
    SCLogAddFDFilter("soo");

    FAIL_IF_NOT(SCLogPrintFDFilters() == 5);

    SCLogRemoveFDFilter("bamboo");
    SCLogRemoveFDFilter("soo");
    SCLogRemoveFDFilter("foo");
    SCLogRemoveFDFilter("noo");

    FAIL_IF_NOT(SCLogPrintFDFilters() == 2);

    SCLogDeInitLogModule();

    PASS;
}

static int SCLogTestInit05(void)
{
    char str[4096];
    memset(str, 'A', sizeof(str));
    SCLogInfo("%s", str);

    PASS;
}

#endif /* UNITTESTS */

void SCLogRegisterTests(void)
{

#ifdef UNITTESTS

    UtRegisterTest("SCLogTestInit01", SCLogTestInit01);
    UtRegisterTest("SCLogTestInit02", SCLogTestInit02);
    UtRegisterTest("SCLogTestInit03", SCLogTestInit03);
    UtRegisterTest("SCLogTestInit04", SCLogTestInit04);
    UtRegisterTest("SCLogTestInit05", SCLogTestInit05);

#endif /* UNITTESTS */

   return;
}
