解决日志api的message字段为空的问题

This commit is contained in:
Alex Yang
2025-12-05 13:25:34 +08:00
parent f429c340fa
commit b9f32dff3b
11 changed files with 193033 additions and 70 deletions

View File

@@ -337,6 +337,16 @@ func HandleMetricsPost(c *gin.Context) {
compatDisk[mountpoint] = diskMetrics.UsedPercent
}
// 转换日志格式为LogMetrics
logMetricsList := make([]LogMetrics, 0, len(req.Logs))
for _, logEntry := range req.Logs {
logMetricsList = append(logMetricsList, LogMetrics{
Source: logEntry.Source,
Time: logEntry.Time.Format(time.RFC3339),
Message: logEntry.Message,
})
}
metrics := map[string]interface{}{
"cpu": req.CPU,
"cpu_hz": req.CPUHz,
@@ -349,6 +359,7 @@ func HandleMetricsPost(c *gin.Context) {
"rx_bytes": totalRxBytes,
},
"network_interfaces": req.Network,
"logs": logMetricsList,
}
broadcastMetricsUpdate(deviceID, metrics)
}
@@ -475,12 +486,12 @@ func broadcastMetricsUpdate(deviceID string, metrics map[string]interface{}) {
// GetCPUMetrics 获取CPU指标
func GetCPUMetrics(c *gin.Context) {
// 获取查询参数
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
startTime := c.DefaultQuery("start_time", "-1h") // 缩短默认查询时间范围到1小时减少默认数据量
endTime := c.DefaultQuery("end_time", "now()")
aggregation := c.DefaultQuery("aggregation", "average")
interval := c.DefaultQuery("interval", "10s") // 添加interval参数默认10秒
limitStr := c.DefaultQuery("limit", "5000") // 添加limit参数默认5000条记录
limitStr := c.DefaultQuery("limit", "5000") // 添加limit参数默认5000条记录
limit, _ := strconv.Atoi(limitStr)
// 查询数据
@@ -505,12 +516,12 @@ func GetCPUMetrics(c *gin.Context) {
// GetMemoryMetrics 获取内存指标
func GetMemoryMetrics(c *gin.Context) {
// 获取查询参数
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
startTime := c.DefaultQuery("start_time", "-1h") // 缩短默认查询时间范围到1小时减少默认数据量
endTime := c.DefaultQuery("end_time", "now()")
aggregation := c.DefaultQuery("aggregation", "average")
interval := c.DefaultQuery("interval", "10s") // 添加interval参数默认10秒
limitStr := c.DefaultQuery("limit", "5000") // 添加limit参数默认5000条记录
limitStr := c.DefaultQuery("limit", "5000") // 添加limit参数默认5000条记录
limit, _ := strconv.Atoi(limitStr)
// 查询数据
@@ -535,12 +546,12 @@ func GetMemoryMetrics(c *gin.Context) {
// GetDiskMetrics 获取磁盘指标
func GetDiskMetrics(c *gin.Context) {
// 获取查询参数
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
startTime := c.DefaultQuery("start_time", "-1h") // 缩短默认查询时间范围到1小时减少默认数据量
endTime := c.DefaultQuery("end_time", "now()")
aggregation := c.DefaultQuery("aggregation", "average")
interval := c.DefaultQuery("interval", "10s") // 添加interval参数默认10秒
limitStr := c.DefaultQuery("limit", "5000") // 添加limit参数默认5000条记录
limitStr := c.DefaultQuery("limit", "5000") // 添加limit参数默认5000条记录
limit, _ := strconv.Atoi(limitStr)
// 查询数据
@@ -580,12 +591,12 @@ func GetDiskMetrics(c *gin.Context) {
// GetNetworkMetrics 获取网络指标
func GetNetworkMetrics(c *gin.Context) {
// 获取查询参数
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
startTime := c.DefaultQuery("start_time", "-1h") // 缩短默认查询时间范围到1小时减少默认数据量
endTime := c.DefaultQuery("end_time", "now()")
aggregation := c.DefaultQuery("aggregation", "average")
interval := c.DefaultQuery("interval", "10s") // 添加interval参数默认10秒
limitStr := c.DefaultQuery("limit", "5000") // 添加limit参数默认5000条记录
limitStr := c.DefaultQuery("limit", "5000") // 添加limit参数默认5000条记录
limit, _ := strconv.Atoi(limitStr)
// 查询发送和接收的网络速率指标
@@ -779,18 +790,18 @@ func GetAllDeviceStatus(c *gin.Context) {
allDevices := deviceStorage.GetDevices()
// 查询每个设备的状态
result := make([]map[string]interface{}, 0, len(allDevices))
for _, device := range allDevices {
// 查询设备监控数据
_, status, _ := globalStorage.QueryDeviceStatus(context.Background(), device.ID)
result := make([]map[string]interface{}, 0, len(allDevices))
for _, device := range allDevices {
// 查询设备监控数据
_, status, _ := globalStorage.QueryDeviceStatus(context.Background(), device.ID)
// 总是返回设备信息,无论是否有监控数据
result = append(result, map[string]interface{}{
"id": device.ID,
"name": device.Name,
"status": status,
})
}
// 总是返回设备信息,无论是否有监控数据
result = append(result, map[string]interface{}{
"id": device.ID,
"name": device.Name,
"status": status,
})
}
c.JSON(http.StatusOK, gin.H{
"devices": result,
@@ -1011,7 +1022,13 @@ func GetDiskDetails(c *gin.Context) {
// GetLogs 获取系统日志
func GetLogs(c *gin.Context) {
// 获取查询参数
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
deviceID := c.Query("device_id") // 必须参数,不能为空
if deviceID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "device_id is required",
})
return
}
startTime := c.DefaultQuery("start_time", "-24h")
endTime := c.DefaultQuery("end_time", "now()")
@@ -1063,6 +1080,6 @@ func GetLogs(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"data": logs,
"logs": logs,
})
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"log"
"math/rand"
"sort"
"strconv"
"strings"
"time"
@@ -82,7 +83,7 @@ func (s *Storage) Close() {
}
// 写入数据到InfluxDB带重试机制
func (s *Storage) writeData(ctx context.Context, measurement string, tags map[string]string, fields map[string]interface{}, deviceID, metricType string) error {
func (s *Storage) writeData(ctx context.Context, measurement string, tags map[string]string, fields map[string]interface{}, deviceID, metricType string, timestamp ...time.Time) error {
// 重试配置 - 减少重试次数和延迟,确保在超时时间内完成
maxRetries := 2
baseDelay := 200 * time.Millisecond
@@ -113,7 +114,14 @@ func (s *Storage) writeData(ctx context.Context, measurement string, tags map[st
}
fieldList = append(fieldList, fieldStr)
}
line := fmt.Sprintf("%s,%s %s %d", measurement, formatTags(tags), strings.Join(fieldList, ","), time.Now().UnixNano())
// 确定时间戳
var ts int64
if len(timestamp) > 0 {
ts = timestamp[0].UnixNano()
} else {
ts = time.Now().UnixNano()
}
line := fmt.Sprintf("%s,%s %s %d", measurement, formatTags(tags), strings.Join(fieldList, ","), ts)
err := writeAPI.WriteRecord(ctx, line)
if err == nil {
@@ -248,8 +256,8 @@ func (s *Storage) WriteLogMetric(ctx context.Context, deviceID string, sequence
"message": message,
}
// 使用新的writeData方法
return s.writeData(ctx, "logs", allTags, fields, deviceID, "log")
// 使用新的writeData方法,传入日志的实际时间
return s.writeData(ctx, "logs", allTags, fields, deviceID, "log", time)
}
// QueryMetrics 查询监控指标,支持采样
@@ -577,7 +585,7 @@ func (s *Storage) QueryLogMetrics(ctx context.Context, deviceID string, startTim
// 按时间倒序排列,获取最新的日志
query += `
|> sort(columns: ["_time"], desc: true)
|> limit(n: 100)` // 限制返回100条最新日志
|> limit(n: 200)` // 限制返回200条记录因为message和sequence是分开存储的
// 执行查询
queryResult, err := queryAPI.Query(ctx, query)
@@ -586,36 +594,65 @@ func (s *Storage) QueryLogMetrics(ctx context.Context, deviceID string, startTim
}
defer queryResult.Close()
// 存储日志数据
logs := make([]map[string]interface{}, 0)
// 使用map存储日志数据key是时间戳和source的组合
logMap := make(map[string]map[string]interface{})
// 处理查询结果
for queryResult.Next() {
if queryResult.TableChanged() {
// 表结构变化,跳过
continue
}
// 获取记录
record := queryResult.Record()
// 构建日志数据
logData := map[string]interface{}{
"time": record.Time(),
"device_id": record.ValueByKey("device_id"),
"source": record.ValueByKey("source"),
"sequence": record.ValueByKey("sequence"),
"message": record.ValueByKey("message"),
"agent_name": record.ValueByKey("agent_name"),
// 获取时间和source作为唯一键
timeStr := record.Time().Format(time.RFC3339Nano)
source := record.ValueByKey("source").(string)
key := timeStr + "-" + source
// 检查是否已经有这个日志条目的基础信息
logData, exists := logMap[key]
if !exists {
// 创建新的日志条目
logData = map[string]interface{}{
"time": record.Time(),
"device_id": record.ValueByKey("device_id"),
"source": source,
"sequence": nil,
"message": nil,
"agent_name": record.ValueByKey("agent_name"),
}
logMap[key] = logData
}
// 根据字段类型更新相应的值
field := record.Field()
switch field {
case "message":
logData["message"] = record.Value()
case "sequence":
logData["sequence"] = record.Value()
}
// 添加到日志列表
logs = append(logs, logData)
}
if queryResult.Err() != nil {
return nil, queryResult.Err()
}
// 将map转换为切片
logs := make([]map[string]interface{}, 0, len(logMap))
for _, logData := range logMap {
logs = append(logs, logData)
}
// 按时间倒序排序
sort.Slice(logs, func(i, j int) bool {
timeI := logs[i]["time"].(time.Time)
timeJ := logs[j]["time"].(time.Time)
return timeI.After(timeJ)
})
// 限制返回100条最新日志
if len(logs) > 100 {
logs = logs[:100]
}
return logs, nil
}

View File

@@ -2,8 +2,10 @@ package main
import (
"fmt"
"io"
"log"
"os"
"time"
"github.com/gin-gonic/gin"
"github.com/monitor/backend/config"
@@ -34,9 +36,17 @@ func isDefaultDBConfig(cfg *config.Config) bool {
// main 函数启动服务器
func main() {
// 配置日志:只输出必要的信息,禁用调试日志
// 配置日志:同时输出到文件和标准输出
logFileName := fmt.Sprintf("monitor-backend-%s.log", time.Now().Format("2006-01-02"))
logFile, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Printf("Warning: Failed to open log file %s, logging only to stdout: %v", logFileName, err)
} else {
defer logFile.Close()
// 创建一个多输出写入器,同时写入文件和标准输出
log.SetOutput(io.MultiWriter(os.Stdout, logFile))
}
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.SetOutput(os.Stdout)
// 加载配置
cfg, err := config.LoadConfig()
@@ -96,8 +106,9 @@ func main() {
r := gin.New()
// 添加必要的中间件
r.Use(gin.Recovery())
// 禁用Gin的默认日志
r.Use(gin.LoggerWithWriter(gin.DefaultWriter, "/health"))
// 设置Gin的日志输出到文件和标准输出
ginLogger := log.New(io.MultiWriter(os.Stdout, logFile), "[GIN] ", log.Ldate|log.Ltime)
r.Use(gin.LoggerWithWriter(ginLogger.Writer()))
// 设置CORS
r.Use(func(c *gin.Context) {

File diff suppressed because it is too large Load Diff

Binary file not shown.

8947
backend/monitor-server.log Normal file

File diff suppressed because it is too large Load Diff