編寫一個可跨平臺的Logger模塊

    編寫項目時,不可避免要使用到日誌模塊,有時候還是十分有幫助的,用過的比較好的有log4cpp和其他一些,不多做介紹。這裏也只是和大家分享以及記錄自己編寫的一個小的實用log模塊而已。

// ccompat.h


#ifndef _CCOMPAT_H
#define _CCOMPAT_H

// Variable length arrays.
// VLA(type, name, size) allocates a variable length array with automatic
// storage duration. VLA_SIZE(name) evaluates to the runtime size of that array
// in bytes.
//
// If C99 VLAs are not available, an emulation using alloca (stack allocation
// "function") is used. Note the semantic difference: alloca'd memory does not
// get freed at the end of the declaration's scope. Do not use VLA() in loops or
// you may run out of stack space.
#if !defined(_MSC_VER) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
// C99 VLAs.
#define VLA(type, name, size) type name[size]
#define SIZEOF_VLA sizeof
#else

// Emulation using alloca.
#ifdef _WIN32
#include <malloc.h>
#elif defined(__linux__)
#include <alloca.h>
#else
#include <stdlib.h>
#if !defined(alloca) && defined(__GNUC__)
#define alloca __builtin_alloca
#endif
#endif

#define VLA(type, name, size)                           \
  const size_t name##_size = (size) * sizeof(type);     \
  type *const name = (type *)alloca(name##_size)
#define SIZEOF_VLA(name) name##_size

#endif

#if !defined(__cplusplus) || __cplusplus < 201103L
#define nullptr NULL
#endif

#ifdef __GNUC__
#define GNU_PRINTF(f, a) __attribute__((__format__(__printf__, f, a)))
#else
#define GNU_PRINTF(f, a)
#endif

#ifndef UNUSED
#define UNUSED(context) (void*)0
#endif

#if !defined(__cplusplus)
#define true  1
#define false 0
#endif



#endif // _CCOMPAT_H

 

// logger.h

#ifndef _LOGGER_H
#define _LOGGER_H

#include <stdint.h>

#include "ccompat.h"
#include <stdio.h>
#include <time.h>
#include <string.h>

#ifndef MIN_LOGGER_LEVEL
#define MIN_LOGGER_LEVEL LOGGER_LEVEL_INFO
#endif

// NOTE: Don't forget to update build system files after modifying the enum.
typedef enum Logger_Level {
    LOGGER_LEVEL_TRACE,
    LOGGER_LEVEL_DEBUG,
    LOGGER_LEVEL_INFO,
    LOGGER_LEVEL_WARNING,
    LOGGER_LEVEL_ERROR
} Logger_Level;
typedef struct Logger Logger;


#define LOGGER_NO_OUT  (0)      /* do not print logger message*/
#define LOGGER_STDOUT  (1)      /* print message to stdout*/
#define LOGGER_TO_FILE (2)      /* save logger message to logger file*/

extern int logger_mode;

typedef void logger_cb(void *context, Logger_Level level, const char *file, int line,
                       const char *func, const char *message, void *userdata);


/**
  * Sets logger output mode.
  */
void set_logger_mode(int mode);

/**
 * Creates a new logger with logging disabled (callback is NULL) by default.
 */
Logger *logger_new(void);

/**
 * Frees all resources associated with the logger.
 */
void logger_kill(Logger *log);

/**
 * Sets the logger callback. Disables logging if set to NULL.
 * The context parameter is passed to the callback as first argument.
 */
void logger_callback_log(Logger *log, logger_cb *function, void *context, void *userdata);

/**
 * Main write function. If logging is disabled, this does nothing.
 *
 * If the logger is NULL, this writes to stderr. This behaviour should not be
 * used in production code, but can be useful for temporarily debugging a
 * function that does not have a logger available. It's essentially
 * fprintf(stderr, ...), but with timestamps and source location. Toxcore must
 * be built with -DUSE_STDERR_LOGGER for this to work. It will cause an
 * assertion failure otherwise.
 */
void logger_write(
    const Logger *log, Logger_Level level, const char *file, int line, const char *func,
    const char *format, ...) GNU_PRINTF(6, 7);


static inline char* get_cur_sys_time() {
    time_t timer;
    struct tm *tblock;
    char* time_tmp;
    timer = time(NULL);
    tblock = localtime(&timer);
    time_tmp = asctime(tblock);
    time_tmp[strlen(time_tmp)-1] = '\0';
    return time_tmp;
}


#define c_debug(fmt,...) \
    do { \
        if( int(logger_mode) ){\
            printf("[DATE: %s ] [ FILE: %s ] [ LINE: %4d ] [ FUNC: %s ] :"fmt"",get_cur_sys_time(),__FILE__,__LINE__,__FUNCTION__,##__VA_ARGS__);   \
        }                   \
    }while(0)

#define LOGGER_WRITE(log, level, ...) \
    do { \
        if (level >= MIN_LOGGER_LEVEL) { \
            logger_write(log, level, __FILE__, __LINE__, __func__, __VA_ARGS__); \
        } \
    } while (0)



/* To log with an logger */
#define LOGGER_TRACE(log, ...)   LOGGER_WRITE(log, LOGGER_LEVEL_TRACE  , __VA_ARGS__)
#define LOGGER_DEBUG(log, ...)   LOGGER_WRITE(log, LOGGER_LEVEL_DEBUG  , __VA_ARGS__)
#define LOGGER_INFO(log, ...)    LOGGER_WRITE(log, LOGGER_LEVEL_INFO   , __VA_ARGS__)
#define LOGGER_WARNING(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_WARNING, __VA_ARGS__)
#define LOGGER_ERROR(log, ...)   LOGGER_WRITE(log, LOGGER_LEVEL_ERROR  , __VA_ARGS__)

#define LOGGER_FATAL(log, ...) \
    do { \
        LOGGER_ERROR(log, __VA_ARGS__); \
        abort(); \
    } while(0)

#define LOGGER_ASSERT(log, cond, ...) \
    do { \
        if (!(cond)) { \
            LOGGER_ERROR(log, "Assertion failed"); \
            LOGGER_FATAL(log, __VA_ARGS__); \
        } \
    } while(0)

#endif // _LOGGER_H
// logger.cpp


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "logger.h"

#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(__WIN32)
#include <windows.h>
#include <pthread.h>
#else
#include <pthread.h>
#endif

struct Logger {
    logger_cb *callback;
    void *context;
    void *userdata;
};
extern int logger_mode = LOGGER_NO_OUT;

#ifdef USE_STDERR_LOGGER
static const char *logger_level_name(Logger_Level level)
{
    switch (level) {
    case LOGGER_LEVEL_TRACE:
        return "TRACE";

    case LOGGER_LEVEL_DEBUG:
        return "DEBUG";

    case LOGGER_LEVEL_INFO:
        return "INFO";

    case LOGGER_LEVEL_WARNING:
        return "WARNING";

    case LOGGER_LEVEL_ERROR:
        return "ERROR";
    }

    return "<unknown>";
}

static void logger_stderr_handler(void *context, Logger_Level level, const char *file, int line, const char *func,
                                  const char *message, void *userdata)
{
    // GL stands for "global logger".
    fprintf(stderr, "[GL] %s %s:%d(%s): %s\n", logger_level_name(level), file, line, func, message);
}

static const Logger logger_stderr = {
    logger_stderr_handler,
    nullptr,
    nullptr,
};
#endif

/**
 * Public Functions
 */

void set_logger_mode(int mode){
    logger_mode = mode;
}

Logger *logger_new(void)
{
    return (Logger *)calloc(1, sizeof(Logger));
}

void logger_kill(Logger *log)
{
    free(log);
}

void logger_callback_log(Logger *log, logger_cb *function, void *context, void *userdata)
{
    log->callback = function;
    log->context  = context;
    log->userdata = userdata;
}


/**
 * @brief logger_save_to_file
 * @param arg
 * @return
 */
#if !defined(__WIN32)
void* logger_save_to_file(void* arg){
#else
DWORD WINAPI logger_save_to_file(LPVOID arg){
#endif
    char logger_text[1024*2]={0};
    printf("[LPVOID arg in thread]:%s\n",(char*)arg);
    strcat(logger_text,(char*)arg);
    FILE * log_file= fopen("./log.txt","a+");
    if(log_file == nullptr) {
        fprintf(stderr, "open logger file failed.\n");
        return NULL;
    }
    fwrite(logger_text,strlen(logger_text),1,log_file);

    fclose(log_file);
    return 0;
}

void logger_write(const Logger *log, Logger_Level level, const char *file, int line, const char *func,
                  const char *format, ...)
{
    if (!log) {
#ifdef USE_STDERR_LOGGER
        log = &logger_stderr;
#else
        fprintf(stderr, "NULL logger not permitted.\n");
        abort();
#endif
    }

    if (!log->callback) {
        printf("callback is nullptr. error occured.");
        return;
    }

    // Only pass the file name, not the entire file path, for privacy reasons.
    // The full path may contain PII of the person compiling toxcore (their
    // username and directory layout).
    const char *filename = strrchr(file, '/');
    file = filename ? filename + 1 : file;

#if defined(_WIN32) || defined(__CYGWIN__)
    // On Windows, the path separator *may* be a backslash, so we look for that
    // one too.
    const char *windows_filename = strrchr(file, '\\');
    file = windows_filename ? windows_filename + 1 : file;
#endif
    // Format message
    char msg[1024]="";
    int size = 0;

    va_list args;
    va_start(args, format);
    size = vsnprintf(msg, sizeof(msg), format, args);
    va_end(args);

    log->callback(log->context, level, file, line, func, msg, log->userdata);


    if(logger_mode != LOGGER_TO_FILE) return;
    // save message to logger file ==>it's bad idea here when save too many message in files.
    char logger_text[1024*2]={0};
    sprintf(logger_text,"[Date: %s ][ %s : %d ][ %s ]: %s\n",get_cur_sys_time(),__FILE__,__LINE__,__func__,msg);

#if defined(__WIN32)
    HANDLE hThread;
    DWORD  threadId;
    hThread = CreateThread(NULL, 0,	logger_save_to_file,(LPVOID)logger_text, 0, &threadId);
    hThread == NULL ? printf("[Thread error]%s\n",stderr): printf("%d\n",GetCurrentThreadId());
    Sleep(1);
#else //linux
    pthread_t tid;
    int tret = pthread_create(&tid, NULL, logger_save_to_file, logger_text);
    if (tret != 0){
        printf("create pthread failed info: %s", strerror(tret));
        return tret;
    }

    sleep(1);
    int ret = pthread_join(tid, NULL);
    if (ret != 0){
        printf("pthread_join failed info: %s\n", strerror(ret));
        return ret;
    }
#endif
}




 

logger模塊功能測試:

#include <iostream>
#include "ccompat.h"
#include "logger.h"
using namespace std;

typedef struct user_data{
    char* username;
    int age;
}*pud;

void logger_cb1(void *context, Logger_Level level, const char *file, int line,
                const char *func, const char *message, void *userdata){

    user_data* tmp = (user_data*)userdata;
    c_debug("[context]= %s "\
            "[file]= %s "\
            "[function]= %s "\
            "[line]= %d "\
            "[message]= %s "\
            "[userdata.username]= %s "\
            "[userdata.age]= %d \n",(char*)context,file,func,line,message,tmp->username,tmp->age);

    // dosomething here
    // ...
}


void* test_func(void* context){
    UNUSED(context);
    printf("ssss\n");

    return UNUSED();
}


void test_logger(Logger* log){

    char* context = "logger_callback_log_test";
    test_func((void*)context);

    VLA(user_data,userdata,1);// userdata 爲user_data* 類型
    printf("%d \n",SIZEOF_VLA(userdata)); // 對齊原則

    userdata->username = "wangxiao";
    userdata->age = 10;

    logger_callback_log(log,logger_cb1,(void*)context,(void*)userdata); // bind logger callback function
    logger_write(log,LOGGER_LEVEL_DEBUG,"./log.txt",__LINE__,"%s","test logger_write to file when callback");

    LOGGER_WRITE(log,LOGGER_LEVEL_INFO,"she is so beautiful");

    LOGGER_FATAL(log,"error 0001");
    LOGGER_ASSERT(log,false,"");
}


int main()
{
    // set logger mode
    set_logger_mode(LOGGER_TO_FILE);

    c_debug("%s %s\n","this is"," MyDebug test.");

    // test logfunction
    Logger* log = logger_new();
    test_logger(log);
    logger_kill(log);

    return 0;
}


打印輸出:

start when: Tue Apr 21 21:41:10 2020

[DATE: Tue Apr 21 21:41:10 2020 ] [ FILE: D:\QtCode\build-ThreadTest-unknown-Debug\MyDebug\main.cpp ] [ LINE: 63 ] [ FUNC: main ] :this is MyDebug test.

ssss

8

[DATE: Tue Apr 21 21:41:10 2020 ] [ FILE: D:\QtCode\build-ThreadTest-unknown-Debug\MyDebug\main.cpp ] [ LINE: 21 ] [ FUNC: logger_cb1 ] :[context]= logger_callback_log_test [file]= log.txt [function]= %s [line]= 48 [message]= test logger_write to file when callback [userdata.username]= wangxiao [userdata.age]= 10

5784

[LPVOID arg in thread]:[Date: Tue Apr 21 21:41:10 2020 ][ D:\QtCode\build-ThreadTest-unknown-Debug\MyDebug\logger.cpp : 159 ][ logger_write ]: test logger_write to file when callback

 

[DATE: Tue Apr 21 21:41:10 2020 ] [ FILE: D:\QtCode\build-ThreadTest-unknown-Debug\MyDebug\main.cpp ] [ LINE: 21 ] [ FUNC: logger_cb1 ] :[context]= logger_callback_log_test [file]= main.cpp [function]= test_logger [line]= 50 [message]= she is so beautiful [userdata.username]= wangxiao [userdata.age]= 10

5784

[LPVOID arg in thread]:[Date: Tue Apr 21 21:41:10 2020 ][ D:\QtCode\build-ThreadTest-unknown-Debug\MyDebug\logger.cpp : 159 ][ logger_write ]: she is so beautiful

 

[DATE: Tue Apr 21 21:41:10 2020 ] [ FILE: D:\QtCode\build-ThreadTest-unknown-Debug\MyDebug\main.cpp ] [ LINE: 21 ] [ FUNC: logger_cb1 ] :[context]= logger_callback_log_test [file]= main.cpp [function]= test_logger [line]= 52 [message]= error 0001 [userdata.username]= wangxiao [userdata.age]= 10

5784

[LPVOID arg in thread]:[Date: Tue Apr 21 21:41:10 2020 ][ D:\QtCode\build-ThreadTest-unknown-Debug\MyDebug\logger.cpp : 159 ][ logger_write ]: error 0001

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章