/***************************************************************************//**
  @file ltrsrv_log.c
  @author Borisov Alexey <borisov@lcard.ru>
  @date   1.02.2011

  Файл содержит функции для инициализации журнала и вывода отладочных сообщений
  в журнал.
  *****************************************************************************/
#include "ltrsrv_defs.h"
#include "ltrsrv_log.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#ifdef LTRD_LOGPROTO_ENABLED
    #include "ltrsrv_log_proto.h"
#endif

#include "config.h"
#include "ltrsrv_opt_parse.h"

#ifndef _WIN32
    #include "errno.h"
#endif

#ifdef LTRD_SYSLOG_ENABLED
    #ifndef _WIN32
        #ifdef HAVE_LIBDAEMON
            #include <libdaemon/dlog.h>
        #else
            #include <syslog.h>
        #endif
    #else
        #include <windows.h>
        #include "ltrsrv_event_msg.h"
    #endif
#endif

#ifdef LTRD_SYSTEMD_ENABLED
    #include <systemd/sd-daemon.h>
#endif


static FILE* f_logfile = NULL;
static t_ltrsrv_log_level f_log_lvl = LTRSRV_LOGLVL_DBG_LOW;
static t_ltrsrv_log_level f_log_save_lvl = LTRSRV_LOGLVL_DBG_LOW;
static char f_log_buf1[128], f_log_buf2[256], f_log_buf3[512];


static int f_log_init = 0;

#ifdef _WIN32
    static char f_log_buf_syserrmsg[256];
    #ifdef  LTRD_SYSLOG_ENABLED
        static HANDLE f_log_handle=NULL;
    #endif
#endif



/* таблица соответствий кодов уровня лога и выводимых строк */
static t_ltrsrv_str_tbl f_loglvl_str[] = {
    {LTRSRV_LOGLVL_ERR_FATAL, "fatal  "},
    {LTRSRV_LOGLVL_ERR,       "error  "},
    {LTRSRV_LOGLVL_WARN,      "warning"},
    {LTRSRV_LOGLVL_INFO,      "info   "},
    {LTRSRV_LOGLVL_DETAIL,    "detail "},
    {LTRSRV_LOGLVL_DBG_HIGH,  "debug  "},
    {LTRSRV_LOGLVL_DBG_MED,   "debug  "},
    {LTRSRV_LOGLVL_DBG_LOW,   "debug  "}
};

static t_ltrsrv_str_tbl f_crate_iface_str[] = {
    {LTR_CRATE_IFACE_USB,   "USB"},
    {LTR_CRATE_IFACE_TCPIP, "TCP"},
};

typedef struct {
    int code;
    int syserr;
    const char* str;
} t_ltrsrv_err_entry;

