Skip to content

File syn_log.c

File List > log > syn_log.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_LOG) || SYN_USE_LOG

#include "../port/syn_port_serial.h"
#include "../port/syn_port_system.h"
#include "../util/syn_assert.h"
#include "../util/syn_fmt.h"
#include "syn_log.h"

#if defined(SYN_USE_FMT) && !SYN_USE_FMT
#error "syn_log requires SYN_USE_FMT=1. Enable it in syn_config.h."
#endif

#if !defined(SYN_LOG_USE_PRINTF) || SYN_LOG_USE_PRINTF
#include <stdio.h>
#endif
#include <string.h>

/* ── State ──────────────────────────────────────────────────────────────── */

static SYN_LogLevel s_level = SYN_LOG_DEBUG; 
static bool s_inited = false;                
/* ── Level labels ─────────────────────────────────────────────────────────────── */

static const char *const s_level_chars = "TDIWEF";

#if SYN_LOG_COLOR
static const char *const s_level_colors[] = {
    "\033[90m",   /* TRACE — gray     */
    "\033[36m",   /* DEBUG — cyan     */
    "\033[32m",   /* INFO  — green    */
    "\033[33m",   /* WARN  — yellow   */
    "\033[31m",   /* ERROR — red      */
    "\033[1;31m", /* FATAL — bold red */
};
#define LOG_COLOR_RESET "\033[0m"
#endif

/* ── API ────────────────────────────────────────────────────────────────── */

void syn_log_init(SYN_LogLevel min_level)
{
    s_level = min_level;
    s_inited = true;
}

void syn_log_set_level(SYN_LogLevel level)
{
    s_level = level;
}

SYN_LogLevel syn_log_get_level(void)
{
    return s_level;
}

void syn_log_va(SYN_LogLevel level, const char *tag, const char *fmt, va_list args)
{
    if (!s_inited) {
        return;
    }

    if (level < s_level) {
        return;
    }

    char buf[SYN_LOG_BUF_SIZE];
    int pos = 0;
    int remaining = (int)sizeof(buf) - 2; /* reserve space for \n\0 */

    /* Color prefix */
#if SYN_LOG_COLOR
    if (level <= SYN_LOG_FATAL) {
        int n = snprintf(buf + pos, (size_t)(remaining - pos), "%s", s_level_colors[level]);
        if (n > 0)
            pos += n;
    }
#endif

    /* Timestamp */
#if SYN_LOG_TIMESTAMP
    {
        uint32_t tick = syn_port_get_tick_ms();
        char num[12];
        buf[pos++] = '[';
        syn_fmt_uint(num, sizeof(num), tick);
        size_t nl = strlen(num);
        size_t pad = (7 > nl) ? 7 - nl : 0;
        while (pad-- > 0)
            buf[pos++] = ' ';
        memcpy(buf + pos, num, nl);
        pos += (int)nl;
        buf[pos++] = ']';
        buf[pos++] = ' ';
    }
#endif

    /* Level + tag */
    {
        char lvl = (level <= SYN_LOG_FATAL) ? s_level_chars[level] : '?';
        buf[pos++] = lvl;
        if (tag != NULL && tag[0] != '\0') {
            buf[pos++] = '/';
            size_t tl = strlen(tag);
            if (tl > (size_t)(remaining - pos - 3))
                tl = (size_t)(remaining - pos - 3);
            memcpy(buf + pos, tag, tl);
            pos += (int)tl;
        }
        buf[pos++] = ':';
        buf[pos++] = ' ';
    }

    /* User message */
    {
#if !defined(SYN_LOG_USE_PRINTF) || SYN_LOG_USE_PRINTF
        int n = vsnprintf(buf + pos, (size_t)(remaining - pos), fmt, args);
        if (n > 0) {
            pos += n;
            if (pos > remaining)
                pos = remaining;
        }
#else
        /* No printf: copy format string literally */
        size_t fl = strlen(fmt);
        if (fl > (size_t)(remaining - pos))
            fl = (size_t)(remaining - pos);
        memcpy(buf + pos, fmt, fl);
        pos += (int)fl;
#endif
    }

    /* Color reset */
#if SYN_LOG_COLOR
    {
        const char *rst = LOG_COLOR_RESET "\n";
        size_t rl = strlen(rst);
        if (rl > sizeof(buf) - (size_t)pos - 1)
            rl = sizeof(buf) - (size_t)pos - 1;
        memcpy(buf + pos, rst, rl);
        pos += (int)rl;
    }
#else
    /* Newline */
    buf[pos++] = '\n';
#endif

    /* Null-terminate */
    if (pos >= (int)sizeof(buf))
        pos = (int)sizeof(buf) - 1;
    buf[pos] = '\0';

    syn_port_serial_write((const uint8_t *)buf, (size_t)pos);
}

void syn_log(SYN_LogLevel level, const char *tag, const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    syn_log_va(level, tag, fmt, args);
    va_end(args);
}

void syn_log_raw(const char *str)
{
    if (!s_inited || str == NULL) {
        return;
    }
    syn_port_serial_write((const uint8_t *)str, strlen(str));
}

void syn_log_hexdump(const char *tag, const void *data, size_t len)
{
    if (!s_inited || data == NULL) {
        return;
    }

    const uint8_t *bytes = (const uint8_t *)data;
    char line[80];
    size_t offset = 0;

    while (offset < len) {
        size_t pos = 0;
        char num[9];

        /* Offset: XXXX  */
        syn_fmt_hex(num, sizeof(num), (uint32_t)offset, 4);
        memcpy(line + pos, num, 4);
        pos += 4;
        line[pos++] = ' ';
        line[pos++] = ' ';

        /* Hex bytes */
        size_t i;
        for (i = 0; i < 16; i++) {
            if (offset + i < len) {
                syn_fmt_hex(num, sizeof(num), bytes[offset + i], 2);
                line[pos++] = num[0];
                line[pos++] = num[1];
                line[pos++] = ' ';
            } else {
                line[pos++] = ' ';
                line[pos++] = ' ';
                line[pos++] = ' ';
            }
            if (i == 7) {
                line[pos++] = ' ';
            }
        }

        /* ASCII */
        line[pos++] = '|';
        for (i = 0; i < 16 && (offset + i) < len; i++) {
            uint8_t c = bytes[offset + i];
            line[pos++] = (c >= 0x20 && c <= 0x7E) ? (char)c : '.';
        }
        line[pos++] = '|';
        line[pos++] = '\n';
        line[pos] = '\0';

        syn_log(SYN_LOG_DEBUG, tag, "%s", line);

        offset += 16;
    }
}

#endif /* SYN_USE_LOG */