- добавлен компонент util/log - логгер - написан адаптер UART для логгера: port/log - util/log покрыт юнит-тестами
169 lines
No EOL
5.5 KiB
C
169 lines
No EOL
5.5 KiB
C
/**
|
||
* @file log.c
|
||
* @brief Реализация платформонезависимого логгера.
|
||
*
|
||
* Единственная внешняя зависимость — стандартная библиотека C
|
||
* (vsnprintf, stdarg). Транспорт абстрагирован через callback.
|
||
*
|
||
* Компилируется всегда. При LOG_LEVEL=0 макросы в log.h
|
||
* разворачиваются в ((void)0) и log_write() не вызывается вообще —
|
||
* но сам .c файл в ROM не попадает: линкер выкидывает неиспользуемые
|
||
* секции (--gc-sections).
|
||
*/
|
||
|
||
#include "log.h"
|
||
|
||
#include <stdarg.h>
|
||
#include <stddef.h>
|
||
#include <stdio.h>
|
||
|
||
/* -------------------------------------------------------------------------- */
|
||
/* Конфигурация */
|
||
/* -------------------------------------------------------------------------- */
|
||
|
||
/** Размер статического буфера одной строки (включая префикс, \r\n, \0). */
|
||
#ifndef LOG_BUF_SIZE
|
||
#define LOG_BUF_SIZE 256U
|
||
#endif
|
||
|
||
/** Перевод строки. \r\n для MCU-Link VCOM. */
|
||
#ifndef LOG_NEWLINE
|
||
#define LOG_NEWLINE "\r\n"
|
||
#endif
|
||
|
||
/* -------------------------------------------------------------------------- */
|
||
/* Внутреннее состояние */
|
||
/* -------------------------------------------------------------------------- */
|
||
|
||
static log_write_cb_t g_s_write_cb = NULL;
|
||
static void *g_s_p_ctx = NULL;
|
||
|
||
/* -------------------------------------------------------------------------- */
|
||
/* Weak-хуки мьютекса — NOP для bare-metal */
|
||
/* -------------------------------------------------------------------------- */
|
||
|
||
__attribute__((weak)) void log_mutex_init(void)
|
||
{
|
||
}
|
||
__attribute__((weak)) void log_mutex_lock(void)
|
||
{
|
||
}
|
||
__attribute__((weak)) void log_mutex_unlock(void)
|
||
{
|
||
}
|
||
__attribute__((weak)) uint32_t log_get_timestamp_ms(void)
|
||
{
|
||
return 0U;
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------- */
|
||
/* Вспомогательное: метка уровня */
|
||
/* -------------------------------------------------------------------------- */
|
||
|
||
static char level_char(int level)
|
||
{
|
||
switch (level)
|
||
{
|
||
case LOG_LEVEL_ERROR:
|
||
return 'E';
|
||
case LOG_LEVEL_WARN:
|
||
return 'W';
|
||
case LOG_LEVEL_INFO:
|
||
return 'I';
|
||
case LOG_LEVEL_DEBUG:
|
||
return 'D';
|
||
case LOG_LEVEL_VERBOSE:
|
||
return 'V';
|
||
default:
|
||
return '?';
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------- */
|
||
/* Публичный API */
|
||
/* -------------------------------------------------------------------------- */
|
||
|
||
void log_init(log_write_cb_t p_write_cb, void *p_ctx)
|
||
{
|
||
g_s_write_cb = p_write_cb;
|
||
g_s_p_ctx = p_ctx;
|
||
}
|
||
|
||
void log_write(int level, const char *p_tag, const char *p_fmt, // NOLINT(readability-function-size)
|
||
...)
|
||
{
|
||
if (g_s_write_cb == NULL)
|
||
{
|
||
return;
|
||
}
|
||
|
||
/* Статический буфер: под мьютексом → нет гонки в FreeRTOS,
|
||
* в bare-metal гонки нет по определению. */
|
||
static char s_buf[LOG_BUF_SIZE];
|
||
|
||
log_mutex_lock();
|
||
|
||
/* Префикс: [timestamp][L][TAG] */
|
||
int prefix_len = snprintf(s_buf, sizeof(s_buf), "[%10lu][%c][%s] ",
|
||
(unsigned long) log_get_timestamp_ms(), level_char(level), p_tag);
|
||
|
||
if (prefix_len < 0)
|
||
{
|
||
log_mutex_unlock();
|
||
return;
|
||
}
|
||
|
||
/* Сообщение пользователя */
|
||
size_t prefix_sz = (size_t) prefix_len;
|
||
size_t remaining = (prefix_sz < sizeof(s_buf)) ? (sizeof(s_buf) - prefix_sz) : 0U;
|
||
|
||
if (remaining > 0U)
|
||
{
|
||
va_list args;
|
||
va_start(args, p_fmt);
|
||
int msg_len = vsnprintf(s_buf + prefix_sz, remaining, p_fmt, args);
|
||
va_end(args);
|
||
|
||
if (msg_len > 0)
|
||
{
|
||
prefix_sz += (size_t) msg_len;
|
||
}
|
||
}
|
||
|
||
/* ВАЖНО: vsnprintf возвращает число символов которые *хотел* записать,
|
||
* а не сколько реально влезло. Если сообщение длиннее remaining,
|
||
* prefix_sz > sizeof(s_buf). Без clamp ниже вычисление avail даёт
|
||
* size_t underflow → огромное число → выход за границу буфера (UB). */
|
||
if (prefix_sz > sizeof(s_buf))
|
||
{
|
||
prefix_sz = sizeof(s_buf);
|
||
}
|
||
|
||
/* Перевод строки — дописать если влезает */
|
||
const char *p_nl = LOG_NEWLINE;
|
||
size_t nl_len = sizeof(LOG_NEWLINE) - 1U; /* без \0 */
|
||
size_t avail = sizeof(s_buf) - prefix_sz;
|
||
|
||
if (avail > nl_len)
|
||
{
|
||
for (size_t i = 0U; i < nl_len; i++)
|
||
{
|
||
s_buf[prefix_sz + i] = p_nl[i];
|
||
}
|
||
prefix_sz += nl_len;
|
||
}
|
||
else
|
||
{
|
||
/* Буфер переполнен — принудительно завершить строку */
|
||
size_t tail = sizeof(s_buf) - nl_len;
|
||
for (size_t i = 0U; i < nl_len; i++)
|
||
{
|
||
s_buf[tail + i] = p_nl[i];
|
||
}
|
||
prefix_sz = sizeof(s_buf);
|
||
}
|
||
|
||
g_s_write_cb(s_buf, prefix_sz, g_s_p_ctx);
|
||
|
||
log_mutex_unlock();
|
||
} |