2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 04:22:33 +08:00
Files
panel/internal/data/log.go
Copilot f2e41a3364 feat: 添加操作日志记录功能和前端日志查看页面 (#1227)
* Initial plan

* feat: 添加操作日志记录功能和前端日志查看页面

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: 修复 HttpLog 中 IP 字符串处理的类型安全问题

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

* feat: 完善面板内日志记录,添加网站/数据库/项目/计划任务/备份CRUD日志

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: 完善更多模块的日志记录(证书/SSH/Webhook/用户/设置/安全)

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: 改进cert_dns删除方法,在删除前获取实体信息用于日志记录

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

* feat: 从session获取操作员ID并在前端显示用户名

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-01-12 23:31:22 +08:00

180 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package data
import (
"bufio"
"encoding/json"
"os"
"path/filepath"
"strings"
"time"
"gorm.io/gorm"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/biz"
)
type logRepo struct {
db *gorm.DB
}
func NewLogRepo(db *gorm.DB) biz.LogRepo {
return &logRepo{
db: db,
}
}
// List 获取日志列表
func (r *logRepo) List(logType string, limit int) ([]biz.LogEntry, error) {
var filename string
switch logType {
case biz.LogTypeApp:
filename = "app.log"
case biz.LogTypeDB:
filename = "db.log"
case biz.LogTypeHTTP:
filename = "http.log"
default:
filename = "app.log"
}
logPath := filepath.Join(app.Root, "panel/storage/logs", filename)
file, err := os.Open(logPath)
if err != nil {
if os.IsNotExist(err) {
return []biz.LogEntry{}, nil
}
return nil, err
}
defer func(file *os.File) { _ = file.Close() }(file)
// 读取所有行
var lines []string
scanner := bufio.NewScanner(file)
// 增加缓冲区大小以处理较长的日志行
buf := make([]byte, 0, 64*1024)
scanner.Buffer(buf, 1024*1024)
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) != "" {
lines = append(lines, line)
}
}
if err = scanner.Err(); err != nil {
return nil, err
}
// 从末尾取指定数量的行
start := 0
if len(lines) > limit {
start = len(lines) - limit
}
lines = lines[start:]
// 倒序处理,最新的在前面
entries := make([]biz.LogEntry, 0, len(lines))
for i := len(lines) - 1; i >= 0; i-- {
entry, err := r.parseLine(lines[i], logType)
if err != nil {
continue
}
entries = append(entries, entry)
}
// 如果是app日志查询用户名
if logType == biz.LogTypeApp {
r.fillOperatorNames(entries)
}
return entries, nil
}
// fillOperatorNames 填充操作员用户名
func (r *logRepo) fillOperatorNames(entries []biz.LogEntry) {
// 收集所有用户ID
userIDs := make(map[uint]bool)
for _, entry := range entries {
if entry.OperatorID > 0 {
userIDs[entry.OperatorID] = true
}
}
if len(userIDs) == 0 {
return
}
// 批量查询用户名
ids := make([]uint, 0, len(userIDs))
for id := range userIDs {
ids = append(ids, id)
}
var users []biz.User
r.db.Select("id", "username").Where("id IN ?", ids).Find(&users)
// 构建ID到用户名的映射
userMap := make(map[uint]string)
for _, user := range users {
userMap[user.ID] = user.Username
}
// 填充用户名
for i := range entries {
if entries[i].OperatorID > 0 {
if username, ok := userMap[entries[i].OperatorID]; ok {
entries[i].OperatorName = username
}
}
}
}
// parseLine 解析日志行
func (r *logRepo) parseLine(line string, logType string) (biz.LogEntry, error) {
var rawEntry map[string]any
if err := json.Unmarshal([]byte(line), &rawEntry); err != nil {
return biz.LogEntry{}, err
}
entry := biz.LogEntry{
Extra: make(map[string]any),
}
// 解析通用字段
if t, ok := rawEntry["time"].(string); ok {
if parsed, err := time.Parse(time.RFC3339Nano, t); err == nil {
entry.Time = parsed
}
}
if level, ok := rawEntry["level"].(string); ok {
entry.Level = level
}
if msg, ok := rawEntry["msg"].(string); ok {
entry.Msg = msg
}
// 解析操作日志特有字段
if logType == biz.LogTypeApp {
if t, ok := rawEntry["type"].(string); ok {
entry.Type = t
}
if opID, ok := rawEntry["operator_id"].(float64); ok {
entry.OperatorID = uint(opID)
}
}
// 其他字段放入Extra
excludeKeys := map[string]bool{
"time": true, "level": true, "msg": true, "type": true, "operator_id": true,
}
for k, v := range rawEntry {
if !excludeKeys[k] {
entry.Extra[k] = v
}
}
return entry, nil
}