「项目复现」Web服务器编程之简易日志模块

[TOC]

​ 本文总结作者学习的Web服务器项目(WebServer),分析其中的主要模块的作用及相关知识点。一个WebServer是一个运行在linux系统上的C++编码的服务器程序,其需要处理来自浏览器程序(客户端)的各种HTTP请求,并对其请求作出HTTP响应。

​ 从上述图中可以看出其中共有7个模块:日志系统模块、监听套接字模块、IO复用模型epoll模块、线程池模块、数据库连接池模块、HTTP连接模

一、日志模块的作用

1、对于程序来讲

​ 日志模块用来程序执行的过程,帮助开发人员了解程序的运行情况,以便快速维护和调试。

2、对程序员来讲

​ 日志模块提供几个输出日志信息的接口(API),该接口的输入参数可能有:日志等级、日志输出平台。

日志等级:info、debug、warning、error。可以标记日志信息的重要程度,控制不同等级的日志信息的输出

日志输出平台:文件、控制台

二、日志模块的实现

1、设置为单例模式

​ 为什么要将日志模块的输出类设置为单例模式:日志模块需要在程序的任何地方被调用,且最好只有一个对象

2、格式化输出

日志输出本质上是对printf()函数的封装。

日志输出信息一般格式:日志等级+时间戳+格式化输出信息

C++的日志模块如何实现格式化输出?

(1)C++的函数的可变参数列表

C++允许定义形参个数和类型不确定的函数,不确定的形参可以使用可变参数列表。

(2)C++的vprintf()函数

vprintf(format,args);

vprintf()作用和printf()相同, 参数format 格式也相同。

printf 底层就是调用 vprintf 函数来将内容输出到控制台的,常规情况下,输出到控制台,多数情况下使用 printf 函数即可。当你需要自己写一个自定义 printf 函数时候才需要 vprintf 函数

vprintf 函数一般和 va_start / va_end 配套使用;

(3)宏和可变参数宏

为了更加方便地调用日志类的输出函数,我们需要将其经常定义为宏。

在C++中经常使用#define来定义宏:

1
2
#define N 1 // 不带参数的宏
#define AREA(x) x*x // 带参数的宏

宏在调用时进行宏替换,宏替换发生在静态编译期,是简单的文本替换,例如

1
int a=AREA(2+2);//a=8,不等于16,AREA(2+2)等价于2+2*2+2

带参数的宏也经常被叫做宏函数,但是宏函数不是真正的函数,其发生在静态编译期,是简单的文本替换。

由于输出函数可能使用了可变参数列表,这时我们就要使用可变参数宏__VA_ARGS__

可变参数宏的定义如下:

1
#define DEBUG(format,...) printf(format,__VA_ARGS__)

其中,...代表一个可以变化的参数表。使用保留名 VA_ARGS 把参数传递给宏。例如:

1
2
3
4
// 调用宏,因为DEBUG()是可变参数宏,所以传递不同个数的参数
DEBUG("X = %d\n", X);
// 处理器会把宏的调用替换成:
printf("X = %d\n", X);

3、日志等级

(1)C++中的enum的使用

枚举类型(enumeration)是 C++ 中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。

应用举例:

1
2
enum color_set1 {RED, BLUE, WHITE, BLACK}; // 定义枚举类型color_set1
enum week {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; // 定义枚举类型week

枚举常量代表该枚举类型的变量可能取的值,编译系统为每个枚举常量指定一个整数值,默认状态下,这个整数就是所列举元素的序号,序号从0开始。 可以在定义枚举类型时为部分或全部枚举常量指定整数值,在指定值之前的枚举常量仍按默认方式取值,而指定值之后的枚举常量按依次加1的原则取值。 各枚举常量的值可以重复。例如:

1
2
3
4
enum fruit_set {apple, orange, banana=1, peach, grape}
//枚举常量apple=0,orange=1, banana=1,peach=2,grape=3。
enum week {Sun=7, Mon=1, Tue, Wed, Thu, Fri, Sat};
//枚举常量Sun,Mon,Tue,Wed,Thu,Fri,Sat的值分别为7、1、2、3、4、5、6。

枚举常量只能以标识符形式表示,而不能是整型、字符型等文字常量。例如,以下定义非法:

1
2
enum letter_set {'a','d','F','s','T'}; //枚举常量不能是字符常量
enum year_set{2000,2001,2002,2003,2004,2005}; //枚举常量不能是整型常量

可改为以下形式则定义合法:

1
2
enum letter_set {a, d, F, s, T};
enum year_set{y2000, y2001, y2002, y2003, y2004, y2005};

枚举类型的使用:

定义枚举类型之后,就可以定义该枚举类型的变量,如:

1
color_set1 color1, color2;

亦可类型与变量同时定义(甚至类型名可省),格式如下:

1
enum {Sun,Mon,Tue,Wed,Thu,Fri,Sat} weekday1, weekday2;

3、日志输出平台

(1)C++的写入文件

1
2
3
4
#include<fstream>
ofstream outfile;
outfile.open("./log.txt",ios::out|ios::app);
outfile<<"待写入的内容";