返回 DeepSeek-Reasonix
tui_diagnostic_log.go
根目录 / internal / cli / tui_diagnostic_log.go
1 package cli
2
3 import (
4 "os"
5 "strings"
6 "sync"
7 )
8
9 // rotatingDiagnosticLog keeps the newest diagnostics of a long session within
10 // limit bytes on disk: the live file holds at most half, and when it fills it
11 // replaces a single ".prev.log" sibling. What precedes a crash is what matters.
12 type rotatingDiagnosticLog struct {
13 mu sync.Mutex
14 file *os.File
15 path string
16 segment int64
17 written int64
18 }
19
20 func newRotatingDiagnosticLog(file *os.File, limit int64) *rotatingDiagnosticLog {
21 return &rotatingDiagnosticLog{file: file, path: file.Name(), segment: max(limit/2, 1)}
22 }
23
24 func (l *rotatingDiagnosticLog) previousPath() string {
25 return strings.TrimSuffix(l.path, ".log") + ".prev.log"
26 }
27
28 func (l *rotatingDiagnosticLog) Write(p []byte) (int, error) {
29 l.mu.Lock()
30 defer l.mu.Unlock()
31 total := len(p)
32 if l.file == nil || total == 0 {
33 return total, nil
34 }
35 if int64(len(p)) > l.segment {
36 p = p[int64(len(p))-l.segment:]
37 }
38 if l.written > 0 && l.written+int64(len(p)) > l.segment && !l.rotateLocked() {
39 return total, nil
40 }
41 n, err := l.file.Write(p)
42 l.written += int64(n)
43 if err != nil {
44 l.closeLocked()
45 }
46 return total, nil
47 }
48
49 // rotateLocked closes before renaming because Windows refuses to rename an
50 // open file.
51 func (l *rotatingDiagnosticLog) rotateLocked() bool {
52 _ = l.file.Sync()
53 _ = l.file.Close()
54 l.file = nil
55 if err := os.Rename(l.path, l.previousPath()); err != nil {
56 return false
57 }
58 file, err := os.OpenFile(l.path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
59 if err != nil {
60 return false
61 }
62 l.file = file
63 l.written = 0
64 return true
65 }
66
67 func (l *rotatingDiagnosticLog) Sync() error {
68 l.mu.Lock()
69 defer l.mu.Unlock()
70 if l.file == nil {
71 return nil
72 }
73 return l.file.Sync()
74 }
75
76 func (l *rotatingDiagnosticLog) Close() error {
77 l.mu.Lock()
78 defer l.mu.Unlock()
79 l.closeLocked()
80 return nil
81 }
82
83 func (l *rotatingDiagnosticLog) closeLocked() {
84 if l.file == nil {
85 return
86 }
87 _ = l.file.Sync()
88 _ = l.file.Close()
89 l.file = nil
90 }
91
91 lines GO