# 摄影作品集网站 - 日志管理方案 ## 🎯 方案概述 这是一个**简单实用的日志管理方案**,专注于日志收集和问题修复。日志查看功能集成到管理后台,提供友好的Web界面。 ### 设计原则 - **集成化**: 日志查看功能集成到管理后台 - **用户友好**: 提供美观易用的Web界面 - **问题导向**: 专注于快速定位和修复问题 - **低维护成本**: 几乎零维护的方案 - **渐进式**: 后续可以根据需要扩展 ## 📋 核心组件 ### 日志管理架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ 后端应用日志 │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 错误日志 │ │ 访问日志 │ │ 业务日志 │ │ │ │ (JSON格式) │ │ (HTTP日志) │ │ (操作日志) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ 管理后台日志模块 │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 日志查看器 │ │ 实时监控 │ │ 统计分析 │ │ │ │ (Web界面) │ │ (自动刷新) │ │ (图表展示) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ API接口层 │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 日志查询API │ │ 统计API │ │ 搜索API │ │ │ │(/api/logs) │ │(/api/stats) │ │(/api/search)│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## 🔧 技术选择 ### 日志方案 - **日志存储**: 本地文件 (JSON格式) - **日志轮转**: lumberjack.v2 - **日志查看**: 管理后台Web界面 - **Trace ID**: 集成OpenTracing (已在架构文档中添加) - **权限控制**: 基于管理后台的用户权限 ### 集成方式 - **前端**: 管理后台的日志管理模块 - **后端**: Gin路由提供日志查询API - **认证**: 复用管理后台的登录认证 - **权限**: 仅管理员可访问日志功能 ## 📝 日志配置 ### 1. 应用日志配置 #### 日志配置文件 (config.yaml) ```yaml # config/config.yaml logger: level: "info" format: "json" output: "file" filename: "/app/logs/app.log" max_size: 100 # MB max_age: 7 # days compress: true # 可选:如果需要链路追踪 tracing: enabled: true service_name: "photography-backend" jaeger: endpoint: "http://localhost:14268/api/traces" sampling_rate: 1.0 ``` ### 2. 日志格式标准化 #### 统一的日志格式 ```json { "timestamp": "2024-01-15T10:30:00Z", "level": "info", "message": "Photo created successfully", "service": "photography-backend", "trace_id": "abc123def456", "request_id": "req-789", "user_id": "user-123", "operation": "create_photo", "photo_id": 1001, "duration": 0.5, "error": null } ``` ### 3. 日志分类 #### 三种核心日志类型 ```bash # 日志目录结构 logs/ ├── app.log # 应用日志 (所有级别) ├── error.log # 错误日志 (ERROR级别) └── access.log # HTTP访问日志 ``` #### 日志级别使用 ```go // 日志级别使用指南 logger.Info("正常业务操作") // 记录重要的业务操作 logger.Warn("需要关注的情况") // 记录警告信息 logger.Error("错误情况") // 记录错误信息 logger.Debug("调试信息") // 开发调试用 ``` ## 🔍 问题诊断工具 ### 1. 命令行工具 #### 快速查看错误 ```bash #!/bin/bash # scripts/check-errors.sh echo "📋 最近的错误日志 (最近1小时):" tail -f /app/logs/app.log | grep -i error | jq -r '.timestamp + " " + .message' echo "📊 错误统计:" grep -i error /app/logs/app.log | jq -r '.message' | sort | uniq -c | sort -nr | head -10 ``` #### 按trace ID查找日志 ```bash #!/bin/bash # scripts/find-by-trace.sh TRACE_ID=$1 if [ -z "$TRACE_ID" ]; then echo "用法: ./find-by-trace.sh " exit 1 fi echo "🔍 查找 trace ID: $TRACE_ID" grep "$TRACE_ID" /app/logs/app.log | jq -r '.timestamp + " [" + .level + "] " + .message' ``` #### 实时监控错误 ```bash #!/bin/bash # scripts/monitor-errors.sh echo "🚨 实时错误监控 (按Ctrl+C退出):" tail -f /app/logs/app.log | grep --line-buffered -i error | while read line; do echo -e "\033[31m[ERROR]\033[0m $(echo "$line" | jq -r '.timestamp + " " + .message')" done ``` ### 2. 管理后台日志模块 #### 日志查看组件 (React/Vue) ```html 📋 日志查看器

