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;
}