static t_ltrsrv_err_entry f_err_str[] = {
    {1                               , 1, "Unknown error code"},
    {LTRSRV_ERR_SUCCESS              , 0, "Ok"},
    {LTRSRV_ERR_MEMALLOC             , 0, "Memory allocation failed"},
    {LTRSRV_ERR_LOG_FILE_OPEN        , 1, "Cannot open log file"},
    {LTRSRV_ERR_NO_FREE_SOCK         , 0, "Have no free space for new wait socket"},
    {LTRSRV_ERR_DAEMONIZE_ERROR      , 1, "Cannot daemonize process"},
    {LTRSRV_ERR_PARSE_OPTION         , 1, "Cannot parse program options"},
    {LTRSRV_ERR_SIGNAL_RESET         , 1, "Cannot reset signal handlers"},
    {LTRSRV_ERR_ALREADY_RUNNING      , 0, "Server is already running"},
    {LTRSRV_ERR_CREATE_PIDFILE       , 1, "Cannot create process pid file"},
    {LTRSRV_ERR_CLOSE_DESCR          , 1, "Cannot close all file descriptors"},
    {LTRSRV_ERR_CREATE_PIPE          , 1, "Cannot create pipe"},
    {LTRSRV_ERR_FIND_USER            , 0, "Cannot find user '" LTRD_PROGRAMM_NAME "'"},
    {LTRSRV_ERR_FIND_GROUP           , 0, "Cannot find group '" LTRD_PROGRAMM_NAME "'"},
    {LTRSRV_ERR_CHANGE_GROUP_LIST    , 1, "Cannot change group list"},
    {LTRSRV_ERR_CHANGE_UID           , 1, "Cannot change user id"},
    {LTRSRV_ERR_CHANGE_GID           , 1, "Cannot change group id"},
    {LTRSRV_ERR_NO_FREE_LOGPROTO_CLIENTS,0, "No free log protocol client slots"},
    {LTRSRV_ERR_CREATE_EVENT         , 1, "Cannot create system event"},
    {LTRSRV_ERR_EVENT_SELECT         , 1, "WSAEventSelect error"},

    {LTRSRV_ERR_CFGFILE_PARSE        , 1, "Cannot parse server configuration file"},
    {LTRSRV_ERR_CFGFILE_ROOT_ELEM    , 0, "Invalid config file root element name"},
    {LTRSRV_ERR_SAVECFG_XML_NODE     , 1, "Cannot save settings: create xml-node error"},
    {LTRSRV_ERR_SAVECFG_FILE         , 1, "Cannot save settings: write to file error"},

    {LTRSRV_ERR_INVALID_LOG_LEVEL    , 0, "Attempt to set invalid log level"},
    {LTRSRV_ERR_INVALID_PARAM        , 0, "Unsupported " LTRD_PROGRAMM_NAME " parameter"},
    {LTRSRV_ERR_INVALID_PARAM_SIZE   , 0, "Invalid " LTRD_PROGRAMM_NAME " parameter value size"},
    {LTRSRV_ERR_INVALID_SVC_OP       , 0, "Invalid service operation"},
    {LTRSRV_ERR_RESOURCE_ALLOC       , 1, "Resource allocation error"},
    {LTRSRV_ERR_DEVNOTIFY_REGISTER   , 1, "Device notification register error"},

    {LTRSRV_ERR_SOCKET_CREATE        , 1, "Cannot create socket"},
    {LTRSRV_ERR_SOCKET_ADDR_FAMILY   , 0, "Unsupported ip address family"},
    {LTRSRV_ERR_SOCKET_INET_ADDR     , 0, "Invlid ip address"},
    {LTRSRV_ERR_SOCKET_SET_NBLOCK    , 1, "Cannot move socket in nonblocking mode"},
    {LTRSRV_ERR_SOCKET_CONNECT       , 1, "Cannot connect to specified ip"},
    {LTRSRV_ERR_SOCKET_SEND          , 1, "Socket send error"},
    {LTRSRV_ERR_SOCKET_RECV          , 1, "Socket receive error"},
    {LTRSRV_ERR_SOCKET_SELECT        , 1, "Select error"},
    {LTRSRV_ERR_SOCKET_ABORTED       , 0, "Socket connection is aborted"},
    {LTRSRV_ERR_SOCKET_CLOSED        , 0, "Socket closed by another side"},
    {LTRSRV_ERR_SOCKET_BIND          , 1, "Socket bind error"},
    {LTRSRV_ERR_SOCKET_LISTEN        , 1, "Socket listen error"},
    {LTRSRV_ERR_SOCKET_ACCEPT        , 1, "Socket accept error"},
    {LTRSRV_ERR_SOCKET_SET_BUF_SIZE  , 1, "Cannot set socket buffer size"},
    {LTRSRV_ERR_SOCKET_CON_RESET,      0, "Connection reset by another side"},
    {LTRSRV_ERR_SOCKET_GEN_ERR,        0, "General socket error"},
    {LTRSRV_ERR_SOCKET_HOST_UNREACH  , 0, "Remote host unreachable"},
    {LTRSRV_ERR_SOCKET_CON_REFUSED   , 0, "Connection refused"},


    {LTRSRV_ERR_CLIENT_HANDLE              , 0, "Invalid client handle"},
    {LTRSRV_ERR_CLIENT_INVALID_INIT_CMD    , 0, "Invalid client initialization command"},
    {LTRSRV_ERR_CLIENT_CMD_SIGN            , 0, "Invalid client command sign"},
    {LTRSRV_ERR_CLIENT_UNSUP_CMD_CODE      , 0, "Unsupported client command code"},
    {LTRSRV_ERR_CLIENT_CRATE_NOT_FOUND     , 0, "Client try to work with unregistered crate"},
    {LTRSRV_ERR_CLIENT_CMD_OVERRUN         , 0, "Client received new command while processed previous"},
    {LTRSRV_ERR_CLIENT_CMD_UNSUP_FOR_SRV_CTL, 0, "Client command is not supported for server control channel"},
    {LTRSRV_ERR_CLIENT_CMD_PARMS            , 0, "Invalid parametrs in client command"},
    {LTRSRV_ERR_CLIENT_CLOSED               , 0, "Client was closed"},


    {LTRSRV_ERR_CRATE_HANDLE               , 0, "Invalid crate handle"},
    {LTRSRV_ERR_CRATE_INTF_CLOSED          , 0, "Crate connection closed"},
    {LTRSRV_ERR_CRATE_INVALID_INTF         , 0, "Invalid crate interface"},
    {LTRSRV_ERR_CRATE_FPGA_INFO_SIZE       , 0, "Received invalid FPGA info size"},
    {LTRSRV_ERR_CRATE_FPGA_INFO_FORMAT     , 0, "Received invalid FPGA info format"},
    {LTRSRV_ERR_CRATE_RAW_DESRC_SIZE       , 0, "Received invalid crate descriptor size"},
    {LTRSRV_ERR_CRATE_FIRMVARE_VER_SIZE    , 0, "Received invalid firmware version size"},
    {LTRSRV_ERR_CRATE_BOOTLOADER_VER_SIZE  , 0, "Received invalid bootloader version size"},
    {LTRSRV_ERR_CRATE_CMD_IN_PROGRESS      , 0, "Another control command executed"},
    {LTRSRV_ERR_CRATE_CRATE_NOT_INIT       , 0, "Crate not initilized"},
    {LTRSRV_ERR_CRATE_CMD_REPLY_LEN       , 0, "Invalid control command reply data length"},
    {LTRSRV_ERR_CRATE_CMD_QUEUE_FULL       , 0, "Crate control command queue is full"},
    {LTRSRV_ERR_CRATE_INVALID_CMD_DATA_SIZE, 0, "Invalid control command data length"},
    {LTRSRV_ERR_CRATE_UNEXPECTED_CMD_CB    , 0, "Unexpected command completion callback"},
    {LTRSRV_ERR_CRATE_UNREGISTRED          , 0, "Unregistered crate handle"},
    {LTRSRV_ERR_CRATE_UNKNOWN_DEVNAME      , 0, "Unkonwn crate device name"},
    {LTRSRV_ERR_CRATE_SEND_BUF_OVFL        , 0, "Crate send buffer overflow"},
    {LTRSRV_ERR_CRATE_INVALID_CMD_IN_QUEUE , 0, "Invalid crate command queue"},
    {LTRSRV_ERR_CRATE_SLOT_EMPTY           , 0, "Client try to work with empty slot"},
    {LTRSRV_ERR_CRATE_INVALID_SLOT_NUM     , 0, "Invalid slot number"},    
    {LTRSRV_ERR_CRATE_INVALID_FIRM_FILE    , 1, "Invalid crate firmware file"},
    {LTRSRV_ERR_CRATE_FIRM_FILE_READ       , 0, "Crate firmware file read error"},
    {LTRSRV_ERR_MODULE_MID                 , 0, "Invalid module mid"},
    {LTRSRV_ERR_MODULE_IN_USE              , 0, LTR_MODULE_NAME " module is already in use"},
    {LTRSRV_ERR_MODULE_EXCEED_MAX_CLIENT   , 0, "Exceeded maximal client connections count to " LTR_MODULE_NAME " module"},
    {LTRSRV_ERR_CRATE_BOARD_REV_SIZE       , 0, "Received invalid board revision size"},
    {LTRSRV_ERR_CRATE_SLOTS_CONFIG_VER_SIZE, 0, "Received invalid slots config version size"},
    {LTRSRV_ERR_CRATE_THERM_OUT_OF_RANGE,    0, "Crate thermometer values are out of range"},
    {LTRSRV_ERR_CRATE_PRIMARY_IFACE_SIZE,    0, "Received invalid crate primary interface size"},
    {LTRSRV_ERR_CRATE_UNEXPECTED_CTL_DATA,   0, "Received unexpected control data"},
    {LTRSRV_ERR_CRATE_DATA_RECON_EXCEEDED  , 0, "Data socket reconnection limit exceeded"},
    {LTRSRV_ERR_CRATE_CLOSING,               0, "Crate is in closing state"},
    {LTRSRV_ERR_CRATE_INSUF_PERMISSON,       0, "Insufficient permission to open crate device"},


    {LTRSRV_ERR_ETH_CMD_EVENT        , 0, "Invalid TCP command socket event"},
    {LTRSRV_ERR_ETH_REPLY_SIGNATURE , 0, "Invalid TCP command reply signature"},
    {LTRSRV_ERR_ETH_REPLY_RESULT    , 0, "Invalid TCP command reply result"},
    {LTRSRV_ERR_ETH_REPLY_SIZE      , 0, "TCP command reply size exceeded expected size"},
    {LTRSRV_ERR_ETH_INVALID_CMD      , 0, "Invalid TCP command code"},
    {LTRSRV_ERR_ETH_CMD_RECV_TOUT    , 0, "Command reply timeout"},
    {LTRSRV_ERR_ETH_CMD_SEND_TOUT    , 0, "Command send timeout"},
    {LTRSRV_ERR_ETH_CONNECTION_TOUT  , 0, "TCP connection tout"},
    {LTRSRV_ERR_ETH_EXCEED_MAX_IP_ENTRY_CNT, 0, "Exceed ip entry max count"},
    {LTRSRV_ERR_ETH_INVALID_IP_ENTRY       , 0, "IP entry does not exist"},
    {LTRSRV_ERR_ETH_CRATE_CONNECTED        , 0, "Respective crate is connected or in connecting state"},


    {LTRSRV_ERR_USB_OPEN_DEVICE, 1, "Cannot open USB-device"},
    {LTRSRV_ERR_USB_TRANSFER_FAILED, 1, "USB transfer failed"},
    {LTRSRV_ERR_USB_TRANSF_BUSY,   0, "All USB transfer are busy"},
    {LTRSRV_ERR_USB_TRANSF_OUT_OF_ORDER, 0, "USB transfer completion is out of order"},
    {LTRSRV_ERR_USB_START_TRANSFER, 0, "USB start transfer error"},
    {LTRSRV_ERR_USB_INVALID_CONFIGURATION, 0, "Invalid USB-device configuration"},
    {LTRSRV_ERR_USB_IOCTL_FAILD, 1, "USB io control request failed"},
    {LTRSRV_ERR_USB_TRANSF_TOUT, 0, "USB transfer timeout"},
    {LTRSRV_ERR_USB_INIT, 1, "Cannot initialize USB system"}
};





