/** * @file myLog.c * @brief 异步日志实现 — 消息池 + 生产者/消费者 + 批量写入 + 文件轮转 * * 架构概述: * 调用 log_prt() → 从消息池取空闲块 → 填充内容 → 入队(生产者) * 工作线程 → 批量出队(消费者) → 控制台彩色输出 + 文件写入 * 首次调用 pthread_once 自动启动工作线程 */ #include "myLog.h" #include "myBase.h" #include #include #include #include #include #include #include #include #include #include /* ========== 可配置参数 ========== */ #define POOL_SIZE 10000 /* 消息池大小 */ #define BATCH_MAX 100 /* 单次批量处理上限 */ #define FLUSH_MS 100 /* 空闲等待间隔(毫秒) */ #define FILE_MAX_SIZE (10*1024*1024) /* 单文件 10MB */ #define FILE_MAX_KEEP 5 /* 保留历史文件数 */ #define CONTENT_MAX 4096 /* 单条日志内容上限 */ /* ========== 数据结构 ========== */ /** 单条日志消息 */ typedef struct log_msg { uint32_t level; char time_str[64]; char src_info[128]; /* "文件名/函数:行号" */ char content[CONTENT_MAX]; struct log_msg *next; /* 空闲链表 / 队列链表 */ } log_msg_t; /** 线程安全队列 */ typedef struct { log_msg_t *head, *tail; int count; pthread_mutex_t lock; pthread_cond_t cond_pop; /* 消费者等待 */ pthread_cond_t cond_push; /* 生产者等待(队列满) */ } msg_queue_t; /** 日志系统全局状态 */ LOCAL struct { msg_queue_t q; pthread_t worker; volatile int running; FILE *fp; long file_bytes; int to_file; int to_console; char dir[256]; } g_log; /* ========== 消息池(预分配,无运行时 malloc) ========== */ LOCAL log_msg_t *g_pool_free; /* 空闲链表头 */ LOCAL log_msg_t g_pool_buf[POOL_SIZE]; /* 预分配数组 */ LOCAL pthread_mutex_t g_pool_lock = PTHREAD_MUTEX_INITIALIZER; LOCAL pthread_once_t g_init_once = PTHREAD_ONCE_INIT; /** 初始化消息池:把数组元素链成空闲链表 */ LOCAL void pool_init(void) { pthread_mutex_lock(&g_pool_lock); if (g_pool_free != NULL) { pthread_mutex_unlock(&g_pool_lock); return; } for (int i = 0; i < POOL_SIZE - 1; i++) g_pool_buf[i].next = &g_pool_buf[i + 1]; g_pool_buf[POOL_SIZE - 1].next = NULL; g_pool_free = &g_pool_buf[0]; pthread_mutex_unlock(&g_pool_lock); } /** 从池中取一个空闲消息块(失败返回 NULL) */ LOCAL log_msg_t *pool_alloc(void) { pthread_mutex_lock(&g_pool_lock); log_msg_t *m = g_pool_free; if (m) g_pool_free = m->next; pthread_mutex_unlock(&g_pool_lock); return m; } /** 归还消息块到池 */ LOCAL void pool_free(log_msg_t *m) { if (!m) return; pthread_mutex_lock(&g_pool_lock); m->next = g_pool_free; g_pool_free = m; pthread_mutex_unlock(&g_pool_lock); } /* ========== 队列操作 ========== */ /** 入队(满则阻塞) */ LOCAL void q_push(log_msg_t *m) { pthread_mutex_lock(&g_log.q.lock); while (g_log.q.count >= POOL_SIZE) pthread_cond_wait(&g_log.q.cond_push, &g_log.q.lock); m->next = NULL; if (!g_log.q.head) g_log.q.head = m; else g_log.q.tail->next = m; g_log.q.tail = m; g_log.q.count++; pthread_cond_signal(&g_log.q.cond_pop); pthread_mutex_unlock(&g_log.q.lock); } /** 出队(空则等待或返回 NULL) */ LOCAL log_msg_t *q_pop(void) { pthread_mutex_lock(&g_log.q.lock); while (g_log.q.count == 0 && g_log.running) pthread_cond_wait(&g_log.q.cond_pop, &g_log.q.lock); if (g_log.q.count == 0) { pthread_mutex_unlock(&g_log.q.lock); return NULL; } log_msg_t *m = g_log.q.head; g_log.q.head = m->next; if (!g_log.q.head) g_log.q.tail = NULL; g_log.q.count--; pthread_cond_signal(&g_log.q.cond_push); pthread_mutex_unlock(&g_log.q.lock); return m; } /* ========== 文件管理 ========== */ /** 拼接日志文件路径:dir/app.log 或 dir/app.log.N */ LOCAL void file_path(char *buf, size_t sz, int idx) { if (idx == 0) snprintf(buf, sz, "%s/app.log", g_log.dir); else snprintf(buf, sz, "%s/app.log.%d", g_log.dir, idx); } /** 创建日志目录 */ LOCAL int ensure_dir(void) { if (!g_log.dir[0]) { /* 默认放到 /tmp/logs */ snprintf(g_log.dir, sizeof(g_log.dir), "/tmp/logs"); } struct stat st; if (stat(g_log.dir, &st) != 0) return mkdir(g_log.dir, 0755); return 0; } /** 轮转日志文件:app.log→.1→.2→...→.N(max),删除最旧的 */ LOCAL void rotate_files(void) { char old[512], neo[512]; if (g_log.fp) { fclose(g_log.fp); g_log.fp = NULL; } /* 删除最旧 */ file_path(old, sizeof(old), FILE_MAX_KEEP); remove(old); /* 依次后移 */ for (int i = FILE_MAX_KEEP - 1; i >= 1; i--) { file_path(old, sizeof(old), i); file_path(neo, sizeof(neo), i + 1); rename(old, neo); } /* 当前文件 → .1 */ file_path(old, sizeof(old), 0); file_path(neo, sizeof(neo), 1); rename(old, neo); g_log.file_bytes = 0; } /** 打开当前日志文件 */ LOCAL FILE *open_file(void) { if (ensure_dir() != 0) return NULL; char path[512]; file_path(path, sizeof(path), 0); /* 检查已有文件大小 */ struct stat st; if (stat(path, &st) == 0) { if (st.st_size > FILE_MAX_SIZE) rotate_files(); else g_log.file_bytes = st.st_size; } FILE *fp = fopen(path, "a"); if (fp) setlinebuf(fp); /* 行缓冲 */ return fp; } /* ========== 格式化 ========== */ /** 当前时间字符串:YYYY-MM-DD HH:MM:SS.mmm */ LOCAL void fmt_time(char *buf, size_t sz) { struct timeval tv; gettimeofday(&tv, NULL); struct tm *t = localtime(&tv.tv_sec); if (t) { snprintf(buf, sz, "%04d-%02d-%02d %02d:%02d:%02d.%03d", t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, (int)(tv.tv_usec / 1000)); } else { snprintf(buf, sz, "----.--.-- --:--:--.---"); } } /** 提取文件名(去掉路径) 并拼接成 "file/func:line" */ LOCAL void fmt_src(char *buf, size_t sz, const char *file, const char *func, uint32_t line) { if (!file || !func) { snprintf(buf, sz, "?:?:0"); return; } const char *p = strrchr(file, '/'); if (!p) p = strrchr(file, '\\'); const char *name = p ? p + 1 : file; snprintf(buf, sz, "%s/%s:%u", name, func, line); } /* ========== 输出 ========== */ /** 批量写控制台 + 文件 */ LOCAL void output_batch(log_msg_t **batch, int cnt) { if (cnt == 0) return; static const char *titles[] = {"INFO", "ERROR"}; static const char *colors[] = {COLOR_WHITE, COLOR_RED}; /* 控制台(带颜色) */ if (g_log.to_console) { for (int i = 0; i < cnt; i++) { log_msg_t *m = batch[i]; printf("%s[%s] [%s%s%s] %s\n", colors[m->level], m->time_str, colors[m->level], titles[m->level], COLOR_RESET, m->content); } fflush(stdout); } /* 文件 */ if (g_log.to_file) { if (!g_log.fp) g_log.fp = open_file(); if (g_log.fp) { for (int i = 0; i < cnt; i++) { log_msg_t *m = batch[i]; int n = fprintf(g_log.fp, "[%s] [%s] %s\n", titles[m->level], m->time_str, m->content); if (n > 0) g_log.file_bytes += n; } fflush(g_log.fp); if (g_log.file_bytes > FILE_MAX_SIZE) rotate_files(); } } } /* ========== 工作线程 ========== */ LOCAL void *worker_thread(void *arg) { (void)arg; while (g_log.running) { log_msg_t *batch[BATCH_MAX]; int cnt = 0; /* 批量取消息 */ for (int i = 0; i < BATCH_MAX; i++) { log_msg_t *m = q_pop(); if (!m) break; batch[cnt++] = m; } if (cnt > 0) { output_batch(batch, cnt); for (int i = 0; i < cnt; i++) pool_free(batch[i]); } else { usleep(FLUSH_MS * 1000); } } return NULL; } /* ========== 全局初始化(pthread_once) ========== */ LOCAL void global_init(void) { pool_init(); /* g_log.q 已在声明时用 {0} 初始化 */ g_log.q.head = g_log.q.tail = NULL; g_log.q.count = 0; g_log.to_console = 1; g_log.to_file = 0; g_log.running = 1; pthread_create(&g_log.worker, NULL, worker_thread, NULL); } /* ========== 公开 API ========== */ int log_init(const char *dir) { if (dir && dir[0]) snprintf(g_log.dir, sizeof(g_log.dir), "%s", dir); pool_init(); /* 确保消息池就绪 */ g_log.to_file = 1; return 0; } void log_prt(uint32_t level, const char *file, const char *func, uint32_t line, const char *fmt, ...) { /* 懒初始化 */ pthread_once(&g_init_once, global_init); if (level > LOG_ERROR || !fmt || !g_log.running) return; /* 从消息池取空闲块 */ log_msg_t *m = pool_alloc(); if (!m) return; /* 池耗尽,丢弃本条 */ m->level = level; fmt_time(m->time_str, sizeof(m->time_str)); fmt_src(m->src_info, sizeof(m->src_info), file, func, line); va_list ap; va_start(ap, fmt); int pos = snprintf(m->content, sizeof(m->content), "[%s] ", m->src_info); if (pos < (int)sizeof(m->content)) vsnprintf(m->content + pos, sizeof(m->content) - pos, fmt, ap); va_end(ap); q_push(m); } void log_file_on(void) { g_log.to_file = 1; } void log_file_off(void) { g_log.to_file = 0; } void log_console_on(void) { g_log.to_console = 1; } void log_console_off(void) { g_log.to_console = 0; } void log_flush(void) { /* 轮询等待队列归零 */ for (;;) { pthread_mutex_lock(&g_log.q.lock); int c = g_log.q.count; pthread_mutex_unlock(&g_log.q.lock); if (c == 0) break; usleep(10000); } } void log_cleanup(void) { if (!g_log.running) return; g_log.running = 0; pthread_cond_signal(&g_log.q.cond_pop); pthread_join(g_log.worker, NULL); if (g_log.fp) { fclose(g_log.fp); g_log.fp = NULL; } /* 释放池中残留消息 */ pthread_mutex_lock(&g_log.q.lock); log_msg_t *cur = g_log.q.head, *tmp; while (cur) { tmp = cur; cur = cur->next; pool_free(tmp); } g_log.q.head = g_log.q.tail = NULL; g_log.q.count = 0; pthread_mutex_unlock(&g_log.q.lock); }