LINUX.ORG.RU

Форматирование даты-времени для лога: strftime не работает с mingw?

 , , , ,


0

2

Всех приветствую!

Необходимо выводить дату-время в лог-файл.

char* get_datetime_str_alt1(char *str_datetime)
{
    char buff[64]={'\0'};

    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
  
    strftime(buff, sizeof(buff), "%e-%b-%Y %Z %T", localtime(&ts.tv_sec));
    sprintf(str_datetime, "%s", buff);
       
    return str_datetime;
}


char* get_datetime_str_alt2(char *str_datetime)
{
    char buff[64]={'\0'};
       
    time_t t = time(NULL);
    struct tm *local = localtime(&t);
  
    strftime(buff, sizeof(buff), "%e-%b-%Y %Z %H:%M:%S", local);
    sprintf(str_datetime, "%s", buff);
       
    return str_datetime;
}

Эти два варианта работают после gcc

1-Aug-2024 MSK 12:32:45: Start!
1-Aug-2024 MSK 12:32:45: Bla-bla
1-Aug-2024 MSK 12:32:46: End!

, но ничего не выводят после mingw:

: Start!
: Bla-bla
: End!

Работает отлично только

char* get_datetime_str(char *str_datetime)
{
    time_t t = time(NULL);
        
    sprintf(str_datetime, "%s", ctime(&t) );
    char *p = strchr(str_datetime, '\n');
    *p = '\0';
 
    return str_datetime;
}

Но форматирование уже выбираю не я:

Thu Aug 01 12:13:08 2024: Start!
Thu Aug 01 12:13:08 2024: Bla-bla
Thu Aug 01 12:13:09 2024: End!

По идее и так работает. Но все таки какая-то неудовлетворенность остается. Сталкивался кто-нибудь с подобным эффектом?

Подозреваю нужно обновлять mingw, но так не хочется: столько библиотек опять устанавливать (к тому же я уже забыл как это делал).

PS gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0

x86_64-w64-mingw32-gcc (GCC) 9.3-win32 20200320 wincrt


Ответ на: комментарий от Gyros

Как я уже сказал, вы можете вообще убрать это вызов, если сделаете три вызова вместо _log_file_write:

// Пишет в file тип сообщения и время
static void _log_file_wtime(FILE *file, enum log_level level); 
// Пишет в file переданные исходно данные
static void _log_file_wdata(FILE *file, const char *fmt, va_list args);
// Пишет в file \n
static void _log_file_wlend(FILE *file);

Так у меня на этапе malloc возникает

__attribute__((format(printf, 1, 2)))
char *my_asprintf(const char *fmt, ...) {
    va_list list;
    va_start(list, fmt);
    char *str = my_vasprintf(fmt, list);
    va_end(list);
    return str;
}

char *my_vasprintf(const char *fmt, va_list args) {
    va_list copy;
    va_copy(copy, args);
    ssize_t size = vsnprintf(NULL, 0, fmt, copy);
    va_end(copy);

    char *str = malloc(size + 1);
    // Алсо неплохо было бы проверить malloc
    vsprintf(str, fmt, args);
    return str;
}
PPP328 ★★★★★
()
Последнее исправление: PPP328 (всего исправлений: 1)
Ответ на: комментарий от Gyros

Для защиты от сбоев, лучше всего

...выводить в лог: открыл файл, записал, закрыл...

sparkie ★★★★★
()
Ответ на: комментарий от PPP328

Не совсем понял «вы можете вообще убрать это вызов» что вы имели в виду?

Итак, моя версия однострочного вывода в лог-файл (сильно не ругайтесь):

// зачем имя файла брать из переменных окружения? - не пойму
// проще так
#define LOG_FILE_NAME "log.txt" 


// взял у Вас начинать имена статических ф-ций знаком '_'
static const char* _get_type_msg(enum msg_type type)
{
    return (DEBUG_MSG   == type) ? "DEBUG"  
          :(INFO_MSG    == type) ? "INFO" 
          :(WARNING_MSG == type) ? "WARNING"
          :(ERROR_MSG   == type) ? "ERROR"
          :(FATAL_MSG   == type) ? "FATAL" 
          : "UNKNOWN";
}

