package seqlog import ( "encoding/binary" "hash/crc32" "os" "github.com/google/uuid" ) // LogWriter 日志写入器 type LogWriter struct { fd *os.File off int64 // 当前写入偏移 wbuf []byte // 8 MiB 复用 index *RecordIndex // 索引管理器(可选) } // NewLogWriter 创建一个新的日志写入器 // index: 外部提供的索引管理器,用于在多个组件间共享 func NewLogWriter(path string, index *RecordIndex) (*LogWriter, error) { if index == nil { return nil, os.ErrInvalid } fd, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return nil, err } off, _ := fd.Seek(0, 2) // 跳到尾部 w := &LogWriter{ fd: fd, off: off, wbuf: make([]byte, 0, 8<<20), index: index, } return w, nil } // Append 追加一条日志记录,返回该记录的偏移量 func (w *LogWriter) Append(data []byte) (int64, error) { // 记录当前偏移(返回给调用者,用于索引) offset := w.off // 生成 UUID v4 id := uuid.New() // 编码:[4B len][4B CRC][16B UUID][data] buf := w.wbuf[:0] buf = binary.LittleEndian.AppendUint32(buf, uint32(len(data))) buf = binary.LittleEndian.AppendUint32(buf, crc32.ChecksumIEEE(data)) buf = append(buf, id[:]...) buf = append(buf, data...) // 落盘 + sync if _, err := w.fd.Write(buf); err != nil { return 0, err } if err := w.fd.Sync(); err != nil { return 0, err } // 数据写入成功,立即更新偏移量(保证 w.off 和文件大小一致) w.off += int64(len(buf)) // 更新索引(如果索引失败,数据已持久化,依赖启动时 rebuild 恢复) if err := w.index.Append(offset); err != nil { // 索引失败不影响 w.off,因为数据已经写入 return 0, err } return offset, nil } // Close 关闭写入器 // 注意:不关闭 index,因为 index 是外部管理的共享资源 func (w *LogWriter) Close() error { if w.fd == nil { return nil } return w.fd.Close() }