嵌入式日志模块

log.h

c 复制代码
#ifndef LOG_H
#define LOG_H

#include <stdarg.h>
#include <assert.h>

#ifdef __cplusplus
extern "C" {
#endif

/* ================= 日志级别 ================= */

typedef enum {
    LOG_LEVEL_DEBUG = 0,
    LOG_LEVEL_INFO,
    LOG_LEVEL_WARN,
    LOG_LEVEL_ERROR,
    LOG_LEVEL_FATAL
} log_level_e;

/* ================= 输出目标 ================= */

typedef enum {
    LOG_OUT_NONE   = 0x00,
    LOG_OUT_STDOUT = 0x01,
    LOG_OUT_FILE   = 0x02,
    LOG_OUT_ALL    = 0x03
} log_out_e;

/* ================= 配置 ================= */

typedef struct {
    log_level_e level;
    log_out_e   out;
    const char* path;
    long        max_size;
    int         max_files;
} log_cfg_t;

/* ================= 丢日志统计(快照) ================= */

typedef struct {
    unsigned int pool_full;
    unsigned int queue_full;
    unsigned int total_dropped;
} log_drop_stat_t;

/* ================= API ================= */

int  log_init(const log_cfg_t* cfg);
void log_deinit(void);

void log_write(log_level_e level, const char* fmt, ...);
void log_flush(void);

void log_get_drop_stat(log_drop_stat_t* stat);

/* ================= 快捷宏 ================= */

#define log_debug(...) log_write(LOG_LEVEL_DEBUG, ##__VA_ARGS__)
#define log_info(...)  log_write(LOG_LEVEL_INFO,  ##__VA_ARGS__)
#define log_warn(...)  log_write(LOG_LEVEL_WARN,  ##__VA_ARGS__)
#define log_error(...) log_write(LOG_LEVEL_ERROR, ##__VA_ARGS__)
#define log_fatal(...) log_write(LOG_LEVEL_FATAL, ##__VA_ARGS__)

/* ================= 断言(工程级) ================= */

#define log_assert(expr) \
    do { \
        if (!(expr)) { \
            log_fatal("assert failed: %s (%s:%d)", \
                       #expr, __FILE__, __LINE__); \
            log_flush(); \
            abort(); \
        } \
    } while (0)

#ifdef __cplusplus
}
#endif

#endif /* LOG_H */

log.c

c 复制代码
#include "log.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdatomic.h>

/* ================= 配置 ================= */

#define LOG_BUF_SIZE    2048
#define LOG_POOL_CNT    256
#define LOG_QUEUE_CNT   128

/* ================= 数据结构 ================= */

/* 日志对象 */
typedef struct {
    char           buf[LOG_BUF_SIZE];
    int            len;
    log_level_e    level;
    atomic_int     in_use;
} log_entry_t;

/* 对象池 */
typedef struct {
    log_entry_t entries[LOG_POOL_CNT];
} log_object_pool_t;

/* 环形队列 */
typedef struct {
    log_entry_t*  queue[LOG_QUEUE_CNT];
    atomic_uint  wr;
    atomic_uint  rd;
    atomic_uint  count;
} log_ringbuf_t;

/* 丢日志统计(原子) */
typedef struct {
    atomic_uint pool_full;
    atomic_uint queue_full;
    atomic_uint total_dropped;
} log_drop_stat_atomic_t;

/* 全局上下文 */
typedef struct {
    log_cfg_t              cfg;
    pthread_t              tid;
    int                    running;
    log_object_pool_t      pool;
    log_ringbuf_t          queue;
    log_drop_stat_atomic_t drop;
} log_ctx_t;

static log_ctx_t g_log;

/* ================= 工具 ================= */

static const char* level_str(log_level_e level)
{
    static const char* s[] = {
        "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
    };
    return (level >= 0 && level < 5) ? s[level] : "UNKNOWN";
}

/* ================= 对象池 ================= */

static log_entry_t* pool_alloc(void)
{
    log_object_pool_t* pool = &g_log.pool;

    for (int i = 0; i < LOG_POOL_CNT; i++) {
        log_entry_t* e = &pool->entries[i];
        int expected = 0;
        if (atomic_compare_exchange_strong(&e->in_use, &expected, 1))
            return e;
    }

    atomic_fetch_add(&g_log.drop.pool_full, 1);
    atomic_fetch_add(&g_log.drop.total_dropped, 1);
    return NULL;
}

static void pool_free(log_entry_t* e)
{
    atomic_store(&e->in_use, 0);
}

/* ================= 环形队列 ================= */

static int queue_push(log_entry_t* e)
{
    log_ringbuf_t* q = &g_log.queue;

    if (atomic_load(&q->count) >= LOG_QUEUE_CNT) {
        atomic_fetch_add(&g_log.drop.queue_full, 1);
        atomic_fetch_add(&g_log.drop.total_dropped, 1);
        pool_free(e);
        return -1;
    }

    unsigned int w = atomic_fetch_add(&q->wr, 1) % LOG_QUEUE_CNT;
    q->queue[w] = e;
    atomic_fetch_add(&q->count, 1);
    return 0;
}

static log_entry_t* queue_pop(void)
{
    log_ringbuf_t* q = &g_log.queue;

    if (atomic_load(&q->count) == 0)
        return NULL;

    unsigned int r = atomic_fetch_add(&q->rd, 1) % LOG_QUEUE_CNT;
    atomic_fetch_sub(&q->count, 1);
    return q->queue[r];
}

/* ================= 文件 ================= */

static int file_write(const char* path, const char* buf, int len)
{
    int fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0644);
    if (fd < 0)
        return -1;

    write(fd, buf, len);
    close(fd);
    return 0;
}