static FILE* _get_log_output_file(enum msg_type type_msg)
{
    return (ERROR_MSG==type_msg || FATAL_MSG==type_msg) ? stderr : stdout;
}

static void _log_file_write(FILE *file, enum msg_type type_msg, const char *msg, bool with_datetime)
{
    if (with_datetime)
    {
       struct timespec ts;
       clock_gettime(CLOCK_REALTIME, &ts);
       struct tm *lt = localtime(&ts.tv_sec);
       const char fmt[]="[%s]: [%02d/%02d/%4d  %02d:%02d:%02d.%03d] %s\n";
       fprintf( file,
                fmt,
               _get_type_msg(type_msg),
                lt->tm_mday,
                lt->tm_mon+1,
                lt->tm_year+1900,
                lt->tm_hour,
                lt->tm_min,
                lt->tm_sec,
                (int)(1e-6*ts.tv_nsec),
                msg );
     }
     else 
       fprintf( file, "[%s]: %s\n", _get_type_msg(type_msg),  msg);
}

void // __attribute__((format(printf, 2, 3))  
 log_write(enum msg_type type_msg, const char *fmt, ...)
{
    // 0. Подготовка к записи: узнаем необходимый размер строки
    va_list args;
    va_start(args, fmt);
    int sz = vsnprintf(NULL, 0, fmt, args); // вызов чтобы узнать размер
    if (sz < 0)
    {
        fprintf(stderr, "vsnprintf:  Memory allocation error.\n");

        //abort(); // Можете тут упасть, например. Такое бывает когда нет памяти
        exit(EXIT_FAILURE); // почему abort? Лучше exit 
    }
    va_end(args);
    

    // 1. Выделяем память  и  формируем строку
    char *str=NULL;
    str = malloc(sz+1);
    if (!str)
    {
        fprintf(stderr, "malloc: Memory allocation error.\n");
        exit(EXIT_FAILURE);
    }
        
    va_start(args, fmt);
    vsnprintf(str, sz+1, fmt, args);
    va_end(args);
    
    // 2. Запись в std 
    _log_file_write(_get_log_output_file(type_msg), type_msg, str, false); // на экран - без даты-времени

    // 3. Запись в файл
    FILE *file = fopen(LOG_FILE_NAME, "a");
    if (!file)
    {
        fprintf(stderr, "File '%s' opening error: %s\n", LOG_FILE_NAME, strerror(errno));
        exit(EXIT_FAILURE);
    }
    _log_file_write(file, type_msg, str, true); // в файл  - с датой-временем

    // 4. Закрытие файла
    fclose(file);

    // 5. Очистка
    free(str);
}

Использование:

    log_write(INFO_MSG, "%s", "Bla-bla");
    log_write(INFO_MSG, "Bla-bla");
    log_write(INFO_MSG, "%d", 123);
    log_write(INFO_MSG, "123");
    log_write(DEBUG_MSG, "%s=%d  %s=%d", "numb1", 128, "numb2", 512);

Результат:

[INFO]: [07/08/2024  13:35:26.286] Bla-bla
[INFO]: [07/08/2024  13:35:26.286] Bla-bla
[INFO]: [07/08/2024  13:35:26.286] 123
[INFO]: [07/08/2024  13:35:26.286] 123
[DEBUG]: [07/08/2024  13:35:26.286] numb1=128  numb2=512

Здесь все работает как надо.

Теперь многострочный вывод:

