⽇志系统的主要功能

  • 记录访问⽇志:记录客⼾端的请求信息,包括请求时间、IP 地址、请求 URL、请求⽅法(如GET/POST)、响应状态码等。
  • 错误⽇志:记录服务器运⾏中出现的错误或异常,帮助开发者快速定位问题。
  • 性能监控:记录服务器性能数据,如请求处理时间、资源使⽤率等,便于优化性能。
  • 安全审计:记录⽤⼾登录、访问控制和安全相关事件,⽤于安全审查和异常⾏为监控。

⽇志系统的重要性

  1. 问题排查与调试
  • 错误定位:当系统发⽣异常或错误时,⽇志是快速定位问题的关键⼿段。通过⽇志,可以追踪代码的执⾏路径,找到错误发⽣的具体位置和原因。
  • 调试辅助:在开发阶段,⽇志有助于了解程序的运⾏状态,验证逻辑是否正确,实现⾼效的调试过程。
  1. 系统监控与运维
  • 性能监控:通过记录关键指标(如响应时间、请求数量、错误率等),⽇志可以帮助运维⼈员监控系统性能,及时发现瓶颈和异常。
  • 安全审计:⽇志可以记录⽤⼾的操作⾏为,帮助识别潜在的安全威胁,满⾜合规性要求。
  1. ⽤⼾⾏为分析
  • 数据驱动决策:通过分析⽇志中的⽤⼾⾏为数据,可以了解⽤⼾的使⽤习惯和偏好,指导产品优化和业务决策。
  • A/B 测试:⽇志可以记录不同版本或功能的使⽤情况,帮助评估 A/B 测试的效果。

预期效果

1
2
3
4
5
6
7
8
9
10
11
12
#include "Logger.h"
int main() {
// 初始化和绑定socket的代码
LOG_INFO("Server starting");
while (true) {
new_socket = accept(server_fd, (struct sockaddr*)&address(socklen_t*)&addrlen);
LOG_INFO("New connection accepted");
// 处理请求和发送响应的代码
LOG_INFO("Connection closed");
}
return 0;
}

constexpr函数(Constexpr Functions):

C++11 引⼊了 constexpr 关键字,⽤来表⽰可以在编译时计算结果的函数或变量。当⼀个函数被声明为 constexpr 时,如果其在编译时的所有实参都是常量表达式,并且函数体内的计算也能够在编译期完成,则这个函数能够返回⼀个编译时常量。

1
2
3
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}

上述 factorial 函数在满⾜条件的情况下可以在编译时期计算阶乘值,并可⽤于初始化静态或常量变量,或者在编译时进⾏计算的地⽅,如数组⼤⼩、枚举值等。此外,从C++14开始, constexpr 函数不再要求是 const 成员函数,也可以有⾮ const 的类成员函数,并且⽀持更复杂的编译时逻辑。

宏(Macro):

  • 可变参数宏:对于复杂的⽇志信息,可使⽤ … 和 va_list 来处理。
    原理:
  • 在C++函数调⽤过程中,所有的参数都会按照特定的顺序压⼊栈中。
  • 对于固定参数列表,编译器知道每个参数的位置和类型,但对可变参数列表则⽆法直接获取。
  • va_list 、 va_start 、 va_arg 和 va_end 这些宏就是⽤来帮助我们安全访问和解析这些未知数量和类型的参数的。
  • ⽰例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include <cstdarg>
    #include <iostream>

    void printVarArgs(const char* format, ...) {
    va_list args;

    va_start(args, format);

    vprintf(format, args); // 使⽤vprintf函数处理可变参数

    va_end(args);
    }
    int main() {
    printVarArgs("%d %f %s", 10, 3.14, "Hello, World!"); // 调⽤可变参数函数
    return 0;
    }
    在这个例⼦中, printVarArgs 是⼀个可变参数函数,它可以接收任何数量和类型的参数,然后通过 vprintf 函数将它们格式化输出。这⾥的 vprintf 类似于 printf ,但能够处理 va_list类型的可变参数。
  1. va_list:
  • 定义⼀个 va_list 类型的变量,⽤于存储指向可变参数列表的指针。
  1. va_start:
  • 初始化 va_list 变量,使其指向第⼀个可变参数的位置。它需要⼀个固定的参数作为参照,这个参数是可变参数列表之前的最后⼀个固定参数。
    1
    2
    va_list args;
    va_start(args, format); // 'format' 是最后⼀个固定参数
  1. va_arg:
  • 从 va_list 指向的位置提取下⼀个参数,并将其转换为指定的类型。每次调⽤ va_arg 都会使va_list 向前移动到下⼀个参数。
    1
    2
    int arg1 = va_arg(args, int);
    double arg2 = va_arg(args, double);
  1. va_end:
  • 清理与 va_list 相关的内部状态,这是必要的清理步骤,在完成所有可变参数的读取后必须调⽤。
    1
    va_end(args)

Logger.h

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <fstream>
#include <string>
#include <chrono>
#include <ctime>
#include <cstdarg> // 引入处理可变参数的头文件

// 日志级别枚举,用于区分不同类型的日志
enum LogLevel {
INFO,
WARNING,
ERROR
};

// Logger类,用于执行日志记录操作
class Logger {
public:
// logMessage静态成员函数,用于记录日志信息
// 参数包括日志级别、格式化字符串以及可变参数列表
static void logMessage(LogLevel level, const char* format, ...) {
// 打开日志文件,以追加模式写入
std::ofstream logFile("server.log", std::ios::app);
// 获取当前时间
auto now = std::chrono::system_clock::now();
auto now_c = std::chrono::system_clock::to_time_t(now);

// 根据日志级别确定日志级别字符串
std::string levelStr;
switch (level) {
case INFO: levelStr = "INFO"; break;
case WARNING: levelStr = "WARNING"; break;
case ERROR: levelStr = "ERROR"; break;
}

// 使用可变参数处理日志信息的格式化
va_list args; // 声明一个可变参数列表,用于存储不定数量的参数
va_start(args, format); // 初始化args变量,并指向可变参数的第一个参数。format是最后一个命名参数
char buffer[2048]; // 声明一个字符数组buffer,大小为2048,用于存储格式化后的日志信息
vsnprintf(buffer, sizeof(buffer), format, args); // 使用vsnprintf格式化字符串,将格式化的内容写入buffer
// 其中:
// - buffer 是目标字符数组
// - sizeof(buffer) 是写入buffer的最大字符数,防止溢出
// - format 是格式字符串,指定日志信息的格式
// - args 是可变参数列表,包含所有传入的可变参数
va_end(args); // 清理args,结束可变参数的处理


// 将时间戳、日志级别和格式化后的日志信息写入日志文件
logFile << std::ctime(&now_c) << " [" << levelStr << "] " << buffer << std::endl;

// 关闭日志文件
logFile.close();
}
};

// 定义宏以简化日志记录操作,提供INFO、WARNING、ERROR三种日志级别的宏
#define LOG_INFO(...) Logger::logMessage(INFO, __VA_ARGS__)
#define LOG_WARNING(...) Logger::logMessage(WARNING, __VA_ARGS__)
#define LOG_ERROR(...) Logger::logMessage(ERROR, __VA_ARGS__)


//当你在代码中调用LOG_INFO("Hello, %s", name)时,
//宏会在编译阶段替换为Logger::logMessage(INFO, "Hello, %s", name)。