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

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

# day22-初步涉及日志库,定义自己的输出流LogStream

在服务器编程中,日志是必不可少的,生产环境中应做到Log Everything All The Time。

一个日志库需要完成的功能主要有:

  • 多种日志级别
  • 日志输出目的地为本地文件
  • 支持日志文件rooling(按天,按大小),简化日志归档
  • 日志文件命名(进程名+创建日期+创建时间+机器名+进程id+后缀log)
  • 日志消息格式固定(日期+时间+线程id+日志级别+源文件名和行号+日志信息)

在之前的操作中,在代码中加入各种信息的输出就是一个简陋的输出端为终端的同步日志系统。如果当我们的服务器发送日志信息后,必须等待日志系统完成写操作才可以继续执行。尽管这个方式可以保证日志数据的完整性和准确率,但是在高并发场景下,会导致服务器的性能下降的非常严重。

而异步日志库在服务器产生日志消息时,会将相应的缓冲区存储起来,等到合适的时机,用一个后台线程统一处理日志信息。这就避免了服务器阻塞在日志系统写操作上,提升服务器的响应性能。

为了存储相应的日志信息,我们需要一个额外的Buffer类,这个类与网络端的Buffer带有一些不同,在网络端中暂时使用了std::string作为存储空间,尽管其非常方便,但是由于其内部是使用动态分配内存的,在频繁的字符串操作中,需要进行内存的动态分配和释放,因此其效率比较低。在日志库中,我们使用了定长的字符数组来存储日志信息,可以直接开辟对应的内存空间用于存储信息,在日志库中,将其定义为FixedBuffer。

class FixedBuffer{
    public:
        FixedBuffer();
        ~FixedBuffer();

        void append(const char *buf, int len); // 添加数据

        const char *data() const; // 数据
        int len() const; // 目前的长度

        char *current(); // 获取当前的指针
        int avail() const; // 剩余的可用空间

        void reset(); // 重置缓冲区
        const char *end() const; // 获取末端指针
            
    private:
        char data_[FixedBufferSize];
        char *cur_;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在这个类中,通过cur_保存当前可写内存空间的位置,并在加入新的数据时,直接从该位置写入,并更新cur_

FixedBuffer::FixedBuffer():cur_(data_){};

void FixedBuffer::append(const char *buf, int len){
    if(avail() > len){
        memcpy(cur_, buf, len);
        cur_ += len;
    }
}
1
2
3
4
5
6
7
8

在日志系统中,并非所有的数据都是字符数据,因此为了进行类型转换,并且采用类似c++风格的stream <<风格。首先需要定义自己的LogStream类,重载<<操作符,之所以不直接时用iostream是因为,其格式化输出麻烦,并且其操作并不是原子化的。

class LogStream{
    typedef LogStream self;
    typedef FixedBuffer Buffer;

public:
    self& operator<<(int num)
    self& operator<<(unsigned int num)
    self& operator<<(char v);
    
private:

    Buffer buffer_;
};
1
2
3
4
5
6
7
8
9
10
11
12
13

在实现时,会首先将相应的类别转换成字符形式,然后加入到buffer中。

LogStream &LogStream::operator<<(int num){
    formatInteger(num);
    return *this;
}
LogStream &LogStream::operator<<(const double& num){
    char buf[32];
    int len = snprintf(buf, sizeof(buf), "%g", num);
    buffer_.append(buf, len);
    return *this;
}
1
2
3
4
5
6
7
8
9
10

此外还有其他重载,就不一一赘述了,具体的对于一般类型,都先将其转成字符或者字符串然后加入到buffer中,在muduo中,针对整形进行了额外的优化,即使用了Matthew wilson设计的旋转除余法进行了转换。

但是LogStream本身是并不支持格式化的,因此需要额外的定义一个不影响其状态的Fmt类。将一个数值类型数据转换成一个长度不超过32位字符串对象Fmt,并重载了支持Fmt输出到LogStream的<<操作符。

在Fmt内部,调用了snprintf函数,将数据进行了格式化。

// LogStream.h
template<typename T>
Fmt::Fmt(const char* fmt, T val)
{
  static_assert(std::is_arithmetic<T>::value == true, "Must be arithmetic type");
  length_ = snprintf(buf_, sizeof(buf_), fmt, val);
  assert(static_cast<size_t>(length_) < sizeof(buf_));
}

inline LogStream & operator<<(LogStream& s, const Fmt& fmt){
    s.append(fmt.data(), fmt.length());
    return s;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

一个简单的流式LogStream就简单的实现了,在/test/test_logstream.cpp对其进行了简单的测试。 进入build文件,并运行make test_logstream会生成相应的可执行文件,执行他就可以进行简单的测试了。

编辑 (opens new window)
上次更新: 2025/05/21, 06:42:57
day21-服务器主动关闭连接
day23-定义前端日志库,实现同步输出

← day21-服务器主动关闭连接 day23-定义前端日志库,实现同步输出→

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