linux下使用c++写一个日志类

1.前言

        日志和输出是一个服务器中必不可少的东西,它可以完整的记录服务器中关键的数据,用日志的形式保存起来。这样不仅可以给产品的升级优化提供准确的服务信息,还可以在数据崩溃的时候,作为版本还原的依据,一个好的日志系统,可以大大的提高产品优化效率,我们这里将利用前几天掌握的模板进行开发

        发一下我的代码把:

        链接:https://pan.baidu.com/s/13-wE2H_IY35kDP89swXTvQ 
        提取码:kviq 

2.开发

①封装一个根据参数获取string的类

        这个类的主要作用是传入任意参数,都可以得到对应的字符串,并且使用“<>”符号当作变量替代符,代码如下:

/**********************************************************
 * Author        : 谢名聪
 * Email         : 869408604@qq.com
 * Last modified : 2022-04-22 15:25
 * Filename      : XLogFun.h
 * Description   : 根据参数获得string
 * *******************************************************/
#ifndef X_LOG_FUN
#define X_LOG_FUN
#include <iostream>
namespace x_log_fun {
    class XLogFun
    {
        public:
            static XLogFun& me() {
                static XLogFun me;
                return me;
            }
        public:
            //模板的特化,处理不能通过to_string转换的变量
            std::string logGetString(const char* s);
            std::string logGetString(char c);
            std::string logGetString(std::string str);
            template<typename T>
            //数字类型
            std::string logGetString(T t)
            {
                return std::to_string(t);
            }

            //定义一个插入参数的参数
            //使用到了变长函数模板
            void insertValue(int index, std::string& str)
            {
                //递归结束
                return;
            }

            template <typename T, typename... Args>
            void insertValue(int index, std::string& str, T t, Args...args)
            {
                while (index + 1 < str.size()) {
                    if (str[index] == '<'  && str[index + 1] == '>' ) {
                        str = str.substr(0, index) + logGetString(t) + str.substr(index + 2, str.size() - 1);
                        insertValue(index++, str, args...);
                        return;
                    }
                    index++;
                }
                insertValue(0, str);
            }

            //返回函数
            template<typename... Args>
            void getLogInfo(std::string& str, Args... args)
            {
                return insertValue(0, str, args...);
            }
    };
}

#endif

#include "XLogFun.h"
//------------------------------------------------
//类函数的半特化不能放在类内实现
//放在类内实现在链接的时候会报错
//
//普通函数的模板函数不能放在类外实现
//放在类外实现在链接的时候会报错
//-----------------------------------------------
//定义一个转换字符串的函数模板
//数字类型的转换 int32_t int64_t uint32_t uint64_t
namespace x_log_fun {
    //半数模板的半特化,const char* 类型的转换
    std::string XLogFun::logGetString(const char* s)
    {
        return std::string(s);
    }
    //char类型
    std::string XLogFun::logGetString(char c)
    {
        std::string str(1, c);
        return str;
    }

    //std::string;类型
    std::string XLogFun::logGetString(std::string str)
    {
        return str;
    }
}

//----------------------------------------------
//后续可以根据自己的需求,不断增强自己的log类,比如引入json,proto的打印

②封装XLog类,用于创建日志文件并写入数据

        这个类也是用了一个单例,毕竟特定的日志文件只需要一个,这样不用梳理输出的先后顺序,这里需要在配置文件中加入log/目录的路径。代码如下

/**********************************************************
 * Author        : 谢名聪
 * Email         : 869408604@qq.com
 * Last modified : 2022-04-22 10:36
 * Filename      : XLog.h
 * Description   :将打印内容写入指定的文件,参数替代符为<>
 * 例如LOG_DEBUG("aaa<>",1) 则写入[DEBUG——时间] aaa1 
 * *******************************************************/
#include "XLogFun.h"
#include "../XThread/XThread.h"
#include "../common/def.h"
#include <fstream>
#include <queue>

struct LogData
{
    uint32_t logType;
    std::string logMsg;
};

class XLog:public XThread
{
    public:
        static XLog& me()
        {
            static XLog me;
            return me;
        }
    public:
        virtual bool exec() override;
    public:
        //配置文件目录
        bool init(std::string path);
    public:
        template<typename... Args>
        void errorLog(std::string str, Args... args) {
            insertLog(LOG_ERROR, str, args...);
        }
        template<typename... Args>
        void warnLog(std::string str, Args... args) {
            insertLog(LOG_WARN, str, args...);
        }
        template<typename... Args>
        void debugLog(std::string str, Args... args) {
            insertLog(LOG_DEBUG, str, args...);
        }
        template<typename... Args>
        void insertLog(uint32_t logType, std::string info, Args... args)
        {
            using namespace x_log_fun;
            LogData data;
            data.logType = logType;
            XLogFun::me().getLogInfo(info, args...);
            data.logMsg = info;
            m_queLogs.push(data);
        }
        void writeLog(LogData info);
    private:
        std::ofstream m_ofs;
        std::queue<LogData> m_queLogs;
};
#include "XLog.h"
#include <time.h>
//-------------------------------------------
//XLog类函数的实现
//------------------------------------------