📋 日志查看器

📊 正在加载统计...
📱 正在加载日志...
``` #### 管理后台日志API接口 集成到现有的管理后台后端: ```go // internal/api/handlers/admin/logs_handler.go package admin import ( "bufio" "encoding/json" "net/http" "os" "strconv" "strings" "github.com/gin-gonic/gin" "photography-backend/pkg/middleware" ) // LogEntry 日志条目 type LogEntry struct { Timestamp string `json:"timestamp"` Level string `json:"level"` Message string `json:"message"` TraceID string `json:"trace_id,omitempty"` UserID string `json:"user_id,omitempty"` Operation string `json:"operation,omitempty"` } // LogHandler 日志处理器 type LogHandler struct { logFile string } // NewLogHandler 创建日志处理器 func NewLogHandler(logFile string) *LogHandler { return &LogHandler{logFile: logFile} } // GetLogs 获取日志列表 // @Summary 获取系统日志 // @Description 获取系统日志列表,支持过滤和搜索 // @Tags 日志管理 // @Accept json // @Produce json // @Param level query string false "日志级别" Enums(error,warn,info,debug) // @Param search query string false "搜索关键词" // @Param trace_id query string false "Trace ID" // @Param lines query int false "返回行数" default(100) // @Success 200 {object} LogListResponse // @Failure 401 {object} ErrorResponse // @Failure 403 {object} ErrorResponse // @Failure 500 {object} ErrorResponse // @Router /admin/api/logs [get] func (h *LogHandler) GetLogs(c *gin.Context) { // 获取参数 levelFilter := c.Query("level") searchFilter := c.Query("search") traceID := c.Query("trace_id") lines := 100 if l := c.Query("lines"); l != "" { if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 1000 { lines = parsed } } // 读取日志文件 logs, err := h.readLogs(lines, levelFilter, searchFilter, traceID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "读取日志失败", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": gin.H{ "logs": logs, "total": len(logs), }, }) } // GetLogStats 获取日志统计 // @Summary 获取日志统计信息 // @Description 获取各级别日志的统计数据 // @Tags 日志管理 // @Accept json // @Produce json // @Success 200 {object} LogStatsResponse // @Failure 401 {object} ErrorResponse // @Failure 403 {object} ErrorResponse // @Failure 500 {object} ErrorResponse // @Router /admin/api/logs/stats [get] func (h *LogHandler) GetLogStats(c *gin.Context) { stats := map[string]int{ "total": 0, "error": 0, "warn": 0, "info": 0, "debug": 0, } file, err := os.Open(h.logFile) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "读取日志文件失败", "details": err.Error(), }) return } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() if line == "" { continue } stats["total"]++ // 解析日志级别 var entry LogEntry if err := json.Unmarshal([]byte(line), &entry); err == nil { if count, exists := stats[entry.Level]; exists { stats[entry.Level] = count + 1 } } } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": stats, }) } // readLogs 读取日志文件 func (h *LogHandler) readLogs(maxLines int, levelFilter, searchFilter, traceFilter string) ([]LogEntry, error) { file, err := os.Open(h.logFile) if err != nil { return nil, err } defer file.Close() var lines []string scanner := bufio.NewScanner(file) for scanner.Scan() { lines = append(lines, scanner.Text()) } // 从后往前读取最新的日志 start := len(lines) - maxLines*2 // 预读更多行用于过滤 if start < 0 { start = 0 } var logs []LogEntry for i := start; i < len(lines) && len(logs) < maxLines; i++ { line := lines[i] if line == "" { continue } var entry LogEntry if err := json.Unmarshal([]byte(line), &entry); err != nil { continue // 跳过无法解析的行 } // 应用过滤条件 if levelFilter != "" && entry.Level != levelFilter { continue } if searchFilter != "" { searchLower := strings.ToLower(searchFilter) if !strings.Contains(strings.ToLower(entry.Message), searchLower) && !strings.Contains(strings.ToLower(entry.TraceID), searchLower) && !strings.Contains(strings.ToLower(entry.Operation), searchLower) { continue } } if traceFilter != "" && !strings.Contains(entry.TraceID, traceFilter) { continue } logs = append(logs, entry) } // 反转数组,让最新的日志在前面 for i, j := 0, len(logs)-1; i < j; i, j = i+1, j-1 { logs[i], logs[j] = logs[j], logs[i] } return logs, nil } // RegisterLogRoutes 注册日志相关路由 func RegisterLogRoutes(r *gin.RouterGroup, logFile string) { logHandler := NewLogHandler(logFile) // 需要管理员权限的路由组 adminGroup := r.Group("") adminGroup.Use(middleware.RequireAuth()) // 需要登录 adminGroup.Use(middleware.RequireAdmin()) // 需要管理员权限 { adminGroup.GET("/logs", logHandler.GetLogs) adminGroup.GET("/logs/stats", logHandler.GetLogStats) } } ``` #### 管理后台前端集成 在管理后台中添加日志管理页面: ```javascript // admin/src/pages/Logs/LogViewer.jsx import React, { useState, useEffect } from 'react'; import { Card, Table, Select, Input, Button, Tag, Space, Statistic, Row, Col, message } from 'antd'; import { ReloadOutlined, SearchOutlined, ExclamationCircleOutlined, WarningOutlined, InfoCircleOutlined, BugOutlined } from '@ant-design/icons'; import { adminApi } from '../../services/api'; const { Option } = Select; const { Search } = Input; const LogViewer = () => { const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(false); const [filters, setFilters] = useState({ level: '', search: '', trace_id: '', lines: 100, }); const [stats, setStats] = useState({}); const [autoRefresh, setAutoRefresh] = useState(false); // 日志级别配置 const levelConfig = { error: { color: 'red', icon: }, warn: { color: 'orange', icon: }, info: { color: 'blue', icon: }, debug: { color: 'default', icon: }, }; // 获取日志数据 const fetchLogs = async () => { setLoading(true); try { const response = await adminApi.get('/logs', { params: filters }); setLogs(response.data.data.logs || []); } catch (error) { message.error('获取日志失败'); } finally { setLoading(false); } }; // 获取统计数据 const fetchStats = async () => { try { const response = await adminApi.get('/logs/stats'); setStats(response.data.data || {}); } catch (error) { console.error('获取统计数据失败', error); } }; useEffect(() => { fetchLogs(); fetchStats(); }, [filters]); // 自动刷新 useEffect(() => { let interval; if (autoRefresh) { interval = setInterval(() => { fetchLogs(); fetchStats(); }, 5000); } return () => interval && clearInterval(interval); }, [autoRefresh, filters]); // 表格列配置 const columns = [ { title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 180, render: (timestamp) => new Date(timestamp).toLocaleString(), }, { title: '级别', dataIndex: 'level', key: 'level', width: 80, render: (level) => { const config = levelConfig[level] || levelConfig.info; return ( {level.toUpperCase()} ); }, }, { title: 'Trace ID', dataIndex: 'trace_id', key: 'trace_id', width: 120, render: (traceId) => traceId ? ( ) : '-', }, { title: '消息', dataIndex: 'message', key: 'message', ellipsis: true, render: (message, record) => { // 高亮搜索关键词 if (filters.search && message.toLowerCase().includes(filters.search.toLowerCase())) { const regex = new RegExp(`(${filters.search})`, 'gi'); const parts = message.split(regex); return parts.map((part, index) => part.toLowerCase() === filters.search.toLowerCase() ? {part} : part ); } return message; }, }, { title: '操作', dataIndex: 'operation', key: 'operation', width: 120, render: (operation) => operation || '-', }, ]; return (
{/* 统计卡片 */} } /> } /> } /> {/* 过滤控件 */} setFilters(prev => ({ ...prev, search: e.target.value }))} prefix={} /> setFilters(prev => ({ ...prev, trace_id: e.target.value }))} /> {/* 日志表格 */} `${record.timestamp}-${index}`} pagination={{ showSizeChanger: false, showQuickJumper: true, showTotal: (total) => `共 ${total} 条日志`, }} scroll={{ x: 800 }} /> ); }; export default LogViewer; ``` ## 🚀 集成到管理后台 ### 1. 后端集成步骤 #### 在main.go中注册日志路由 ```go // cmd/server/main.go func main() { // ... 其他初始化代码 // 创建Gin引擎 r := gin.Default() // 注册API路由 apiGroup := r.Group("/api") { // 其他API路由... } // 注册管理后台路由 adminGroup := r.Group("/admin/api") { // 注册日志管理路由 admin.RegisterLogRoutes(adminGroup, "logs/app.log") // 其他管理后台路由... } r.Run(":8080") } ``` #### 添加权限中间件 ```go // pkg/middleware/auth.go package middleware import ( "net/http" "github.com/gin-gonic/gin" ) // RequireAuth 需要登录认证 func RequireAuth() gin.HandlerFunc { return func(c *gin.Context) { // 检查JWT token或session token := c.GetHeader("Authorization") if token == "" { c.JSON(http.StatusUnauthorized, gin.H{ "error": "未授权访问", }) c.Abort() return } // 验证token并获取用户信息 // ... token验证逻辑 c.Next() } } // RequireAdmin 需要管理员权限 func RequireAdmin() gin.HandlerFunc { return func(c *gin.Context) { // 检查用户是否是管理员 userRole := c.GetString("user_role") if userRole != "admin" { c.JSON(http.StatusForbidden, gin.H{ "error": "需要管理员权限", }) c.Abort() return } c.Next() } } ``` ### 2. 前端集成步骤 #### 在管理后台路由中添加日志管理 ```javascript // admin/src/router/index.js import LogViewer from '../pages/Logs/LogViewer.jsx'; const routes = [ // 其他路由... { path: '/admin', component: AdminLayout, children: [ { path: 'logs', component: LogViewer, meta: { title: '日志管理', requireAuth: true, requireAdmin: true } } // 其他子路由... ] } ]; ``` #### 在管理后台菜单中添加日志入口 ```javascript // admin/src/layout/AdminLayout.jsx const menuItems = [ { key: 'dashboard', icon: , label: '仪表盘', path: '/admin/dashboard' }, { key: 'photos', icon: , label: '照片管理', path: '/admin/photos' }, { key: 'logs', icon: , label: '日志管理', path: '/admin/logs' }, // 其他菜单项... ]; ``` ### 3. 配置更新 #### 更新应用配置文件 ```yaml # config/config.yaml app: name: "photography-backend" version: "1.0.0" environment: "production" logger: level: "info" format: "json" output: "file" filename: "logs/app.log" max_size: 100 max_age: 7 compress: true tracing: enabled: true service_name: "photography-backend" sampling_rate: 1.0 admin: log_access: true # 启用日志访问功能 log_retention_days: 30 # 日志保留天数 ``` ### 4. 一键部署脚本 #### 部署管理后台日志功能 ```bash #!/bin/bash # deploy-admin-logs.sh echo "🚀 部署管理后台日志功能..." # 创建必要目录 mkdir -p logs mkdir -p admin/src/pages/Logs # 确保日志文件存在 touch logs/app.log chmod 664 logs/app.log # 创建日志轮转配置 cat > /etc/logrotate.d/photography-backend << 'EOF' /path/to/photography/logs/*.log { daily rotate 30 compress delaycompress missingok notifempty create 664 app app postrotate systemctl reload photography-backend endscript } EOF # 设置权限 chown -R app:app logs/ chmod 755 logs/ echo "✅ 管理后台日志功能部署完成!" echo "" echo "📋 访问方式:" echo " 1. 登录管理后台: http://localhost:8080/admin" echo " 2. 进入日志管理页面" echo " 3. 使用管理员账号访问" echo "" echo "🔧 API地址:" echo " 日志列表: GET /admin/api/logs" echo " 日志统计: GET /admin/api/logs/stats" ``` ## 🔧 故障排查流程 ### 1. 问题诊断步骤 ```bash # 快速诊断脚本 #!/bin/bash # scripts/quick-diagnosis.sh echo "🔍 快速问题诊断" # 1. 检查服务状态 echo "1. 检查服务状态..." docker-compose ps # 2. 检查最近错误 echo "2. 最近10条错误日志..." tail -n 1000 logs/app.log | grep -i error | tail -10 | jq -r '.timestamp + " " + .message' # 3. 检查磁盘空间 echo "3. 检查磁盘空间..." df -h # 4. 检查日志文件大小 echo "4. 检查日志文件大小..." ls -lh logs/ # 5. 检查内存使用 echo "5. 检查内存使用..." docker stats --no-stream photography-backend echo "✅ 诊断完成" ``` ### 2. 常见问题解决 #### 问题1:无法找到错误原因 ```bash # 1. 获取完整的错误上下文 grep -B 5 -A 5 "error_message" logs/app.log # 2. 按时间范围查找 grep "2024-01-15T10:" logs/app.log | grep -i error ``` #### 问题2:需要追踪特定用户的操作 ```bash # 按用户ID查找 grep "user-123" logs/app.log | jq -r '.timestamp + " " + .message' # 按操作类型查找 grep "create_photo" logs/app.log | jq -r '.timestamp + " " + .message' ``` #### 问题3:日志文件太大 ```bash # 手动轮转日志 mv logs/app.log logs/app.log.old sudo systemctl restart photography-backend # 或者使用logrotate sudo logrotate -f /etc/logrotate.d/photography-backend ``` ## 🎯 总结 这个集成到管理后台的日志方案提供了: ### ✨ 核心特性 - **🏢 集成化管理** - 完全集成到管理后台,统一的用户体验 - **🔐 权限控制** - 基于管理后台的认证和授权体系 - **📊 专业界面** - 使用Ant Design组件,美观且专业 - **🔍 强大搜索** - 支持关键词、级别、Trace ID多维度过滤 - **⏰ 实时监控** - 自动刷新功能,实时观察系统状态 - **📈 统计分析** - 直观的统计卡片,快速了解系统健康状况 ### 🚀 部署简单 1. 运行部署脚本: `./deploy-admin-logs.sh` 2. 访问管理后台: `http://localhost:8080/admin` 3. 登录管理员账号,进入日志管理页面 ### 💡 使用场景 - **错误排查**: 管理员快速定位和分析错误日志 - **性能监控**: 通过Trace ID追踪完整请求链路 - **运营监控**: 实时观察系统运行状态 - **历史分析**: 搜索和分析历史日志数据 - **团队协作**: 多个管理员可以同时查看和分析日志 ### 🔧 技术优势 - **集成性**: 完全集成到现有管理后台 - **安全性**: 基于管理后台的权限控制 - **专业性**: 使用成熟的UI组件库 - **可扩展**: 易于添加新的日志分析功能 - **用户友好**: 直观的界面,无需学习成本 ### 🔐 权限控制 - **认证**: 需要管理后台登录 - **授权**: 仅管理员可访问日志功能 - **安全**: API接口有完整的权限验证 ### 📋 API接口 - `GET /admin/api/logs` - 获取日志列表 - `GET /admin/api/logs/stats` - 获取日志统计 这就是最适合生产环境的日志管理方案 - **专业、安全、易用**!