static void _log_file_write_header(FILE *file, enum msg_type type_msg, bool with_datetime)
{
    if (with_datetime)
    {
       struct timespec ts;
       clock_gettime(CLOCK_REALTIME, &ts);
       struct tm *lt = localtime(&ts.tv_sec);
       const char fmt[]="[%s]: [%02d/%02d/%4d  %02d:%02d:%02d.%03d]\n{\n";
       fprintf( file,
                fmt,
               _get_type_msg(type_msg),
                lt->tm_mday,
                lt->tm_mon+1,
                lt->tm_year+1900,
                lt->tm_hour,
                lt->tm_min,
                lt->tm_sec,
                (int)(1e-6*ts.tv_nsec)
                );
     }
     else 
       fprintf( file, "[%s]:\n{\n", _get_type_msg(type_msg) );
}

static void _log_file_write_body(FILE *file, const char *msg)
{
    fprintf( file, "%s", msg);
}

static void _log_file_write_footer(FILE *file)
{
    fprintf( file, "\n}\n" );
}

void // __attribute__((format(printf, 2, 3))  
 log_write_multiline(enum msg_type type_msg, const char *fmt, ...)
{
    // 0. Подготовка к записи: узнаем необходимый размер строки
    va_list args;
    va_start(args, fmt);
    int sz = vsnprintf(NULL, 0, fmt, args); // вызов чтобы узнать размер
    if (sz < 0)
    {
        fprintf(stderr, "vsnprintf:  Memory allocation error.\n");
        exit(EXIT_FAILURE); // Можете тут упасть, например. Такое бывает когда нет памяти
    }
    va_end(args);
    

    // 1. Выделяем память  и  формируем строку
    char *str=NULL;
    str = malloc(sz+1);
    if (!str)
    {
        fprintf(stderr, "malloc: Memory allocation error.\n");
        exit(EXIT_FAILURE);
    }
    va_start(args, fmt); 
    vsnprintf(str, sz+1, fmt, args);
    va_end(args);
    
    // 2. Запись в std
    _log_file_write_header(_get_log_output_file(type_msg), type_msg, false); // на экран - без даты-времени
    _log_file_write_body(_get_log_output_file(type_msg), str);
    _log_file_write_footer(_get_log_output_file(type_msg));

    // 3. Запись в файл
    FILE *file = fopen(LOG_FILE_NAME, "a");
    if (!file)
    {
        fprintf(stderr, "File '%s' opening error: %s\n", LOG_FILE_NAME, strerror(errno));
        exit(EXIT_FAILURE);
    }
    _log_file_write_header(file, type_msg, true); // в файл  - с датой-временем
    _log_file_write_body(file, str);
    _log_file_write_footer(file); 

    // 4. Закрытие файла
    fclose(file);

    // 5. Очистка
    free(str);
}

Предположим мне надо вывести матрицу и число

char* mat4_get_str(const char* name, const MAT4 *m)
{
    const char fmt[]="%s = \n    [%9.3f, %9.3f, %9.3f, %9.3f]\n    |%9.3f, %9.3f, %9.3f, %9.3f|\n    |%9.3f, %9.3f, %9.3f, %9.3f|\n    [%9.3f, %9.3f, %9.3f, %9.3f]\n";
    int sz = snprintf(NULL, 0, fmt, name, m->M[0], m->M[4], m->M[ 8], m->M[12],
                                          m->M[1], m->M[5], m->M[ 9], m->M[13],
                                          m->M[2], m->M[6], m->M[10], m->M[14]);
    char *str=NULL;
    str = malloc(sz+1);
    if (!str)
    {
        fprintf(stderr, "malloc: Memory allocation error.\n");
        exit(EXIT_FAILURE);
    }

    snprintf(str, sz, fmt, name, m->M[0], m->M[4], m->M[ 8], m->M[12],
                                 m->M[1], m->M[5], m->M[ 9], m->M[13],
                                 m->M[2], m->M[6], m->M[10], m->M[14]);
    return str;   
}

Использование:

    log_write_multiline(DEBUG_MSG, "%s=%d  %s=%d", "numb3", 1024, "numb4", 64);

    char * str = mat4_get_str("A", &A); // тут конечно некрасиво, что надо указатель запоминать
    log_write_multiline(INFO_MSG, "%s \n %f", str, 0.55f);
    free(str); // а после вывода освобождать
    // Есть идеи сделать лучше?

