手写一个日志

[日志等级][进程的pid][filena][filenumber][time](日志内容(以特定的当时进行输出))

日志概念

日志是来记录信息,向显示器打印 或者向指定文件进行打印,使用特点的格式

那今天写的日志格式就是以下

[日志等级][进程pid][filename][filenumber][time]日志内容(以特定的方式进行输入输出)

了解日志等级

我们在写我们的日志的时候,我们可以分为五个等级

分别是DEBUG, INFO  WARNING, ERROR ,  FATAL 

代表的意思就是调试信息,常规信息,警告 错误,致命信息。

那我们写代码的时候就可以使用enum的方式枚举这些日志等级

enum //枚举日志等级
{
    DEBUG = 1,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

那我们打印日志信息的结果是这样的

我们就可以进行封装一个类来存储我们的日志信息

这个类就可以叫做logmessage

class logmessage
{
public:
    std::string _level;//日志等级
    pid_t _pid;//进程pid
    std::string _filename;//文件名
    int _filenumber;//行数
    std::string _curr_time;//时间
    std::string _message_info;//显示内容
};

那我们需要再封装一个类来使用日志,其中功能也包括填充我们的日志信息

class Log
{
public:
    Log()
    {
    }
    void LogMessage(std::string filename, int filenumber, int level, const char *format, ...)
    {
        logmessage lg;
        lg._level = LevelToString(level);
        lg._pid = getpid();
        lg._filename = filename;
        lg._filenumber = filenumber;

    }
    ~Log() {}
};

那我们就可以在这个log类中完善信息,然后去调用,因为level最后输出的时候是个字符串,所以需要我们进行转字符串的操作。

std::string LevelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case INFO:
        return "INFO";
    case WARNING:
        return "WARNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return "UNKNOW";
    }
}

获取日期时间

现在我们还需要填充logmessage的一个时间信息,可以调用C语言的函数来进行,我们需要了解获取时间戳的函数。

在二号手册当中,time函数内直接写nullptr就行,但是我们是需要打印出这样的格式

所以还需要一个函数来获取就是localtime,会填充到struct tm这个结构体当中,我们只需要通过指针的方式来获取就可以了,代码如下

std::string GetCurrTime()
{
    time_t now = time(nullptr);
    struct tm* curr_time = localtime(&now);
    char time_info[128];
    snprintf(time_info,sizeof(time_info), "%d-%02d-%02d %02d:%02d:%02d\n",curr_time->tm_year+1900,\
    curr_time->tm_mon+1,curr_time->tm_mday,curr_time->tm_hour,\
    curr_time->tm_min,curr_time->tm_sec);
    return time_info;
}

使用vsnprintf打印日志信息

其实这里就是如何使用这个函数来进行打印可变参数的内容,我们就可以先定义一个指针,然后类似循环遍历的方法对我们的数组进行打印。那我们这里就直接使用,不会的可以去查C语言的文章,之前也是写过的。

 void LogMessage(std::string filename, int filenumber, int level, const char *format, ...)
    {
        logmessage lg;
        lg._level = LevelToString(level);
        lg._pid = getpid();
        lg._filename = filename;
        lg._filenumber = filenumber;
        va_list ap;
        va_start(ap,format);
        char log_buffer[1024];
        vsnprintf(log_buffer,sizeof(log_buffer),format,ap);
        va_end(ap);
        lg._message_info = log_buffer;
        
    }

那我们就可以自定义传入format的方法进行打印

确定输出方向

这里我们的输出方向就两个,一个是默认向显示器进行打印,一个是向我们的文件进行输出,那我们这个时候可以定义两个宏,然后添加一个属性就是_type确定我们输出的方向。

因为需要打印到我们的文件上,所以需要定义一个路径

做好准备工作,我们就来写我们的刷新策略,如果是要刷新到显示器上的就可以写成以下

输出屏幕

 void FlushLogToScreen(const logmessage &lg)
    {
        printf("[%s][%d][%s][%d][%s] %s",
               lg._level.c_str(),
               lg._pid,
               lg._filename.c_str(),
               lg._filenumber,
               lg._curr_time.c_str(),
               lg._message_info.c_str());
    }

输出到文件 

 void FlushLogToFile(const logmessage &lg)
    {
        std::ofstream out(_logfile,std::ios::app);
        if(!out.is_open())
        {
            return ;
        }
        char buffer[1024];
        snprintf(buffer, sizeof(buffer),"[%s][%d][%s][%d][%s] %s",
               lg._level.c_str(),
               lg._pid,
               lg._filename.c_str(),
               lg._filenumber,
               lg._curr_time.c_str(),
               lg._message_info.c_str());
        out.write(buffer,strlen(buffer));
        out.close();
    }

这里需要注意我们打开方式是以写的方式打开,以写的方式打开一个文件的话,会清空内容,所以我们需要以app的方式也就是追加的方式打开这个文件。

解决线程安全问题

需要用锁来解决,我们可以把他放到一个文件当中,这样我们在使用的时候就只要定义一个对象。

代码如下

#pragma once

#include <pthread.h>

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        pthread_mutex_lock(_mutex);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);
    }
private:
    pthread_mutex_t *_mutex;
};

那接下来就需要思考我们在哪里定义这个对象,因为显示器和文件是共享的,最好的办法就是在显示刷新的时候或者文件进行刷新到缓存区的时候,那合起来我们可以对文件刷新和显示器刷新做一个简单的封装,这样只要在外面进行枷锁。

void FlushLog(const logmessage &lg)
    {
        LockGuard lock (&glock);
        switch (_type)
        {
        case SCREEN_TYPE:
            FlushLogToScreen(lg);
            break;
        case FILE_TYPE:
            FlushLogToFile(lg);
            break;
        }
    }

这样就能保证我们的日志是安全的那接下来我们需要写一个宏,来使得调用日志的时候更加简单便捷。

使用宏来对我们的日志进行调用

Log lg;
#define LOG(level,format,...) do{lg.LogMessage(__FILE__,__LINE__,level,format,##__VA_ARGS__);}while(0)
#define EnableScreen() do{lg.Enable(SCREEN_TYPE)}while(0)
#define EnableFile() do{lg.Enable(FILE_TYPE)}while(0)

完整代码

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <ctime>
#include <stdarg.h>
#include <fstream>
#include <cstring>
#include <pthread.h>
#include "LockGuard.hpp"
enum // 枚举日志等级
{
    DEBUG = 1,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

class logmessage
{
public:
    std::string _level;        // 日志等级
    pid_t _pid;                // 进程pid
    std::string _filename;     // 文件名
    int _filenumber;           // 行数
    std::string _curr_time;    // 时间
    std::string _message_info; // 显示内容
};
//
std::string LevelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case INFO:
        return "INFO";
    case WARNING:
        return "WARNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return "UNKNOW";
    }
}
std::string GetCurrTime()
{
    time_t now = time(nullptr);
    struct tm *curr_time = localtime(&now);
    char time_info[128];
    snprintf(time_info, sizeof(time_info), "%d-%02d-%02d %02d:%02d:%02d", curr_time->tm_year + 1900,
             curr_time->tm_mon + 1, curr_time->tm_mday, curr_time->tm_hour,
             curr_time->tm_min, curr_time->tm_sec);
    return time_info;
}
#define SCREEN_TYPE 1
#define FILE_TYPE 2
pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;
const std::string glogfile = "./log.txt";
class Log
{
public:
    // Log() : _type(SCREEN_TYPE)
    // {
    // }
    Log(const std::string logfile = glogfile):_type(SCREEN_TYPE),_logfile(logfile)
    {}
    void Enable(int type)
    {
        _type = type;
    }
    void FlushLogToScreen(const logmessage &lg)
    {
        printf("[%s][%d][%s][%d][%s] %s",
               lg._level.c_str(),
               lg._pid,
               lg._filename.c_str(),
               lg._filenumber,
               lg._curr_time.c_str(),
               lg._message_info.c_str());
    }
    void FlushLogToFile(const logmessage &lg)
    {
        std::ofstream out(_logfile,std::ios::app);
        if(!out.is_open())
        {
            return ;
        }
        char buffer[1024];
        snprintf(buffer, sizeof(buffer),"[%s][%d][%s][%d][%s] %s",
               lg._level.c_str(),
               lg._pid,
               lg._filename.c_str(),
               lg._filenumber,
               lg._curr_time.c_str(),
               lg._message_info.c_str());
        out.write(buffer,strlen(buffer));
        out.close();
    }
    void FlushLog(const logmessage &lg)
    {
        LockGuard lock(&glock);
        switch (_type)
        {
        case SCREEN_TYPE:
            FlushLogToScreen(lg);
            break;
        case FILE_TYPE:
            FlushLogToFile(lg);
            break;
        }
    }
    void LogMessage(std::string filename, int filenumber, int level, const char *format, ...)
    {
        logmessage lg;
        lg._level = LevelToString(level);
        lg._pid = getpid();
        lg._filename = filename;
        lg._filenumber = filenumber;
        lg._curr_time = GetCurrTime();
        va_list ap;
        va_start(ap, format);
        char log_buffer[1024];
        vsnprintf(log_buffer, sizeof(log_buffer), format, ap);
        va_end(ap);
        lg._message_info = log_buffer;
        FlushLog(lg);
    }
    ~Log() {}

private:
    int _type;
    std::string _logfile;
};

Log lg;
#define LOG(level,format,...) do{lg.LogMessage(__FILE__,__LINE__,level,format,##__VA_ARGS__);}while(0)
#define EnableScreen() do{lg.Enable(SCREEN_TYPE)}while(0)
#define EnableFile() do{lg.Enable(FILE_TYPE)}while(0)

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在冬天去看海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值