//打印当前时间
std::string currentTimeToStr(void){
    char tmp[64];
    time_t t = time(NULL);
    tm *_tm = localtime(&t);
    int year  = _tm->tm_year+1900;
    int month = _tm->tm_mon+1;
    int date  = _tm->tm_mday;
    int hh = _tm->tm_hour;
    int mm = _tm->tm_min;
    int ss = _tm->tm_sec;
    sprintf(tmp,"%04d-%02d-%02d-%02d-%02d-%02d", year,month,date,hh,mm,ss);
    return std::string(tmp);
}

bool XLog::init(std::string path)
{
    std::string file = path + "/LOG_" + currentTimeToStr(); 
    m_ofs.open(file, std::ios::out);
    return true;
}

bool XLog::exec()
{
    while(status()) {
        while (m_queLogs.size()) {
            auto info = m_queLogs.front();
            m_queLogs.pop();
            writeLog(info);
        }
    }
    //关闭文件
    m_ofs.close();
    return true;
}

void XLog::writeLog(LogData info)
{
    std::string strInfo;
    if (info.logType == LOG_ERROR) {
        strInfo = "[ERROR_" + currentTimeToStr() + "]";
    } else if (info.logType == LOG_WARN) {
        strInfo = "[WARN_" + currentTimeToStr() + "]";
    } else if (info.logType == LOG_DEBUG) {
        strInfo = "[DEBUG_" + currentTimeToStr() + "]";
    }
    strInfo += info.logMsg;
    m_ofs << strInfo << std::endl;
}

③封装一个输出类XPrint

        这里可以做一个输出类,从而在流程中摒弃cout。这样也可以统一管理我们的输出,比如可以加上“在线上版本去掉输出”的限制,再或者特殊的输出指定不同的颜色,代码如下:

/**********************************************************
 * Author        : 谢名聪
 * Email         : 869408604@qq.com
 * Last modified : 2022-04-23 08:07
 * Filename      : XPrint.h
 * Description   : 写一个自己的打印类,去替代cout,主要可以
 * 增加颜色的控制,用醒目的颜色来标出自己的打印,便于找问题
 * *******************************************************/

#ifndef X_PRINT_H
#define X_PRINT_H

#include "XLogFun.h"
#include<iostream>


class XPrint
{
    public:
        static XPrint& me()
        {
            static XPrint me;
            return me;
        }
    public:
        //错误输出 红色
        template<typename... Args>
        void errorPrint(std::string str, Args... args) {
            using namespace x_log_fun;
            XLogFun::me().getLogInfo(str, args...);
            printf("\033[31m %s\n\033[0m",str.c_str());
        }
        //警告输出 黄色
        template<typename... Args>
        void warnPrint(std::string str, Args... args) {
            using namespace x_log_fun;
            XLogFun::me().getLogInfo(str, args...);
            printf("\033[33m %s\n\033[0m",str.c_str());
        }
        //调试输出 绿色
        template<typename... Args>
        void debugPrint(std::string str, Args... args) {
            using namespace x_log_fun;
            XLogFun::me().getLogInfo(str, args...);
            printf("\033[32m %s\n\033[0m",str.c_str());
        }
};


#endif

④优化一下Makefile文件

    这里有很多文件都是在.h中实现的,但是我们的Makefile中,并没有关联.h,这里就需要做出一些修改了。

cc=g++

cc_flags=\
	 -std=c++11 \
	 -I../include \
	 -MMD

ln_flags=\
	 -L../lib -lprotobuf \
	 -L../lib -lmysqlclient \
	 -lpthread \
	 -lm \
	 -ldl \

obj=\
    ../proto/src/User.pb.o \
    XInclude/XConfig/XConfig.o \
    XInclude/XMysql/XMysql.o \
    XInclude/XThread/XThread.o \
    XInclude/XLog/XLog.o \
    XInclude/XLog/XLogFun.o \
    XProcess/XProcess.o \
    Process.o \
    main.o \

    
target=process

$(target) : $(obj)
	$(cc) $(ln_flags) $(obj) -o $(target)

%.o : %.cpp
	$(cc) $(cc_flags) -c $< -o $@

%.o: %.cc
	$(cc) $(cc_flags) -c $< -o $@


-include $(obj:.o=.d)

