C基础 多用户分级日志库 sclog
Posted 喜欢兰花山丘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C基础 多用户分级日志库 sclog相关的知识,希望对你有一定的参考价值。
引言 - sclog 总的设计思路
sclog在之前已经内置到simplec 简易c开发框架中一个日志库. 最近对其重新设计了一下. 减少了对外暴露的接口.
也是C开发中一个轮子. 比较简单, 非常适合学习理解,最后自己写一个自己喜欢的日志库.
首先分析分级设计的总的思路.
主要是围绕上面思路设计. 分6个等级. 2中类型的日志文件. sc.log 普通文件, 什么信息都接受, sc.log.wf只接受异常信息. 需要紧急处理的.
继续说明日志消息体的设计思路
到这里设计的总思路已经清楚了. 后面会介绍详细的实现的设计思路.
前言 - sclog 实现设计思路
到这里我们需要看一下实现方面的思路了. 向上面的基准时间, 客户端ip, 全局id, 模块名称. 用户一次请求的所有流程都是必须一样的.
详细设计如下
struct slinfo { unsigned logid; //请求的logid,唯一id char reqip[_INT_LITTLE]; //请求方ip char times[_INT_LITTLE]; //当前时间串 struct timeval timev; //处理时间,保存值,统一用毫秒 char mod[_INT_LITTLE]; //当前线程的模块名称,不能超过_INT_LITTLE - 1 };
并且上面数据 是每个线程(进程)都必须保存一份. 同样这里核心设计使用线程的私有变量pthread_key_t 类型.
其中对于输出字符串输出格式,采用宏控制如下
// //关于日志切分,需要用第三方插件例如crontab , 或者下次我自己写一个监测程序. #define _INT_LITTLE (64) //保存时间或IP长度 #define _INT_LOG (1024<<3) //最多8k日志 #define _STR_SCLOG_DIR "logs" //日志相对路径目录,如果不需要需要配置成"" #define _STR_SCLOG_LOG "sc.log" //普通log日志 DEBUG,INFO,NOTICE,WARNING,FATAL都会输出 #define _STR_SCLOG_WFLOG "sc.log.wf" //级别比较高的日志输出 FATAL和WARNING #define _STR_SCLOG_FATAL "FATAL" //错误,后端使用 #define _STR_SCLOG_WARNING "WARNING" //警告,前端使用错误,用这个 #define _STR_SCLOG_NOTICE "NOTICE" //系统使用,一般标记一条请求完成,使用这个日志 #define _STR_SCLOG_INFO "INFO" //普通的日志打印 #define _STR_SCLOG_TRACE "TRACE" //测试用的日志标记当前日志的开始和结束 #define _STR_SCLOG_DEBUG "DEBUG" //测试用的日志打印,在发布版这些日志会被清除掉 /** * fstr : 为标识串 例如 _STR_SCLOG_FATAL, 必须是双引号括起来的串 ** ** 拼接一个 printf 输出格式串 **/ #define SCLOG_PUTS(fstr) "[" fstr "][%s:%d:%s][logid:%u][reqip:%s][mod:%s]"
其中 SCLOG_PUTS 前面还缺少 [%s %u] 输出时间信息. 这个内置后面函数中实现. 因为C库提供的 time 返回时间函数不是线程安全的.
后面自己封装一个. 主要是围绕下面函数
/** * 获取 当前时间串,并塞入tstr中C长度并返回 ** 使用举例 char tstr[64]; puts(gettimes(tstr, LEN(tstr))); **tstr : 保存最后生成的最后串 **len : tstr数组的长度 ** : 返回tstr首地址 **/ int sh_times(char tstr[], int len) { struct tm st; time_t t = time(NULL); localtime_r(&t, &st); return (int)strftime(tstr, len, "%F %X", &st); }
对于localtime_r 是linux上函数套路. 对于window封装如下
//为了解决 不通用功能 #define localtime_r(t, tm) localtime_s(tm, t)
改变参数行为, 完成了上面函数统一.
还有一个统一设计思路, 需要支持微妙级别时间量. linux 自带 gettimeofday供支持. 但是为了跨品台 在M$上实现如下
#if defined(_MSC_VER) /** * Linux sys/time.h 中获取时间函数在Windows上一种移植实现 **tv : 返回结果包含秒数和微秒数 **tz : 包含的时区,在window上这个变量没有用不返回 ** : 默认返回0 **/ int gettimeofday(struct timeval * tv, void * tz) { time_t clock; struct tm tm; SYSTEMTIME wtm; GetLocalTime(&wtm); tm.tm_year = wtm.wYear - 1900; tm.tm_mon = wtm.wMonth - 1; //window的计数更好写 tm.tm_mday = wtm.wDay; tm.tm_hour = wtm.wHour; tm.tm_min = wtm.wMinute; tm.tm_sec = wtm.wSecond; tm.tm_isdst = -1; //不考虑夏令时 clock = mktime(&tm); tv->tv_sec = (long)clock; //32位使用,接口已经老了 tv->tv_usec = wtm.wMilliseconds * 1000; return _RT_OK; } #endif
这些基本工作完成后. 普通跨品台的前戏操作基本就完成了.
linux 上文件结构
window上文件结构
而我们需要使用这个日志库, 对外暴露的接口是
/** * FATAL... 日志打印宏 ** fmt : 输出的格式串,需要""包裹起来 ** ... : 后面的参数,服务于fmt **/ #define SL_FATAL(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_FATAL, fmt, ##__VA_ARGS__) #define SL_WARNING(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_WARNING, fmt, ##__VA_ARGS__) #define SL_NOTICE(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_NOTICE, fmt, ##__VA_ARGS__) #define SL_INFO(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_INFO, fmt, ##__VA_ARGS__) // 发布状态下,关闭SL_DEBUG 宏,需要重新编译,没有改成运行时的判断,这个框架主要围绕单机部分多服务器 #if defined(_DEBUG) # define SL_TRACE(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_TRACE, fmt, ##__VA_ARGS__) # define SL_DEBUG(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_DEBUG, fmt, ##__VA_ARGS__) #else # define SL_TRACE(fmt, ...) /* 人生难道就是123*/ # define SL_DEBUG(fmt, ...) /* 爱过哎 */ #endif //-------------------------------------------------------------------------------------------| // 第二部分 对日志信息体操作的get和set,这里隐藏了信息体的实现 //-------------------------------------------------------------------------------------------| /** * 线程的私有数据初始化 ** ** mod : 当前线程名称 ** reqip : 请求的ip ** logid : 分配的唯一标识id, 默认0 ** return : _RT_OK 表示正常,_RF_EM内存分配错误 **/ extern int sl_init(const char mod[_INT_LITTLE], const char reqip[_INT_LITTLE], unsigned logid);
到这里基本对外接口, 大致分析清楚里, 理解更仔细推荐直接看附带的源码文件.
文件附录:
sclog.h 对外暴露的接口
#ifndef _H_SIMPLE_SCLOG #define _H_SIMPLE_SCLOG #include "schead.h" //-------------------------------------------------------------------------------------------| // 第一部分 共用的参数宏 //-------------------------------------------------------------------------------------------| // //关于日志切分,需要用第三方插件例如crontab , 或者下次我自己写一个监测程序. #define _INT_LITTLE (64) //保存时间或IP长度 #define _INT_LOG (1024<<3) //最多8k日志 #define _STR_SCLOG_DIR "logs" //日志相对路径目录,如果不需要需要配置成"" #define _STR_SCLOG_LOG "sc.log" //普通log日志 DEBUG,INFO,NOTICE,WARNING,FATAL都会输出 #define _STR_SCLOG_WFLOG "sc.log.wf" //级别比较高的日志输出 FATAL和WARNING #define _STR_SCLOG_FATAL "FATAL" //错误,后端使用 #define _STR_SCLOG_WARNING "WARNING" //警告,前端使用错误,用这个 #define _STR_SCLOG_NOTICE "NOTICE" //系统使用,一般标记一条请求完成,使用这个日志 #define _STR_SCLOG_INFO "INFO" //普通的日志打印 #define _STR_SCLOG_TRACE "TRACE" //测试用的日志标记当前日志的开始和结束 #define _STR_SCLOG_DEBUG "DEBUG" //测试用的日志打印,在发布版这些日志会被清除掉 /** * fstr : 为标识串 例如 _STR_SCLOG_FATAL, 必须是双引号括起来的串 ** ** 拼接一个 printf 输出格式串 **/ #define SCLOG_PUTS(fstr) "[" fstr "][%s:%d:%s][logid:%u][reqip:%s][mod:%s]" /** * fstr : 只能是 _STR_SCLOG_* 开头的宏 ** fmt : 必须是""括起来的宏.单独输出的格式宏 ** ... : 对映fmt参数集 ** ** 拼接这里使用的宏,为sl_printf 打造一个模板 **/ #define SCLOG_PRINTF(fstr, fmt, ...) \\ sl_printf(SCLOG_PUTS(fstr) fmt "\\n", __FILE__, __LINE__, __func__, \\ sl_getlogid(), sl_getreqip(), sl_getmod(), ##__VA_ARGS__) /** * FATAL... 日志打印宏 ** fmt : 输出的格式串,需要""包裹起来 ** ... : 后面的参数,服务于fmt **/ #define SL_FATAL(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_FATAL, fmt, ##__VA_ARGS__) #define SL_WARNING(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_WARNING, fmt, ##__VA_ARGS__) #define SL_NOTICE(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_NOTICE, fmt, ##__VA_ARGS__) #define SL_INFO(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_INFO, fmt, ##__VA_ARGS__) // 发布状态下,关闭SL_DEBUG 宏,需要重新编译,没有改成运行时的判断,这个框架主要围绕单机部分多服务器 #if defined(_DEBUG) # define SL_TRACE(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_TRACE, fmt, ##__VA_ARGS__) # define SL_DEBUG(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_DEBUG, fmt, ##__VA_ARGS__) #else # define SL_TRACE(fmt, ...) /* 人生难道就是123*/ # define SL_DEBUG(fmt, ...) /* 爱过哎 */ #endif //-------------------------------------------------------------------------------------------| // 第二部分 对日志信息体操作的get和set,这里隐藏了信息体的实现 //-------------------------------------------------------------------------------------------| /** * 线程的私有数据初始化 ** ** mod : 当前线程名称 ** reqip : 请求的ip ** logid : 分配的唯一标识id, 默认0 ** return : _RT_OK 表示正常,_RF_EM内存分配错误 **/ extern int sl_init(const char mod[_INT_LITTLE], const char reqip[_INT_LITTLE], unsigned logid); /** * 获取日志信息体的唯一的logid **/ unsigned sl_getlogid(void); /** * 获取日志信息体的请求ip串,返回NULL表示没有初始化 **/ const char* sl_getreqip(void); /** * 获取日志信息体的名称,返回NULL表示没有初始化 **/ const char* sl_getmod(void); //-------------------------------------------------------------------------------------------| // 第三部分 对日志系统具体的输出输入接口部分 //-------------------------------------------------------------------------------------------| /** * 日志系统首次使用初始化,找对对映日志文件路径,创建指定路径 **/ extern void sl_start(void); /** * 这个函数不希望你使用,是一个内部限定死的日志输出内容.推荐使用相应的宏 **打印相应级别的日志到对映的文件中. ** ** format : 必须是""号括起来的宏,开头必须是 [FALTAL:%s]后端错误 ** [WARNING:%s]前端错误, [NOTICE:%s]系统使用, [INFO:%s]普通信息, ** [DEBUG:%s] 开发测试用 ** ** return : 返回输出内容长度 **/ int sl_printf(const char* format, ...); #endif // !_H_SIMPLE_SCLOG
scatom.h 跨平台的原子操作
#ifndef _H_SIMPLEC_SCATOM #define _H_SIMPLEC_SCATOM /* * 作者 : wz * * 描述 : 简单的原子操作,目前只考虑 VS(CL) 小端机 和 gcc * 推荐用 posix 线程库 */ // 如果 是 VS 编译器 #if defined(_MSC_VER) #include <Windows.h> //忽略 warning C4047: “==”:“void *”与“LONG”的间接级别不同 #pragma warning(disable:4047) // v 和 a 都是 long 这样数据 #define ATOM_FETCH_ADD(v, a) \\ InterlockedExchangeAdd((LONG*)&(v), (LONG)(a)) #define ATOM_ADD_FETCH(v, a) \\ InterlockedAdd((LONG*)&(v), (LONG)(a)) #define ATOM_SET(v, a) \\ InterlockedExchange((LONG*)&(v), (LONG)(a)) #define ATOM_CMP(v, c, a) \\ (c == InterlockedCompareExchange((LONG*)&(v), (LONG)(a), (LONG)c)) /* 对于 InterlockedCompareExchange(v, c, a) 等价于下面 long tmp = v ; v == a ? v = c : ; return tmp; 咱们的 ATOM_FETCH_CMP(v, c, a) 等价于下面 long tmp = v ; v == c ? v = a : ; return tmp; */ #define ATOM_FETCH_CMP(v, c, a) \\ InterlockedCompareExchange((LONG*)&(v), (LONG)(a), (LONG)c) #define ATOM_LOCK(v) \\ while(ATOM_SET(v, 1)) \\ Sleep(0) #define ATOM_UNLOCK(v) \\ ATOM_SET(v, 0) //否则 如果是 gcc 编译器 #elif defined(__GNUC__) #include <unistd.h> /* type tmp = v ; v += a ; return tmp ; type 可以是 8,16,32,64 bit的类型 */ #define ATOM_FETCH_ADD(v, a) \\ __sync_fetch_add_add(&(v), (a)) /* v += a ; return v; */ #define ATOM_ADD_FETCH(v, a) \\ __sync_add_and_fetch(&(v), (a)) /* type tmp = v ; v = a; return tmp; */ #define ATOM_SET(v, a) \\ __sync_lock_test_and_set(&(v), (a)) /* bool b = v == c; b ? v=a : ; return b; */ #define ATOM_CMP(v, c, a) \\ __sync_bool_compare_and_swap(&(v), (c), (a)) /* type tmp = v ; v == c ? v = a : ; return v; */ #define ATOM_FETCH_CMP(v, c, a) \\ __sync_val_compare_and_swap(&(v), (c), (a)) /* 加锁等待,知道 ATOM_SET 返回合适的值 _INT_USLEEP 是操作系统等待纳秒数,可以优化,看具体操作系统 使用方式 int lock = 0; ATOM_LOCK(lock); //to do think ... ATOM_UNLOCK(lock); */ #define _INT_USLEEP (2) #define ATOM_LOCK(v) \\ while(ATOM_SET(v, 1)) \\ usleep(_INT_USLEEP) /* 对ATOM_LOCK 解锁, 当然 直接调用相当于 v = 0; */ #define ATOM_UNLOCK(v) \\ __sync_lock_release(&(v)) #endif // !_MSC_VER && !__GNUC__ #endif // !_H_SIMPLEC_SCATOM
schead.h 跨平台的基础头文件
#ifndef _H_SIMPLEC_SCHEAD #define _H_SIMPLEC_SCHEAD #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <errno.h> #include <string.h> #include <time.h> #include <stdint.h> #include <stddef.h> /* * 1.0 错误定义宏 用于判断返回值状态的状态码 _RF表示返回标志 * 使用举例 : int flag = scconf_get("pursue"); if(flag != _RT_OK) { sclog_error("get config %s error! flag = %d.", "pursue", flag); exit(EXIT_FAILURE); } * 这里是内部 使用的通用返回值 标志 */ #define _RT_OK (0) //结果正确的返回宏 #define _RT_EB (-1) //错误基类型,所有错误都可用它,在不清楚的情况下 #define _RT_EP (-2) //参数错误 #define _RT_EM (-3) //内存分配错误 #define _RT_EC (-4) //文件已经读取完毕或表示链接关闭 #define _RT_EF (-5) //文件打开失败 /* * 1.1 定义一些 通用的函数指针帮助,主要用于基库的封装中 * 有构造函数, 释放函数, 比较函数等 */ typedef void * (*pnew_f)(); typedef void (*vdel_f)(void* node); // icmp_f 最好 是 int cmp(const void* ln,const void* rn); 标准结构 typedef int (*icmp_f)(); /* * c 如果是空白字符返回 true, 否则返回false * c : 必须是 int 值,最好是 char 范围 */ #define sh_isspace(c) \\ ((c==\' \')||(c>=\'\\t\'&&c<=\'\\r\')) /* * 2.0 如果定义了 __GNUC__ 就假定是 使用gcc 编译器,为Linux平台 * 否则 认为是 Window 平台,不可否认宏是丑陋的 */ #if defined(__GNUC__) //下面是依赖 Linux 实现,等待毫秒数 #include <unistd.h> #include <sys/time.h> #define SLEEPMS(m) \\ usleep(m * 1000) // 屏幕清除宏, 依赖系统脚本 #define CONSOLE_CLEAR() \\ system("printf \'\\ec\'") #else // 这里创建等待函数 以毫秒为单位 , 需要依赖操作系统实现 #include <Windows.h> #include <direct.h> // 加载多余的头文件在 编译阶段会去掉 #define rmdir _rmdir #define CONSOLE_CLEAR() \\ system("cls") /** * Linux sys/time.h 中获取时间函数在Windows上一种移植实现 **tv : 返回结果包含秒数和微秒数 **tz : 包含的时区,在window上这个变量没有用不返回 ** : 默认返回0 **/ extern int gettimeofday(struct timeval* tv, void* tz); //为了解决 不通用功能 #define localtime_r(t, tm) localtime_s(tm, t) #define SLEEPMS(m) \\ Sleep(m) #endif // !__GNUC__ 跨平台的代码都很丑陋 //3.0 浮点数据判断宏帮助, __开头表示不希望你使用的宏 #define __DIFF(x, y) ((x)-(y)) //两个表达式做差宏 #define __IF_X(x, z) ((x)<z && (x)>-z) //判断宏,z必须是宏常量 #define EQ(x, y, c) EQ_ZERO(__DIFF(x,y), c) //判断x和y是否在误差范围内相等 //3.1 float判断定义的宏 #define _FLOAT_ZERO (0.000001f) //float 0的误差判断值 #define EQ_FLOAT_ZERO(x) __IF_X(x, _FLOAT_ZERO) //float 判断x是否为零是返回true #define EQ_FLOAT(x, y) EQ(x, y, _FLOAT_ZERO) //判断表达式x与y是否相等 //3.2 double判断定义的宏 #define _DOUBLE_ZERO (0.000000000001) //double 0误差判断值 #define EQ_DOUBLE_ZERO(x) __IF_X(x, _DOUBLE_ZERO) //double 判断x是否为零是返回true #define EQ_DOUBLE(x,y) EQ(x, y, _DOUBLE_ZERO) //判断表达式x与y是否相等 //4.0 控制台打印错误信息, fmt必须是双引号括起来的宏 #ifndef CERR #define CERR(fmt, ...) \\ fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\\n",\\ __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__) #endif // !CERR //4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量 #ifndef CERR_EXIT #define CERR_EXIT(fmt,...) \\ CERR(fmt, ##__VA_ARGS__),exit(EXIT_FAILURE) #endif // !CERR_EXIT //4.2 执行后检测,如果有错误直接退出 #ifndef IF_CHECK #define IF_CHECK(code) \\ if((code) < 0) \\ CERR_EXIT(#code) #endif // !IF_CHECK //5.0 获取数组长度,只能是数组类型或""字符串常量,后者包含\'\\0\' #ifndef LEN #define LEN(arr) \\ (sizeof(arr)/sizeof(*(arr))) #endif/* !ARRLEN */ //7.0 置空操作 #ifndef BZERO //v必须是个变量 #define BZERO(v) \\ memset(&v,0,sizeof(v)) #endif/* !BZERO */ //9.0 scanf 健壮的 #ifndef SAFETY_SCANF #define _STR_SAFETY_SCANF "Input error, please according to the prompt!" #define SAFETY_SCANF(scanf_code, ...) \\ while(printf(__VA_ARGS__), scanf_code){\\ while(\'\\n\' != getchar()) \\ ;\\ puts(_STR_SAFETY_SCANF);\\ }\\ while(\'\\n\' != getchar()) #endif /*!SAFETY_SCANF*/ //10.0 简单的time帮助宏 #ifndef TIME_PRINT #define _STR_TIME_PRINT "The current code block running time:%lf seconds\\n" #define TIME_PRINT(code) \\ do{\\ clock_t __st, __et;\\ __st=clock();\\ code\\ __et=clock();\\ printf(_STR_TIME_PRINT, (0.0 + __et - __st) / CLOCKS_PER_SEC);\\ } while(0) #endif // !TIME_PRINT /* * 10.1 这里是一个 在 DEBUG 模式下的测试宏 * * 用法 : * DEBUG_CODE({ * puts("debug start..."); * }); */ #ifndef DEBUG_CODE # ifdef _DEBUG # define DEBUG_CODE(code) code # else # define DEBUG_CODE(code) # endif // ! _DEBUG #endif // ! DEBUG_CODE //11.0 等待的宏 是个单线程没有加锁 #define _STR_PAUSEMSG "请按任意键继续. . ." extern void sh_pause(void); #ifndef INIT_PAUSE # ifdef _DEBUG # define INIT_PAUSE() atexit(sh_pause) # else # define INIT_PAUSE() /* 别说了,都重新开始吧 */ # endif #endif // !INIT_PAUSE //12.0 判断是大端序还是小端序,大端序返回true extern bool sh_isbig(void); /** * sh_free - 简单的释放内存函数,对free再封装了一下 **可以避免野指针 **pobj:指向待释放内存的指针(void*) **/ extern void sh_free(void ** pobj); /** * 获取 当前时间串,并塞入tstr中长度并返回 need tstr >= 20, 否则返回NULL ** 使用举例 char tstr[64]; sh_times(tstr, LEN(tstr)); puts(tstr); **tstr : 保存最后生成的最后串 **len : tstr数组的长度 ** : 返回tstr首地址 **/ extern int sh_times(char tstr[], int len); /* * 比较两个结构体栈上内容是否相等,相等返回true,不等返回false * a : 第一个结构体值 * b : 第二个结构体值 * : 相等返回true, 否则false */ #define STRUCTCMP(a, b) \\ (!memcmp(&a, &b, sizeof(a))) #endif// ! _H_SIMPLEC_SCHEAD
schead.c 跨平台的基础头文件实现文件
#include "schead.h" //简单通用的等待函数 void sh_pause(void) { rewind(stdin); printf(_STR_PAUSEMSG); getchar(); } //12.0 判断是大端序还是小端序,大端序返回true bool sh_isbig(void) { static union { unsigned short _s; unsigned char _c; } __u = { 1 }; return __u._c == 0; } /** * sh_free - 简单的释放内存函数,对free再封装了一下 **可以避免野指针 **@pobj:指向待释放内存的指针(void*) **/ void sh_free(void ** pobj) { if (pobj == NULL || *pobj == NULL) return; free(*pobj); *pobj = NULL; } #if defined(_MSC_VER) /** * Linux sys/time.h 中获取时间函数在Windows上一种移植实现 **tv : 返回结果包含秒数和微秒数 **tz : 包含的时区,在window上这个变量没有用不返回 ** : 默认返回0 **/ int gettimeofday(struct timeval * tv, void * tz) { time_t clock; struct tm tm; SYSTEMTIME wtm; GetLocalTime(&wtm); tm.tm_year = wtm.wYear - 1900; tm.tm_mon = wtm.wMonth - 1; //window的计数更好写 tm.tm_mday = wtm.wDay; tm.tm_hour = wtm.wHour; tm.tm_min = wtm.wMinute; tm.tm_sec = wtm.wSecond; tm.tm_isdst = -1; //不考虑夏令时 clock = mktime(&tm); tv->tv_sec = (long)clock; //32位使用,接口已经老了 tv->tv_usec = wtm.wMilliseconds * 1000; return _RT_OK; } #endif /** * 获取 当前时间串,并塞入tstr中C长度并返回 ** 使用举例 char tstr[64]; puts(gettimes(tstr, LEN(tstr))); **tstr : 保存最后生成的最后串 **len : tstr数组的长度 ** : 返回tstr首地址 **/ int sh_times(char tstr[], int len) { struct tm st; time_t t = time(NULL); localtime_r(&t, &st); return (int)strftime(tstr, len, "%F %X", &st); }
正文 - 最后的实现细节
在看之前推荐下载demo文件 .
linux 上 项目 http://files.cnblogs.com/files/life2refuel/sclog_linux.zip
window 上项目 http://files.cnblogs.com/files/life2refuel/sclog_window.zip
window 因为没有 pthread 库. 需要自己下载 pthread for window 处理. 编译的时候遇到问题再去处理.
我遇到的问题是 文件太老了, 删除一个无效的pthread宏, 结构 添加上面window 导入 lib的代码.
具体的下载安装可以参照下面博文, 翻到最最后面.
http://www.cnblogs.com/life2refuel/p/5135899.html
本文也是对于上面博文的日志库部分升级版.
前戏部分完毕, 进入核心层
先看需要的私有数据和私有函数
//错误重定向宏 具体应用 于 "mkdir -p \\"" _STR_SCLOG_PATH "\\" >" _STR_TOOUT " 2>" _STR_TOERR #define _STR_TOOUT "__out__" #define _STR_TOERR "__err__" #define _STR_LOGID "__lgd__" //保存logid,持久化 static struct { pthread_key_t key; //全局线程私有变量 pthread_once_t once; //全局初始化用的类型 unsigned logid; //默认的全局logid, 唯一标识 FILE * log; //log文件指针 FILE * wf; //wf文件指针 } _slmain = { (pthread_key_t)0, PTHREAD_ONCE_INIT, 0, NULL, NULL }; //内部简单的释放函数,服务于pthread_key_create 防止线程资源泄露 static void _slinfo_destroy(void* slinfo) { free(slinfo); } static void _gkey(void) { pthread_key_create(&_slmain.key, _slinfo_destroy); }
上面 私有静态全局变量我放入一个结构中保存. 简单封装吧. 后面两个函数是为了线程私有变量初始化,销毁
以上是关于C基础 多用户分级日志库 sclog的主要内容,如果未能解决你的问题,请参考以下文章