#define LOGSTR_LOGLVL(lvl) ltrsrv_log_get_str(lvl, f_loglvl_str, sizeof(f_loglvl_str)/sizeof(f_loglvl_str[0]))
#define LOGSTR_CRATE_INTF(intf) ltrsrv_log_get_str(intf, f_crate_iface_str, sizeof(f_crate_iface_str)/sizeof(f_crate_iface_str[0]))


const char* ltrsrv_log_get_crate_iface_str(t_ltr_crate_interface iface) {
    return LOGSTR_CRATE_INTF(iface);
}


const char* ltrsrv_log_get_str(int code, const t_ltrsrv_str_tbl *tbl, int tbl_size) {
    int i, fnd;
    const char* str = "unknown code";
    for (i=0, fnd=0; (i < tbl_size) && !fnd; i++) {
        if (tbl[i].code == code) {
            str = tbl[i].str;
            fnd = 1;
        }
    }
    return str;
}

/* получение по коду ошибки записи с описанием ошибки */
const t_ltrsrv_err_entry* ltrsrv_log_get_err_str(int code) {
    int i, fnd;
    t_ltrsrv_err_entry* str = &f_err_str[0];;
    for (i=1, fnd=0; (i < (int)(sizeof(f_err_str)/sizeof(f_err_str[0]))) && !fnd; i++) {
        if (f_err_str[i].code == code) {
            str = &f_err_str[i];;
            fnd = 1;
        }
    }
    return str;
}



