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
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);
}

上面 私有静态全局变量我放入一个结构中保存. 简单封装吧. 后面两个函数是为了线程私有变量初始化,销毁

以上是关于C基础 多用户分级日志库 sclog的主要内容,如果未能解决你的问题,请参考以下文章

glog

C零基础视频-32-指针铺垫知识:计算机分级结构

道路视频摄像机智能分析功能及分级要求

需求分析

VRP系统——路由器配置之信息中心基础

测试基础bug分级测试流程ISO9126质量模型