优化:运行日志业务语义与可视化展示改造,去除内部协议干扰并统一中文呈现
Some checks are pending
Security Checks / security (push) Waiting to run
Some checks are pending
Security Checks / security (push) Waiting to run
This commit is contained in:
parent
aec0d23ac0
commit
12475f5565
@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -8,6 +9,50 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var knownActionMap = map[string]string{
|
||||
"imap_server.(*serverSession).Login": "IMAP 客户端登录",
|
||||
"imap_server.(*serverSession).Close": "IMAP 断开连接",
|
||||
"imap_server.(*serverSession).Select": "IMAP 选择邮箱",
|
||||
"imap_server.(*serverSession).Fetch": "IMAP 获取邮件",
|
||||
"imap_server.(*serverSession).Append": "IMAP 追加邮件",
|
||||
"imap_server.(*serverSession).Delete": "IMAP 删除邮箱",
|
||||
"pop3_server.action.Pass": "POP3 客户端登录",
|
||||
"pop3_server.action.Retr": "POP3 获取邮件",
|
||||
"smtp_server.(*Session).AuthPlain": "SMTP 客户端认证",
|
||||
"send.doSend": "SMTP 邮件投递",
|
||||
"send.doSend.func2": "SMTP 邮件投递",
|
||||
"send.Send": "发送邮件",
|
||||
}
|
||||
|
||||
var cleanFuncRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)\.(?:\(\*?[a-zA-Z0-9_]+\)\.)?([a-zA-Z0-9_]+)(?:\.func\d+)?$`)
|
||||
|
||||
func friendlyActionFromCaller(function string) string {
|
||||
if action, ok := knownActionMap[function]; ok {
|
||||
return action
|
||||
}
|
||||
m := cleanFuncRe.FindStringSubmatch(function)
|
||||
if len(m) == 3 {
|
||||
pkg := m[1]
|
||||
fn := m[2]
|
||||
pkgLabel := map[string]string{
|
||||
"imap_server": "IMAP 服务",
|
||||
"pop3_server": "POP3 服务",
|
||||
"smtp_server": "SMTP 服务",
|
||||
"send": "邮件投递",
|
||||
"controllers": "接口处理",
|
||||
"services": "系统服务",
|
||||
"res_init": "系统初始化",
|
||||
"hooks": "插件钩子",
|
||||
"adminreset": "超级管理",
|
||||
}[pkg]
|
||||
if pkgLabel == "" {
|
||||
pkgLabel = pkg
|
||||
}
|
||||
return pkgLabel + " · " + fn
|
||||
}
|
||||
return "运行事件 · " + function
|
||||
}
|
||||
|
||||
// runtimeEventFields 为文件日志提供稳定的事件边界;没有完成信号时只记为状态记录。
|
||||
// 不从任意消息中的“成功”或日志级别猜测业务结果,也不改变调用方的字段。
|
||||
func runtimeEventFields(entry *log.Entry) map[string]any {
|
||||
@ -30,7 +75,7 @@ func runtimeEventFields(entry *log.Entry) map[string]any {
|
||||
if index := strings.LastIndex(function, "/"); index >= 0 {
|
||||
function = function[index+1:]
|
||||
}
|
||||
action += " · " + function
|
||||
action = friendlyActionFromCaller(function)
|
||||
}
|
||||
fields["action"] = action
|
||||
}
|
||||
@ -38,4 +83,4 @@ func runtimeEventFields(entry *log.Entry) map[string]any {
|
||||
fields["result"] = "recorded"
|
||||
}
|
||||
return fields
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,11 @@ import (
|
||||
func (s *serverSession) Login(username, pwd string) error {
|
||||
user, err := auth.AuthenticateProtocol("imap", s.remoteAddress, username, pwd)
|
||||
if err != nil {
|
||||
log.WithContext(s.ctx).Info("IMAP authentication failed")
|
||||
log.WithContext(s.ctx).WithFields(log.Fields{
|
||||
"action": "IMAP 客户端登录",
|
||||
"result": "failure",
|
||||
"object": s.remoteAddress,
|
||||
}).Info("IMAP 客户端认证失败")
|
||||
return &imap.Error{
|
||||
Type: imap.StatusResponseTypeNo,
|
||||
Code: imap.ResponseCodeAuthenticationFailed,
|
||||
@ -24,6 +28,10 @@ func (s *serverSession) Login(username, pwd string) error {
|
||||
s.ctx.UserAccount = user.Account
|
||||
s.ctx.Role = user.EffectiveRole()
|
||||
s.ctx.IsAdmin = s.ctx.Role == access.RoleSuperAdmin || s.ctx.Role == access.RoleAdmin
|
||||
log.WithContext(s.ctx).Debug("IMAP authentication succeeded")
|
||||
log.WithContext(s.ctx).WithFields(log.Fields{
|
||||
"action": "IMAP 客户端登录",
|
||||
"result": "success",
|
||||
"object": s.remoteAddress,
|
||||
}).Debug("IMAP 客户端认证成功")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -105,7 +105,11 @@ func (a action) Pass(session *gopop.Session, pwd string) error {
|
||||
}
|
||||
user, err := auth.AuthenticateProtocol("pop3", remoteAddress, session.User, pwd)
|
||||
if err != nil {
|
||||
log.WithContext(session.Ctx).Info("POP3 authentication failed")
|
||||
log.WithContext(session.Ctx).WithFields(log.Fields{
|
||||
"action": "POP3 客户端登录",
|
||||
"result": "failure",
|
||||
"object": remoteAddress,
|
||||
}).Info("POP3 客户端认证失败")
|
||||
return errors2.New("password error")
|
||||
}
|
||||
|
||||
|
||||
@ -64,7 +64,11 @@ func (s *Session) Auth(mech string) (sasl.Server, error) {
|
||||
func (s *Session) AuthPlain(username, pwd string) error {
|
||||
user, err := auth.AuthenticateProtocol("smtp", s.RemoteAddress.String(), username, pwd)
|
||||
if err != nil {
|
||||
log.WithContext(s.Ctx).Info("SMTP authentication failed")
|
||||
log.WithContext(s.Ctx).WithFields(log.Fields{
|
||||
"action": "SMTP 客户端认证",
|
||||
"result": "failure",
|
||||
"object": s.RemoteAddress.String(),
|
||||
}).Info("SMTP 客户端认证失败")
|
||||
return errors.New("password error")
|
||||
}
|
||||
|
||||
|
||||
@ -277,7 +277,11 @@ func doSend(ctx *context.Context, fromDomain string, data []byte, to []*parsemai
|
||||
recordFailure(err)
|
||||
return
|
||||
}
|
||||
log.WithContext(ctx).Infof("SMTP STARTTLS on 25 Send Error. %s", err.Error())
|
||||
log.WithContext(ctx).WithFields(log.Fields{
|
||||
"action": "SMTP 邮件投递",
|
||||
"result": "failure",
|
||||
"object": domain.mxHost + ":25",
|
||||
}).Infof("SMTP 25 端口 STARTTLS 投递失败: %s", err.Error())
|
||||
|
||||
// 再试用587投递
|
||||
err = smtp.SendMailWithTls(strings.TrimSuffix(domain.mxHost, "."), domain.mxHost+":587", nil, from, fromDomain, buildAddress(tos), data)
|
||||
@ -293,7 +297,11 @@ func doSend(ctx *context.Context, fromDomain string, data []byte, to []*parsemai
|
||||
recordFailure(err)
|
||||
return
|
||||
}
|
||||
log.WithContext(ctx).Infof("SMTPS on 587 Send Error. %s", err.Error())
|
||||
log.WithContext(ctx).WithFields(log.Fields{
|
||||
"action": "SMTP 邮件投递",
|
||||
"result": "failure",
|
||||
"object": domain.mxHost + ":587",
|
||||
}).Infof("SMTP 587 端口投递失败: %s", err.Error())
|
||||
|
||||
// 再次尝试465投递
|
||||
err = smtp.SendMailWithTls(strings.TrimSuffix(domain.mxHost, "."), domain.mxHost+":465", nil, from, fromDomain, buildAddress(tos), data)
|
||||
@ -309,7 +317,11 @@ func doSend(ctx *context.Context, fromDomain string, data []byte, to []*parsemai
|
||||
recordFailure(err)
|
||||
return
|
||||
}
|
||||
log.WithContext(ctx).Infof("SMTPS on 465 Send Error. %s", err.Error())
|
||||
log.WithContext(ctx).WithFields(log.Fields{
|
||||
"action": "SMTP 邮件投递",
|
||||
"result": "failure",
|
||||
"object": domain.mxHost + ":465",
|
||||
}).Infof("SMTP 465 端口投递失败: %s", err.Error())
|
||||
|
||||
// 所有安全投递方式均失败后记录错误,不允许回退到明文连接。
|
||||
recordFailure(err)
|
||||
|
||||
@ -179,4 +179,4 @@ const {
|
||||
} = useLogs()
|
||||
</script>
|
||||
|
||||
<style scoped src="./LogSettings.css"></style>
|
||||
<style scoped src="./LogSettings.css"></style>
|
||||
@ -22,6 +22,86 @@ export function timeParts(value) {
|
||||
return { date: localDate.format(date), time: localTime.format(date) }
|
||||
}
|
||||
|
||||
export function cleanRuntimeMessage(text) {
|
||||
if (!text) return ''
|
||||
const boundary = text.lastIndexOf(' @event ')
|
||||
if (boundary >= 0) {
|
||||
return text.slice(0, boundary).trim()
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
export function friendlyOutcomeText(text) {
|
||||
if (!text) return '(无详细说明)'
|
||||
const trimmed = text.trim()
|
||||
const dictionary = {
|
||||
'IMAP authentication failed': 'IMAP 客户端登录认证失败',
|
||||
'IMAP authentication succeeded': 'IMAP 客户端登录认证成功',
|
||||
'POP3 authentication failed': 'POP3 客户端登录认证失败',
|
||||
'POP3 authentication succeeded': 'POP3 客户端登录认证成功',
|
||||
'SMTP authentication failed': 'SMTP 客户端登录认证失败',
|
||||
'SMTP authentication succeeded': 'SMTP 客户端登录认证成功',
|
||||
'SMTP MAIL accepted': 'SMTP 接收发件人地址',
|
||||
'SMTP RCPT accepted': 'SMTP 接收收件人地址',
|
||||
'Plugin Stop': '停止插件执行',
|
||||
'Server Start Success': '服务启动成功',
|
||||
'开始绑定监听端口': '开始绑定并监听网络端口'
|
||||
}
|
||||
if (dictionary[trimmed]) return dictionary[trimmed]
|
||||
|
||||
if (/^SMTP STARTTLS on 25 Send Error\.\s*(.*)$/i.test(trimmed)) {
|
||||
const detail = trimmed.replace(/^SMTP STARTTLS on 25 Send Error\.\s*/i, '')
|
||||
return `SMTP 25 端口 STARTTLS 投递失败:${detail}`
|
||||
}
|
||||
if (/^SMTPS on 587 Send Error\.\s*(.*)$/i.test(trimmed)) {
|
||||
const detail = trimmed.replace(/^SMTPS on 587 Send Error\.\s*/i, '')
|
||||
return `SMTP 587 端口投递失败:${detail}`
|
||||
}
|
||||
if (/^SMTPS on 465 Send Error\.\s*(.*)$/i.test(trimmed)) {
|
||||
const detail = trimmed.replace(/^SMTPS on 465 Send Error\.\s*/i, '')
|
||||
return `SMTP 465 端口投递失败:${detail}`
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
export function friendlyActionText(action) {
|
||||
if (!action) return '历史运行事件'
|
||||
const known = {
|
||||
'运行事件 · imap_server.(*serverSession).Login': 'IMAP 客户端登录',
|
||||
'运行事件 · imap_server.(*serverSession).Close': 'IMAP 断开连接',
|
||||
'运行事件 · imap_server.(*serverSession).Select': 'IMAP 选择邮箱',
|
||||
'运行事件 · imap_server.(*serverSession).Fetch': 'IMAP 获取邮件',
|
||||
'运行事件 · imap_server.(*serverSession).Append': 'IMAP 追加邮件',
|
||||
'运行事件 · imap_server.(*serverSession).Delete': 'IMAP 删除邮箱',
|
||||
'运行事件 · pop3_server.action.Pass': 'POP3 客户端登录',
|
||||
'运行事件 · pop3_server.action.Retr': 'POP3 获取邮件',
|
||||
'运行事件 · smtp_server.(*Session).AuthPlain': 'SMTP 客户端认证',
|
||||
'运行事件 · send.doSend': 'SMTP 邮件投递',
|
||||
'运行事件 · send.doSend.func2': 'SMTP 邮件投递',
|
||||
'运行事件 · send.Send': '发送邮件'
|
||||
}
|
||||
if (known[action]) return known[action]
|
||||
|
||||
const m = action.match(/^运行事件\s*·\s*([a-zA-Z0-9_]+)\.(?:\(\*?[a-zA-Z0-9_]+\)\.)?([a-zA-Z0-9_]+)(?:\.func\d+)?$/)
|
||||
if (m) {
|
||||
const pkg = m[1]
|
||||
const fn = m[2]
|
||||
const pkgLabel = {
|
||||
imap_server: 'IMAP 服务',
|
||||
pop3_server: 'POP3 服务',
|
||||
smtp_server: 'SMTP 服务',
|
||||
send: '邮件投递',
|
||||
controllers: '接口处理',
|
||||
services: '系统服务',
|
||||
res_init: '系统初始化',
|
||||
hooks: '插件钩子',
|
||||
adminreset: '超级管理'
|
||||
}[pkg] || pkg
|
||||
return `${pkgLabel} · ${fn}`
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
// 只识别已知的日志头;未知格式原样展示,不丢弃记录或猜测级别。
|
||||
export function parseRuntimeLine(line, sequence) {
|
||||
const raw = String(line ?? '')
|
||||
@ -67,10 +147,17 @@ export function parseRuntimeLine(line, sequence) {
|
||||
// 旧日志或截断记录保留原文,不能猜测结果。
|
||||
}
|
||||
}
|
||||
const displayMessage = cleanRuntimeMessage(message)
|
||||
const rawAction = event.action || '历史运行事件'
|
||||
const action = friendlyActionText(rawAction)
|
||||
const rawOutcome = event.outcome || content
|
||||
const outcome = friendlyOutcomeText(rawOutcome)
|
||||
|
||||
return {
|
||||
sequence, raw, createdAt, level, source, sourceShort, requestId, message, summary,
|
||||
action: event.action || '历史运行事件', result: event.result || 'unknown',
|
||||
object: event.object || event.section || '', outcome: event.outcome || content,
|
||||
sequence, raw, createdAt, level, source, sourceShort, requestId,
|
||||
message: displayMessage, summary,
|
||||
action, result: event.result || 'unknown',
|
||||
object: event.object || event.section || '', outcome,
|
||||
...timeParts(createdAt), ...levels[level]
|
||||
}
|
||||
}
|
||||
@ -90,4 +177,4 @@ export function formatFileSize(value) {
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user