/** Получение уровня лога, сохранненый в файле настроек
    @return Уровень лога */
t_ltrsrv_log_level ltrsrv_log_get_save_lvl(void) {
    return f_log_save_lvl;
}

/** Получение текущего уровня лога
    @return Уровень лога */
t_ltrsrv_log_level ltrsrv_log_get_cur_lvl(void) {
    return f_log_lvl;
}

/***************************************************************************//**
 Установить текущей уровень лога
    @param[in] lvl          Уровень лога
    @param[in] permanent    Если 0, то изменяется только уровень лога для текущей,
                            сессии. Если 1, то изменяется также сохраняемый в
                            файле уровень лога
    @param[in] modify       Нужно ли указывать, что файл настроек требуется
                            перезаписать, если настройки изменлись
    @return                 Код ошибки
 ******************************************************************************/
int ltrsrv_log_set_lvl(t_ltrsrv_log_level lvl, int permanent, int modify) {
    if (lvl<=LTRSRV_LOGLVL_DBG_LOW) {
        f_log_lvl = lvl;
        if (permanent && (f_log_save_lvl!=lvl)) {
            f_log_save_lvl = lvl;
            if (modify) {
                ltrsrv_settings_mark_modified();
            }
        }
    }
    return 0;
}


/** Инициализация лога */
void ltrsrv_log_init(void) {
#if defined(_WIN32) && defined(LTRD_SYSLOG_ENABLED)
    f_log_handle = RegisterEventSource(NULL, TEXT(LTRD_PROGRAMM_NAME));
#endif

    f_log_init = 1;
}