Вывод в файл:

[DEBUG]: [07/08/2024  13:53:29.108]
{
numb3=1024  numb4=64
}
[INFO]: [07/08/2024  13:53:29.108]
{
A = 
    [    0.909,     0.000,     0.000,     0.000]
    |    0.000,     1.428,     0.000,     0.000|
    |    0.000,     0.000,    -1.020,    -0.202|
    [    0.000,     0.000,     0.000,     0.000] 
 0.550000
}

Неоптимизировано (повторы кода), но все отлично работает.

Что скажете?

Gyros
() автор топика
Ответ на: комментарий от Gyros

Не совсем понял «вы можете вообще убрать это вызов» что вы имели в виду?

Окей, я имел в виду код вообще без аллокаций (на стороне кода).

Смотрите, разберем то, что вы хотите отправить:

[INFO]: [07/08/2024  13:35:26.286] Variable ENV_SUPERUSER is not set \n
^^^^^^
Тип сообщения
        ^^^^^^^^^^^^^^^^^^^^^^^^^^
        Время/дата
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                   Сообщение с %s
                                                                     ^^
                                                                     Каретка

Если бы у вас не было двух первых частей, то мы могли бы использовать vfprintf прямо с теми данными, что нам пришли. Поэтому можно вывести данные кусками, это все равно ни на чем не скажется:

// Пишет в file тип сообщения и время
static void _log_file_wtime(FILE *file, enum log_level level) {
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts); 
    struct tm *lt = localtime(&ts.tv_sec);

    fprintf(
        file,
        "[%s]: [%02d/%02d/%4d  %02d:%02d:%02d.%03d] ",
        _log_str_level(level),
        lt->tm_mday, 
        lt->tm_mon+1, 
        lt->tm_year+1900, 
        lt->tm_hour, 
        lt->tm_min, 
        lt->tm_sec, 
        (int)(1e-6*ts.tv_nsec)
    );
}

// Пишет в file переданные исходно данные
static void _log_file_wdata(FILE *file, const char *fmt, va_list args) {
    vfprintf(file, fmt, args);
}

// Пишет в file \n
static void _log_file_wlend(FILE *file) {
    fprintf(file, "\n");
}

И эти вызовы переезжают на замену вот этому: _log_file_write:

