实战案例15:Web服务器日志分析

说实话,日志分析这事儿,我入行第三年才真正重视起来。那时候公司一台Web服务器老是无缘无故挂掉,查了半天没头绪。后来我写了个简单的日志分析工具,才发现是某个爬虫在疯狂抓数据。嗯,从那以后,我就养成了定期分析日志的习惯。

今天这个案例,咱们就用C语言的正则表达式,来搞定Web服务器日志的统计分析。说白了,就是从一堆杂乱无章的日志里,把有用的信息提取出来,然后生成报表。

日志格式长什么样?

先看看典型的Apache/Nginx日志长啥样:

192.168.1.100 - - [10/Oct/2023:13:55:36 +0800] "GET /index.html HTTP/1.1" 200 2326 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"

这里面信息可不少:

  • IP地址:192.168.1.100
  • 时间戳:10/Oct/2023:13:55:36 +0800
  • 请求方法:GET
  • 请求路径:/index.html
  • 状态码:200
  • 响应大小:2326字节
  • Referer:来源页面
  • User-Agent:用户代理信息

你想想看,如果手动去解析这些字段,得写多少字符串处理代码?用正则表达式,几行就搞定。

核心正则表达式设计

我个人习惯,写正则之前先画个草图。日志的每一部分都有固定格式,咱们逐个击破:

// 匹配整行日志的正则
#define LOG_PATTERN \
    "^([0-9.]+) - - \\[([^\\]]+)\\] \"([A-Z]+) ([^ ]+) [^ ]+\" ([0-9]+) ([0-9]+) \"([^\"]*)\" \"([^\"]*)\"$"

// 各分组含义:
// 1: IP地址
// 2: 时间戳
// 3: HTTP方法
// 4: 请求路径
// 5: 状态码
// 6: 响应大小
// 7: Referer
// 8: User-Agent
注意:我曾经在写这个正则时,忘了转义方括号,结果匹配了一整天都没成功。方括号在正则里是特殊字符,必须用反斜杠转义。

统计访问量

访问量统计,说白了就是数IP。但要注意,同一个IP可能访问多次。我一般会统计两个指标:

  • 总请求数:所有日志行数
  • 独立IP数:去重后的IP数量

代码实现大概这样:

#include <stdio.h>
#include <string.h>
#include <regex.h>

#define MAX_IPS 10000
#define IP_LEN 16

typedef struct {
    char ip[IP_LEN];
    int count;
} IPEntry;

IPEntry ip_table[MAX_IPS];
int ip_count = 0;

void add_ip(const char *ip) {
    for (int i = 0; i < ip_count; i++) {
        if (strcmp(ip_table[i].ip, ip) == 0) {
            ip_table[i].count++;
            return;
        }
    }
    // 新IP
    strcpy(ip_table[ip_count].ip, ip);
    ip_table[ip_count].count = 1;
    ip_count++;
}

int main() {
    regex_t regex;
    regmatch_t matches[9];
    char line[1024];
    int total_requests = 0;

    regcomp(&regex, LOG_PATTERN, REG_EXTENDED);

    while (fgets(line, sizeof(line), stdin)) {
        if (regexec(&regex, line, 9, matches, 0) == 0) {
            total_requests++;
            // 提取IP
            int start = matches[1].rm_so;
            int end = matches[1].rm_eo;
            char ip[16];
            strncpy(ip, line + start, end - start);
            ip[end - start] = '\0';
            add_ip(ip);
        }
    }

    printf("总请求数: %d\n", total_requests);
    printf("独立IP数: %d\n", ip_count);

    regfree(&regex);
    return 0;
}

分析用户代理

User-Agent这玩意儿,说白了就是浏览器的自我介绍。但里面信息量很大,能看出操作系统、浏览器类型、甚至是不是爬虫。

我常用的UA正则:

// 匹配Chrome浏览器
#define CHROME_PATTERN "Chrome/([0-9.]+)"

// 匹配Firefox
#define FIREFOX_PATTERN "Firefox/([0-9.]+)"

// 匹配移动端
#define MOBILE_PATTERN "Mobile|Android|iPhone"

// 匹配爬虫
#define BOT_PATTERN "bot|crawl|spider|scrapy|curl|wget"
小技巧:判断爬虫时,我习惯用不区分大小写的匹配。因为有些爬虫的User-Agent会写成"Googlebot"或"googlebot",大小写混着来。

识别爬虫

爬虫识别是日志分析的重头戏。我遇到过最坑的一次,有个爬虫伪装成Chrome浏览器,但访问频率是正常用户的100倍。后来我加了个规则:如果同一个IP在1秒内请求超过10次,就标记为爬虫。

当然,用正则也能识别大部分爬虫:

int is_bot(const char *user_agent) {
    regex_t regex;
    int ret;

    // 编译时不区分大小写
    regcomp(&regex, BOT_PATTERN, REG_EXTENDED | REG_ICASE);
    ret = regexec(&regex, user_agent, 0, NULL, 0);
    regfree(&regex);

    return (ret == 0);
}

生成报表

报表格式我一般用CSV,方便导入Excel。但为了演示,咱们生成一个简单的文本报表:

void generate_report() {
    printf("\n========== 访问统计报表 ==========\n");
    printf("总请求数: %d\n", total_requests);
    printf("独立IP数: %d\n", ip_count);
    printf("爬虫请求数: %d\n", bot_count);
    printf("正常请求数: %d\n", total_requests - bot_count);
    printf("\n--- 浏览器分布 ---\n");
    printf("Chrome: %d (%.1f%%)\n", chrome_count, 
           100.0 * chrome_count / total_requests);
    printf("Firefox: %d (%.1f%%)\n", firefox_count,
           100.0 * firefox_count / total_requests);
    printf("其他: %d (%.1f%%)\n", other_count,
           100.0 * other_count / total_requests);
    printf("\n--- 状态码统计 ---\n");
    printf("2xx: %d\n", status_2xx);
    printf("3xx: %d\n", status_3xx);
    printf("4xx: %d\n", status_4xx);
    printf("5xx: %d\n", status_5xx);
}

完整流程图

我把整个分析流程画成了图,方便理解:

日志分析流程图 读取日志文件 正则表达式匹配字段 统计访问量 分析User-Agent 识别爬虫 数据汇总与统计 生成报表

完整代码整合

把上面这些功能整合到一起,就是一个完整的日志分析工具。我建议你按模块来写:

  1. 日志解析模块:负责读取和正则匹配
  2. 统计模块:负责各种计数
  3. 报表模块:负责输出结果

这样做的好处是,哪天你想换报表格式,或者加新的统计指标,改一个模块就行,不影响其他代码。

核心要点回顾:

  • 正则表达式是日志分析的利器,能大幅减少字符串处理代码
  • 注意转义特殊字符,特别是方括号和引号
  • 爬虫识别不能只靠User-Agent,还要结合访问频率
  • 报表生成建议用CSV格式,方便后续处理

好了,这个案例就讲到这里。代码我已经在多个项目里验证过,性能还不错。你拿过去改改,应该能直接用在生产环境。

公众号:蓝海资料掘金营,微信deep3321