日志系统的设计

在Logger.h中定义首先需要定义日志的级别,我将其定义成枚举类型:

enum LogLevel
{
    kTrace,    // 最详细的跟踪信息(调试阶段使用)
    kDebug,    // 调试信息(开发人员排查问题)
    kInfo,     // 正常运行的信息(记录关键流程)
    kWarn,     // 警告(非错误但需注意的情况)
    kError,    // 错误(功能失败但程序继续运行)
    kMaxNumOfLogLevel, // 总级别数(非实际日志级别)
};

随后设置Logger类,这个类需要实现:1、设置当前的日志等级SetLogLevel。2、获取日志等级。3、写日志。

Logger.h

#pragma once
#include <iostream>
#include "NonCopyable.h"
namespace tmms
{
    namespace base
    {
        enum LogLevel
        {
            kTrace,
            kDebug,
            kInfo,
            kWarn,
            kError,
            kMaxNumOfLogLevel,
        };

        class Logger:public NonCopyable
        {
        public:
            Logger() = default;
            ~Logger() = default;

            void SetLogLevel(const LogLevel &level);
            LogLevel GetLogLevel() const;
            void Write( const std::string &msg);
        private:
            LogLevel level_{kDebug};
        };
    }
}

无法复制或赋值Logger对象,避免多实例导致的资源竞争。

Logger.cpp

#include "Logger.h"
#include <iostream>
using namespace tmms::base;
void Logger::SetLogLevel(const LogLevel &level){
    level_ = level;
}
LogLevel Logger::GetLogLevel()const{
    return level_;
}
void Logger::Write(const std::string &msg){

    std::cout << msg;
}

在LogStream中实现Stream的拼接用于格式化输出日志,日志中包含有[时间][线程ID][文件名:行号][函数名]

LogStream.h

#pragma once
#include "Logger.h"
#include <sstream>
namespace tmms
{
    namespace base
    {
        extern Logger * g_logger;
        class LogStream
        {
        public:
            LogStream(Logger* logger, const char* file, int line, LogLevel l, const char *func);
            ~LogStream();

            // 模板实现必须放在头文件中
            template<class T>
            LogStream& operator<<(const T& value)
            {
                stream_<< value;
                return *this;
            }

        private:
            std::ostringstream stream_;
            Logger *logger_{nullptr};
        };
    }
}


 // 修正日志宏定义(添加缺失的__func__参数)
#define LOG_TRACE LogStream(tmms::base::g_logger, __FILE__, __LINE__, tmms::base::kTrace, __func__)
#define LOG_DEBUG LogStream(tmms::base::g_logger, __FILE__, __LINE__, tmms::base::kDebug, __func__)
#define LOG_INFO  LogStream(tmms::base::g_logger, __FILE__, __LINE__, tmms::base::kInfo, __func__)
#define LOG_WARN  LogStream(tmms::base::g_logger, __FILE__, __LINE__, tmms::base::kWarn, __func__)
#define LOG_ERROR LogStream(tmms::base::g_logger, __FILE__, __LINE__, tmms::base::kError, __func__)

LogStream.cpp

#include "LogStream.h"
#include <string>
#include "TTime.h"
#include <unistd.h>
#include <sys/syscall.h>
#include <cstring>
using namespace tmms::base;


Logger* tmms::base::g_logger = nullptr;
static thread_local pid_t thread_id = 0;
const char *log_string[]{

};

LogStream::LogStream(Logger* logger, const char* file, int line, LogLevel l, const char *func)
:logger_(logger)
{
    if(l < tmms::base::g_logger->GetLogLevel())
        return;
    // 初始化时清空流缓冲区
    stream_.str("");
    stream_.clear();

    // 统一格式:[时间][线程ID][文件名:行号][函数名]
    stream_ << "[" << TTime::ISOTime() << "]";  // 时间戳

    // 获取线程ID
    if(thread_id == 0){
        thread_id = static_cast<pid_t>(syscall(SYS_gettid));
    }
    stream_ << "[TID:" << thread_id << "]";     // 线程信息

    // 文件名处理
    const char * file_name = strrchr(file,'/');
    file_name = file_name ? file_name + 1 : file;
    stream_ << "[" << file_name << ":" << line << "]"; // 源码位置

    // 函数名处理
    if(func){
        stream_ << "[FN:" << func << "]";
    }
    // 日志内容前缀
    stream_ << " ";
}

LogStream::~LogStream()
{
    stream_ << "\n";  // 添加换行
    logger_->Write(stream_.str());
}

对这个代码的测试可以

#include "LogStream.h"
#include "Logger.h"

using namespace tmms::base;

void TestLog()
{
    LOG_TRACE << "test trace!!!";
    LOG_DEBUG << "test debug!!!";
    LOG_INFO  << "test info!!!";
    LOG_WARN  << "test warn!!!"; // 修正宏名大小写
    LOG_ERROR << "test error!!!";
}
int main (int argc,const char **argv){
    tmms::base::g_logger = new Logger();
    tmms::base::g_logger->SetLogLevel(kTrace);
    //tmms::base::g_logger->SetLogLevel(kWarn);
    TestLog();
    return 0;
}

在创建Logger实例子之后设置需要记录的日志等级,随后测试日志系统。

注意,在运行我的代码的时候需要保证写一个不能多次实例化的NonCopyable.h

#pragma once

namespace tmms
{
    namespace base
    {
        class NonCopyable
        {
        protected:
            NonCopyable() = default;
            ~NonCopyable() = default;

        private:
            NonCopyable(const NonCopyable &) = delete;
            NonCopyable &operator=(const NonCopyable &) = delete;
        };
    }
}

以及一个关于事件的库TTime.cpp

#include "TTime.h"
using namespace tmms::base;
#include <sys/time.h>
int64_t TTime::NowMS()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec*1000+tv.tv_usec/1000;
}

int64_t TTime::Now()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec;
}
int64_t TTime::Now(int &year,int &month,int&day,int &hour,int&minute,int&second)
{
    struct tm tm;
    time_t t = time(NULL);
    localtime_r(&t, &tm);
    year = tm.tm_year + 1900;
    month = tm.tm_mon + 1;
    day = tm.tm_mday;
    hour = tm.tm_hour;
    minute = tm.tm_min;
    second = tm.tm_sec;
    return t;
}
std::string TTime::ISOTime()
{
    struct timeval tv;
    struct tm tm;
    gettimeofday(&tv, NULL);
    time_t t = time(NULL);
    localtime_r(&t, &tm);
    char buf[128] = {0};
    auto n = sprintf(buf, "%4d-%02d-%02dT%02d:%02d:%02d",
    tm.tm_year + 1900,
    tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    return std::string(buf,buf+n);
}

TTime.h

#pragma once
#include <cstdint>
#include <string>
namespace tmms
{
    namespace base
    {
        class TTime
        {
        public:
            static int64_t NowMS();
            static int64_t Now();
            static int64_t Now(int &year,int &month,int&day,int &hour,int&minute,int&second);
            static std::string ISOTime();
        };
    }
}

时间的库主要是为了获取当前时间,记录日志的开始时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值