C基礎 多用戶分級日誌庫 sclog

来源:http://www.cnblogs.com/life2refuel/archive/2016/06/19/5597170.html
-Advertisement-
Play Games

引言 - 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_SCLOG
View 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_SCATOM
View 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_SCHEAD
View 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);
    

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 第一部分 瞭解C# C#是微軟公司在2000年7月發佈的一種全新且簡單、安全、面向對象的程式設計語言,是專門為.NET的應用而開發的。體現了當今最新的程式設計技術的功能和精華。.NET框架為C#提供了一個強大的、易用的、邏輯結構一致的設計環境。其特點: 語言簡潔 保留了C++的強大功能; 快速應用開 ...
  • 普通上傳 view: Controller: Ajax上傳 用普通的ajax提交表單的時候,不能把文件流傳到後端去,所以要用到jquery.form.js jquery.form.js到官網下載或者從這裡下載:http://pan.baidu.com/s/1c2JS60C view: Control ...
  • 原文: "Controller methods and views" 作者: "Rick Anderson" 翻譯: "謝煬(Kiler)" 校對: "孟帥洋(書緣)" 、 "張仁建(第二年.夏)" 、 "許登洋(Seay)" 、 "姚阿勇(Dr.Yao)" 、 "婁宇(Lyrics)" 我們已經初 ...
  • 無錫一個2線城市,本地人,今年29了~自身情況老婆快生小孩啦~ 當前公司技術部搬去上海了~我由於家庭情況不能去上海,領導留我在無錫繼續做~但身邊一個人交流不方便便有了離職想法。 主要想找個公司長期發展,不想換來換去,現在有2家公司都發了off. A公司,9.5K,離家進一點走過去10分鐘,大小休(2 ...
  • 昨天Insus.NET有實現《使用ViewModel來實現多個Model傳送至視圖》http://www.cnblogs.com/insus/p/5594134.html 那今天Insus.NET想使用另一種方法來實現這個傳遞多個model至視圖中去。ExpandoObject動態對象。一些數據還是 ...
  • 我們在開發微信相關的應用的時候,一般需要完善的基礎模塊支持,包括微信公眾號,微信企業號,以及一些業務模塊的支持,一般隨著功能的增多,我們需要非常清晰的界定他們的關係。模塊的分拆以及合併往往需要考慮的代碼的重用,而且儘量做到簡單而不重覆。本篇隨筆基於我的微信框架的各個模塊的功能介紹以及他們關係的描述。 ...
  • 分類:C#、VS2015 創建日期:2016-06-18 使用教材:(十二五國家級規劃教材)《C#程式設計及應用教程》(第3版) 一、使用別人已經設計好的類簡化你的代碼編寫工作量 當讓你去處理一堆亂七八糟的事情時,要把它弄的有條理,首先需要“先把它分成幾大部分”(劃分為不同的“命名空間”或者“包”) ...
  • 本文內容全部出自《Python基礎教程》第二版,在此分享自己的學習之路。 lxx___歡迎轉載:http://www.cnblogs.com/Marlowes/p/5538341.htmllxx___ Created on Marlowes 本章將會給讀者展示一些例子,這些例子會使用多種Python ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...