1G级线上日志文件 的异常定位系统性方案
一、快速定位流程
python
import datetime
import random
def generate_springboot_log(file_name, file_size_gb):
# 模拟Spring Boot日志内容
log_levels = ["INFO", "DEBUG", "WARNING", "ERROR"]
classes = ["com.example.controller.HomeController", "com.example.service.UserService",
"com.example.repository.UserRepository", "com.example.config.WebConfig"]
messages = [
"Accessing /home endpoint",
"User not found with id: {}",
"Invalid request parameters",
"Database connection established",
"Starting application",
"Shutting down application"
]
# 计算需要写入的行数
file_size_bytes = file_size_gb * 1024 ** 3
# 首先生成一条样本日志行,计算其长度
sample_log = f"[2023-10-01 12:34:56.789] {random.choice(classes)} - {random.choice(log_levels)} : {random.choice(messages)}"
average_line_length = len(sample_log) + 1 # 加1是因为每行最后还有一个换行符
with open(file_name, "w", encoding="utf-8") as f:
# 计算需要写入的行数
lines_needed = int(file_size_bytes // average_line_length) + 1
for _ in range(lines_needed):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] # 保留毫秒
log_level = random.choice(log_levels)
clazz = random.choice(classes)
message = random.choice(messages)
# 如果日志消息中包含格式化的参数,添加示例数值
if "{}" in message:
message = message.format(random.randint(1, 100))
log_line = f"[{timestamp}] {clazz} - {log_level} : {message}\n"
f.write(log_line)
generate_springboot_log("springboot_log_1gb.log", 1)
思路:利用时间过滤数据,再利用管道流过滤输出到单独的文件,最后在小文件里面操作就行。
shell
[2025-03-16 21:14:11.476] com.example.repository.UserRepository - INFO : Starting application
[2025-03-16 21:14:11.476] com.example.service.UserService - WARNING : User not found with id: 76
[2025-03-16 21:14:11.476] com.example.service.UserService - INFO : Shutting down application
[2025-03-16 21:14:11.476] com.example.service.UserService - ERROR : Starting application
[2025-03-16 21:14:11.476] com.example.config.WebConfig - INFO : User not found with id: 83
[2025-03-16 21:14:11.476] com.example.service.UserService - WARNING : Accessing /home endpoint
[2025-03-16 21:14:11.476] com.example.repository.UserRepository - ERROR : Accessing /home endpoint
[2025-03-16 21:14:11.476] com.example.config.WebConfig - INFO : User not found with id: 57
[2025-03-16 21:14:11.476] com.example.repository.UserRepository - INFO : User not found with id: 89
[2025-03-16 21:14:11.476] com.example.controller.HomeController - ERROR : Database connection established
[2025-03-16 21:14:11.476] com.example.config.WebConfig - ERROR : Accessing /home endpoint
[2025-03-16 21:14:11.476] com.example.controller.HomeController - INFO : Shutting down application
[2025-03-16 21:14:11.476] com.example.repository.UserRepository - INFO : Database connection established
[2025-03-16 21:14:11.476] com.example.config.WebConfig - DEBUG : User not found with id: 16
[2025-03-16 21:14:11.476] com.example.controller.HomeController - WARNING : Database connection established
[2025-03-16 21:14:11.476] com.example.controller.HomeController - ERROR : Accessing /home endpoint
[2025-03-16 21:14:11.476] com.example.controller.HomeController - WARNING : Invalid request parameters
[2025-03-16 21:14:11.476] com.example.repository.UserRepository - WARNING : Invalid request parameters
[2025-03-16 21:14:11.476] com.example.controller.HomeController - WARNING : Accessing /home endpoint
[2025-03-16 21:14:11.476] com.example.service.UserService - DEBUG : Starting application
[2025-03-16 21:14:11.476] com.example.config.WebConfig - ERROR : Starting application
[2025-03-16 21:14:11.476] com.example.config.WebConfig - WARNING : User not found with id: 42
shell
awk -v start="[2025-03-16 21:14:11.476]" -v end="[2025-03-16 21:14:11.477]" '($0) >= start && ($0) < end {print}' springboot_log_1gb.log | grep 'ERROR' -> test.log
html
($0) >= start && ($0) < end {print}:对每一行内容 $0 进行比较。如果当前行的时间在 start 和 end 之间(包含等于的情况),则打印该行,(测试($0) <= end {print}得到的结果不包括等于)
二、高效处理大文件的技巧
分割和并行处理 : 使用<font style="color:rgb(37, 43, 58);">split</font>
将日志文件分割为小文件,分别处理,再合并结果。
shell
split -l 100000 springboot_log_1gb.log chunk_
-l 100000:每个小文件包含1000行。
chunk_ :前缀,生成 chunk_aa、chunk_ab 等文件。
shell
rm -rf chunk_*
shell
split -b 200M --verbose springboot_log_1gb.log split_access_
-b 200M:每个分片200MB
--verbose:显示分割进度
输出文件命名示例:split_access_aa,split_access_ab
shell
split -l 5000 -d --additional-suffix=.log springboot_log_1gb.log "split_$(date +%Y%m%d)_"
-additional-suffix:添加文件扩展名
$(date +%Y%m%d):动态生成日期前缀
split_20250316_991230.log
超大文件分块处理:
shell
split -l 100000 app.log chunk_ # 每10万行切割文件
find . -name "chunk_*" | xargs -P 4 grep "ERROR" >> errors.txt
优势:避免单文件内存溢出问题
html
find . -name "chunk_*":查找当前目录(.)及其子目录中所有文件名以 "chunk_" 开头的文件。
* 是通配符,表示任意字符序列。
xargs -P 4:使用 xargs 将输入的文件名作为参数传递给后续命令。-P 4 表示同时运行 4 个进程,提高处理速度。
grep "ERROR":在每个文件中搜索包含 "ERROR" 的行。grep 是一个强大的文本搜索工具,支持正则表达式。
>> errors.txt:将 grep 找到的所有包含 "ERROR" 的行追加到 errors.txt 文件中。
如果 errors.txt 不存在,会创建一个新文件。
shell
find . -name "chunk_*" | xargs rm
动态追加写入:
shell
tail -f app.log | grep --line-buffered "ERROR" | xargs -I{} sh -c 'echo {} >> errors.txt'
-line-buffered:强制逐行输出缓冲
xargs:将标准输入转换为命令行参数。
-I{}:指定{}作为每行输入的占位符。
sh -c:执行一个命令。
echo {} >> errors.txt:将每一行ERROR日志追加到errors.txt文件中。
避免内存问题:
shell
grep "ERROR" logfile.log | awk '{print $0}' # 避免 `cat logfile.log | ...`
方法 | 10GB日志耗时 | CPU占用 | 内存峰值 |
---|---|---|---|
原生grep | 22分钟 | 98% | 1.8GB |
xargs单线程 | 19分钟 | 25% | 800MB |
xargs并行(-P 8) | 4分30秒 | 620% | 2.1GB |
指标来源于网络,仅供参考
三、实时监控与自动化
shell
tail -f /path/to/logfile.log | grep "ERROR" # 实时显示新增的ERROR日志
异常复现的时候可以使用
shell
tail -f logfile.log | awk '/ERROR/ {print strftime("%Y-%m-%d %H:%M:%S"), $0}' # 添加时间戳
