[注释规范] 头文件全部函数声明加详细注释 + myLog.c 全面注释 + 代码风格记忆更新
- myLog.h: 所有函数/macro 加 @brief/@param/@return 注释 - myFunc.h: 65 个函数全部加详细注释,结构体成员逐行注释 - myLog.c: 每个内部函数加功能注释、关键逻辑行间注释、}后空行、return前空行 - 代码风格规范已写入 /memories/code-style.md
This commit is contained in:
parent
b5e4a361b4
commit
33252501c3
|
|
@ -1,6 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* @file myFunc.h
|
* @file myFunc.h
|
||||||
* @brief 基础工具函数 — 校验/字符串/时间/文件/进程/环境/IPC
|
* @brief 基础工具函数接口 — 65 个函数,7 大分类
|
||||||
|
* @details 校验计算 / 字符串转换 / 时间处理 / 文件目录操作 /
|
||||||
|
* 进程管理 / 环境变量路径 / IPC 消息队列
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _MY_FUNC_H_
|
#ifndef _MY_FUNC_H_
|
||||||
|
|
@ -13,141 +15,306 @@ extern "C"
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* ---- 系统时间 ---- */
|
/**
|
||||||
|
* @brief 系统时间结构体(位域存储,用于嵌入式装置内部表示)
|
||||||
|
* @note year: 7bit(0-127+1900), month:4bit(1-12), day:5bit(1-31)
|
||||||
|
* hour:5bit(0-23), min:6bit(0-59), ms:16bit(0-59999)
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint32_t ms : 16;
|
uint32_t ms : 16; /* 毫秒(实际存储秒*1000+毫秒,范围 0-59999) */
|
||||||
uint32_t min : 6;
|
uint32_t min : 6; /* 分钟 0-59 */
|
||||||
uint32_t : 2;
|
uint32_t : 2; /* 保留 */
|
||||||
uint32_t hour: 5;
|
uint32_t hour: 5; /* 小时 0-23 */
|
||||||
uint32_t : 3;
|
uint32_t : 3; /* 保留 */
|
||||||
uint32_t day : 5;
|
uint32_t day : 5; /* 日期 1-31 */
|
||||||
uint32_t week: 3;
|
uint32_t week: 3; /* 星期 */
|
||||||
uint32_t month:4;
|
uint32_t month:4; /* 月份 1-12 */
|
||||||
uint32_t : 4;
|
uint32_t : 4; /* 保留 */
|
||||||
uint32_t year: 7;
|
uint32_t year: 7; /* 年份偏移(实际值-1900),范围 0-127 */
|
||||||
uint32_t : 1;
|
uint32_t : 1; /* 保留 */
|
||||||
} stru_time_sys;
|
} stru_time_sys;
|
||||||
|
|
||||||
/* ---- 日历时间 ---- */
|
/**
|
||||||
|
* @brief 日历时间结构体(Unix 时间 + 微秒)
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
time_t sec;
|
time_t sec; /* Unix 时间戳(秒) */
|
||||||
time_t usec;
|
time_t usec; /* 微秒部分 */
|
||||||
} stru_time_cal;
|
} stru_time_cal;
|
||||||
|
|
||||||
/* ---- IPC 消息文本 ---- */
|
/**
|
||||||
|
* @brief IPC 消息文本载荷
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
int qid;
|
int qid; /* 响应队列 ID */
|
||||||
int type;
|
int type; /* 响应类型(enum_ipc_resp) */
|
||||||
int len;
|
int len; /* 有效数据长度 */
|
||||||
char text[4096];
|
char text[4096]; /* 消息内容 */
|
||||||
} stru_ipc_text;
|
} stru_ipc_text;
|
||||||
|
|
||||||
/* ---- IPC 响应类型 ---- */
|
/**
|
||||||
|
* @brief IPC 响应类型
|
||||||
|
*/
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
IPC_RESP_NONE = 1,
|
IPC_RESP_NONE = 1, /* 无需响应 */
|
||||||
IPC_RESP_ONCE,
|
IPC_RESP_ONCE, /* 单次响应 */
|
||||||
IPC_RESP_ALWAYS
|
IPC_RESP_ALWAYS /* 持续响应 */
|
||||||
} enum_ipc_resp;
|
} enum_ipc_resp;
|
||||||
|
|
||||||
/* ---- IPC 消息 ---- */
|
/**
|
||||||
|
* @brief IPC 消息(System V 消息队列格式)
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
long id;
|
long id; /* 消息类型 ID */
|
||||||
stru_ipc_text t;
|
stru_ipc_text t; /* 消息载荷 */
|
||||||
} stru_ipc_msg;
|
} stru_ipc_msg;
|
||||||
|
|
||||||
/* ---- 文件描述头(256字节) ---- */
|
/**
|
||||||
|
* @brief 文件描述头结构(256 字节,含 CRC 校验)
|
||||||
|
* @note flag 魔数为 0xA55AA55AA55AA55A
|
||||||
|
*/
|
||||||
#pragma pack(1)
|
#pragma pack(1)
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint64_t flag;
|
uint64_t flag; /* 魔数标识 0xA55AA55AA55AA55A */
|
||||||
char ver[16];
|
char ver[16]; /* 版本号 */
|
||||||
char author[16];
|
char author[16]; /* 作者 */
|
||||||
char modify[32];
|
char modify[32]; /* 修改时间 YYYY-MM-DD HH-MM-SS */
|
||||||
uint16_t crc;
|
uint16_t crc; /* CRC16 校验 */
|
||||||
char desc[128];
|
char desc[128]; /* 附加描述 */
|
||||||
char bak[54];
|
char bak[54]; /* 对齐填充(总计 256 字节) */
|
||||||
} stru_file_info;
|
} stru_file_info;
|
||||||
#pragma pack()
|
#pragma pack()
|
||||||
|
|
||||||
/* ===== 校验 ===== */
|
/* ================================================================
|
||||||
|
* 校验计算函数(CRC16 使用 Modbus 多项式 0xA001)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 8 位累加校验和 */
|
||||||
uint8_t func_cal_sum_byte(const uint8_t *d, uint32_t n);
|
uint8_t func_cal_sum_byte(const uint8_t *d, uint32_t n);
|
||||||
|
|
||||||
|
/** 16 位累加校验和 */
|
||||||
uint16_t func_cal_sum_word(const uint8_t *d, uint32_t n);
|
uint16_t func_cal_sum_word(const uint8_t *d, uint32_t n);
|
||||||
|
|
||||||
|
/** CRC16(Modbus 多项式,初始值 0xFFFF) */
|
||||||
uint16_t func_cal_crc16(const uint8_t *d, uint32_t n);
|
uint16_t func_cal_crc16(const uint8_t *d, uint32_t n);
|
||||||
|
|
||||||
|
/** CRC32(标准多项式,初始值 0xFFFFFFFF) */
|
||||||
uint32_t func_cal_crc32(const uint8_t *d, uint32_t n);
|
uint32_t func_cal_crc32(const uint8_t *d, uint32_t n);
|
||||||
|
|
||||||
|
/** 文件 CRC16(4KB 缓冲区流式计算) */
|
||||||
uint16_t func_cal_file_crc16(const char *path);
|
uint16_t func_cal_file_crc16(const char *path);
|
||||||
|
|
||||||
|
/** 文件 CRC32(4KB 缓冲区流式计算) */
|
||||||
uint32_t func_cal_file_crc32(const char *path);
|
uint32_t func_cal_file_crc32(const char *path);
|
||||||
|
|
||||||
/* ===== 字符串转换 ===== */
|
/* ================================================================
|
||||||
|
* 字符串转换函数
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 单个十六进制字符 → 数值('F'→15) */
|
||||||
uint8_t func_hex_ch(char c);
|
uint8_t func_hex_ch(char c);
|
||||||
|
|
||||||
|
/** 单个十进制字符 → 数值('9'→9) */
|
||||||
uint8_t func_dec_ch(char c);
|
uint8_t func_dec_ch(char c);
|
||||||
|
|
||||||
|
/** 十六进制字符串 "FF" → int 255 */
|
||||||
int func_hex2dec(char *s);
|
int func_hex2dec(char *s);
|
||||||
|
|
||||||
|
/** 十进制字符串 "255" → int 255(支持负号) */
|
||||||
int func_dec2dec(char *s);
|
int func_dec2dec(char *s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 十六进制字符串 → 字节缓冲区
|
||||||
|
* @param s 源字符串(如 "0102FF")
|
||||||
|
* @param dst 目标缓冲区
|
||||||
|
* @param len [out]实际字节数
|
||||||
|
* @return 0 成功,-1 奇数长度
|
||||||
|
*/
|
||||||
int func_hex2buf(const char *s, char *dst, int *len);
|
int func_hex2buf(const char *s, char *dst, int *len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief int → 十进制字符串,右对齐前补零
|
||||||
|
* @param width 最小宽度(不足补 0,如 width=4 val=255 → "0255")
|
||||||
|
*/
|
||||||
char *func_dec2str(char *buf, int sz, int val, int width);
|
char *func_dec2str(char *buf, int sz, int val, int width);
|
||||||
|
|
||||||
|
/** int → 十六进制字符串(width=0 时不补零) */
|
||||||
char *func_hex2str(char *buf, int sz, int val, int width);
|
char *func_hex2str(char *buf, int sz, int val, int width);
|
||||||
|
|
||||||
|
/** float → 字符串 */
|
||||||
char *func_float2str(char *buf, int sz, float f, int width);
|
char *func_float2str(char *buf, int sz, float f, int width);
|
||||||
|
|
||||||
|
/** 字节数组 → 十六进制字符串(如 {0x01,0xFF} → "01FF") */
|
||||||
char *func_buf2str(char *buf, int sz, const char *src, int n);
|
char *func_buf2str(char *buf, int sz, const char *src, int n);
|
||||||
|
|
||||||
|
/** 字符串按空格分割为 argc/argv */
|
||||||
int func_str2argv(char *s, uint8_t *argc, char *argv[], uint8_t max);
|
int func_str2argv(char *s, uint8_t *argc, char *argv[], uint8_t max);
|
||||||
|
|
||||||
/* ===== 时间 ===== */
|
/* ================================================================
|
||||||
|
* 时间处理函数
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 系统时间 → "YYYY-MM-DD HH:MM:SS.mmm" 格式字符串 */
|
||||||
char *func_time_sys2str(char *buf, int sz, stru_time_sys *t);
|
char *func_time_sys2str(char *buf, int sz, stru_time_sys *t);
|
||||||
|
|
||||||
|
/** 字符串 → 系统时间,解析失败返回 -1 */
|
||||||
int func_str2time_sys(const char *s, stru_time_sys *t);
|
int func_str2time_sys(const char *s, stru_time_sys *t);
|
||||||
|
|
||||||
|
/** 获取当前时间(type: 1=stru_time_sys, 2=stru_time_cal) */
|
||||||
int func_get_time(void *t, int type);
|
int func_get_time(void *t, int type);
|
||||||
|
|
||||||
|
/** 设置系统时间(需要 root 权限) */
|
||||||
int func_set_time(void *t, int type);
|
int func_set_time(void *t, int type);
|
||||||
|
|
||||||
|
/** 系统时间 → 日历时间 */
|
||||||
void func_time_sys2cal(stru_time_sys *src, stru_time_cal *dst);
|
void func_time_sys2cal(stru_time_sys *src, stru_time_cal *dst);
|
||||||
|
|
||||||
|
/** 日历时间 → 系统时间 */
|
||||||
void func_time_cal2sys(stru_time_cal *src, stru_time_sys *dst);
|
void func_time_cal2sys(stru_time_cal *src, stru_time_sys *dst);
|
||||||
|
|
||||||
/* ===== 文件目录 ===== */
|
/* ================================================================
|
||||||
|
* 文件目录操作函数
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 判断目录是否存在(返回 1=存在, 0=不存在) */
|
||||||
int func_dir_exist(const char *p);
|
int func_dir_exist(const char *p);
|
||||||
|
|
||||||
|
/** 递归创建目录 */
|
||||||
int func_make_dirs(const char *p);
|
int func_make_dirs(const char *p);
|
||||||
|
|
||||||
|
/** 递归删除目录(含所有内容) */
|
||||||
int func_del_dirs(const char *p);
|
int func_del_dirs(const char *p);
|
||||||
|
|
||||||
|
/** 通过系统命令删除目录(rm -rf) */
|
||||||
int func_del_dirs_cmd(const char *p);
|
int func_del_dirs_cmd(const char *p);
|
||||||
|
|
||||||
|
/** 判断文件是否存在(返回 1=存在, 0=不存在) */
|
||||||
int func_file_exist(const char *p);
|
int func_file_exist(const char *p);
|
||||||
|
|
||||||
|
/** 获取文件大小(字节),失败返回 0 */
|
||||||
uint32_t func_file_size(const char *p);
|
uint32_t func_file_size(const char *p);
|
||||||
|
|
||||||
|
/** 读取文件到预分配缓冲区,out 返回实际读取字节数 */
|
||||||
int func_read_file(const char *p, char *buf, uint32_t cap, uint32_t *out);
|
int func_read_file(const char *p, char *buf, uint32_t cap, uint32_t *out);
|
||||||
|
|
||||||
|
/** 读取文件到动态分配缓冲区(调用者负责 free) */
|
||||||
uint8_t *func_read_file_alloc(const char *p, uint32_t *out);
|
uint8_t *func_read_file_alloc(const char *p, uint32_t *out);
|
||||||
|
|
||||||
|
/** 写缓冲区到文件(覆盖模式) */
|
||||||
int func_write_file(const char *p, const char *buf, uint32_t n);
|
int func_write_file(const char *p, const char *buf, uint32_t n);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取结构化的文件描述头
|
||||||
|
* @param check 1=校验 flag 和 CRC,0=直接读取
|
||||||
|
*/
|
||||||
int func_read_file_info(const char *p, stru_file_info *info, int check);
|
int func_read_file_info(const char *p, stru_file_info *info, int check);
|
||||||
|
|
||||||
/* ===== 进程 ===== */
|
/* ================================================================
|
||||||
|
* 进程管理函数(基于 /proc 文件系统)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 获取当前进程名称 */
|
||||||
int func_proc_self_name(char *buf, uint32_t sz);
|
int func_proc_self_name(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 通过 PID 获取进程名称 */
|
||||||
int func_proc_name_by_pid(int pid, char *buf, uint32_t sz);
|
int func_proc_name_by_pid(int pid, char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取当前进程 PID */
|
||||||
int func_proc_pid(void);
|
int func_proc_pid(void);
|
||||||
|
|
||||||
|
/** 通过进程名称获取 PID(返回 -1 表示未找到) */
|
||||||
int func_proc_pid_by_name(const char *name);
|
int func_proc_pid_by_name(const char *name);
|
||||||
|
|
||||||
|
/** 通过 PID 判断进程是否存在 */
|
||||||
int func_proc_exist_by_pid(int pid);
|
int func_proc_exist_by_pid(int pid);
|
||||||
|
|
||||||
|
/** 通过名称判断进程是否存在 */
|
||||||
int func_proc_exist_by_name(const char *name);
|
int func_proc_exist_by_name(const char *name);
|
||||||
|
|
||||||
|
/** 通过 PID 获取进程可执行文件路径 */
|
||||||
int func_proc_path_by_pid(int pid, char *buf, uint32_t sz);
|
int func_proc_path_by_pid(int pid, char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取当前进程可执行文件路径 */
|
||||||
int func_proc_self_path(char *buf, uint32_t sz);
|
int func_proc_self_path(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取当前进程所在目录 */
|
||||||
int func_proc_self_dir(char *buf, uint32_t sz);
|
int func_proc_self_dir(char *buf, uint32_t sz);
|
||||||
|
|
||||||
/* ===== 环境变量路径 ===== */
|
/* ================================================================
|
||||||
|
* 环境变量路径函数
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 获取工作目录路径(环境变量 WORK_PATH) */
|
||||||
int func_get_work_path(char *buf, uint32_t sz);
|
int func_get_work_path(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取系统配置路径(SYS_CFG_PATH) */
|
||||||
int func_get_syscfg_path(char *buf, uint32_t sz);
|
int func_get_syscfg_path(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取应用根路径(APP_ROOT_PATH) */
|
||||||
int func_get_app_root(char *buf, uint32_t sz);
|
int func_get_app_root(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取历史数据根路径(HIS_ROOT_PATH) */
|
||||||
int func_get_his_root(char *buf, uint32_t sz);
|
int func_get_his_root(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取日志根路径(LOG_ROOT_PATH) */
|
||||||
int func_get_log_root(char *buf, uint32_t sz);
|
int func_get_log_root(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取数据库根路径(DBC_ROOT_PATH) */
|
||||||
int func_get_dbc_root(char *buf, uint32_t sz);
|
int func_get_dbc_root(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取脚本路径(SHELL_PATH) */
|
||||||
int func_get_shell_path(char *buf, uint32_t sz);
|
int func_get_shell_path(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取更新包路径(UPDATE_PATH) */
|
||||||
int func_get_update_path(char *buf, uint32_t sz);
|
int func_get_update_path(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 拼接应用路径:APP_ROOT_PATH/app_name */
|
||||||
int func_get_app_path(const char *app, char *buf, uint32_t sz);
|
int func_get_app_path(const char *app, char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 拼接应用配置路径:APP_ROOT_PATH/app_name/ext */
|
||||||
int func_get_app_cfg(const char *app, const char *ext, char *buf, uint32_t sz);
|
int func_get_app_cfg(const char *app, const char *ext, char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 拼接应用历史路径:HIS_ROOT_PATH/app_name */
|
||||||
int func_get_app_his(const char *app, char *buf, uint32_t sz);
|
int func_get_app_his(const char *app, char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 拼接应用日志路径:LOG_ROOT_PATH/app_name */
|
||||||
int func_get_app_log(const char *app, char *buf, uint32_t sz);
|
int func_get_app_log(const char *app, char *buf, uint32_t sz);
|
||||||
|
|
||||||
/* ===== IPC 消息队列 ===== */
|
/* ================================================================
|
||||||
|
* IPC 消息队列函数(System V 消息队列)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 按进程名创建消息队列 */
|
||||||
int func_ipc_create_by_name(char *name, int *qid);
|
int func_ipc_create_by_name(char *name, int *qid);
|
||||||
|
|
||||||
|
/** 按 PID 创建消息队列 */
|
||||||
int func_ipc_create_by_pid(int pid, int *qid);
|
int func_ipc_create_by_pid(int pid, int *qid);
|
||||||
|
|
||||||
|
/** 按进程名获取消息队列 ID(不存在则创建) */
|
||||||
int func_ipc_get_by_name(const char *name, int *qid);
|
int func_ipc_get_by_name(const char *name, int *qid);
|
||||||
|
|
||||||
|
/** 按 PID 获取消息队列 ID(不存在则创建) */
|
||||||
int func_ipc_get_by_pid(int pid, int *qid);
|
int func_ipc_get_by_pid(int pid, int *qid);
|
||||||
|
|
||||||
|
/** 发送原始字节到消息队列 */
|
||||||
int func_ipc_send_buf(int qid, uint8_t *tx, uint32_t n);
|
int func_ipc_send_buf(int qid, uint8_t *tx, uint32_t n);
|
||||||
|
|
||||||
|
/** 从消息队列接收原始字节 */
|
||||||
int func_ipc_recv_buf(int qid, uint8_t *rx, uint32_t n);
|
int func_ipc_recv_buf(int qid, uint8_t *rx, uint32_t n);
|
||||||
|
|
||||||
|
/** 发送结构体消息 */
|
||||||
int func_ipc_send_msg(int qid, stru_ipc_msg *m);
|
int func_ipc_send_msg(int qid, stru_ipc_msg *m);
|
||||||
|
|
||||||
|
/** 接收结构体消息 */
|
||||||
int func_ipc_recv_msg(int qid, stru_ipc_msg *m);
|
int func_ipc_recv_msg(int qid, stru_ipc_msg *m);
|
||||||
|
|
||||||
|
/** 删除消息队列 */
|
||||||
int func_ipc_delete(int qid);
|
int func_ipc_delete(int qid);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* @file myLog.h
|
* @file myLog.h
|
||||||
* @brief 异步日志系统接口(纯 C)
|
* @brief 异步日志系统接口(纯 C)
|
||||||
|
* @details 提供 INFO/ERROR 两级异步日志,非阻塞写入。
|
||||||
|
* 支持控制台彩色输出、文件输出、文件按大小轮转。
|
||||||
|
* 首次调用 log_prt 时自动初始化,无需手动 init。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _MY_LOG_H_
|
#ifndef _MY_LOG_H_
|
||||||
|
|
@ -11,24 +14,52 @@ extern "C"
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/** 日志级别:INFO(普通信息) */
|
||||||
#define LOG_INFO 0
|
#define LOG_INFO 0
|
||||||
|
/** 日志级别:ERROR(错误信息,控制台红色输出) */
|
||||||
#define LOG_ERROR 1
|
#define LOG_ERROR 1
|
||||||
|
|
||||||
/** 初始化(首次调用 log_prt 时自动触发,也可手动调用) */
|
/**
|
||||||
|
* @brief 手动初始化日志系统(可选,log_prt 自动懒初始化)
|
||||||
|
* @param dir 日志文件目录路径,传 NULL 则默认 /tmp/logs
|
||||||
|
* @return 0 成功
|
||||||
|
*/
|
||||||
int log_init(const char *dir);
|
int log_init(const char *dir);
|
||||||
|
|
||||||
/** 核心日志函数(非阻塞,消息入队后立即返回) */
|
/**
|
||||||
|
* @brief 核心日志打印函数(非阻塞,消息入队列后立即返回)
|
||||||
|
* @param level 日志级别 LOG_INFO 或 LOG_ERROR
|
||||||
|
* @param file 源文件名(通常传 __FILE__)
|
||||||
|
* @param func 函数名(通常传 __FUNCTION__)
|
||||||
|
* @param line 行号(通常传 __LINE__)
|
||||||
|
* @param fmt printf 风格格式化字符串
|
||||||
|
* @param ... 可变参数
|
||||||
|
*/
|
||||||
void log_prt(uint32_t level, const char *file, const char *func,
|
void log_prt(uint32_t level, const char *file, const char *func,
|
||||||
uint32_t line, const char *fmt, ...);
|
uint32_t line, const char *fmt, ...);
|
||||||
|
|
||||||
void log_file_on(void); /* 开启文件写入 */
|
/** 开启日志文件写入 */
|
||||||
void log_file_off(void); /* 关闭文件写入 */
|
void log_file_on(void);
|
||||||
void log_console_on(void); /* 开启控制台输出 */
|
|
||||||
void log_console_off(void); /* 关闭控制台输出 */
|
|
||||||
void log_flush(void); /* 等待队列清空 */
|
|
||||||
void log_cleanup(void); /* 停止线程、释放资源 */
|
|
||||||
|
|
||||||
|
/** 关闭日志文件写入 */
|
||||||
|
void log_file_off(void);
|
||||||
|
|
||||||
|
/** 开启控制台彩色输出 */
|
||||||
|
void log_console_on(void);
|
||||||
|
|
||||||
|
/** 关闭控制台输出 */
|
||||||
|
void log_console_off(void);
|
||||||
|
|
||||||
|
/** 等待异步队列清空(阻塞直到所有消息写入完成) */
|
||||||
|
void log_flush(void);
|
||||||
|
|
||||||
|
/** 停止工作线程、关闭文件、释放消息池内存 */
|
||||||
|
void log_cleanup(void);
|
||||||
|
|
||||||
|
/** INFO 级别日志宏 */
|
||||||
#define LOG_I(fmt, ...) log_prt(LOG_INFO, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
#define LOG_I(fmt, ...) log_prt(LOG_INFO, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
/** ERROR 级别日志宏 */
|
||||||
#define LOG_E(fmt, ...) log_prt(LOG_ERROR, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
#define LOG_E(fmt, ...) log_prt(LOG_ERROR, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* @file myFunc.h
|
* @file myFunc.h
|
||||||
* @brief 基础工具函数 — 校验/字符串/时间/文件/进程/环境/IPC
|
* @brief 基础工具函数接口 — 65 个函数,7 大分类
|
||||||
|
* @details 校验计算 / 字符串转换 / 时间处理 / 文件目录操作 /
|
||||||
|
* 进程管理 / 环境变量路径 / IPC 消息队列
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _MY_FUNC_H_
|
#ifndef _MY_FUNC_H_
|
||||||
|
|
@ -13,141 +15,306 @@ extern "C"
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* ---- 系统时间 ---- */
|
/**
|
||||||
|
* @brief 系统时间结构体(位域存储,用于嵌入式装置内部表示)
|
||||||
|
* @note year: 7bit(0-127+1900), month:4bit(1-12), day:5bit(1-31)
|
||||||
|
* hour:5bit(0-23), min:6bit(0-59), ms:16bit(0-59999)
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint32_t ms : 16;
|
uint32_t ms : 16; /* 毫秒(实际存储秒*1000+毫秒,范围 0-59999) */
|
||||||
uint32_t min : 6;
|
uint32_t min : 6; /* 分钟 0-59 */
|
||||||
uint32_t : 2;
|
uint32_t : 2; /* 保留 */
|
||||||
uint32_t hour: 5;
|
uint32_t hour: 5; /* 小时 0-23 */
|
||||||
uint32_t : 3;
|
uint32_t : 3; /* 保留 */
|
||||||
uint32_t day : 5;
|
uint32_t day : 5; /* 日期 1-31 */
|
||||||
uint32_t week: 3;
|
uint32_t week: 3; /* 星期 */
|
||||||
uint32_t month:4;
|
uint32_t month:4; /* 月份 1-12 */
|
||||||
uint32_t : 4;
|
uint32_t : 4; /* 保留 */
|
||||||
uint32_t year: 7;
|
uint32_t year: 7; /* 年份偏移(实际值-1900),范围 0-127 */
|
||||||
uint32_t : 1;
|
uint32_t : 1; /* 保留 */
|
||||||
} stru_time_sys;
|
} stru_time_sys;
|
||||||
|
|
||||||
/* ---- 日历时间 ---- */
|
/**
|
||||||
|
* @brief 日历时间结构体(Unix 时间 + 微秒)
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
time_t sec;
|
time_t sec; /* Unix 时间戳(秒) */
|
||||||
time_t usec;
|
time_t usec; /* 微秒部分 */
|
||||||
} stru_time_cal;
|
} stru_time_cal;
|
||||||
|
|
||||||
/* ---- IPC 消息文本 ---- */
|
/**
|
||||||
|
* @brief IPC 消息文本载荷
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
int qid;
|
int qid; /* 响应队列 ID */
|
||||||
int type;
|
int type; /* 响应类型(enum_ipc_resp) */
|
||||||
int len;
|
int len; /* 有效数据长度 */
|
||||||
char text[4096];
|
char text[4096]; /* 消息内容 */
|
||||||
} stru_ipc_text;
|
} stru_ipc_text;
|
||||||
|
|
||||||
/* ---- IPC 响应类型 ---- */
|
/**
|
||||||
|
* @brief IPC 响应类型
|
||||||
|
*/
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
IPC_RESP_NONE = 1,
|
IPC_RESP_NONE = 1, /* 无需响应 */
|
||||||
IPC_RESP_ONCE,
|
IPC_RESP_ONCE, /* 单次响应 */
|
||||||
IPC_RESP_ALWAYS
|
IPC_RESP_ALWAYS /* 持续响应 */
|
||||||
} enum_ipc_resp;
|
} enum_ipc_resp;
|
||||||
|
|
||||||
/* ---- IPC 消息 ---- */
|
/**
|
||||||
|
* @brief IPC 消息(System V 消息队列格式)
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
long id;
|
long id; /* 消息类型 ID */
|
||||||
stru_ipc_text t;
|
stru_ipc_text t; /* 消息载荷 */
|
||||||
} stru_ipc_msg;
|
} stru_ipc_msg;
|
||||||
|
|
||||||
/* ---- 文件描述头(256字节) ---- */
|
/**
|
||||||
|
* @brief 文件描述头结构(256 字节,含 CRC 校验)
|
||||||
|
* @note flag 魔数为 0xA55AA55AA55AA55A
|
||||||
|
*/
|
||||||
#pragma pack(1)
|
#pragma pack(1)
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint64_t flag;
|
uint64_t flag; /* 魔数标识 0xA55AA55AA55AA55A */
|
||||||
char ver[16];
|
char ver[16]; /* 版本号 */
|
||||||
char author[16];
|
char author[16]; /* 作者 */
|
||||||
char modify[32];
|
char modify[32]; /* 修改时间 YYYY-MM-DD HH-MM-SS */
|
||||||
uint16_t crc;
|
uint16_t crc; /* CRC16 校验 */
|
||||||
char desc[128];
|
char desc[128]; /* 附加描述 */
|
||||||
char bak[54];
|
char bak[54]; /* 对齐填充(总计 256 字节) */
|
||||||
} stru_file_info;
|
} stru_file_info;
|
||||||
#pragma pack()
|
#pragma pack()
|
||||||
|
|
||||||
/* ===== 校验 ===== */
|
/* ================================================================
|
||||||
|
* 校验计算函数(CRC16 使用 Modbus 多项式 0xA001)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 8 位累加校验和 */
|
||||||
uint8_t func_cal_sum_byte(const uint8_t *d, uint32_t n);
|
uint8_t func_cal_sum_byte(const uint8_t *d, uint32_t n);
|
||||||
|
|
||||||
|
/** 16 位累加校验和 */
|
||||||
uint16_t func_cal_sum_word(const uint8_t *d, uint32_t n);
|
uint16_t func_cal_sum_word(const uint8_t *d, uint32_t n);
|
||||||
|
|
||||||
|
/** CRC16(Modbus 多项式,初始值 0xFFFF) */
|
||||||
uint16_t func_cal_crc16(const uint8_t *d, uint32_t n);
|
uint16_t func_cal_crc16(const uint8_t *d, uint32_t n);
|
||||||
|
|
||||||
|
/** CRC32(标准多项式,初始值 0xFFFFFFFF) */
|
||||||
uint32_t func_cal_crc32(const uint8_t *d, uint32_t n);
|
uint32_t func_cal_crc32(const uint8_t *d, uint32_t n);
|
||||||
|
|
||||||
|
/** 文件 CRC16(4KB 缓冲区流式计算) */
|
||||||
uint16_t func_cal_file_crc16(const char *path);
|
uint16_t func_cal_file_crc16(const char *path);
|
||||||
|
|
||||||
|
/** 文件 CRC32(4KB 缓冲区流式计算) */
|
||||||
uint32_t func_cal_file_crc32(const char *path);
|
uint32_t func_cal_file_crc32(const char *path);
|
||||||
|
|
||||||
/* ===== 字符串转换 ===== */
|
/* ================================================================
|
||||||
|
* 字符串转换函数
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 单个十六进制字符 → 数值('F'→15) */
|
||||||
uint8_t func_hex_ch(char c);
|
uint8_t func_hex_ch(char c);
|
||||||
|
|
||||||
|
/** 单个十进制字符 → 数值('9'→9) */
|
||||||
uint8_t func_dec_ch(char c);
|
uint8_t func_dec_ch(char c);
|
||||||
|
|
||||||
|
/** 十六进制字符串 "FF" → int 255 */
|
||||||
int func_hex2dec(char *s);
|
int func_hex2dec(char *s);
|
||||||
|
|
||||||
|
/** 十进制字符串 "255" → int 255(支持负号) */
|
||||||
int func_dec2dec(char *s);
|
int func_dec2dec(char *s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 十六进制字符串 → 字节缓冲区
|
||||||
|
* @param s 源字符串(如 "0102FF")
|
||||||
|
* @param dst 目标缓冲区
|
||||||
|
* @param len [out]实际字节数
|
||||||
|
* @return 0 成功,-1 奇数长度
|
||||||
|
*/
|
||||||
int func_hex2buf(const char *s, char *dst, int *len);
|
int func_hex2buf(const char *s, char *dst, int *len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief int → 十进制字符串,右对齐前补零
|
||||||
|
* @param width 最小宽度(不足补 0,如 width=4 val=255 → "0255")
|
||||||
|
*/
|
||||||
char *func_dec2str(char *buf, int sz, int val, int width);
|
char *func_dec2str(char *buf, int sz, int val, int width);
|
||||||
|
|
||||||
|
/** int → 十六进制字符串(width=0 时不补零) */
|
||||||
char *func_hex2str(char *buf, int sz, int val, int width);
|
char *func_hex2str(char *buf, int sz, int val, int width);
|
||||||
|
|
||||||
|
/** float → 字符串 */
|
||||||
char *func_float2str(char *buf, int sz, float f, int width);
|
char *func_float2str(char *buf, int sz, float f, int width);
|
||||||
|
|
||||||
|
/** 字节数组 → 十六进制字符串(如 {0x01,0xFF} → "01FF") */
|
||||||
char *func_buf2str(char *buf, int sz, const char *src, int n);
|
char *func_buf2str(char *buf, int sz, const char *src, int n);
|
||||||
|
|
||||||
|
/** 字符串按空格分割为 argc/argv */
|
||||||
int func_str2argv(char *s, uint8_t *argc, char *argv[], uint8_t max);
|
int func_str2argv(char *s, uint8_t *argc, char *argv[], uint8_t max);
|
||||||
|
|
||||||
/* ===== 时间 ===== */
|
/* ================================================================
|
||||||
|
* 时间处理函数
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 系统时间 → "YYYY-MM-DD HH:MM:SS.mmm" 格式字符串 */
|
||||||
char *func_time_sys2str(char *buf, int sz, stru_time_sys *t);
|
char *func_time_sys2str(char *buf, int sz, stru_time_sys *t);
|
||||||
|
|
||||||
|
/** 字符串 → 系统时间,解析失败返回 -1 */
|
||||||
int func_str2time_sys(const char *s, stru_time_sys *t);
|
int func_str2time_sys(const char *s, stru_time_sys *t);
|
||||||
|
|
||||||
|
/** 获取当前时间(type: 1=stru_time_sys, 2=stru_time_cal) */
|
||||||
int func_get_time(void *t, int type);
|
int func_get_time(void *t, int type);
|
||||||
|
|
||||||
|
/** 设置系统时间(需要 root 权限) */
|
||||||
int func_set_time(void *t, int type);
|
int func_set_time(void *t, int type);
|
||||||
|
|
||||||
|
/** 系统时间 → 日历时间 */
|
||||||
void func_time_sys2cal(stru_time_sys *src, stru_time_cal *dst);
|
void func_time_sys2cal(stru_time_sys *src, stru_time_cal *dst);
|
||||||
|
|
||||||
|
/** 日历时间 → 系统时间 */
|
||||||
void func_time_cal2sys(stru_time_cal *src, stru_time_sys *dst);
|
void func_time_cal2sys(stru_time_cal *src, stru_time_sys *dst);
|
||||||
|
|
||||||
/* ===== 文件目录 ===== */
|
/* ================================================================
|
||||||
|
* 文件目录操作函数
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 判断目录是否存在(返回 1=存在, 0=不存在) */
|
||||||
int func_dir_exist(const char *p);
|
int func_dir_exist(const char *p);
|
||||||
|
|
||||||
|
/** 递归创建目录 */
|
||||||
int func_make_dirs(const char *p);
|
int func_make_dirs(const char *p);
|
||||||
|
|
||||||
|
/** 递归删除目录(含所有内容) */
|
||||||
int func_del_dirs(const char *p);
|
int func_del_dirs(const char *p);
|
||||||
|
|
||||||
|
/** 通过系统命令删除目录(rm -rf) */
|
||||||
int func_del_dirs_cmd(const char *p);
|
int func_del_dirs_cmd(const char *p);
|
||||||
|
|
||||||
|
/** 判断文件是否存在(返回 1=存在, 0=不存在) */
|
||||||
int func_file_exist(const char *p);
|
int func_file_exist(const char *p);
|
||||||
|
|
||||||
|
/** 获取文件大小(字节),失败返回 0 */
|
||||||
uint32_t func_file_size(const char *p);
|
uint32_t func_file_size(const char *p);
|
||||||
|
|
||||||
|
/** 读取文件到预分配缓冲区,out 返回实际读取字节数 */
|
||||||
int func_read_file(const char *p, char *buf, uint32_t cap, uint32_t *out);
|
int func_read_file(const char *p, char *buf, uint32_t cap, uint32_t *out);
|
||||||
|
|
||||||
|
/** 读取文件到动态分配缓冲区(调用者负责 free) */
|
||||||
uint8_t *func_read_file_alloc(const char *p, uint32_t *out);
|
uint8_t *func_read_file_alloc(const char *p, uint32_t *out);
|
||||||
|
|
||||||
|
/** 写缓冲区到文件(覆盖模式) */
|
||||||
int func_write_file(const char *p, const char *buf, uint32_t n);
|
int func_write_file(const char *p, const char *buf, uint32_t n);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取结构化的文件描述头
|
||||||
|
* @param check 1=校验 flag 和 CRC,0=直接读取
|
||||||
|
*/
|
||||||
int func_read_file_info(const char *p, stru_file_info *info, int check);
|
int func_read_file_info(const char *p, stru_file_info *info, int check);
|
||||||
|
|
||||||
/* ===== 进程 ===== */
|
/* ================================================================
|
||||||
|
* 进程管理函数(基于 /proc 文件系统)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 获取当前进程名称 */
|
||||||
int func_proc_self_name(char *buf, uint32_t sz);
|
int func_proc_self_name(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 通过 PID 获取进程名称 */
|
||||||
int func_proc_name_by_pid(int pid, char *buf, uint32_t sz);
|
int func_proc_name_by_pid(int pid, char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取当前进程 PID */
|
||||||
int func_proc_pid(void);
|
int func_proc_pid(void);
|
||||||
|
|
||||||
|
/** 通过进程名称获取 PID(返回 -1 表示未找到) */
|
||||||
int func_proc_pid_by_name(const char *name);
|
int func_proc_pid_by_name(const char *name);
|
||||||
|
|
||||||
|
/** 通过 PID 判断进程是否存在 */
|
||||||
int func_proc_exist_by_pid(int pid);
|
int func_proc_exist_by_pid(int pid);
|
||||||
|
|
||||||
|
/** 通过名称判断进程是否存在 */
|
||||||
int func_proc_exist_by_name(const char *name);
|
int func_proc_exist_by_name(const char *name);
|
||||||
|
|
||||||
|
/** 通过 PID 获取进程可执行文件路径 */
|
||||||
int func_proc_path_by_pid(int pid, char *buf, uint32_t sz);
|
int func_proc_path_by_pid(int pid, char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取当前进程可执行文件路径 */
|
||||||
int func_proc_self_path(char *buf, uint32_t sz);
|
int func_proc_self_path(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取当前进程所在目录 */
|
||||||
int func_proc_self_dir(char *buf, uint32_t sz);
|
int func_proc_self_dir(char *buf, uint32_t sz);
|
||||||
|
|
||||||
/* ===== 环境变量路径 ===== */
|
/* ================================================================
|
||||||
|
* 环境变量路径函数
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 获取工作目录路径(环境变量 WORK_PATH) */
|
||||||
int func_get_work_path(char *buf, uint32_t sz);
|
int func_get_work_path(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取系统配置路径(SYS_CFG_PATH) */
|
||||||
int func_get_syscfg_path(char *buf, uint32_t sz);
|
int func_get_syscfg_path(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取应用根路径(APP_ROOT_PATH) */
|
||||||
int func_get_app_root(char *buf, uint32_t sz);
|
int func_get_app_root(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取历史数据根路径(HIS_ROOT_PATH) */
|
||||||
int func_get_his_root(char *buf, uint32_t sz);
|
int func_get_his_root(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取日志根路径(LOG_ROOT_PATH) */
|
||||||
int func_get_log_root(char *buf, uint32_t sz);
|
int func_get_log_root(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取数据库根路径(DBC_ROOT_PATH) */
|
||||||
int func_get_dbc_root(char *buf, uint32_t sz);
|
int func_get_dbc_root(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取脚本路径(SHELL_PATH) */
|
||||||
int func_get_shell_path(char *buf, uint32_t sz);
|
int func_get_shell_path(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 获取更新包路径(UPDATE_PATH) */
|
||||||
int func_get_update_path(char *buf, uint32_t sz);
|
int func_get_update_path(char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 拼接应用路径:APP_ROOT_PATH/app_name */
|
||||||
int func_get_app_path(const char *app, char *buf, uint32_t sz);
|
int func_get_app_path(const char *app, char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 拼接应用配置路径:APP_ROOT_PATH/app_name/ext */
|
||||||
int func_get_app_cfg(const char *app, const char *ext, char *buf, uint32_t sz);
|
int func_get_app_cfg(const char *app, const char *ext, char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 拼接应用历史路径:HIS_ROOT_PATH/app_name */
|
||||||
int func_get_app_his(const char *app, char *buf, uint32_t sz);
|
int func_get_app_his(const char *app, char *buf, uint32_t sz);
|
||||||
|
|
||||||
|
/** 拼接应用日志路径:LOG_ROOT_PATH/app_name */
|
||||||
int func_get_app_log(const char *app, char *buf, uint32_t sz);
|
int func_get_app_log(const char *app, char *buf, uint32_t sz);
|
||||||
|
|
||||||
/* ===== IPC 消息队列 ===== */
|
/* ================================================================
|
||||||
|
* IPC 消息队列函数(System V 消息队列)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** 按进程名创建消息队列 */
|
||||||
int func_ipc_create_by_name(char *name, int *qid);
|
int func_ipc_create_by_name(char *name, int *qid);
|
||||||
|
|
||||||
|
/** 按 PID 创建消息队列 */
|
||||||
int func_ipc_create_by_pid(int pid, int *qid);
|
int func_ipc_create_by_pid(int pid, int *qid);
|
||||||
|
|
||||||
|
/** 按进程名获取消息队列 ID(不存在则创建) */
|
||||||
int func_ipc_get_by_name(const char *name, int *qid);
|
int func_ipc_get_by_name(const char *name, int *qid);
|
||||||
|
|
||||||
|
/** 按 PID 获取消息队列 ID(不存在则创建) */
|
||||||
int func_ipc_get_by_pid(int pid, int *qid);
|
int func_ipc_get_by_pid(int pid, int *qid);
|
||||||
|
|
||||||
|
/** 发送原始字节到消息队列 */
|
||||||
int func_ipc_send_buf(int qid, uint8_t *tx, uint32_t n);
|
int func_ipc_send_buf(int qid, uint8_t *tx, uint32_t n);
|
||||||
|
|
||||||
|
/** 从消息队列接收原始字节 */
|
||||||
int func_ipc_recv_buf(int qid, uint8_t *rx, uint32_t n);
|
int func_ipc_recv_buf(int qid, uint8_t *rx, uint32_t n);
|
||||||
|
|
||||||
|
/** 发送结构体消息 */
|
||||||
int func_ipc_send_msg(int qid, stru_ipc_msg *m);
|
int func_ipc_send_msg(int qid, stru_ipc_msg *m);
|
||||||
|
|
||||||
|
/** 接收结构体消息 */
|
||||||
int func_ipc_recv_msg(int qid, stru_ipc_msg *m);
|
int func_ipc_recv_msg(int qid, stru_ipc_msg *m);
|
||||||
|
|
||||||
|
/** 删除消息队列 */
|
||||||
int func_ipc_delete(int qid);
|
int func_ipc_delete(int qid);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* @file myLog.h
|
* @file myLog.h
|
||||||
* @brief 异步日志系统接口(纯 C)
|
* @brief 异步日志系统接口(纯 C)
|
||||||
|
* @details 提供 INFO/ERROR 两级异步日志,非阻塞写入。
|
||||||
|
* 支持控制台彩色输出、文件输出、文件按大小轮转。
|
||||||
|
* 首次调用 log_prt 时自动初始化,无需手动 init。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _MY_LOG_H_
|
#ifndef _MY_LOG_H_
|
||||||
|
|
@ -11,24 +14,52 @@ extern "C"
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/** 日志级别:INFO(普通信息) */
|
||||||
#define LOG_INFO 0
|
#define LOG_INFO 0
|
||||||
|
/** 日志级别:ERROR(错误信息,控制台红色输出) */
|
||||||
#define LOG_ERROR 1
|
#define LOG_ERROR 1
|
||||||
|
|
||||||
/** 初始化(首次调用 log_prt 时自动触发,也可手动调用) */
|
/**
|
||||||
|
* @brief 手动初始化日志系统(可选,log_prt 自动懒初始化)
|
||||||
|
* @param dir 日志文件目录路径,传 NULL 则默认 /tmp/logs
|
||||||
|
* @return 0 成功
|
||||||
|
*/
|
||||||
int log_init(const char *dir);
|
int log_init(const char *dir);
|
||||||
|
|
||||||
/** 核心日志函数(非阻塞,消息入队后立即返回) */
|
/**
|
||||||
|
* @brief 核心日志打印函数(非阻塞,消息入队列后立即返回)
|
||||||
|
* @param level 日志级别 LOG_INFO 或 LOG_ERROR
|
||||||
|
* @param file 源文件名(通常传 __FILE__)
|
||||||
|
* @param func 函数名(通常传 __FUNCTION__)
|
||||||
|
* @param line 行号(通常传 __LINE__)
|
||||||
|
* @param fmt printf 风格格式化字符串
|
||||||
|
* @param ... 可变参数
|
||||||
|
*/
|
||||||
void log_prt(uint32_t level, const char *file, const char *func,
|
void log_prt(uint32_t level, const char *file, const char *func,
|
||||||
uint32_t line, const char *fmt, ...);
|
uint32_t line, const char *fmt, ...);
|
||||||
|
|
||||||
void log_file_on(void); /* 开启文件写入 */
|
/** 开启日志文件写入 */
|
||||||
void log_file_off(void); /* 关闭文件写入 */
|
void log_file_on(void);
|
||||||
void log_console_on(void); /* 开启控制台输出 */
|
|
||||||
void log_console_off(void); /* 关闭控制台输出 */
|
|
||||||
void log_flush(void); /* 等待队列清空 */
|
|
||||||
void log_cleanup(void); /* 停止线程、释放资源 */
|
|
||||||
|
|
||||||
|
/** 关闭日志文件写入 */
|
||||||
|
void log_file_off(void);
|
||||||
|
|
||||||
|
/** 开启控制台彩色输出 */
|
||||||
|
void log_console_on(void);
|
||||||
|
|
||||||
|
/** 关闭控制台输出 */
|
||||||
|
void log_console_off(void);
|
||||||
|
|
||||||
|
/** 等待异步队列清空(阻塞直到所有消息写入完成) */
|
||||||
|
void log_flush(void);
|
||||||
|
|
||||||
|
/** 停止工作线程、关闭文件、释放消息池内存 */
|
||||||
|
void log_cleanup(void);
|
||||||
|
|
||||||
|
/** INFO 级别日志宏 */
|
||||||
#define LOG_I(fmt, ...) log_prt(LOG_INFO, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
#define LOG_I(fmt, ...) log_prt(LOG_INFO, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
/** ERROR 级别日志宏 */
|
||||||
#define LOG_E(fmt, ...) log_prt(LOG_ERROR, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
#define LOG_E(fmt, ...) log_prt(LOG_ERROR, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
||||||
|
|
@ -22,112 +22,155 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
/* ========== 可配置参数 ========== */
|
/* ========== 可配置参数 ========== */
|
||||||
|
|
||||||
|
/** 消息池预分配大小 */
|
||||||
#define POOL_SIZE 10000
|
#define POOL_SIZE 10000
|
||||||
|
/** 单次批量处理消息数上限 */
|
||||||
#define BATCH_MAX 100
|
#define BATCH_MAX 100
|
||||||
|
/** 空闲等待间隔(毫秒) */
|
||||||
#define FLUSH_MS 100
|
#define FLUSH_MS 100
|
||||||
|
/** 单日志文件最大 10MB */
|
||||||
#define FILE_MAX_SIZE (10 * 1024 * 1024)
|
#define FILE_MAX_SIZE (10 * 1024 * 1024)
|
||||||
|
/** 保留历史文件数 */
|
||||||
#define FILE_MAX_KEEP 5
|
#define FILE_MAX_KEEP 5
|
||||||
|
/** 单条日志内容上限 4KB */
|
||||||
#define CONTENT_MAX 4096
|
#define CONTENT_MAX 4096
|
||||||
|
|
||||||
/* ========== 数据结构 ========== */
|
/* ========== 数据结构 ========== */
|
||||||
|
|
||||||
/** 单条日志消息 */
|
/**
|
||||||
|
* @brief 单条日志消息(消息池节点 + 队列节点合一)
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint32_t level;
|
uint32_t level; /* 日志级别 LOG_INFO/LOG_ERROR */
|
||||||
char time_str[64];
|
char time_str[64]; /* 时间戳 YYYY-MM-DD HH:MM:SS.mmm */
|
||||||
char src_info[128]; /* "文件名/函数:行号" */
|
char src_info[128]; /* "文件名/函数:行号" */
|
||||||
char content[CONTENT_MAX];
|
char content[CONTENT_MAX]; /* 格式化后的日志内容 */
|
||||||
struct log_msg *next; /* 空闲链表 / 队列链表 */
|
struct log_msg *next; /* 空闲链表/队列链表指针 */
|
||||||
} stru_log_msg;
|
} stru_log_msg;
|
||||||
|
|
||||||
/** 线程安全队列 */
|
/**
|
||||||
|
* @brief 线程安全消息队列(生产者-消费者模式)
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
stru_log_msg *head;
|
stru_log_msg *head; /* 队头 */
|
||||||
stru_log_msg *tail;
|
stru_log_msg *tail; /* 队尾 */
|
||||||
int count;
|
int count; /* 当前消息数 */
|
||||||
pthread_mutex_t lock;
|
pthread_mutex_t lock; /* 互斥锁 */
|
||||||
pthread_cond_t cond_pop;
|
pthread_cond_t cond_pop; /* 消费者等待条件 */
|
||||||
pthread_cond_t cond_push;
|
pthread_cond_t cond_push; /* 生产者等待条件(队列满) */
|
||||||
} stru_msg_queue;
|
} stru_msg_queue;
|
||||||
|
|
||||||
/** 日志系统全局状态 */
|
/**
|
||||||
|
* @brief 日志系统全局状态
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
stru_msg_queue q;
|
stru_msg_queue q; /* 消息队列 */
|
||||||
pthread_t worker;
|
pthread_t worker; /* 工作线程 */
|
||||||
volatile int running;
|
volatile int running; /* 线程运行标志 */
|
||||||
FILE *fp;
|
FILE *fp; /* 当前日志文件句柄 */
|
||||||
long file_bytes;
|
long file_bytes; /* 当前文件已写入字节数 */
|
||||||
int to_file;
|
int to_file; /* 文件输出开关 */
|
||||||
int to_console;
|
int to_console; /* 控制台输出开关 */
|
||||||
char dir[256];
|
char dir[256]; /* 日志目录路径 */
|
||||||
} stru_log_state;
|
} stru_log_state;
|
||||||
|
|
||||||
LOCAL stru_log_state g_log;
|
LOCAL stru_log_state g_log;
|
||||||
|
|
||||||
/* ========== 消息池(预分配,无运行时 malloc) ========== */
|
/* ========== 消息池(预分配,无运行时 malloc) ========== */
|
||||||
LOCAL stru_log_msg *g_pool_free;
|
|
||||||
LOCAL stru_log_msg g_pool_buf[POOL_SIZE];
|
LOCAL stru_log_msg *g_pool_free; /* 空闲链表头 */
|
||||||
|
LOCAL stru_log_msg g_pool_buf[POOL_SIZE]; /* 预分配数组 */
|
||||||
LOCAL pthread_mutex_t g_pool_lock = PTHREAD_MUTEX_INITIALIZER;
|
LOCAL pthread_mutex_t g_pool_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
LOCAL pthread_once_t g_init_once = PTHREAD_ONCE_INIT;
|
LOCAL pthread_once_t g_init_once = PTHREAD_ONCE_INIT;
|
||||||
|
|
||||||
/** 初始化消息池:把数组元素链成空闲链表 */
|
/**
|
||||||
|
* @brief 初始化消息池:把预分配数组链成空闲链表
|
||||||
|
* @detail 只在首次调用时执行一次(由 global_init 调用)
|
||||||
|
*/
|
||||||
LOCAL void pool_init(void)
|
LOCAL void pool_init(void)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&g_pool_lock);
|
pthread_mutex_lock(&g_pool_lock);
|
||||||
|
|
||||||
if (g_pool_free != NULL)
|
if (g_pool_free != NULL)
|
||||||
{
|
{
|
||||||
pthread_mutex_unlock(&g_pool_lock);
|
pthread_mutex_unlock(&g_pool_lock);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 链成空闲链表 */
|
||||||
for (int i = 0; i < POOL_SIZE - 1; i++)
|
for (int i = 0; i < POOL_SIZE - 1; i++)
|
||||||
{
|
{
|
||||||
g_pool_buf[i].next = &g_pool_buf[i + 1];
|
g_pool_buf[i].next = &g_pool_buf[i + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
g_pool_buf[POOL_SIZE - 1].next = NULL;
|
g_pool_buf[POOL_SIZE - 1].next = NULL;
|
||||||
g_pool_free = &g_pool_buf[0];
|
g_pool_free = &g_pool_buf[0];
|
||||||
|
|
||||||
pthread_mutex_unlock(&g_pool_lock);
|
pthread_mutex_unlock(&g_pool_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 从池中取一个空闲消息块(失败返回 NULL) */
|
/**
|
||||||
|
* @brief 从池中取一个空闲消息块
|
||||||
|
* @return 成功返回指针,池耗尽返回 NULL
|
||||||
|
*/
|
||||||
LOCAL stru_log_msg *pool_alloc(void)
|
LOCAL stru_log_msg *pool_alloc(void)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&g_pool_lock);
|
pthread_mutex_lock(&g_pool_lock);
|
||||||
|
|
||||||
stru_log_msg *m = g_pool_free;
|
stru_log_msg *m = g_pool_free;
|
||||||
|
|
||||||
if (m)
|
if (m)
|
||||||
{
|
{
|
||||||
g_pool_free = m->next;
|
g_pool_free = m->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(&g_pool_lock);
|
pthread_mutex_unlock(&g_pool_lock);
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 归还消息块到池 */
|
/**
|
||||||
|
* @brief 归还消息块到池
|
||||||
|
*/
|
||||||
LOCAL void pool_free(stru_log_msg *m)
|
LOCAL void pool_free(stru_log_msg *m)
|
||||||
{
|
{
|
||||||
if (!m)
|
if (!m)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_lock(&g_pool_lock);
|
pthread_mutex_lock(&g_pool_lock);
|
||||||
|
|
||||||
m->next = g_pool_free;
|
m->next = g_pool_free;
|
||||||
g_pool_free = m;
|
g_pool_free = m;
|
||||||
|
|
||||||
pthread_mutex_unlock(&g_pool_lock);
|
pthread_mutex_unlock(&g_pool_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 队列操作 ========== */
|
/* ========== 队列操作 ========== */
|
||||||
|
|
||||||
/** 入队(满则阻塞) */
|
/**
|
||||||
|
* @brief 入队(生产者端)
|
||||||
|
* @detail 队列满时阻塞等待消费者腾出空间
|
||||||
|
*/
|
||||||
LOCAL void q_push(stru_log_msg *m)
|
LOCAL void q_push(stru_log_msg *m)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&g_log.q.lock);
|
pthread_mutex_lock(&g_log.q.lock);
|
||||||
|
|
||||||
|
/* 队列满则等待 */
|
||||||
while (g_log.q.count >= POOL_SIZE)
|
while (g_log.q.count >= POOL_SIZE)
|
||||||
{
|
{
|
||||||
pthread_cond_wait(&g_log.q.cond_push, &g_log.q.lock);
|
pthread_cond_wait(&g_log.q.cond_push, &g_log.q.lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
m->next = NULL;
|
m->next = NULL;
|
||||||
|
|
||||||
|
/* 尾插 */
|
||||||
if (!g_log.q.head)
|
if (!g_log.q.head)
|
||||||
{
|
{
|
||||||
g_log.q.head = m;
|
g_log.q.head = m;
|
||||||
|
|
@ -136,40 +179,59 @@ LOCAL void q_push(stru_log_msg *m)
|
||||||
{
|
{
|
||||||
g_log.q.tail->next = m;
|
g_log.q.tail->next = m;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_log.q.tail = m;
|
g_log.q.tail = m;
|
||||||
g_log.q.count++;
|
g_log.q.count++;
|
||||||
|
|
||||||
pthread_cond_signal(&g_log.q.cond_pop);
|
pthread_cond_signal(&g_log.q.cond_pop);
|
||||||
pthread_mutex_unlock(&g_log.q.lock);
|
pthread_mutex_unlock(&g_log.q.lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 出队(空则等待或返回 NULL) */
|
/**
|
||||||
|
* @brief 出队(消费者端)
|
||||||
|
* @detail 队列空且线程运行中时阻塞等待
|
||||||
|
* @return 成功返回消息指针,线程停止返回 NULL
|
||||||
|
*/
|
||||||
LOCAL stru_log_msg *q_pop(void)
|
LOCAL stru_log_msg *q_pop(void)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&g_log.q.lock);
|
pthread_mutex_lock(&g_log.q.lock);
|
||||||
|
|
||||||
while (g_log.q.count == 0 && g_log.running)
|
while (g_log.q.count == 0 && g_log.running)
|
||||||
{
|
{
|
||||||
pthread_cond_wait(&g_log.q.cond_pop, &g_log.q.lock);
|
pthread_cond_wait(&g_log.q.cond_pop, &g_log.q.lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_log.q.count == 0)
|
if (g_log.q.count == 0)
|
||||||
{
|
{
|
||||||
pthread_mutex_unlock(&g_log.q.lock);
|
pthread_mutex_unlock(&g_log.q.lock);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 头摘 */
|
||||||
stru_log_msg *m = g_log.q.head;
|
stru_log_msg *m = g_log.q.head;
|
||||||
|
|
||||||
g_log.q.head = m->next;
|
g_log.q.head = m->next;
|
||||||
|
|
||||||
if (!g_log.q.head)
|
if (!g_log.q.head)
|
||||||
{
|
{
|
||||||
g_log.q.tail = NULL;
|
g_log.q.tail = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_log.q.count--;
|
g_log.q.count--;
|
||||||
|
|
||||||
pthread_cond_signal(&g_log.q.cond_push);
|
pthread_cond_signal(&g_log.q.cond_push);
|
||||||
pthread_mutex_unlock(&g_log.q.lock);
|
pthread_mutex_unlock(&g_log.q.lock);
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 文件管理 ========== */
|
/* ========== 文件管理 ========== */
|
||||||
|
|
||||||
/** 拼接日志文件路径:dir/app.log 或 dir/app.log.N */
|
/**
|
||||||
|
* @brief 拼接日志文件路径
|
||||||
|
* @param idx 0=当前文件(dir/app.log), N>=1=历史文件(dir/app.log.N)
|
||||||
|
*/
|
||||||
LOCAL void file_path(char *buf, size_t sz, int idx)
|
LOCAL void file_path(char *buf, size_t sz, int idx)
|
||||||
{
|
{
|
||||||
if (idx == 0)
|
if (idx == 0)
|
||||||
|
|
@ -182,38 +244,49 @@ LOCAL void file_path(char *buf, size_t sz, int idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建日志目录 */
|
/**
|
||||||
|
* @brief 确保日志目录存在,不存在则创建
|
||||||
|
* @return 0 成功
|
||||||
|
*/
|
||||||
LOCAL int ensure_dir(void)
|
LOCAL int ensure_dir(void)
|
||||||
{
|
{
|
||||||
if (!g_log.dir[0])
|
if (!g_log.dir[0])
|
||||||
{
|
{
|
||||||
|
/* 默认放到 /tmp/logs */
|
||||||
snprintf(g_log.dir, sizeof(g_log.dir), "/tmp/logs");
|
snprintf(g_log.dir, sizeof(g_log.dir), "/tmp/logs");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
if (stat(g_log.dir, &st) != 0)
|
if (stat(g_log.dir, &st) != 0)
|
||||||
{
|
{
|
||||||
return mkdir(g_log.dir, 0755);
|
return mkdir(g_log.dir, 0755);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 轮转日志文件:app.log→.1→.2→...→.N(max),删除最旧的 */
|
/**
|
||||||
|
* @brief 轮转日志文件
|
||||||
|
* @detail app.log→app.log.1→app.log.2→...→app.log.N(max)→删除
|
||||||
|
*/
|
||||||
LOCAL void rotate_files(void)
|
LOCAL void rotate_files(void)
|
||||||
{
|
{
|
||||||
char old[512];
|
char old[512];
|
||||||
char neo[512];
|
char neo[512];
|
||||||
|
|
||||||
|
/* 关闭当前文件 */
|
||||||
if (g_log.fp)
|
if (g_log.fp)
|
||||||
{
|
{
|
||||||
fclose(g_log.fp);
|
fclose(g_log.fp);
|
||||||
g_log.fp = NULL;
|
g_log.fp = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 删除最旧 */
|
/* 删除最旧的历史文件 */
|
||||||
file_path(old, sizeof(old), FILE_MAX_KEEP);
|
file_path(old, sizeof(old), FILE_MAX_KEEP);
|
||||||
remove(old);
|
remove(old);
|
||||||
|
|
||||||
/* 依次后移 */
|
/* 历史文件依次后移一位 */
|
||||||
for (int i = FILE_MAX_KEEP - 1; i >= 1; i--)
|
for (int i = FILE_MAX_KEEP - 1; i >= 1; i--)
|
||||||
{
|
{
|
||||||
file_path(old, sizeof(old), i);
|
file_path(old, sizeof(old), i);
|
||||||
|
|
@ -221,25 +294,32 @@ LOCAL void rotate_files(void)
|
||||||
rename(old, neo);
|
rename(old, neo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 当前文件 → .1 */
|
/* 当前文件重命名为 .1 */
|
||||||
file_path(old, sizeof(old), 0);
|
file_path(old, sizeof(old), 0);
|
||||||
file_path(neo, sizeof(neo), 1);
|
file_path(neo, sizeof(neo), 1);
|
||||||
rename(old, neo);
|
rename(old, neo);
|
||||||
|
|
||||||
g_log.file_bytes = 0;
|
g_log.file_bytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开当前日志文件 */
|
/**
|
||||||
|
* @brief 打开当前日志文件(追加模式,行缓冲)
|
||||||
|
* @return 成功返回 FILE*, 失败返回 NULL
|
||||||
|
*/
|
||||||
LOCAL FILE *open_file(void)
|
LOCAL FILE *open_file(void)
|
||||||
{
|
{
|
||||||
if (ensure_dir() != 0)
|
if (ensure_dir() != 0)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
char path[512];
|
char path[512];
|
||||||
|
|
||||||
file_path(path, sizeof(path), 0);
|
file_path(path, sizeof(path), 0);
|
||||||
|
|
||||||
/* 检查已有文件大小 */
|
/* 检查已有文件大小,超出则先轮转 */
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
if (stat(path, &st) == 0)
|
if (stat(path, &st) == 0)
|
||||||
{
|
{
|
||||||
if (st.st_size > FILE_MAX_SIZE)
|
if (st.st_size > FILE_MAX_SIZE)
|
||||||
|
|
@ -253,21 +333,28 @@ LOCAL FILE *open_file(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *fp = fopen(path, "a");
|
FILE *fp = fopen(path, "a");
|
||||||
|
|
||||||
if (fp)
|
if (fp)
|
||||||
{
|
{
|
||||||
setlinebuf(fp);
|
setlinebuf(fp); /* 行缓冲模式 */
|
||||||
}
|
}
|
||||||
|
|
||||||
return fp;
|
return fp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 格式化 ========== */
|
/* ========== 格式化 ========== */
|
||||||
|
|
||||||
/** 当前时间字符串:YYYY-MM-DD HH:MM:SS.mmm */
|
/**
|
||||||
|
* @brief 当前时间戳字符串 "YYYY-MM-DD HH:MM:SS.mmm"
|
||||||
|
*/
|
||||||
LOCAL void fmt_time(char *buf, size_t sz)
|
LOCAL void fmt_time(char *buf, size_t sz)
|
||||||
{
|
{
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
|
||||||
gettimeofday(&tv, NULL);
|
gettimeofday(&tv, NULL);
|
||||||
|
|
||||||
struct tm *t = localtime(&tv.tv_sec);
|
struct tm *t = localtime(&tv.tv_sec);
|
||||||
|
|
||||||
if (t)
|
if (t)
|
||||||
{
|
{
|
||||||
snprintf(buf, sz, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
|
snprintf(buf, sz, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
|
||||||
|
|
@ -281,70 +368,91 @@ LOCAL void fmt_time(char *buf, size_t sz)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 提取文件名(去掉路径) 并拼接成 "file/func:line" */
|
/**
|
||||||
|
* @brief 格式化文件来源信息 "文件名/函数名:行号"
|
||||||
|
* @detail 自动去除文件路径前缀,仅保留文件名
|
||||||
|
*/
|
||||||
LOCAL void fmt_src(char *buf, size_t sz,
|
LOCAL void fmt_src(char *buf, size_t sz,
|
||||||
const char *file, const char *func, uint32_t line)
|
const char *file, const char *func, uint32_t line)
|
||||||
{
|
{
|
||||||
if (!file || !func)
|
if (!file || !func)
|
||||||
{
|
{
|
||||||
snprintf(buf, sz, "?:?:0");
|
snprintf(buf, sz, "?:?:0");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 提取纯文件名(去掉路径前缀) */
|
||||||
const char *p = strrchr(file, '/');
|
const char *p = strrchr(file, '/');
|
||||||
|
|
||||||
if (!p)
|
if (!p)
|
||||||
{
|
{
|
||||||
p = strrchr(file, '\\');
|
p = strrchr(file, '\\');
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *name = p ? p + 1 : file;
|
const char *name = p ? p + 1 : file;
|
||||||
|
|
||||||
snprintf(buf, sz, "%s/%s:%u", name, func, line);
|
snprintf(buf, sz, "%s/%s:%u", name, func, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 输出 ========== */
|
/* ========== 输出 ========== */
|
||||||
|
|
||||||
/** 批量写控制台 + 文件 */
|
/**
|
||||||
|
* @brief 批量写入控制台 + 文件
|
||||||
|
* @detail 控制台带颜色输出,文件带级别标记
|
||||||
|
*/
|
||||||
LOCAL void output_batch(stru_log_msg **batch, int cnt)
|
LOCAL void output_batch(stru_log_msg **batch, int cnt)
|
||||||
{
|
{
|
||||||
if (cnt == 0)
|
if (cnt == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *titles[] = { "INFO", "ERROR" };
|
static const char *titles[] = { "INFO", "ERROR" };
|
||||||
static const char *colors[] = { COLOR_WHITE, COLOR_RED };
|
static const char *colors[] = { COLOR_WHITE, COLOR_RED };
|
||||||
|
|
||||||
/* 控制台(带颜色) */
|
/* 控制台彩色输出 */
|
||||||
if (g_log.to_console)
|
if (g_log.to_console)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < cnt; i++)
|
for (int i = 0; i < cnt; i++)
|
||||||
{
|
{
|
||||||
stru_log_msg *m = batch[i];
|
stru_log_msg *m = batch[i];
|
||||||
|
|
||||||
printf("%s[%s] [%s%s%s] %s\n",
|
printf("%s[%s] [%s%s%s] %s\n",
|
||||||
colors[m->level], m->time_str,
|
colors[m->level], m->time_str,
|
||||||
colors[m->level], titles[m->level],
|
colors[m->level], titles[m->level],
|
||||||
COLOR_RESET, m->content);
|
COLOR_RESET, m->content);
|
||||||
}
|
}
|
||||||
|
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 文件 */
|
/* 文件输出 */
|
||||||
if (g_log.to_file)
|
if (g_log.to_file)
|
||||||
{
|
{
|
||||||
if (!g_log.fp)
|
if (!g_log.fp)
|
||||||
{
|
{
|
||||||
g_log.fp = open_file();
|
g_log.fp = open_file();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_log.fp)
|
if (g_log.fp)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < cnt; i++)
|
for (int i = 0; i < cnt; i++)
|
||||||
{
|
{
|
||||||
stru_log_msg *m = batch[i];
|
stru_log_msg *m = batch[i];
|
||||||
int n = fprintf(g_log.fp, "[%s] [%s] %s\n",
|
int n = fprintf(g_log.fp, "[%s] [%s] %s\n",
|
||||||
titles[m->level], m->time_str, m->content);
|
titles[m->level],
|
||||||
|
m->time_str, m->content);
|
||||||
|
|
||||||
if (n > 0)
|
if (n > 0)
|
||||||
{
|
{
|
||||||
g_log.file_bytes += n;
|
g_log.file_bytes += n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fflush(g_log.fp);
|
fflush(g_log.fp);
|
||||||
|
|
||||||
|
/* 超出限制则轮转 */
|
||||||
if (g_log.file_bytes > FILE_MAX_SIZE)
|
if (g_log.file_bytes > FILE_MAX_SIZE)
|
||||||
{
|
{
|
||||||
rotate_files();
|
rotate_files();
|
||||||
|
|
@ -355,9 +463,14 @@ LOCAL void output_batch(stru_log_msg **batch, int cnt)
|
||||||
|
|
||||||
/* ========== 工作线程 ========== */
|
/* ========== 工作线程 ========== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 日志消费线程主循环
|
||||||
|
* @detail 批量从队列取消息 → 输出 → 归还消息池
|
||||||
|
*/
|
||||||
LOCAL void *worker_thread(void *arg)
|
LOCAL void *worker_thread(void *arg)
|
||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg;
|
||||||
|
|
||||||
while (g_log.running)
|
while (g_log.running)
|
||||||
{
|
{
|
||||||
stru_log_msg *batch[BATCH_MAX];
|
stru_log_msg *batch[BATCH_MAX];
|
||||||
|
|
@ -367,16 +480,20 @@ LOCAL void *worker_thread(void *arg)
|
||||||
for (int i = 0; i < BATCH_MAX; i++)
|
for (int i = 0; i < BATCH_MAX; i++)
|
||||||
{
|
{
|
||||||
stru_log_msg *m = q_pop();
|
stru_log_msg *m = q_pop();
|
||||||
|
|
||||||
if (!m)
|
if (!m)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
batch[cnt++] = m;
|
batch[cnt++] = m;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cnt > 0)
|
if (cnt > 0)
|
||||||
{
|
{
|
||||||
output_batch(batch, cnt);
|
output_batch(batch, cnt);
|
||||||
|
|
||||||
|
/* 归还消息块到池 */
|
||||||
for (int i = 0; i < cnt; i++)
|
for (int i = 0; i < cnt; i++)
|
||||||
{
|
{
|
||||||
pool_free(batch[i]);
|
pool_free(batch[i]);
|
||||||
|
|
@ -384,45 +501,67 @@ LOCAL void *worker_thread(void *arg)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
/* 队列空,休眠等待 */
|
||||||
usleep(FLUSH_MS * 1000);
|
usleep(FLUSH_MS * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 全局初始化(pthread_once) ========== */
|
/* ========== 全局初始化(pthread_once) ========== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 全局初始化(由 pthread_once 确保只执行一次)
|
||||||
|
* @detail 初始化消息池 + 启动工作线程
|
||||||
|
*/
|
||||||
LOCAL void global_init(void)
|
LOCAL void global_init(void)
|
||||||
{
|
{
|
||||||
pool_init();
|
pool_init();
|
||||||
/* g_log.q 已在声明时用 {0} 初始化 */
|
|
||||||
|
/* 状态字段已在声明时用 {0} 初始化 */
|
||||||
g_log.q.head = NULL;
|
g_log.q.head = NULL;
|
||||||
g_log.q.tail = NULL;
|
g_log.q.tail = NULL;
|
||||||
g_log.q.count = 0;
|
g_log.q.count = 0;
|
||||||
g_log.to_console = 1;
|
g_log.to_console = 1;
|
||||||
g_log.to_file = 0;
|
g_log.to_file = 0;
|
||||||
g_log.running = 1;
|
g_log.running = 1;
|
||||||
|
|
||||||
pthread_create(&g_log.worker, NULL, worker_thread, NULL);
|
pthread_create(&g_log.worker, NULL, worker_thread, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 公开 API ========== */
|
/* ========== 公开 API ========== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化日志系统
|
||||||
|
* @param dir 日志文件目录(NULL 则默认 /tmp/logs)
|
||||||
|
* @return 0 成功
|
||||||
|
*/
|
||||||
int log_init(const char *dir)
|
int log_init(const char *dir)
|
||||||
{
|
{
|
||||||
if (dir && dir[0])
|
if (dir && dir[0])
|
||||||
{
|
{
|
||||||
snprintf(g_log.dir, sizeof(g_log.dir), "%s", dir);
|
snprintf(g_log.dir, sizeof(g_log.dir), "%s", dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
pool_init();
|
pool_init();
|
||||||
g_log.to_file = 1;
|
g_log.to_file = 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 核心日志打印(非阻塞)
|
||||||
|
* @detail 首次调用 pthread_once 自动触发全局初始化。
|
||||||
|
* 从消息池获取空闲块 → 填充时间/来源/内容 → 入队。
|
||||||
|
* 消息池耗尽时静默丢弃本条日志。
|
||||||
|
*/
|
||||||
void log_prt(uint32_t level, const char *file, const char *func,
|
void log_prt(uint32_t level, const char *file, const char *func,
|
||||||
uint32_t line, const char *fmt, ...)
|
uint32_t line, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
/* 懒初始化 */
|
/* 懒初始化:仅首次调用执行 */
|
||||||
pthread_once(&g_init_once, global_init);
|
pthread_once(&g_init_once, global_init);
|
||||||
|
|
||||||
if (level > LOG_ERROR || !fmt || !g_log.running)
|
if (level > LOG_ERROR || !fmt || !g_log.running)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -430,24 +569,33 @@ void log_prt(uint32_t level, const char *file, const char *func,
|
||||||
|
|
||||||
/* 从消息池取空闲块 */
|
/* 从消息池取空闲块 */
|
||||||
stru_log_msg *m = pool_alloc();
|
stru_log_msg *m = pool_alloc();
|
||||||
|
|
||||||
if (!m)
|
if (!m)
|
||||||
{
|
{
|
||||||
return;
|
return; /* 池耗尽,静默丢弃 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 填充消息字段 */
|
||||||
m->level = level;
|
m->level = level;
|
||||||
fmt_time(m->time_str, sizeof(m->time_str));
|
fmt_time(m->time_str, sizeof(m->time_str));
|
||||||
fmt_src(m->src_info, sizeof(m->src_info), file, func, line);
|
fmt_src(m->src_info, sizeof(m->src_info), file, func, line);
|
||||||
|
|
||||||
|
/* 格式化日志内容 */
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
int pos = snprintf(m->content, sizeof(m->content), "[%s] ", m->src_info);
|
|
||||||
|
int pos = snprintf(m->content, sizeof(m->content),
|
||||||
|
"[%s] ", m->src_info);
|
||||||
|
|
||||||
if (pos < (int)sizeof(m->content))
|
if (pos < (int)sizeof(m->content))
|
||||||
{
|
{
|
||||||
vsnprintf(m->content + pos, sizeof(m->content) - pos, fmt, ap);
|
vsnprintf(m->content + pos, sizeof(m->content) - pos, fmt, ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
|
/* 入队(生产者端) */
|
||||||
q_push(m);
|
q_push(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -471,6 +619,9 @@ void log_console_off(void)
|
||||||
g_log.to_console = 0;
|
g_log.to_console = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 等待消息队列清空(阻塞直到所有日志写入完成)
|
||||||
|
*/
|
||||||
void log_flush(void)
|
void log_flush(void)
|
||||||
{
|
{
|
||||||
for (;;)
|
for (;;)
|
||||||
|
|
@ -478,39 +629,57 @@ void log_flush(void)
|
||||||
pthread_mutex_lock(&g_log.q.lock);
|
pthread_mutex_lock(&g_log.q.lock);
|
||||||
int c = g_log.q.count;
|
int c = g_log.q.count;
|
||||||
pthread_mutex_unlock(&g_log.q.lock);
|
pthread_mutex_unlock(&g_log.q.lock);
|
||||||
|
|
||||||
if (c == 0)
|
if (c == 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
usleep(10000);
|
|
||||||
|
usleep(10000); /* 10ms 轮询 */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 停止日志系统并释放所有资源
|
||||||
|
* @detail 停止工作线程 → 刷新文件 → 释放队列残留消息
|
||||||
|
*/
|
||||||
void log_cleanup(void)
|
void log_cleanup(void)
|
||||||
{
|
{
|
||||||
if (!g_log.running)
|
if (!g_log.running)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 通知线程停止 */
|
||||||
g_log.running = 0;
|
g_log.running = 0;
|
||||||
pthread_cond_signal(&g_log.q.cond_pop);
|
pthread_cond_signal(&g_log.q.cond_pop);
|
||||||
|
|
||||||
|
/* 等待线程退出 */
|
||||||
pthread_join(g_log.worker, NULL);
|
pthread_join(g_log.worker, NULL);
|
||||||
|
|
||||||
|
/* 关闭文件 */
|
||||||
if (g_log.fp)
|
if (g_log.fp)
|
||||||
{
|
{
|
||||||
fclose(g_log.fp);
|
fclose(g_log.fp);
|
||||||
g_log.fp = NULL;
|
g_log.fp = NULL;
|
||||||
}
|
}
|
||||||
/* 释放池中残留消息 */
|
|
||||||
|
/* 释放队列中残留消息 */
|
||||||
pthread_mutex_lock(&g_log.q.lock);
|
pthread_mutex_lock(&g_log.q.lock);
|
||||||
|
|
||||||
stru_log_msg *cur = g_log.q.head;
|
stru_log_msg *cur = g_log.q.head;
|
||||||
|
|
||||||
while (cur)
|
while (cur)
|
||||||
{
|
{
|
||||||
stru_log_msg *tmp = cur;
|
stru_log_msg *tmp = cur;
|
||||||
|
|
||||||
cur = cur->next;
|
cur = cur->next;
|
||||||
pool_free(tmp);
|
pool_free(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_log.q.head = NULL;
|
g_log.q.head = NULL;
|
||||||
g_log.q.tail = NULL;
|
g_log.q.tail = NULL;
|
||||||
g_log.q.count = 0;
|
g_log.q.count = 0;
|
||||||
|
|
||||||
pthread_mutex_unlock(&g_log.q.lock);
|
pthread_mutex_unlock(&g_log.q.lock);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue