RTU_ALL_AI/src/public/liblog/src/myLog.c

378 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file myLog.c
* @brief 异步日志实现 — 消息池 + 生产者/消费者 + 批量写入 + 文件轮转
*
* 架构概述:
* 调用 log_prt() → 从消息池取空闲块 → 填充内容 → 入队(生产者)
* 工作线程 → 批量出队(消费者) → 控制台彩色输出 + 文件写入
* 首次调用 pthread_once 自动启动工作线程
*/
#include "myLog.h"
#include "myBase.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
/* ========== 可配置参数 ========== */
#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);
}