static void file_rotate(const char* path, int max_files)
{
    char old[512], new[512];

    for (int i = max_files - 1; i >= 1; i--) {
        snprintf(old, sizeof(old), "%s.%d", path, i);
        snprintf(new, sizeof(new), "%s.%d", path, i + 1);
        rename(old, new);
    }

    snprintf(old, sizeof(old), "%s.1", path);
    rename(path, old);
}

static void backend_write(const log_entry_t* e)
{
    if (g_log.cfg.out & LOG_OUT_STDOUT)
        write(STDOUT_FILENO, e->buf, e->len);

    if (g_log.cfg.out & LOG_OUT_FILE) {
        struct stat st;
        if (!stat(g_log.cfg.path, &st) &&
            st.st_size >= g_log.cfg.max_size)
            file_rotate(g_log.cfg.path, g_log.cfg.max_files);

        file_write(g_log.cfg.path, e->buf, e->len);
    }
}

/* ================= FATAL ================= */

static void fatal_direct(const char* fmt, va_list ap)
{
    char buf[LOG_BUF_SIZE];
    int len = vsnprintf(buf, sizeof(buf), fmt, ap);

    int fd = open(g_log.cfg.path,
                  O_WRONLY | O_APPEND | O_CREAT, 0644);
    if (fd >= 0) {
        write(fd, buf, len);
        fsync(fd);
        close(fd);
    }
}

/* ================= 工作线程 ================= */

static void* log_worker(void* arg)
{
    while (g_log.running) {
        log_entry_t* e = queue_pop();
        if (!e) {
            usleep(10000);
            continue;
        }

        backend_write(e);
        pool_free(e);
    }
    return NULL;
}

/* ================= 对外 API ================= */

int log_init(const log_cfg_t* cfg)
{
    if (!cfg || !cfg->path)
        return -1;

    memset(&g_log, 0, sizeof(g_log));
    g_log.cfg = *cfg;
    g_log.running = 1;

    if (pthread_create(&g_log.tid, NULL, log_worker, NULL) != 0)
        return -1;

    return 0;
}

void log_deinit(void)
{
    if (!g_log.running)
        return;

    g_log.running = 0;
    pthread_join(g_log.tid, NULL);
}

void log_flush(void)
{
    while (atomic_load(&g_log.queue.count) > 0)
        usleep(5000);

    if (g_log.cfg.out & LOG_OUT_FILE) {
        int fd = open(g_log.cfg.path, O_WRONLY);
        if (fd >= 0) {
            fsync(fd);
            close(fd);
        }
    }
}

void log_get_drop_stat(log_drop_stat_t* stat)
{
    if (!stat) return;

    stat->pool_full     = atomic_load(&g_log.drop.pool_full);
    stat->queue_full    = atomic_load(&g_log.drop.queue_full);
    stat->total_dropped = atomic_load(&g_log.drop.total_dropped);
}

void log_write(log_level_e level, const char* fmt, ...)
{
    if (!g_log.running || level < g_log.cfg.level)
        return;

    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);

    if (level == LOG_LEVEL_FATAL) {
        va_list ap;
        va_start(ap, fmt);
        fatal_direct(fmt, ap);
        va_end(ap);
        return;
    }

    log_entry_t* e = pool_alloc();
    if (!e)
        return;

    e->level = level;
    e->len = snprintf(e->buf, sizeof(e->buf),
        "[%ld.%03ld][%s] ",
        ts.tv_sec, ts.tv_nsec / 1000000,
        level_str(level));

    va_list ap;
    va_start(ap, fmt);
    e->len += vsnprintf(e->buf + e->len,
                        sizeof(e->buf) - e->len,
                        fmt, ap);
    va_end(ap);

    e->buf[e->len++] = '\n';

    queue_push(e);
}

main.c

c 复制代码
int main(void)
{
    log_cfg_t cfg = {
        .level     = LOG_LEVEL_DEBUG,
        .out       = LOG_OUT_ALL,
        .path      = "./app.log",
        .max_size  = 10 * 1024 * 1024,
        .max_files = 5
    };

    log_init(&cfg);

    log_debug("system init");
    log_info("version 1.0");
    log_warn("memory low");
    log_error("network error");
    log_fatal("critical failure");

    sleep(1);
    log_deinit();
    return 0;
}
相关推荐
血小溅2 小时前
三大 AI 编码框架深度对比:GSD vs OpenSpec vs Superpowers
人工智能·后端
ThanksGive2 小时前
层级时间轮看门狗
后端
GetcharZp2 小时前
告别繁琐命令行!这款容器可视化神器,让 Docker/K8s 管理变得如此简单
后端
铁皮饭盒6 小时前
bun直接tsx,优雅!
javascript·后端
Cosolar6 小时前
藏在 Claude Code 里的极致浪漫:完整 187 条 Spinner Verbs 全收录
后端·程序员·代码规范
Csvn7 小时前
Linux 防火墙管理 — firewalld 实战
后端
Csvn7 小时前
`functools.lru_cache` —— 一行代码搞定缓存加速
后端·python
leeyi8 小时前
Multi-Agent:让多个 AI 分工协作完成复杂任务
后端·aigc·agent
长栎8 小时前
你的策略模式是 Map<String, Strategy>?那不过是最廉价的 if-else 替代品
后端