提交 f43abc19 authored 作者: 陈泽健's avatar 陈泽健

feat(monitoring): 添加ERROR日志监测功能

- 在自动化服务监测脚本中新增ERROR日志监测模块
- 实现对ubains-ERROR.log的最近1小时错误日志时间段聚合分析
- 添加对内与对外日志的同时监测支持
- 配置错误日志时间段分组展示,每段最多显示10行示例
- 更新邮件通知收件人列表,移除wb@huazhaochina.com
- 调整报告结构,ERROR日志监测作为独立章节展示
- 修改PRD文档中的服务检测需求描述
- 优化日志时间段统计和错误归类逻辑
上级 e51ffd71
......@@ -18,7 +18,7 @@ HOST_NAME="${HOST_NAME_OVERRIDE:-$(hostname 2>/dev/null || echo localhost)}"
# 邮件通知配置
MAIL_TO="czj@huazhaochina.com,pgy@huazhaochina.com,zxb@huazhaochina.com,wb@huazhaochina.com"
MAIL_TO="czj@huazhaochina.com,pgy@huazhaochina.com,zxb@huazhaochina.com"
MAIL_SUBJECT_PREFIX="自动化服务监测报告"
# ✅ 邮件标题:支持自定义
......@@ -1023,6 +1023,312 @@ send_report_mail() {
return 0
}
# ==============================
# 3.1 - ERROR 日志监测(ubains-ERROR.log)
# ==============================
ERROR_TAIL_LINES="${ERROR_TAIL_LINES:-5000}" # 扫描最后 N 行
ERROR_GROUP_GAP_SECONDS="${ERROR_GROUP_GAP_SECONDS:-60}" # 相邻错误<=60s 归为同一时间段
ERROR_LOOKBACK_SECONDS="${ERROR_LOOKBACK_SECONDS:-3600}" # 最近 1 小时窗口(PRD)
ERROR_PRINT_MAX_LINES_PER_RANGE="${ERROR_PRINT_MAX_LINES_PER_RANGE:-20}" # 每个时间段最多重点打印多少行
ERROR_MAX_RANGES_IN_REPORT="${ERROR_MAX_RANGES_IN_REPORT:-5}" # 最多展示最近 N 个时间段
# 每个时间段重点打印的最大行数(你之前是 20,可按需求降到 10)
ERROR_PRINT_MAX_LINES_PER_RANGE="${ERROR_PRINT_MAX_LINES_PER_RANGE:-10}" # 每段最多重点打印多少行
# 根据平台类型,返回 meeting 服务目录(不含 logs)
get_meeting_service_dirs() {
local platform="$1" # new|legacy
local dirs=()
if [[ "$platform" == "new" ]]; then
dirs+=("/data/services/api/java-meeting/java-meeting2.0")
dirs+=("/data/services/api/java-meeting/java-meeting3.0")
else
dirs+=("/var/www/java/api-java-meeting2.0")
fi
printf "%s\n" "${dirs[@]}"
}
# 从一行日志中提取时间戳并转 epoch(尽量兼容常见格式)
# 支持:YYYY-MM-DD HH:MM:SS 或 YYYY/MM/DD HH:MM:SS
extract_epoch_from_log_line() {
local line="$1"
local dt
dt="$(echo "$line" | grep -Eo '20[0-9]{2}[-/][0-9]{2}[-/][0-9]{2}[ T][0-9]{2}:[0-9]{2}:[0-9]{2}' | head -n 1)"
[[ -z "$dt" ]] && return 1
# 统一成 date 友好的格式:YYYY-MM-DD HH:MM:SS
dt="${dt//\//-}"
dt="${dt/T/ }"
date -d "$dt" +%s 2>/dev/null
}
# 扫描某个 ubains-ERROR.log,输出聚合后的“时间段 + 示例行”到 stdout(Markdown)
scan_ubains_error_log_to_md() {
local error_log="$1"
local title="$2"
local tail_lines="$3"
[[ -f "$error_log" ]] || return 0
local now_ts from_ts
now_ts="$(date +%s)"
from_ts=$(( now_ts - ERROR_LOOKBACK_SECONDS ))
echo "### ERROR日志:${title}"
echo
echo "- 文件:\`${error_log}\`"
echo "- 扫描范围:最后 ${tail_lines} 行"
echo "- 统计窗口:最近 ${ERROR_LOOKBACK_SECONDS}s($(date -d @"$from_ts" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$from_ts") ~ $(date -d @"$now_ts" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$now_ts"))"
echo "- 聚合规则:相邻错误日志时间间隔 ≤ ${ERROR_GROUP_GAP_SECONDS}s 视为同一时间段"
echo "- 输出限制:最多展示最近 ${ERROR_MAX_RANGES_IN_REPORT} 个时间段;每段最多打印 ${ERROR_PRINT_MAX_LINES_PER_RANGE} 行"
echo
local tmp
tmp="$(mktemp)"
# 只收集最近 1 小时内的记录
tail -n "$tail_lines" "$error_log" 2>/dev/null | while IFS= read -r line; do
local epoch
epoch="$(extract_epoch_from_log_line "$line" || true)"
[[ -z "$epoch" ]] && continue
(( epoch < from_ts )) && continue
(( epoch > now_ts )) && continue
printf "%s|%s\n" "$epoch" "$line"
done > "$tmp"
if [[ ! -s "$tmp" ]]; then
echo "- ✅ 最近 1 小时内未发现 ERROR 日志记录(或日志中未解析到时间戳)"
echo
rm -f "$tmp"
return 0
fi
sort -n "$tmp" -o "$tmp"
# 聚合并暂存每个时间段到数组(最后只输出最近 N 段)
local range_start=0 range_end=0 range_count=0 last_ts=0
local range_idx=0
# 用临时文件保存“每个时间段一条记录:start|end|count|sample_file”
local ranges_meta
ranges_meta="$(mktemp)"
: > "$ranges_meta"
local range_lines_tmp
range_lines_tmp="$(mktemp)"
: > "$range_lines_tmp"
local printed=0
while IFS='|' read -r ts line; do
[[ -z "$ts" ]] && continue
if (( range_start == 0 )); then
range_start=$ts
range_end=$ts
last_ts=$ts
range_count=1
printed=0
: > "$range_lines_tmp"
echo "$line" >> "$range_lines_tmp"
printed=1
continue
fi
local gap=$(( ts - last_ts ))
if (( gap <= ERROR_GROUP_GAP_SECONDS )); then
range_end=$ts
last_ts=$ts
range_count=$((range_count + 1))
if (( printed < ERROR_PRINT_MAX_LINES_PER_RANGE )); then
echo "$line" >> "$range_lines_tmp"
printed=$((printed + 1))
fi
else
# flush 一个时间段:把样例保存到独立文件,记录 meta
range_idx=$((range_idx + 1))
local sample_file
sample_file="$(mktemp)"
cp "$range_lines_tmp" "$sample_file"
echo "${range_start}|${range_end}|${range_count}|${sample_file}" >> "$ranges_meta"
# reset
range_start=$ts
range_end=$ts
last_ts=$ts
range_count=1
printed=0
: > "$range_lines_tmp"
echo "$line" >> "$range_lines_tmp"
printed=1
fi
done < "$tmp"
# flush 最后一个时间段
if (( range_start > 0 )); then
range_idx=$((range_idx + 1))
local sample_file
sample_file="$(mktemp)"
cp "$range_lines_tmp" "$sample_file"
echo "${range_start}|${range_end}|${range_count}|${sample_file}" >> "$ranges_meta"
fi
# 总段数 & 只输出最近 N 段(按结束时间排序取最后 N 段)
local total_ranges
total_ranges="$(wc -l < "$ranges_meta" | tr -d ' ')"
echo "- 最近 1 小时内共发现 **${total_ranges}** 个 ERROR 时间段"
echo
# 取最近的 N 段(按 end 时间排序)
local shown=0
while IFS='|' read -r rs re rc sf; do
local start_h end_h
start_h="$(date -d @"$rs" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$rs")"
end_h="$(date -d @"$re" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$re")"
shown=$((shown + 1))
echo "- **时间段${shown}**:**${start_h} ~ ${end_h}**(错误条数:${rc})"
echo
echo "````log"
cat "$sf"
echo "````"
echo
done < <(sort -t'|' -k2,2n "$ranges_meta" | tail -n "$ERROR_MAX_RANGES_IN_REPORT")
if (( total_ranges > ERROR_MAX_RANGES_IN_REPORT )); then
echo "> 仅展示最近 ${ERROR_MAX_RANGES_IN_REPORT} 个时间段,其余 $((total_ranges - ERROR_MAX_RANGES_IN_REPORT)) 个时间段已省略(仍计入统计)。"
echo
fi
# cleanup
while IFS='|' read -r _ _ _ sf; do
[[ -f "$sf" ]] && rm -f "$sf"
done < "$ranges_meta"
rm -f "$tmp" "$ranges_meta" "$range_lines_tmp"
}
# 返回 meeting 的“对内日志文件列表”(每行:title|path)
get_meeting_internal_error_logs() {
local platform="$1"
while IFS= read -r service_dir; do
[[ -z "$service_dir" ]] && continue
local title="meeting-对内-$(basename "$service_dir")"
local p="${service_dir}/logs/ubains-ERROR.log"
echo "${title}|${p}"
done < <(get_meeting_service_dirs "$platform")
}
# 返回 meeting 的“对外日志文件列表”(每行:title|path)
# 说明:PRD 未明确对外日志路径,这里采用“同目录优先 + nginx error/access 作为候选”
get_meeting_external_error_logs() {
local platform="$1"
while IFS= read -r service_dir; do
[[ -z "$service_dir" ]] && continue
# 1) 如果对外也写在同目录(很多项目是同一套日志),那这里会和对内重合:允许,但在报告上分组展示更符合 PRD
echo "meeting-对外-$(basename "$service_dir")|${service_dir}/logs/ubains-ERROR.log"
# 2) nginx 常见路径候选(存在就扫)
# 你也可以按实际项目替换/补充
# echo "meeting-对外-nginx-error|/var/log/nginx/error.log"
# echo "meeting-对外-nginx-access|/var/log/nginx/access.log"
done < <(get_meeting_service_dirs "$platform")
}
# 把 ERROR 监测结果写入报告(Markdown)
write_error_log_section_to_report() {
local out_file="$1"
{
echo "## 二、ERROR日志监测(最近1小时,按时间段聚合)"
echo
echo "> 说明:本章节独立于“日志暴涨”判定;用于统计最近 1 小时内 ERROR 日志出现的时间段,并重点打印(每段最多 ${ERROR_PRINT_MAX_LINES_PER_RANGE} 行)。"
echo
} >> "$out_file"
if [[ "${HAS_UJAVA:-0}" -ne 1 ]]; then
echo "- ℹ️ 未识别到会议预定系统(ujava),跳过 ERROR 日志扫描。" >> "$out_file"
echo >> "$out_file"
return 0
fi
# 2.1 对内(meeting)
{
echo "### 2.1 对内日志(meeting 后端)"
echo
} >> "$out_file"
local any_internal=0
while IFS='|' read -r title log_path; do
[[ -z "$log_path" ]] && continue
if [[ -f "$log_path" ]]; then
any_internal=1
scan_ubains_error_log_to_md "$log_path" "$title" "$ERROR_TAIL_LINES" >> "$out_file"
else
{
echo "#### ${title}"
echo
echo "- 文件:\`${log_path}\`"
echo "- ⚠️ 未找到日志文件"
echo
} >> "$out_file"
fi
done < <(get_meeting_internal_error_logs "$PLATFORM_TYPE")
if [[ "$any_internal" -eq 0 ]]; then
echo "- ⚠️ 对内日志未发现可扫描的 ERROR 文件(全部缺失或不可读)。" >> "$out_file"
echo >> "$out_file"
fi
# 2.2 对外(候选:同目录 or nginx)
{
echo "### 2.2 对外日志"
echo
echo
} >> "$out_file"
local any_external=0
# 用去重避免 nginx 路径被重复扫描多次
local seen_tmp
seen_tmp="$(mktemp)"
: > "$seen_tmp"
while IFS='|' read -r title log_path; do
[[ -z "$log_path" ]] && continue
# 去重 key
if grep -Fxq "$log_path" "$seen_tmp"; then
continue
fi
echo "$log_path" >> "$seen_tmp"
if [[ -f "$log_path" ]]; then
any_external=1
# nginx access.log 不一定含 ERROR;scan_ubains_error_log_to_md 会按时间戳过滤并聚合
scan_ubains_error_log_to_md "$log_path" "$title" "$ERROR_TAIL_LINES" >> "$out_file"
else
{
echo "#### ${title}"
echo
echo "- 文件:\`${log_path}\`"
echo "- (未找到,已跳过)"
echo
} >> "$out_file"
fi
done < <(get_meeting_external_error_logs "$PLATFORM_TYPE")
rm -f "$seen_tmp" 2>/dev/null || true
if [[ "$any_external" -eq 0 ]]; then
echo "- ⚠️ 对外日志未发现可扫描的文件(全部缺失或不可读)。" >> "$out_file"
echo >> "$out_file"
fi
}
#################### 5. 报告输出(md) ####################
write_md_report() {
mkdir -p "$REPORT_DIR" 2>/dev/null || true
......@@ -1067,6 +1373,7 @@ write_md_report() {
disk_peak_time="$(date -d @"$DISK_PEAK_TS" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S')"
fi
# 1) 先写“主报告第一部分:暴涨概览”
{
echo "# 自动化服务监测报告"
echo
......@@ -1098,15 +1405,22 @@ write_md_report() {
echo
done
fi
echo
} > "$report_file"
echo "## 二、内存资源消耗"
# 2) 再追加:ERROR 日志监测(独立于暴涨)
write_error_log_section_to_report "$report_file"
# 3) 再追加:其余章节(编号顺延)
{
echo "## 三、内存资源消耗"
echo
echo "- 当前内存使用:${MEM_LAST_USED_MB} MB"
echo "- 内存使用平均值:${mem_avg_used} MB"
echo "- 内存使用峰值:${MEM_PEAK_USED_MB} MB"
echo "- 峰值发生时间:${mem_peak_time}"
echo
echo "## 、MySQL 连接数监测"
echo "## 、MySQL 连接数监测"
echo
echo "- 当前 MySQL 连接数:${MYSQL_LAST_CONN}"
echo "- MySQL 连接平均值:${mysql_avg_conn}"
......@@ -1115,9 +1429,7 @@ write_md_report() {
echo "- 最近暴涨判定状态:**${MYSQL_LAST_BURST_STATUS}**"
echo "- 暴涨详情:${MYSQL_LAST_BURST_DESC}"
echo
# 新增:硬盘空间
echo "## 四、硬盘空间检测"
echo "## 五、硬盘空间检测"
echo
echo "- 当前硬盘使用率(根分区 / ):${DISK_LAST_USED_PCT}%"
echo "- 硬盘使用率平均值:${disk_avg_used}%"
......@@ -1126,11 +1438,9 @@ write_md_report() {
echo
echo "> 说明:为保证稳定性,当前仅采集根分区(/)的使用率,不再采集/输出 df 详细列表。"
echo
# 原来的容器章节顺延编号
echo "## 五、容器信息检测"
echo "## 六、容器信息检测"
echo
echo "### 5.1 运行中的容器"
echo "### 6.1 运行中的容器"
echo
if [[ -z "${CONTAINER_RUNNING_LIST//[[:space:]]/}" ]]; then
echo "- 未采集到运行中的容器信息。"
......@@ -1143,7 +1453,7 @@ write_md_report() {
done
fi
echo
echo "### 5.2 未运行的容器(Exited/其它非 Up 状态)"
echo "### 6.2 未运行的容器(Exited/其它非 Up 状态)"
echo
if [[ -z "${CONTAINER_EXITED_LIST//[[:space:]]/}" ]]; then
echo "- 未采集到未运行的容器信息。"
......@@ -1156,9 +1466,9 @@ write_md_report() {
done
fi
echo
echo "> 说明:本报告由 \`AutomatedServiceMonitoring.sh\` 自动生成,仅反映本次执行时刻的监测结果(当前版本未启用 ERROR 上下文分析)。"
echo "> 说明:本报告由 \`AutomatedServiceMonitoring.sh\` 自动生成,仅反映本次执行时刻的监测结果。"
echo
} > "$report_file"
} >> "$report_file"
echo "$report_file"
}
......
......@@ -2,9 +2,6 @@
### 如何使用claude code快速获取弹窗的所有信息元素
在输入框输入:
用chrome浏览器打开这个链接: https://192.168.5.44/#/LoginAdmin。然后,通过chrome devtools mcp完成:输入账号 admin@xty 密码Ubains@4321 验证码csba
完成登录,点击【运维设置】按钮展开运维设置模块,点击【信息模板】进入模块,"获取信息模板模块中点击【新增】按钮后弹出的弹窗里所有表单元素和按钮的XPath,用placeholder或button文本定位",按照这个流程你将信息
告警,转录配置模块下的组织设置编辑、人员设置中的人员添加、区域设置中的区域添加、热词类别的热词添加、集控管理-版本升级模块、会议概览-全景地图模块-搜索框、会议概览-会议指引的新增、会议室指引的新增、授权管理-
会议授权的搜索框、日志管理-操作日志的搜索框、通知日志的搜索框、释放日志的搜索框、联动日志的搜索框、运维日志的搜索框的元素都依次获取一下,获取不到的可以跳过并记录下来,最终输出表格给我吧。用chrome浏览器打开这个链接: https://192.168.5.44/#/LoginAdmin。然后,通过chrome devtools mcp完成:输入账号 admin@xty 密码Ubains@4321 验证码csba
完成登录,点击【运维设置】按钮展开运维设置模块,点击【信息模板】进入模块,"获取信息模板模块中点击【新增】按钮后弹出的弹窗里所有表单元素和按钮的XPath,用placeholder或button文本定位",按照这个流程你将信息
告警,转录配置模块下的组织设置编辑、人员设置中的人员添加、区域设置中的区域添加、热词类别的热词添加、集控管理-版本升级模块、会议概览-全景地图模块-搜索框、会议概览-会议指引的新增、会议室指引的新增、授权管理-
会议授权的搜索框、日志管理-操作日志的搜索框、通知日志的搜索框、释放日志的搜索框、联动日志的搜索框、运维日志的搜索框的元素都依次获取一下,获取不到的可以跳过并记录下来,最终输出表格给我吧。
\ No newline at end of file
完成登录,点击【运维设置】按钮展开运维设置模块,点击【信息模板】进入模块,"获取信息模板模块中点击【新增】按钮后弹出的弹窗里所有表单元素和按钮的XPath,用placeholder或button文本定位",按照这个流程你将信息告警,转录配置模块下的组织设置编辑、人员设置中的人员添加、区域设置中的区域添加、热词类别的热词添加、集控管理-版本升级模块、会议概览-全景地图模块-搜索框、会议概览-会议指引的新增、会议室指引的新增、授权管理-会议授权的搜索框、日志管理-操作日志的搜索框、通知日志的搜索框、释放日志的搜索框、联动日志的搜索框、运维日志的搜索框的元素都依次获取一下,获取不到的可以跳过并记录下来,最终输出表格给我吧。
用chrome浏览器打开这个链接: https://192.168.5.44/#/LoginAdmin。然后,通过chrome devtools mcp完成:输入账号 admin@xty 密码Ubains@4321 验证码csba完成登录,点击【运维设置】按钮展开运维设置模块,点击【信息模板】进入模块,"获取信息模板模块中点击【新增】按钮后弹出的弹窗里所有表单元素和按钮的XPath,用placeholder或button文本定位",按照这个流程你将信息告警,转录配置模块下的组织设置编辑、人员设置中的人员添加、区域设置中的区域添加、热词类别的热词添加、集控管理-版本升级模块、会议概览-全景地图模块-搜索框、会议概览-会议指引的新增、会议室指引的新增、授权管理-会议授权的搜索框、日志管理-操作日志的搜索框、通知日志的搜索框、释放日志的搜索框、联动日志的搜索框、运维日志的搜索框的元素都依次获取一下,获取不到的可以跳过并记录下来,最终输出表格给我吧。
\ No newline at end of file
# 服务自检需求说明文档
# 服务检测需求说明文档
## 📋 概述
......@@ -35,7 +35,7 @@
或 delta_lines/elapsed ≥ rate_threshold_per_sec(默认 5 行/秒),判定为暴涨。
记录暴涨时间段为 [上次采样时间 ~ 本次采样时间],分别用服务器 date -d @epoch 转成人类可读时间。
- 记录所有ERROR日志信息上下50行的时间段
- 通过监测ubains-ERROR.log记录最近1小时内的ERROR日志信息的时间段,补充对于对内与对外日志的同时监测。
新平台:
预定系统:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论