//go:build windows package main import ( "errors" "fmt" "io" "os" "strings" "unicode/utf8" ) const truncatedLogRecord = "\n[Слишком длинная запись сокращена]\n" type rotatingLog struct { file *os.File path string limit, size int64 } func openRotatingLog(path string, limit int64) (*rotatingLog, error) { if limit < int64(len(truncatedLogRecord)+16) { return nil, fmt.Errorf("log size limit is too small") } for _, name := range []string{path, path + ".1"} { info, err := os.Lstat(name) if errors.Is(err, os.ErrNotExist) { continue } if err != nil { return nil, err } if !info.Mode().IsRegular() { return nil, fmt.Errorf("log target is not a regular file") } if name != path { if err = os.Remove(name); err != nil { return nil, err } } } f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) if err != nil { return nil, err } return &rotatingLog{file: f, path: path, limit: limit}, nil } func (w *rotatingLog) Write(text string) error { if w.file == nil { return os.ErrClosed } if text == "" { return nil } if !utf8.ValidString(text) { text = strings.ToValidUTF8(text, "?") } if int64(len(text)) > w.limit { end := int(w.limit) - len(truncatedLogRecord) for end > 0 && !utf8.ValidString(text[:end]) { end-- } text = text[:end] + truncatedLogRecord } if w.size > 0 && w.size+int64(len(text)) > w.limit { if err := w.rotate(); err != nil { return err } } n, err := w.file.WriteString(text) w.size += int64(n) if err == nil && n != len(text) { return io.ErrShortWrite } return err } func (w *rotatingLog) rotate() error { err := w.file.Close() w.file = nil if err != nil { return err } if info, e := os.Lstat(w.path + ".1"); e == nil { if !info.Mode().IsRegular() { return fmt.Errorf("log archive is not a regular file") } if e = os.Remove(w.path + ".1"); e != nil { return e } } else if !errors.Is(e, os.ErrNotExist) { return e } if err = os.Rename(w.path, w.path+".1"); err != nil { return err } w.file, err = os.OpenFile(w.path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) w.size = 0 return err } func (w *rotatingLog) Close() error { if w.file == nil { return nil } e := errors.Join(w.file.Sync(), w.file.Close()) w.file = nil return e }