引言 - sclog 總的設計思路 sclog在之前已經內置到simplec 簡易c開發框架中一個日誌庫. 最近對其重新設計了一下. 減少了對外暴露的介面. 也是C開發中一個輪子. 比較簡單, 非常適合學習理解,最後自己寫一個自己喜歡的日誌庫. 首先分析分級設計的總的思路. 主要是圍繞上面思路設計. ...
引言 - 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_SCLOGView Code
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_SCATOMView Code
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_SCHEADView Code
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); }View Code
正文 - 最後的實現細節
在看之前推薦下載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); }
上面 私有靜態全局變數我放入一個結構中保存. 簡單封裝吧. 後面兩個函數是為了線程私有變數初始化,銷毀必須要的.
私有變數是線程''獨有的'' 具體需要在各自線程或進程中 初始化
/** * 線程的私有數據初始化 ** ** mod : 當前線程名稱 ** reqip : 請求的ip ** logid : 分配的唯一標識id, 預設0 ** return : _RT_OK 表示正常,_RF_EM記憶體分配錯誤 **/ int sl_init(const char mod[_INT_LITTLE], const char reqip[_INT_LITTLE], unsigned logid) { struct slinfo* pl; //保證 _gkey只被執行一次 pthread_once(&_slmain.once, _gkey); if((pl = pthread_getspecific(_slmain.key)) == NULL){ //重新構建 if ((pl = malloc(sizeof(struct slinfo))) == NULL) return _RT_EM; } gettimeofday(&pl->timev, NULL); //設置日誌logid, 有設置, 沒有預設原子自增 pl->logid = logid ? logid : ATOM_ADD_FETCH(_slmain.logid, 1); strncpy(pl->mod, mod, _INT_LITTLE); //複製一些數據 strncpy(pl->reqip, reqip, _INT_LITTLE); //設置私有變數 pthread_setspecific(_slmain.key, pl); return _RT_OK; }
其中logid = 0的時候是預設的, 系統分配一個給它. 否則的話表示設置的, 外部傳入的. 應用場景是.
進程A將這個請求處理完畢轉給進程B, 順帶把logid也傳過去.
後面是sclog 日誌庫的開啟和銷毀
/** * 日誌系統首次使用初始化,找對對映日誌文件路徑,創建指定路徑 **/ void sl_start(void) { FILE *lid; //單例只執行一次 if (NULL == _slmain.log) { //先多級創建目錄,簡易不藉助巨集實現跨平臺,system返回值是很複雜,預設成功! system("mkdir -p \"" _STR_SCLOG_DIR "\" >" _STR_TOOUT " 2>" _STR_TOERR); rmdir("-p"); remove(_STR_TOOUT); remove(_STR_TOERR); } if (NULL == _slmain.log) { _slmain.log = fopen(_STR_SCLOG_DIR "/" _STR_SCLOG_LOG, "a+"); if (NULL == _slmain.log) CERR_EXIT("__slmain.log fopen %s error!", _STR_SCLOG_LOG); } //繼續打開 wf 文件 if (NULL == _slmain.wf) { _slmain.wf = fopen(_STR_SCLOG_DIR "/" _STR_SCLOG_WFLOG, "a+"); if (!_slmain.wf) { fclose(_slmain.log); //其實這都沒有必要,圖個心安 CERR_EXIT("__slmain.log fopen %s error!", _STR_SCLOG_WFLOG);