__attribute__((format(printf, 2, 3)) 
void log(enum log_level level, const char *fmt, ...) {

    va_list args;
    va_start(args, fmt);
    FILE *stdfile = _log_std_file(level);
    _log_file_whead(stdfile, level);
    _log_file_wdata(stdfile, fmt, args);
    _log_file_wlend(stdfile);
    va_end(args);


    static const char *log_env_file = "LOG_FILENAME";
    const char *filename = getenv(log_env_file);
    
    if (filename) {
        FILE *file = fopen(filename, "a");
        if (!file)
            // Ну вы тут сами обработку ошибок напишите, мне лень

        va_start(args, fmt);
        _log_file_whead(file, level);
        _log_file_wdata(file, fmt, args);
        _log_file_wlend(file);
        va_end(args);
        fclose(file); 
    }
}

ВСЁ. На нашей стороне аллокаций нет. Нет буферов, нет каких-то проверок.

UPD: Окей, я хлебнул черного бобового сока, распишу какие у вас косяки: 1.

   return (DEBUG_MSG   == type) ? "DEBUG"  
          :(INFO_MSG    == type) ? "INFO" 

Вот так делать не стоит совершенно. Поясняю, чем отличаются вот такие «колбасы» от switch. Если компилятор не «поймёт», что вы вообще делаете или если ваши перечисления идут не по порядку (не 0,1,2, а 10,20,31,18), то в таком случае он сгенерит отличную «колбасу» из cmp/jg/je, в которой будет проверять вообще всех членов множества по очереди. Если их там будет два десятка и логов будет сыпать много - то начнутся и кэш-миссы и общее замедление. На свищ компиль сразу понимает, что вы вообще творите и сразу строит или хэш-таблицу коллов, если они по порядку, то будет например вот так:

        push    rax
        cmp     edi, 5
        jae     .LBB1_2
        movsxd  rax, edi
        mov     rax, qword ptr [8*rax + .Lswitch.table._get_type_msg]
        pop     rcx
        ret

или (если они не по порядку), то создаст ряд простейших математических операций, чтобы сгруппировать их. Например:

enum msg_type {
    LOG_DBG,
    LOG_INF = 30,
    LOG_WRN,
    LOG_ERR = 40,
    LOG_CRT
};

const char *_get_type_msg(enum msg_type type) {
    switch (type) {
        case LOG_DBG: return "DEBUG";
        case LOG_INF: return "INFO";
        case LOG_WRN: return "WARNING";
        case LOG_ERR: return "ERROR";
        case LOG_CRT: return "FATAL";
    }
}
_get_type_msg:
        mov     eax, offset .L.str
        add     edi, -30; << Смотреть сюда
        cmp     edi, 11 ; <<
        ja      .LBB0_6
        jmp     qword ptr [8*rdi + .LJTI0_0]
.LBB0_2:
        mov     eax, offset .L.str.1
        ret
.LBB0_3:
        mov     eax, offset .L.str.2
        ret
.LBB0_4:
        mov     eax, offset .L.str.3
        ret
.LBB0_5:
        mov     eax, offset .L.str.4
.LBB0_6:
        ret

Еще один плюс switch см. в п.2
То же самое замечание для ? stderr : stdout. Лучше напишите switch, чем потом забудете какой-то член и получите вывод не в тот поток.

  1. Зачем нужен abort(), а не exit. Я умышленно поставил abort, потому что а) при нормальном кодинге до него код никогда не дойдет. Если всё происходит в пределах одного модуля и у вас нет отправки мусора вместо enum - то abort() даже не будет после компиляции. Если всё происходит в разных юнитах транзакции, то тогда такой abort позволяет отловить место, где вы отправили именно что мусор, потому что по контракту вызова там должен быть enum. Разница между abort и exit в том, что abort создает core-файл, который позволяет найти конкретное место падения.

2.1 Про abort() после switch без default. Если вы добавите еще один член множества, то вы забудете добавить его во всех местах, где есть этот switch по множеству. В случае, если вы НЕ добавить default - компилятор вам скажет, что тут не обработан один из членов множества.

  1. Чем плох ваш мультистроковый вывод. Уровень сообщений добавляется по двум причинам:
  • Или вы хотите через например ENV контролировать уровень вывода (сравните вывод curl и curl -v / -vv)
  • Или в итоговом файле вы хотите отфильтровать записи:
grep "INFO" log.file
[INFO] ...
[INFO] ...

С мультилайном такой трюк не прокатит. Поэтому или откажитесь от мультилайна, или напишите отдельную функцию, которая через strtok будет искать \n и в цикле вызывать однолайновый log. Так мы сделали, например, для вывода логотипа при старте. На самом деле для ваших целей тут тоже никакие аллокации не нужны, если вы работаете с фиксированными типами:

// P.S. Я в упор не понимаю, почему вы не перенесете name в MAT4, но как хотите
void log_M4x4(enum log_level level, const char *name, const MAT4x4 *m) {
    log(level, "[MAT4x4] %s = [", name);
    log(level, "    [%9.3f, %9.3f, %9.3f, %9.3f],", m->M[0], m->M[4], m->M[ 8], m->M[12]);
    log(level, "    [%9.3f, %9.3f, %9.3f, %9.3f],", m->M[1], m->M[5], m->M[ 9], m->M[13]);
    log(level, "    [%9.3f, %9.3f, %9.3f, %9.3f],", m->M[2], m->M[6], m->M[10], m->M[14]);
    log(level, "    [%9.3f, %9.3f, %9.3f, %9.3f],", m->M[3], m->M[7], m->M[11], m->M[15]);
    log(level, "}");
}

