Consolexin's blog Consolexin's blog
首页
  • 算法基础

    • 图论
    • 字符串
    • 动态规划
    • 二分
    • 滑动窗口
    • 排序
  • Project

    • CppServer
  • 相关书籍

    • 现代C++编程
  • 书籍

    • SQL必知必会
    • MySQL必知必会
分类
标签
归档
GitHub (opens new window)

Consolexinhun

小学生
首页
  • 算法基础

    • 图论
    • 字符串
    • 动态规划
    • 二分
    • 滑动窗口
    • 排序
  • Project

    • CppServer
  • 相关书籍

    • 现代C++编程
  • 书籍

    • SQL必知必会
    • MySQL必知必会
分类
标签
归档
GitHub (opens new window)
  • README
  • day01-从一个最简单的socket开始
  • day02-不要放过任何一个错误
  • day03-高并发还得用epoll
  • day04-来看看我们的第一个类
  • day05-epoll高级用法-Channel登场
  • day06-服务器与事件驱动核心类登场
  • day07-为我们的服务器添加一个Acceptor
  • day08-一切皆是类,连TCP连接也不例外
  • day09-缓冲区-大作用
  • day10-加入线程池到服务器
  • day11-完善线程池,加入一个简单的测试程序
  • day12-将服务器改写为主从Reactor多线程模式
  • day13-支持业务逻辑自定义、完善Connection类
  • day14-重构核心库、使用智能指针
  • day15-重构Connection、修改生命周期
  • day16-使用CMake工程化
  • day17-使用EventLoopThreadPool、移交EventLoop
  • day18-HTTP有限状态转换机
  • day19-创建HTTP响应,实现HTTP服务器
  • day20-定时器的创建使用
  • day21-服务器主动关闭连接
  • day22-初步涉及日志库,定义自己的输出流LogStream
  • day23-定义前端日志库,实现同步输出
  • day24-异步日志库
  • day25-更有效的缓冲区
  • day26-监听写事件
  • day27-处理静态文件,实现POST请求
  • day28-文件服务器的简单实现,文件的展示和下载
  • day29-文件的上传
  • day30-WebBench的测试
  • CppServer
consolexinhun
2025-04-20

day23-定义前端日志库,实现同步输出

# day23-定义前端日志库,实现同步输出

在之前的工作中,定义了日志库的输出流。而一个完整的异步日志库系统还需要其前端内容和后端处理。

对于前端的内容,其主要是提供给用户访问日志库的接口,并对日志信息进行一定的格式化,提供给用户将日志信息写入缓冲区的功能。

为了实现该功能,我们创建一个Logger类。

为了针对不同的日志等级进行不同的操作,首先需要定义不同的日志等级,通常,日志等级包含如下几个部分:

  • DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的(开发过程中使用)

  • INFO 表明消息在粗粒度级别上突出强调应用程序的运行过程。

  • WARN 系统能正常运行,但可能会出现潜在错误的情形。

  • ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。

  • FATAL 指出每个严重的错误事件将会导致应用程序的退出。

因此首先定义这几个日志等级。

class Logger
{
public:
    enum LogLevel
    {
        DEBUG,
        INFO,
        WARN,
        ERROR,
        FATAL
    };
}
1
2
3
4
5
6
7
8
9
10
11
12

通常,我们希望日志系统包含时间发生的时间,日志等级,发生的事务已经所在的源码位置,例如

20230703 19:56:51.441099Z347201 INFO  HttpServer Listening on 127.0.0.1:1234 - HttpServer.cpp:31
1

而这些内容通常用户并不关心如何实现。为了方便实现该功能,创建一个Impl类,该类主要是对日志信息进行组装,将相应的数据放入到Buffer。

class Impl{
    public:
        DISALLOW_COPY_AND_MOVE(Impl);
        typedef Logger::LogLevel LogLevel;
        Impl(const SourceFile &source, int line, Logger::LogLevel level);
        void FormattedTime();// 格式化时间信息
        void Finish();// 完成格式化,并补充输出源码文件和源码位置

        LogStream &stream();
        const char *loglevel() const;// 获取LogLevel的字符串
        LogLevel level_;// 日志级别

    private:
        Logger::SourceFile sourcefile_; // 源代码名称
        int line_;// 源代码行数
        
