zoukankan      html  css  js  c++  java
  • 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 "
    ", __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>='	'&&c<='
    '))
    
    /*
     *    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 "
    ",
             __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 获取数组长度,只能是数组类型或""字符串常量,后者包含''
    #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('
    ' != getchar()) 
                ;
            puts(_STR_SAFETY_SCANF);
        }
        while('
    ' != getchar())
    #endif /*!SAFETY_SCANF*/
    
    //10.0 简单的time帮助宏
    #ifndef TIME_PRINT
    #define _STR_TIME_PRINT "The current code block running time:%lf seconds
    "
    #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);
            }
        }
    
        //读取文件内容,读取文件内容,持久化
        if ((lid = fopen(_STR_LOGID, "r")) != NULL)
            fscanf(lid, "%u", &_slmain.logid);
    
        //简单判断是否有初始化的必要
        if (_slmain.log && _slmain.wf) {
            //这里可以单独开启一个线程或进程,处理日志整理但是 这个模块可以让运维做,按照规则搞
            sl_init("main thread", "0.0.0.0", 0);
    
            //注册退出操作
            atexit(_sl_end);
        }
    }

    主要打开文件对象, 注册退出清理操作.

    /**
    *    日志关闭时候执行,这个接口,关闭打开的文件句柄
    **/
    static void _sl_end(void)
    {
        FILE* lid;
        void* pl;
    
        // 在简单地方多做安全操作值得,在核心地方用算法优化的才能稳固
        if (NULL == _slmain.log)
            return;
    
        //重置当前系统打开文件结构体
        fclose(_slmain.log);
        fclose(_slmain.wf);
    
        //写入文件
        lid = fopen(_STR_LOGID, "w");
        if (NULL != lid) {
            fprintf(lid, "%u", _slmain.logid);
            fclose(lid);
        }
    
        BZERO(_slmain);
    
        //主动释放私有变量,其实主进程 相当于一个线程是不合理的!还是不同的生存周期的
        pl = pthread_getspecific(_slmain.key);
        _slinfo_destroy(pl);
        pthread_setspecific(_slmain.key, NULL);
    }

    清理主要清楚句柄, 私有变量等. 后面 是文本输出操作.

    int
    sl_printf(const char* format, ...)
    {
        int len;
        va_list ap;
        char logs[_INT_LOG]; //这个不是一个好的设计,最新c 中支持 int a[n];
        char tstr[_INT_LITTLE];// [%s] => [2016-01-08 23:59:59]
    
        if (NULL == _slmain.log) {
            CERR("%s fopen %s | %s error!",_STR_SCLOG_DIR, _STR_SCLOG_LOG, _STR_SCLOG_WFLOG);
            return _RT_EF;
        }
    
        //初始化时间参数
        sh_times(tstr, _INT_LITTLE);
        len = snprintf(logs, LEN(logs), "[%s %s]", tstr, _sl_gettimes());
        va_start(ap, format);
        vsnprintf(logs + len, LEN(logs) - len, format, ap);
        va_end(ap);
    
        // 写普通文件 log
        fputs(logs, _slmain.log); //把锁机制去掉了,fputs就是线程安全的
    
        // 写警告文件 wf
        if (format[1] == 'F' || format[1] == 'W') //当为FATAL或WARNING需要些写入到警告文件中
            fputs(logs, _slmain.wf);
    
        return _RT_OK;
    }

     很实在 中间 打印了 [%s, %s] 操作. [当前时间, 经过的时间]. 这里使用的一个技巧是. 用空间换时间.

    char logs[_INT_LOG]; 声明了8k的栈空间保存数据.

    sclog.c 实现文件 

    #include "sclog.h"
    #include "scatom.h"
    #include "pthread.h"
    #include <stdarg.h>
    
    //-------------------------------------------------------------------------------------------|
    // 第二部分 对日志信息体操作的get和set,这里隐藏了信息体的实现
    //-------------------------------------------------------------------------------------------|
    
    //错误重定向宏 具体应用 于 "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);
    }
    
    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
    };
    
    /**
    *    线程的私有数据初始化
    **
    ** 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
    **/
    unsigned 
    sl_getlogid(void) {
        struct slinfo* pl = pthread_getspecific(_slmain.key);
        if (NULL == pl) //返回0表示没有找见
            return 0u;
        return pl->logid;
    }
    
    /**
    *    获取日志信息体的请求ip串,返回NULL表示没有初始化
    **/
    const char* 
    sl_getreqip(void) {
        struct slinfo* pl = pthread_getspecific(_slmain.key);
        if (NULL == pl) //返回NULL表示没有找见
            return NULL;
        return pl->reqip;
    }
    
    /**
    *    获取日志信息体的名称,返回NULL表示没有初始化
    **/
    const char* 
    sl_getmod(void)
    {
        struct slinfo* pl = pthread_getspecific(_slmain.key);
        if (NULL == pl) //返回NULL表示没有找见
            return NULL;
        return pl->mod;
    }
    
    
    //-------------------------------------------------------------------------------------------|
    // 第三部分 对日志系统具体的输出输入接口部分
    //-------------------------------------------------------------------------------------------|
    
    /**
    *    日志关闭时候执行,这个接口,关闭打开的文件句柄
    **/
    static void _sl_end(void)
    {
        FILE* lid;
        void* pl;
    
        // 在简单地方多做安全操作值得,在核心地方用算法优化的才能稳固
        if (NULL == _slmain.log)
            return;
    
        //重置当前系统打开文件结构体
        fclose(_slmain.log);
        fclose(_slmain.wf);
    
        //写入文件
        lid = fopen(_STR_LOGID, "w");
        if (NULL != lid) {
            fprintf(lid, "%u", _slmain.logid);
            fclose(lid);
        }
    
        BZERO(_slmain);
    
        //主动释放私有变量,其实主进程 相当于一个线程是不合理的!还是不同的生存周期的
        pl = pthread_getspecific(_slmain.key);
        _slinfo_destroy(pl);
        pthread_setspecific(_slmain.key, NULL);
    }
    
    /**
    *    日志系统首次使用初始化,找对对映日志文件路径,创建指定路径
    **/
    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);
            }
        }
    
        //读取文件内容,读取文件内容,持久化
        if ((lid = fopen(_STR_LOGID, "r")) != NULL)
            fscanf(lid, "%u", &_slmain.logid);
    
        //简单判断是否有初始化的必要
        if (_slmain.log && _slmain.wf) {
            //这里可以单独开启一个线程或进程,处理日志整理但是 这个模块可以让运维做,按照规则搞
            sl_init("main thread", "0.0.0.0", 0);
    
            //注册退出操作
            atexit(_sl_end);
        }
    }
    
    /**
    *    获取日志信息体的时间串,返回NULL表示没有初始化
    **/
    static const char* _sl_gettimes(void) {
        struct timeval et; //记录时间
        unsigned td;
    
        struct slinfo* pl = pthread_getspecific(_slmain.key);
        if (NULL == pl) //返回NULL表示没有找见
            return NULL;
    
        gettimeofday(&et, NULL);
        //同一用微秒记
        td = 1000000 * (et.tv_sec - pl->timev.tv_sec) + et.tv_usec - pl->timev.tv_usec;
        snprintf(pl->times, LEN(pl->times), "%u", td);
    
        return pl->times;
    }
    
    int
    sl_printf(const char* format, ...)
    {
        int len;
        va_list ap;
        char logs[_INT_LOG]; //这个不是一个好的设计,最新c 中支持 int a[n];
        char tstr[_INT_LITTLE];// [%s] => [2016-01-08 23:59:59]
    
        if (NULL == _slmain.log) {
            CERR("%s fopen %s | %s error!",_STR_SCLOG_DIR, _STR_SCLOG_LOG, _STR_SCLOG_WFLOG);
            return _RT_EF;
        }
    
        //初始化时间参数
        sh_times(tstr, _INT_LITTLE);
        len = snprintf(logs, LEN(logs), "[%s %s]", tstr, _sl_gettimes());
        va_start(ap, format);
        vsnprintf(logs + len, LEN(logs) - len, format, ap);
        va_end(ap);
    
        // 写普通文件 log
        fputs(logs, _slmain.log); //把锁机制去掉了,fputs就是线程安全的
    
        // 写警告文件 wf
        if (format[1] == 'F' || format[1] == 'W') //当为FATAL或WARNING需要些写入到警告文件中
            fputs(logs, _slmain.wf);
    
        return _RT_OK;
    }
    View Code

    到这里基本设计都已经完成了.

     我们最后来个测试demo main.c

    #include "sclog.h"
    #include "pthread.h"
    
    static void * _test_one(void * arg) {
        sl_init("test_one", "8.8.8.8", 0);
        SL_TRACE("test_one log test start!");
        for (int i = 0; i < 100; ++i) {
            SL_FATAL("pthread test one fatal is at %d, It's %s.", i, "OK");
            SL_WARNING("pthread test one warning is at %d, It's %s.", i, "OK");
            SL_INFO("pthread test one info is at %d, It's %s.", i, "OK");
            SL_DEBUG("pthread test one debug is at %d, It's %s.", i, "OK");
            SLEEPMS(1); //等待1s
        }
        SL_TRACE("test_one log test end!");
        return NULL;
    }
    
    // 线程二测试函数
    static void * _test_two(void * arg) {
        //线程分离,自回收
        pthread_detach(pthread_self());
        sl_init("test_two", "8.8.8.8", 0);
        SL_TRACE("test_two log test start!");
        for (int i = 0; i < 3; ++i) {
            SL_FATAL("pthread test two fatal is at %d, It's %s.", i, "OK");
            SL_WARNING("pthread test two warning is at %d, It's %s.", i, "OK");
            SL_INFO("pthread test two info is at %d, It's %s.", i, "OK");
            SL_DEBUG("pthread test two debug is at %d, It's %s.", i, "OK");
            SLEEPMS(2); //等待1s
        }
        SL_TRACE("test_two SL_TRACE test end!");
        return NULL;
    }
    
    int main(int argc, char * argv[]) {
        pthread_t tone, ttwo;
    
        //注册等待函数
        INIT_PAUSE();
    
        sl_start();
        SL_NOTICE("main log test start!");
    
        pthread_create(&tone, NULL, _test_one, NULL);
        pthread_create(&ttwo, NULL, _test_two, NULL);
    
        pthread_join(tone, NULL);
    
        SL_NOTICE("main log test end!");
    
        return 0;
    }

    linux 上编译 Makefile 如下

    CC = gcc
    DEBUG = -Wall -ggdb2
    LIB = -lpthread
    RUN = $(CC) $(DEBUG) -o $@ $^
    RUO = $(CC) -c -o $@ $^
    
    # create main.out run file
    main.out:main.o sclog.o schead.o
        $(RUN) $(LIB)
    
    # create object file
    %.o:%.c
        $(RUO)
    
    # clean file
    clean:
        rm -rf *.o *.i *.s *.out *~ __* logs ; ls -la

     最终测试结果

    一切正常. 这里没有附加 -I_DEBUG, 默认采用Release发布版所以没有DEBUG日志.

    window测试结果也很正常. 到这里基本就结束了.

    后记

      这是一个sclog小日志库升级的介绍报文, 错误是难免的欢迎指正. C/C++ 写多了, 越发觉得上层语言的可贵. 希望它们蓬勃发展, 将老的低效的都顶下来.

    这种开发, 坑太多, 青黄不接, 生产率低下, 闷!

      

  • 相关阅读:
    Python多版本共存
    Windows下安装glumy过程
    安卓手机刷机
    动态规划学习
    Linux下载源
    背包问题扩展
    mongol学习
    云爬虫测试
    arc的安装
    Centos
  • 原文地址:https://www.cnblogs.com/life2refuel/p/5597170.html
Copyright © 2011-2022 走看看