P.P.S. У вас, кстати, жесточайший косяк с форматтером и выводом. В форматтере у вас 4 блока, в выводе 3. У вас куча байтов пишутся мимо кассы. Это потому, что вы вывели форматтер в отдельную переменную, компилятор не может проверить, что вы посылаете:

    const char fmt[]="%s = \n    [%9.3f, %9.3f, %9.3f, %9.3f]\n    |%9.3f, %9.3f, %9.3f, %9.3f|\n    |%9.3f, %9.3f, %9.3f, %9.3f|\n    [%9.3f, %9.3f, %9.3f, %9.3f]\n";
    int sz = snprintf(NULL, 0, fmt, name, m->M[0], m->M[4], m->M[ 8], m->M[12],
                                          m->M[1], m->M[5], m->M[ 9], m->M[13],
                                          m->M[2], m->M[6], m->M[10], m->M[14]);
PPP328 ★★★★★
()
Последнее исправление: PPP328 (всего исправлений: 1)
Ответ на: комментарий от PPP328

Управление уровнем логирования.

Что-то не вижу в ваших логах самого главного - управления уровнем логирования. Как сделать так, чтобы писались только предупреждения и ошибки? И чтобы не важные логи, которые не выводятся, практически не влияли на основной код, вплоть до не попадния в код программы?

И журнал лучше всегда писать в один файл, а не использовать хитрую логику по выбору файла в зависимости от уровня важности лога (потом поробуй собери и синхронизируй записи из разных файлов, что после чего случилось). Или напрямую указать в настройках логирования, как уровень в какой файл пишется

anonymous
()
Ответ на: комментарий от anonymous

Управление уровнем указывается элементарно. Тут уже есть зачаток - _log_std_file для std, для файла делается аналогичная функция, которая либо сама формирует для каждого уровня возврат FILE, или берет имя из ENV. Таким же образом делается игнор вывода - функция возвращает NULL, а дальше или сразу же делать

if (file) {
   print
}

или в каждую печатную функцию добавить

if (!file)
    return;

Я не стал это расписывать, потому что у вопрошающего господина до этого пока не дошло, тут более простые вопросы.

PPP328 ★★★★★
()
Ответ на: комментарий от anonymous

вплоть до не попадния в код программы?

Более костыльно, конечно, но это тоже возможно (правда я не представляю ситуацию, когда вам это может понадобиться):

#if defined (DEBUG)
#   define log_inf(fmt, ...) log(LOG_INF, fmt, __VA_ARGS__)
#else
#   define log_inf(fmt, ...)
#endif

И управляйте как хотите

PPP328 ★★★★★
()
Последнее исправление: PPP328 (всего исправлений: 1)
Ответ на: комментарий от PPP328

правда я не представляю ситуацию, когда вам это может понадобиться

В время разработки пишу в логи всё, даже логи уровня TRACE (пониже чем DEBUG), устанавливая в настройках логирования LOG_LEVEL=TRACE или LOG_LEVEL=ALL. В готовом продукте уровень логирования меняется (в зависимости от пожеланий заказчика).

Настраивать уровень логирования можно во время компиляции или во время выполнения (в командной строке –verbose LEVEL или в конфиг-файле). Можно заморочится и реализовать все способы настройки, если надо.

anonymous
()
Ответ на: комментарий от PPP328

Спасибо за разбор полета!

Переделал под switch:

const char *_get_type_msg(enum msg_type type)
{
   switch (type)
   {
        case DEBUG_MSG:   return "DEBUG";
        case INFO_MSG:    return "INFO";
        case WARNING_MSG: return "WARNING";
        case ERROR_MSG:   return "ERROR";
        case FATAL_MSG:   return "FATAL";
	default:          return "UNKNOWN";
    }
}

