IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C/C++的日志系统设计 -> 正文阅读

[C++知识库]C/C++的日志系统设计

前言:

日志系统在程序运行中有着非常大的作用,用于记录程序的运行情况,在程序出错后查看日志,方便地定位出错的大概范围。
在设计日志系统之前,先考虑一下日志需要输出什么信息呢?什么信息才是有用的信息,都知道写日志是一种对文件的io操作,所以尽可能避免输出没用的信息。
有用的信息:关键变量的值、运行的位置(哪个文件、哪个函数、哪一行)、时间、线程号、进程号等等。

一、日志系统的设计

1、日志的级别
在测试、调试、交付等场景需要输出不同的级别日志。

//常见的日志级别
enum LOGLEVEL
{
	LOG_LEVEL_NONE,
	LOG_LEVEL_ERROR,     // error
	LOG_LEVEL_WARNING,   // warning
	LOG_LEVEL_DEBUG,     // debug
	LOG_LEVEL_INFO,      // info	
};

2、日志的输出地
日志输出的地方可能不同,终端、控制台、UI界面、文件等等都有。

enum LOGTARGET
{
	LOG_TERM      = 0x00,
	LOG_FILE      = 0x01,
	LOG_UI        = 0x10
};

3、日志的作用域
日志做到什么时候都可以输出,可作用于全程序文件,考虑到多线程情况下,必须保证日志的输出需要得到线程安全的保障,所以需要一个全局且唯一的日志器
使用设计模式中的单例模式-----日志器
单例模式在上一节中讲过,可使用现代单例模式写法。

二、C++版本的日志系统的实现

Logger.h

/** 
 * 日志类头文件, Logger.h
 *  2022.02.1
 **/
 
#ifndef __LOGGER_H__
#define __LOGGER_H__
 
#include <string>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <list>
 
//struct FILE;
 
#define LogInfo(...)        Logger::GetInstance().AddToQueue("INFO", __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__)
#define LogWarning(...)     Logger::GetInstance().AddToQueue("WARNING", __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__)
#define LogError(...)       Logger::GetInstance().AddToQueue("ERROR", __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__)
 
class Logger
{
public:
    static Logger& GetInstance();
 
    void SetFileName(const char* filename);
    bool Start();
    void Stop();
 
    void AddToQueue(const char* pszLevel, const char* pszFile, int lineNo, const char* pszFuncSig, char* pszFmt, ...);
 
private:
    Logger() = default;
    Logger(const Logger& rhs) = delete;
    Logger& operator =(Logger& rhs) = delete;
 
    void threadfunc();
 
private:
    std::string                     filename_;
    FILE*                           fp_{};
    std::shared_ptr<std::thread>    spthread_;
    std::mutex                      mutex_;
    std::condition_variable         cv_;            //有新的日志到来的标识
    bool                            exit_{false};
    std::list<std::string>          queue_;
};
 
#endif //!__LOGGER_H__

Logger.cpp

/**
 * 日志类实现文件, Logger.cpp
 *  2022.02.1
 **/
 
#include "Logger.h"
#include <time.h>
#include <stdio.h>
#include <memory>
#include <stdarg.h>
 
Logger& Logger::GetInstance()
{
    static Logger logger;
    return logger;
}
 
void Logger::SetFileName(const char* filename)
{
    filename_ = filename;
}
 