/***************************************************************************//**
 Установить дополнительний файл для сохранения лога, помимо системного лога
    @param[in]     Имя файла
    @return        Код ошибки
 ******************************************************************************/
int ltrsrv_log_set_file(const char* filename) {
    int err = 0;

    if ((f_logfile!=NULL) && (f_logfile != stdout)) {
        fclose(f_logfile);
    }
    f_logfile = NULL;

    if ((filename!=NULL) && filename[0]) {
        f_logfile = fopen(filename, "a");
        if (f_logfile== NULL)
            err = LTRSRV_ERR_LOG_FILE_OPEN;
    }    
    return err;
}

/** Закрытие лога */
void ltrsrv_log_close(void) {
    f_log_init = 0;
    if (f_logfile!=NULL) {
        fclose(f_logfile);
        f_logfile = NULL;
    }
#ifndef _WIN32
    //closelog();
#endif

#if defined(_WIN32) && defined(LTRD_SYSLOG_ENABLED)
    DeregisterEventSource(f_log_handle);
#endif
}


int ltrsrv_log_make_crate_srcstr(t_ltr_crate* crate) {
    int err = 0;
    if (crate!=NULL) {
        const char* intf_str = LOGSTR_CRATE_INTF(crate->par.intf);

        if (crate->log_str != NULL)
            free(crate->log_str);

        switch (crate->par.state) {
            case LTR_CRATE_CONSTATE_INITIALIZATION:
                crate->log_str = (char*)malloc(sizeof(LTRSRV_LOGSRC_CRATE_INIT) +
                                      strlen(intf_str) +
                                      strnlen(crate->par.location, LTR_CRATE_LOCATION_SIZE) +
                                      + 10);
                if (crate->log_str) {
                    sprintf(crate->log_str, "%s [%s, %s]", LTRSRV_LOGSRC_CRATE_INIT,
                        intf_str, crate->par.location);
                } else {
                    err = LTRSRV_ERR_MEMALLOC;
                }
                break;
            case LTR_CRATE_CONSTATE_WORK:
                crate->log_str = (char*)malloc(sizeof(LTRSRV_LOGSRC_CRATE) +
                                      strlen(intf_str) +
                                      strnlen(crate->descr.crate_type_name, LTR_CRATE_TYPE_NAME) +
                                      strnlen(crate->descr.serial, LTR_CRATE_SERIAL_SIZE) + 10);
                if (crate->log_str) {
                    sprintf(crate->log_str, "%s %s [%s, %s]", LTRSRV_LOGSRC_CRATE,
                        crate->descr.crate_type_name, intf_str, crate->descr.serial);
                } else {
                    err = LTRSRV_ERR_MEMALLOC;
                }
                break;
            default:
                break;
        }
    } else {
        err = LTRSRV_ERR_CRATE_HANDLE;
    }
    return err;
}