Мне нравится свой мультилайн. Я могу для любого объекта something сделать ф-цию something_to_str, а дальше просто использовать для вывода в лог. (Как в C# у любого объекта есть ф-ция ToString()) Единственный недостаток - нужно освобождать строку. Мне кажется это лучше, чем писать для каждого something функцию log_something. Запись матрицы в лог - это не относится к матрице как структуре/классу. И имя матрицы тоже не относится; имя матрицы появляется только когда нужна печать на экран или в файл.

Мне не надо отфильтровывать записи и контролировать уровень вывода. У меня простенький вариант.

Исправил свой жуткий косяк c форматированием (из-за того что недокопипастил):

char* mat4_to_str(const char* name, const MAT4 *m)
{
    const char fmt[]="%s = \n\t[%9.3f, %9.3f, %9.3f, %9.3f]\n\t|%9.3f, %9.3f, %9.3f, %9.3f|\n\t|%9.3f, %9.3f, %9.3f, %9.3f|\n\t[%9.3f, %9.3f, %9.3f, %9.3f]\n";
    int sz = snprintf(NULL, 0, fmt, name, m->M[0], m->M[4], m->M[ 8], m->M[12],
                                          m->M[1], m->M[5], m->M[ 9], m->M[13],
                                          m->M[2], m->M[6], m->M[10], m->M[14],
                                          m->M[3], m->M[7], m->M[11], m->M[15]);
    char *str=NULL;
    str = malloc(sz+1);
    if (!str)
    {
        fprintf(stderr, "malloc: Memory allocation error.\n");
        exit(EXIT_FAILURE);
    }

    snprintf(str, sz+1, fmt, name, m->M[0], m->M[4], m->M[ 8], m->M[12],
                                   m->M[1], m->M[5], m->M[ 9], m->M[13],
                                   m->M[2], m->M[6], m->M[10], m->M[14],
                                   m->M[3], m->M[7], m->M[11], m->M[15]);
    return str;   
}

«Разница между abort и exit в том, что abort создает core-файл, который позволяет найти конкретное место падения»

А можете привести пример? Я просто не представляю, что за core-файл и как он может помочь.

Насчет многопоточного использования:

#include <pthread.h>
#include <time.h>
...
pthread_mutext_t mutx;
...
status = pthread_mutex_init(&mutx, NULL);
...
pthread_mutex_lock(&mutx);
log_write(DEBUG_MSG, "%s=%d  %s=%d", "numb1", 128, "numb2", 512);
pthread_mutex_unlock(&mutx);

Вы имели в виду так lock/unlock-ами обкладывать запись в лог?

Gyros
() автор топика
Последнее исправление: Gyros (всего исправлений: 2)
Ответ на: комментарий от Gyros

Переделал под switch:

Уберите default, после switch сделайте return «Unknown». Вы получите предупреждение компилятора, если добавите новый тип уровня и забудете описать его в switch.

Единственный недостаток - нужно освобождать строку.

Да как хотите, я просто указал вам что ваши логи нельзя будет грепать.

А можете привести пример?

Пример чего? Что такое core файл? https://www.cse.unsw.edu.au/~learn/debugging/modules/gdb_coredumps/

Насчет многопоточного использования:

Нет. Lock/unlock должен быть в начале и конце самого log. Init нет нужды инициализировать через pthread_mutex_init:

static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;

void log(enum log_level level, const char *fmt, ...) {
    pthread_mutex_lock(&log_mutex);

    ...

    pthread_mutex_unlock(&log_mutex);
}

Таким образом вызывая log в разных потоках вы не получите смесь вывода в итоговом стриме и/или файле.

PPP328 ★★★★★
()
Ответ на: комментарий от anonymous

Удачи вам корректно определить размер секции для блокировки. А потом бороться с тем, что размер поменялся между открытием и установкой блокировки.

PPP328 ★★★★★
()
Ответ на: комментарий от Gyros

Я вижу ф-ции WinAPI. Это уход от кроссплатформенности.

А кроссплатформенность — это что, по-твоему?

korvin_ ★★★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.