bool Logger::Start()
{
    if (filename_.empty())
    {
        time_t now = time(NULL);
        struct tm* t = localtime(&now);
        char timestr[64] = { 0 };
        sprintf(timestr, "%04d%02d%02d%02d%02d%02d.imserver.log", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
        filename_ = timestr;
    }
 
    fp_ = fopen(filename_.c_str(), "wt+");
    if (fp_ == NULL)
        return false;
 
    spthread_.reset(new std::thread(std::bind(&Logger::threadfunc, this)));
 
    return true;
}
 
void Logger::Stop()
{
    exit_ = true;
    cv_.notify_one();
 
    //等待时间线程结束
    spthread_->join();
}
 
void Logger::AddToQueue(const char* pszLevel, const char* pszFile, int lineNo, const char* pszFuncSig, char* pszFmt, ...)
{
    char msg[256] = { 0 };
 
    va_list vArgList;                            
    va_start(vArgList, pszFmt);
    vsnprintf(msg, 256, pszFmt, vArgList);
    va_end(vArgList);
 
    time_t now = time(NULL);
    struct tm* tmstr = localtime(&now);
    char content[512] = { 0 };
    sprintf(content, "[%04d-%02d-%02d %02d:%02d:%02d][%s][0x%04x][%s:%d %s]%s\n",
                tmstr->tm_year + 1900,
                tmstr->tm_mon + 1,
                tmstr->tm_mday,
                tmstr->tm_hour,
                tmstr->tm_min,
                tmstr->tm_sec,
                pszLevel,
                std::this_thread::get_id(),
                pszFile,
                lineNo,
                pszFuncSig,
                msg);
 
    {
        std::lock_guard<std::mutex> guard(mutex_);
        queue_.emplace_back(content);
    }
 
    cv_.notify_one();
}
 
void Logger::threadfunc()
{
    if (fp_ == NULL)
        return;
 
    while (!exit_)
    {
        //写日志
        std::unique_lock<std::mutex> guard(mutex_);
        while (queue_.empty())
        {
            if (exit_)
                return;
 
            cv_.wait(guard);
        }
 
        //写日志
        const std::string& str = queue_.front();
 
        fwrite((void*)str.c_str(), str.length(), 1, fp_);
        fflush(fp_);
        queue_.pop_front();
    }
}

三、C版本的日志系统的实现

#ifndef _LOG_H_
#define _LOG_H_

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <time.h>

//可自定义级别日志输出
#define LOGC(type, format, ...) logC(__func__, __FILE__, __LINE__, type, format, ##__VA_ARGS__)
/*调试日志宏定义*/
#define DEBUG_PRINT 0
#define LOG_PRINT(fmt, ...) do{\
	if(DEBUG_PRINT)\
	{\
		printf(fmt"  [line:%d] [%s]\n", ##__VA_ARGS__, __LINE__, __FUNCTION__);\
	}\
}while(0);

// 日志不同级别输出的宏定义
#define LOG_DEBUG(format, ...) 	logC(__func__, __FILE__, __LINE__, "DEBUG", format, ##__VA_ARGS__)
#define LOG_ERROR(format, ...) 	logC(__func__, __FILE__, __LINE__, "ERROR", format, ##__VA_ARGS__)
#define LOG_WARNING(format, ...) 	logC(__func__, __FILE__, __LINE__, "WARNING", format, ##__VA_ARGS__)
#define LOG_ACTION(format, ...) 	logC(__func__, __FILE__, __LINE__, "ACTION", format, ##__VA_ARGS__)
#define LOG_SYSTEM(format, ...) 	logC(__func__, __FILE__, __LINE__, "SYSTEM", format, ##__VA_ARGS__)

//	日志输出地:0 为输出到终端, 1 为输出到文件
static unsigned char mPrintLogPlaceFlag = 0;
//	日志是否打印:0 为不可打印	, 1 为可打印
static unsigned char mPrintDebugLogFlag = 1;
//  日志文件句柄
static FILE* mFile = NULL;
//	日志文件名,默认值
static char* mFileName = "./main.log"; 
//	日志文件的大小
static unsigned int mFileSize;

// 获取本地时间
static const char* getSystemTime()
{
	char *time_str = (char*)malloc(sizeof(char)*128);
	time_t loacl_time;
    time(&loacl_time);
    strftime(time_str, sizeof(time_str)*128, "[%Y.%m.%d %X]", localtime(&loacl_time));
	return time_str;
}
//	设置日志输出地
static void setLogPrintLogPlace(unsigned char flag)
{
	if(flag != 0 && flag != 1) return ;
	mPrintLogPlaceFlag = flag;
}
//	设置日志是否打印
static void setPrintDebugLog(unsigned char flag)
{
	if(flag != 0 && flag != 1) return ;
	mPrintDebugLogFlag = flag;
}
//	设置日志文件名
void setPrintFileName(char* fileName)
{
	if(fileName == NULL) return ;
	mFileName = fileName;
}
//	获取文件大小
static long getFileSize(const char *path)
{
	long filesize = -1;
	struct stat statbuff;
	
	if(stat(path, &statbuff) < 0){
	    return filesize;
	}
	else{
	    filesize = statbuff.st_size;
	}
	return filesize;
}
//	打开日志文件
static void openLogFile()
{	
	/*判断文件是否已经打开*/
	if(NULL != mFile)
	{
		LOG_PRINT("[ACTION] file opened!");
		return ;
	}
	/*判断文件名是否有定义*/
	if(NULL == mFileName)
	{
		LOG_PRINT("[ERROR] file name is NULL.");
		return ;
	}
	
	/*判断文件是否存在*/
	if (access(mFileName, F_OK) < 0)
	{
		LOG_PRINT("[WARNING] file not exist!");
	}
	
	/*打开文件*/
	mFile = fopen(mFileName, "a");
	if(NULL == mFile)
	{
		LOG_PRINT("[ERROR] open file failed!");
		return ;
	}
	LOG_PRINT("[DEBUG] open file name = %s", mFileName);
}
//	日志初始化
void LogInit()
{
	//打开日志文件
	openLogFile();
}
//	输出日志
void logC(const char *func, const char *file, const int line,
          const char *type, const char *format, ...)
{
    // 获取本地时间
	const char* time_str = getSystemTime();
    // 日志内容格式转换
    va_list ap;
    va_start(ap, format);
    char fmt_str[2048];
    vsnprintf(fmt_str, sizeof(fmt_str), format, ap);
    va_end(ap);
 
	//判断日志输出地
	if(mPrintLogPlaceFlag == 0 && mPrintDebugLogFlag == 1)
	{
		//日志输出到终端
		fprintf(stderr, "[%s]%s[%s@%s:%d] %s\n", type, time_str, func, 
				file, line, fmt_str);
	}
	else if(mPrintLogPlaceFlag == 1)
	{
		if(mFile == NULL)
		{
			openLogFile();
		}
		//日志输出到文件
		fprintf(mFile, "[%s]%s[%s@%s:%d] %s\n", type, time_str, func, 
					file, line, fmt_str);
	}
}
//日志打印资源释放
void LogDestroy(void)
{
	if(mFile != NULL)
	{
		fclose(mFile);
		mFile = NULL;
	}
	return;
}

#endif
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-16 12:52:35  更:2022-02-16 12:54:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:16:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码