int ltrsrv_log_make_module_srcstr(t_ltr_crate* crate, int slot)  {
    int err = 0;
    if (crate!=NULL) {
        t_ltr_module *module = &crate->modules[slot];
        int len;
        if (module->log_str != NULL)
            free(module->log_str);
        len = strlen(crate->log_str) + strlen(module->name) + 16;
        module->log_str = (char*)malloc(len);
        if (module->log_str==NULL) {
            err = LTRSRV_ERR_MEMALLOC;
        } else {
            sprintf(module->log_str, "%s (%s, slot %d)", module->name, crate->log_str, slot+1);
        }
    }else {
        err = LTRSRV_ERR_CRATE_HANDLE;
    }
    return err;
}

void ltrsrv_log_vrec(t_ltrsrv_log_level lvl, const char* src, int err, int syserr,
                     const char* fmt, va_list args) {

    if (lvl <= f_log_lvl) {
        time_t cur_time = time(NULL);
        struct tm *tm = localtime(&cur_time);
        const t_ltrsrv_err_entry *err_entry = NULL;
        const char* syserr_str = 0;

        /* выводим уровень лога и источник сообщения*/
        sprintf(f_log_buf1, "%s:%s: ", LOGSTR_LOGLVL(lvl), src ? src : "");

        vsprintf(f_log_buf2, fmt, args);

        if ((lvl<=LTRSRV_LOGLVL_WARN) && err) {
            err_entry = ltrsrv_log_get_err_str(err);
            if (err_entry->syserr && (syserr == 0))
                syserr = LTRSRV_LAST_SYSERR;
            if (syserr != 0) {
#ifdef _WIN32
                FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |  FORMAT_MESSAGE_IGNORE_INSERTS,
                               NULL, syserr, 0,
                               f_log_buf_syserrmsg, sizeof(f_log_buf_syserrmsg),
                               NULL);
                syserr_str = f_log_buf_syserrmsg;
#else
                syserr_str = strerror(syserr);
#endif
                sprintf(f_log_buf3, ": %s : last syserr = %d (%s)",
                        err_entry->str, syserr, syserr_str);
            } else {
                sprintf(f_log_buf3, ": %s", err_entry->str);
            }
        } else {
            f_log_buf3[0] = 0;
        }

        if (f_logfile) {
            fprintf(f_logfile, "[%d.%d.%d %d:%02d:%02d]:", tm->tm_mday, tm->tm_mon, tm->tm_year+1900,
               tm->tm_hour, tm->tm_min, tm->tm_sec);
            fprintf(f_logfile, "%s%s%s\n", f_log_buf1, f_log_buf2, f_log_buf3);
            fflush(f_logfile);
        }

#ifdef LTRD_LOGPROTO_ENABLED
        ltrsrv_logproto_add_rec(lvl, err, "%s:%s%s", src == NULL ? "" : src, f_log_buf2, f_log_buf3);
#endif



#ifdef LTRD_SYSLOG_ENABLED
#ifdef _WIN32
        if (f_log_handle!=NULL) {
            WORD strNum = 0;
            LPCSTR lpszStrings[6];
            DWORD eventID;

            static WORD f_types[] = {
                EVENTLOG_ERROR_TYPE,
                EVENTLOG_ERROR_TYPE,
                EVENTLOG_WARNING_TYPE,
                EVENTLOG_INFORMATION_TYPE,
                EVENTLOG_INFORMATION_TYPE,
                EVENTLOG_INFORMATION_TYPE,
                EVENTLOG_INFORMATION_TYPE,
                EVENTLOG_INFORMATION_TYPE};
            static DWORD f_eventsID[] = {
                LTRD_MSG_ERROR,
                LTRD_MSG_ERROR,
                LTRD_MSG_WARNING,
                LTRD_MSG_INFO,
                LTRD_MSG_DETAIL,
                LTRD_MSG_DEBUG,
                LTRD_MSG_DEBUG,
                LTRD_MSG_DEBUG};

            lpszStrings[strNum++] = src;
            eventID = f_eventsID[lvl];
            if (eventID==LTRD_MSG_ERROR) {
                char err_code_buf[32];
                sprintf(err_code_buf, "%d", err);
                lpszStrings[strNum++] = err_code_buf;
                lpszStrings[strNum++] = err_entry ? err_entry->str : "";

                if (err_entry && err_entry->syserr) {
                    char syserr_code_buf[32];
                    sprintf(syserr_code_buf, "%d", syserr);
                    lpszStrings[strNum++] = syserr_code_buf;
                    lpszStrings[strNum++] = syserr_str;

                    eventID=LTRD_MSG_ERROR_SYS;
                }
            }
            lpszStrings[strNum++] = f_log_buf2;


            ReportEventA(f_log_handle,        // event log handle
                        f_types[lvl], // event type
                        0,                   // event category
                        eventID,       // event identifier
                        NULL,                // no security identifier
                        strNum,                   // size of lpszStrings array
                        0,                   // no binary data
                        lpszStrings,         // array of strings
                        NULL);               // no binary data
        }
#else
        if (g_opts.mode != LTRD_MODE_SYSTEMD) {
            int syst_loglvl = lvl <= LTRSRV_LOGLVL_ERR  ? LOG_ERR :
                                                  lvl == LTRSRV_LOGLVL_WARN ? LOG_WARNING :
                                                  lvl == LTRSRV_LOGLVL_INFO ? LOG_NOTICE  :
                                                  lvl == LTRSRV_LOGLVL_DETAIL ?  LOG_INFO : LOG_DEBUG;            
    #ifdef HAVE_LIBDAEMON
            if (g_opts.mode == LTRD_MODE_DAEMON) {
                daemon_log(syst_loglvl, "%s%s%s", f_log_buf1, f_log_buf2, f_log_buf3);
            } else {
    #endif
                syslog(syst_loglvl, "%s%s%s", f_log_buf1, f_log_buf2, f_log_buf3);
    #ifdef HAVE_LIBDAEMON
            }
    #endif
        }
#ifdef LTRD_SYSTEMD_ENABLED
        else {
            const char *sysd_lvl = lvl <= LTRSRV_LOGLVL_ERR  ? SD_ERR :
                                                         lvl == LTRSRV_LOGLVL_WARN ? SD_WARNING :
                                                         lvl == LTRSRV_LOGLVL_INFO ? SD_NOTICE :
                                                         lvl == LTRSRV_LOGLVL_DETAIL ? SD_INFO : SD_DEBUG;
            fprintf(stderr, "%s %s%s%s\n", sysd_lvl, f_log_buf1, f_log_buf2, f_log_buf3);
        }
#endif
#endif
#endif

#ifdef LTRD_STDOUT_ENABLED
        if (g_opts.mode == LTRD_MODE_CONSOLE) {
            printf("%s%s%s\n", f_log_buf1, f_log_buf2, f_log_buf3);
            fflush(stdout);
        }
#endif
    }
}