clean:
	rm -f $(obj) $(obj:.o=.d) $(obj:.o=.d*) $(target) core.* log/*

⑥修改一下run.sh文件

        创建一个log目录保存日志

ulimit -c 10000
export LD_LIBRARY_PATH=../lib/
mkdir log
./process config.ini 

⑦修改一下config.ini文件

        加入log_path的配置

##代表注释后面的内容
#中括号内是配置的类型
[mysql]
type=1
port= 3306
host =127.0.0.1
pwd = 123456
user=root
db=test

[server]
name=xprocess
type=1
port=10000
ip=127.0.0.1
id=1
log_path=log

⑧定义一个宏,让代码简洁一些

        在XInclude/common/def.h中加入宏定义

/**********************************************************
 * Author        : 谢名聪
 * Email         : 869408604@qq.com
 * Last modified : 2022-04-21 11:44
 * Filename      : def.h
 * Description   : 一些通用的宏定义,类的定义放在这里 
 * *******************************************************/
#ifndef BASE_DEF_H
#define BASE_DEF_H
//------------------------------------------
//日志类型
#define LOG_ERROR 1
#define LOG_WARN 2
#define LOG_DEBUG 3

#define SYS_ERROR_LOG XLog::me().errorLog
#define SYS_WARN_LOG XLog::me().warnLog
#define SYS_DEBUG_LOG XLog::me().debugLog

//--------------------------------------------
//输出
#define SYS_ERROR_PRINT XPrint::me().errorPrint
#define SYS_WARN_PRINT XPrint::me().warnPrint
#define SYS_DEBUG_PRINT XPrint::me().debugPrint


#endif

⑨在Process类中加入加载代码

bool Process::startAsynTools()
{
    //开启日志
    XLog::me().run();

    for (auto tool : m_asynTools) {
        tool->run();
    }
    return true;
}

bool Process::stopAsynTools()
{
    for (auto tool : m_asynTools) {
        tool->stop();
    }
    //关闭日志
    XLog::me().stop();

    return true;
}

bool Process::initLog(std::string path)
{
    if (!XLog::me().init(path)) {
        SYS_ERROR_PRINT("log init error");
        return false;
    }
    
    return true;
}

⑩在XProcess类中写一个测试例子

bool XProcess::initProcess()
{
    //加载数据库
    std::string host = getConfigValue("mysql", "host");
    std::string port = getConfigValue("mysql", "port");
    std::string user = getConfigValue("mysql", "user");
    std::string pwd = getConfigValue("mysql", "pwd");
    std::string db = getConfigValue("mysql", "db");
    std::string sqlType = getConfigValue("mysql", "type");
    uint32_t type = std::atoi(sqlType.c_str());
    if (!addMysqlServer(type, host.c_str(), port.c_str(), user.c_str(), pwd.c_str(), db.c_str())) {
        SYS_ERROR_PRINT("addMysqlServer error");
        return false;
    }

    //加载异步数据库
    if (!addAsynMysqlServer(type, host.c_str(), port.c_str(), user.c_str(), pwd.c_str(), db.c_str())) {
        SYS_ERROR_PRINT("addAsynMysqlServer error");
        return false;
    }

    //加载服务名字和类型
    std::string serverName = getConfigValue("server", "name");
    std::string serverType = getConfigValue("server", "type");
    std::string serverId = getConfigValue("server", "id");

    m_name = serverName;
    m_type = std::atoi(serverType.c_str());
    m_id = std::atoi(serverId.c_str());
    
    //加载配置
    std::string path = getConfigValue("server", "log_path");
    if (!initLog(path)) {
        SYS_ERROR_PRINT("init log error");
        return false;
    }
    return true;
}

bool XProcess::startProcess()
{
    while (m_stop == false) {
        /*
        //获取系统时间戳
        time_t timeReal;
        time(&timeReal);
        timeReal = timeReal + 8 * 3600;
        tm* t = gmtime(&timeReal); 
        printf("%d-%02d-%02d %02d:%02d:%02d\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); 
        */
        /*
        std::string sql = "select * from user_data;";
        SYS_DEBUG_PRINT("exeAsynSql():<>",sql); 
        exeAsynSql(1, sql.c_str(), std::bind(&XProcess::testAsynSql, this, std::placeholders::_1));
        */
        SYS_ERROR_LOG("error日志 <> <> ",100,1);
        SYS_WARN_LOG("Warn日志 <> <>", 200,'c');
        SYS_DEBUG_LOG("Debug日志 <>", "ssssssssss");
        
        SYS_ERROR_PRINT("error日志 <> <> ",100,1);
        SYS_WARN_PRINT("Warn日志 <> <>", 200,'c');
        SYS_DEBUG_PRINT("Debug日志 <>", "ssssssssss");


        sleep(1);

    };
    return true;
}

3.看看效果吧

4.总结

         做到这就算是把日志类封装好了,后面肯定还需要继续升级和优化。这里封装的时候碰到了一些坑,比如写模板的时候,碰到了很多链接时报错的问题,还有就是一些头文件重复包含的问题,感觉自己的修炼之路还有非常远的路要走啊。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值