Files
hi-server/internal/logic/common/logMessageReportLogic.go
T

109 lines
3.6 KiB
Go

package common
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net"
"strings"
"time"
"github.com/gin-gonic/gin"
logmessage "github.com/perfect-panel/server/internal/model/logmessage"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type ReportLogMessageLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewReportLogMessageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ReportLogMessageLogic {
return &ReportLogMessageLogic{ Logger: logger.WithContext(ctx), ctx: ctx, svcCtx: svcCtx }
}
func (l *ReportLogMessageLogic) ReportLogMessage(req *types.ReportLogMessageRequest, c *gin.Context) (resp *types.ReportLogMessageResponse, err error) {
ip := clientIP(c)
ua := c.GetHeader("User-Agent")
locale := c.GetHeader("Accept-Language")
// 简单限流:设备ID优先,其次IP
limitKey := "logmsg:" + strings.TrimSpace(req.DeviceId)
if limitKey == "logmsg:" { limitKey = "logmsg:" + ip }
count, _ := l.svcCtx.Redis.Incr(l.ctx, limitKey).Result()
if count == 1 { _ = l.svcCtx.Redis.Expire(l.ctx, limitKey, 60*time.Second).Err() }
if count > 120 { // 每分钟最多120条
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TooManyRequests), "too many reports")
}
// 指纹生成
h := sha256.New()
h.Write([]byte(strings.Join([]string{req.Message, req.Stack, req.ErrorCode, req.AppVersion, req.Platform}, "|")))
digest := hex.EncodeToString(h.Sum(nil))
var ctxStr string
if req.Context != nil {
if b, e := json.Marshal(req.Context); e == nil {
ctxStr = string(b)
}
}
var occurredAt *time.Time
if req.OccurredAt > 0 {
t := time.UnixMilli(req.OccurredAt)
occurredAt = &t
}
var userIdPtr *int64
if req.UserId > 0 { userIdPtr = &req.UserId }
row := &logmessage.LogMessage{
Platform: req.Platform,
AppVersion: req.AppVersion,
OsName: req.OsName,
OsVersion: req.OsVersion,
DeviceId: req.DeviceId,
UserId: userIdPtr,
SessionId: req.SessionId,
Level: req.Level,
ErrorCode: req.ErrorCode,
Message: safeTruncate(req.Message, 1024*64),
Stack: safeTruncate(req.Stack, 1024*1024),
Context: ctxStr,
ClientIP: ip,
UserAgent: safeTruncate(ua, 255),
Locale: safeTruncate(locale, 16),
Digest: digest,
OccurredAt: occurredAt,
}
if err = l.svcCtx.LogMessageModel.Insert(l.ctx, row); err != nil {
// 唯一指纹冲突时尝试查询已有记录返回ID
ex, _, findErr := l.svcCtx.LogMessageModel.Filter(l.ctx, &logmessage.FilterParams{ Keyword: req.Message, Page: 1, Size: 1 })
if findErr == nil && len(ex) > 0 {
return &types.ReportLogMessageResponse{ Id: ex[0].Id }, nil
}
l.Errorf("[ReportLogMessage] insert error: %v", err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "insert log_message failed: %v", err)
}
return &types.ReportLogMessageResponse{ Id: row.Id }, nil
}
func safeTruncate(s string, n int) string {
if len(s) <= n { return s }
return s[:n]
}
func clientIP(c *gin.Context) string {
ip := c.ClientIP()
if ip != "" { return ip }
host, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
if err == nil && host != "" { return host }
return ""
}