/***************************************************************************//**
 Запись отладочного сообщения в лог
    @param[in] lvl     Уровень отладочного сообщения (сообщение будет выведено
                       только если текущий уровень лога больше или равень
                       указаного в lvl)
    @param[in] src     Строка с описанием источника отладочного сообщения
    @param[in] err     Код ошибки (только для уровеней #LTRSRV_LOGLVL_ERR и
                       #LTRSRV_LOGLVL_ERR_FATAL)
    @param[in] fmt     Формат для вывода отладочного сообщения (аналогично
                       printf())
 ******************************************************************************/
void ltrsrv_log_rec(t_ltrsrv_log_level lvl, const char* src, int err,
                    const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    ltrsrv_log_vrec(lvl, src, err, 0, fmt, args);
    va_end(args);
}

/***************************************************************************//**
 Запись отладочного сообщения об ошибке в лог с явным указанием кода системной
    ошибки. Для случая когда возникновение ошибки и вывод в лог могут быть сильно
    разнесены и код последней ошибки может уже измениться в других вызовах, а он
    важен на момент обнаружения ошибки.
    @param[in] src     Строка с описанием источника отладочного сообщения
    @param[in] err     Код ошибки из LTRSRV_ERR_XXX
    @param[in] syserr  Системный код ошибки
    @param[in] fmt     Формат для вывода отладочного сообщения (аналогично
                       printf())
 ******************************************************************************/
void ltrsrv_log_syserr(const char* src, int err, int syserr, const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    ltrsrv_log_vrec(LTRSRV_LOGLVL_ERR, src, err, syserr, fmt, args);
    va_end(args);
}
