package latencylog import ( "encoding/json" "os" "path/filepath" "sync" "time" "omnisocketgo/cmd/internal/protocol" ) const ( NodeRolePeer = "peer" //客户端节点 NodeRoleServer = "server" //云端转发节点 ) // 记录的消息事件的类型常量。 const ( EventAAppPrepBegin = "A_APP_PREP_BEGIN" // A 端应用开始准备这条消息 EventATXSched = "A_TX_SCHED" // A 端进入 Linux qdisc 之前 EventATXSoftware = "A_TX_SOFTWARE" // A 端即将交给网卡驱动 EventATXHardware = "A_TX_HARDWARE" // A 端网卡真正发出到物理介质 EventBRXHardware = "B_RX_HARDWARE" // B 端网卡真正从物理介质收到 EventBRXSoftware = "B_RX_SOFTWARE" // B 端驱动把数据交给 Linux 接收栈 EventBAppRecv = "B_APP_RECV" // B 端应用真正读到完整消息 EventBPersistBegin = "B_PERSIST_BEGIN" // B 端开始写盘 EventBPersistEnd = "B_PERSIST_END" // B 端写盘完成 EventSendHandoffBegin = "send_handoff_begin" // 调试事件:应用把消息交给传输层开始 EventSendHandoffEnd = "send_handoff_end" // 调试事件:应用把消息交给传输层结束 ) // Event 是一条时延时间戳日志记录。 type Event struct { TsUnixNano int64 `json:"ts_unix_nano"` NodeRole string `json:"node_role"` NodeID string `json:"node_id"` Event string `json:"event"` MessageType protocol.MessageType `json:"message_type"` MessageID uint64 `json:"message_id"` From string `json:"from"` To string `json:"to"` FileName string `json:"file_name,omitempty"` BodySize int `json:"body_size"` } // Logger 负责接收事件并将其写入外部介质。 type Logger interface { LogEvent(Event) error } // NoopLogger 是默认的空实现。 type NoopLogger struct{} // LogEvent 对空日志实现始终返回 nil。 func (NoopLogger) LogEvent(Event) error { return nil } // JSONLLogger 以 JSONL 形式追加写日志文件。 type JSONLLogger struct { mu sync.Mutex closeOnce sync.Once closeErr error file *os.File } // NewJSONLLogger 创建一个线程安全的 JSONL 文件日志器。 func NewJSONLLogger(path string) (*JSONLLogger, error) { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o755); err != nil { return nil, err } file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) if err != nil { return nil, err } return &JSONLLogger{file: file}, nil } // LogEvent 以单行 JSON 的形式追加一条事件。 func (l *JSONLLogger) LogEvent(event Event) error { line, err := json.Marshal(event) if err != nil { return err } l.mu.Lock() defer l.mu.Unlock() if _, err := l.file.Write(append(line, '\n')); err != nil { return err } return nil } // Close 关闭底层文件;重复调用是安全的。 func (l *JSONLLogger) Close() error { l.closeOnce.Do(func() { l.closeErr = l.file.Close() }) return l.closeErr } // IsBusinessMessage 判断消息是否属于要参与 A-C-B 时延分析的业务消息。 func IsBusinessMessage(msg protocol.Message) bool { switch msg.Type { case protocol.MessageTypeText, protocol.MessageTypeFile: return true default: return false } } // NewMessageEvent 用当前 UTC 时间为一条业务消息构造事件。 func NewMessageEvent(nodeRole, nodeID, eventName string, msg protocol.Message) Event { return NewMessageEventAt(time.Now().UTC().UnixNano(), nodeRole, nodeID, eventName, msg) } // NewMessageEventAt 用指定的 UnixNano 时间为一条业务消息构造事件。 func NewMessageEventAt(tsUnixNano int64, nodeRole, nodeID, eventName string, msg protocol.Message) Event { return Event{ TsUnixNano: tsUnixNano, NodeRole: nodeRole, NodeID: nodeID, Event: eventName, MessageType: msg.Type, MessageID: msg.ID, From: msg.From, To: msg.To, FileName: msg.FileName, BodySize: len(msg.Body), } } // LogBestEffort 写一条事件,失败时静默忽略,避免打断主收发流程。 func LogBestEffort(logger Logger, event Event) { if logger == nil { return } _ = logger.LogEvent(event) } // LogMessageEvent 为业务消息构造并写入一条事件。 func LogMessageEvent(logger Logger, nodeRole, nodeID, eventName string, msg protocol.Message) { if !IsBusinessMessage(msg) { return } LogBestEffort(logger, NewMessageEvent(nodeRole, nodeID, eventName, msg)) } // LogMessageEventAt 为业务消息写入一条指定时间戳的事件。 func LogMessageEventAt(logger Logger, nodeRole, nodeID, eventName string, tsUnixNano int64, msg protocol.Message) { if !IsBusinessMessage(msg) { return } LogBestEffort(logger, NewMessageEventAt(tsUnixNano, nodeRole, nodeID, eventName, msg)) }