        LogStream stream_;// 日志缓存流
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

在该类中定义了相应的格式化操作,并在实例化时,我们就直接将相应的信息放入到缓存中。


Logger::Impl::Impl(const Logger::SourceFile &source, int line, Logger::LogLevel level)
    : sourcefile_(source),
      line_(line),
      level_(level){

    // 格式化时间
    FormattedTime();
    // 输出线程id
    stream_ << std::this_thread::get_id();
    // 日志等级
    stream_ << Template(loglevel(), 6);
}
void Logger::Impl::FormattedTime(){
    //格式化输出时间
    TimeStamp now = TimeStamp::Now();
    time_t seconds = static_cast<time_t>(now.microseconds() / kMicrosecond2Second);
    int microseconds = static_cast<int>(now.microseconds() % kMicrosecond2Second);

    // 变更日志记录的时间,如果不在同一秒,则更新时间。
    // 方便在同一秒内输出多个日志信息
    if (t_lastsecond != seconds) {
        struct tm tm_time;
        localtime_r(&seconds, &tm_time);
        snprintf(t_time, sizeof(t_time), "%4d%02d%02d %02d:%02d:%02d.",
                tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
                tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
        t_lastsecond = seconds;
    }

    Fmt us(".%06dZ", microseconds);
    stream_ << Template(t_time, 17) << Template(us.data(), 9);
}


void Logger::Impl::Finish(){
    stream_ << "-" << sourcefile_.data_ << ":" << line_ << "\n";
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

当前的内容知识将日志的信息进行了组装和放入缓冲区中,而对于实际的日志信息还没有进行任何处理。 我们希望用户在使用该日志库时可以通过简单的操作即可,例如

LOG_INFO << "LOG Message";
1

通过LOG_INFO来规定日志的等级,并输入相应的日志信息。

MUDUO通过定义一系列的宏来实现了这一操作,具体的当使用LOG_*之类的宏会创建一个临时匿名Logger对象,这个对象中包含一个Impl对象,而Impl对象拥有一个LogStream对象。LOG_*宏就会返回一个LogStream的引用。用于将内容输入到该LogStream中的Buffer中。

#define LOG_DEBUG if (loglevel() <= Logger::DEBUG) \
  Logger(__FILE__, __LINE__, Logger::DEBUG, __func__).stream()
#define LOG_INFO if (loglevel() <= Logger::INFO) \
  Logger(__FILE__, __LINE__, Logger::INFO).stream()
#define LOG_WARN Logger(__FILE__, __LINE__, Logger::WARN).stream()
#define LOG_ERROR Logger(__FILE__, __LINE__, Logger::ERROR).stream()
#define LOG_FATAL Logger(__FILE__, __LINE__, Logger::FATAL).stream()
1
2
3
4
5
6
7

在析构时,则会将相应的信息输出,如果发生了FATAL错误,还会更新缓存区并终止程序。为了设置日志的输出位置,Logger定义了两个函数指针用于指定输出位置和更新缓存区。并设置默认输出为stdout。

// Logger.h
typedef void (*OutputFunc)(const char *data, int len); // 定义函数指针
typedef void (*FlushFunc)();
// 默认fwrite到stdout
static void setOutput(OutputFunc);
// 默认fflush到stdout
static void setFlush(FlushFunc);

// Logger.cpp
void defaultOutput(const char* msg, int len){
    fwrite(msg, 1, len, stdout);  // 默认写出到stdout
}
void defaultFlush(){
    fflush(stdout);    // 默认flush到stdout
}
Logger::OutputFunc g_output = defaultOutput;
Logger::FlushFunc g_flush = defaultFlush;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

一个完整的析构过程为:

Logger::~Logger()
{
    impl_.Finish(); // 补足源代码位置和行数
    const LogStream::Buffer& buf(stream().buffer());  // 获取缓冲区
    g_output(buf.data(), buf.len());  // 默认输出到stdout
 
    // 当日志级别为FATAL时,flush设备缓冲区并终止程序
    if (impl_.level_ == FATAL) {
        g_flush();
        abort();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

至此,一个简单的同步日志库基本实现了,其主要流程是,当用户使用一个LOG_*的日志宏时,会创建一个临时匿名对象Logger,然后Logger内部会有一个Impl对象,当该对象创建时,会将当前的时间,线程等信息加入到Buffer中。之后会该日志宏返回Impl中LogStream的引用,并将相应的信息输入其拥有的Buffer中。当调用结束Logger对象被析构时,会调用g_output将日志信息输出。并根据不同的日志等级执行不同的操作。

在HttpServer和TcpServer处增加了两个简单的日志处理,运行

编辑 (opens new window)
上次更新: 2025/05/21, 06:42:57
day22-初步涉及日志库,定义自己的输出流LogStream
day24-异步日志库

← day22-初步涉及日志库,定义自己的输出流LogStream day24-异步日志库→

最近更新
01
6-其他操作
05-20
02
4-联结
05-20
03
7-管理
05-20
更多文章>
Theme by Vdoing | Copyright © 2019-2025 Consolexinhun | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×