编写项目时,不可避免要使用到日志模块,有时候还是十分有帮助的,用过的比较好的有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