@@ -34,16 +34,18 @@ type CaddyLog struct {
3434}
3535
3636var (
37- isTerminal bool
38- totalLines int
39- lastSize int64
40- startTime time.Time
41- lastH , lastW int
42- lastCPUTime int64
43- lastSampleTime time.Time
44- cachedLogs []CaddyLog
45- cachedStats LogStats
46- remoteIP bool
37+ isTerminal bool
38+ totalLines int
39+ lastSize int64
40+ startTime time.Time
41+ lastH , lastW int
42+ lastCPUTime int64
43+ lastSampleTime time.Time
44+ cachedLogs []CaddyLog
45+ cachedStats LogStats
46+ remoteIP bool
47+ lastParsedOffset int64
48+ parseLeftover string
4749)
4850
4951type LogStats struct {
@@ -173,17 +175,41 @@ func getLastLines(filePath string, n int) ([]string, int64) {
173175 var cursor int64 = 0
174176 bufSize := int64 (4096 )
175177 if fileSize < bufSize { bufSize = fileSize }
178+ buf := make ([]byte , bufSize )
179+
180+ var leftover string
176181 for cursor < fileSize {
182+ readSize := bufSize
177183 cursor += bufSize
178- if cursor > fileSize { cursor = fileSize }
184+ if cursor > fileSize {
185+ readSize = fileSize - (cursor - bufSize )
186+ cursor = fileSize
187+ }
188+
179189 file .Seek (fileSize - cursor , io .SeekStart )
180- buf := make ([]byte , bufSize )
181- file .Read (buf )
182- chunk := strings .Split (string (buf ), "\n " )
183- if len (lines ) == 0 && chunk [len (chunk )- 1 ] == "" { chunk = chunk [:len (chunk )- 1 ] }
190+ c , err := file .Read (buf [:readSize ])
191+ if c <= 0 || err != nil { break }
192+
193+ content := string (buf [:c ]) + leftover
194+ chunk := strings .Split (content , "\n " )
195+
196+ if fileSize - cursor > 0 {
197+ leftover = chunk [0 ]
198+ chunk = chunk [1 :]
199+ } else {
200+ leftover = ""
201+ }
202+
203+ if len (lines ) == 0 && len (chunk ) > 0 && chunk [len (chunk )- 1 ] == "" {
204+ chunk = chunk [:len (chunk )- 1 ]
205+ }
206+
184207 lines = append (chunk , lines ... )
185208 if len (lines ) > n { return lines [len (lines )- n :], fileSize }
186209 }
210+ if leftover != "" {
211+ lines = append ([]string {leftover }, lines ... )
212+ }
187213 return lines , fileSize
188214}
189215
@@ -331,23 +357,75 @@ func main() {
331357
332358func processLogs (filePath string , lCount int , ha bool , e bool , all bool , f string , host string , count bool , dash bool , width int , shouldUpdate bool ) {
333359 if shouldUpdate {
334- fetchCount := lCount * 10
335- if all { fetchCount = totalLines }
336- if fetchCount < 10 { fetchCount = 10 }
337- rawLines , _ := getLastLines (filePath , fetchCount )
338- newLogs := make ([]CaddyLog , 0 , len (rawLines ))
339- for _ , line := range rawLines {
340- if line == "" { continue }
341- var l CaddyLog
342- if err := json .Unmarshal ([]byte (line ), & l ); err == nil {
343- if ! remoteIP && l .Request .ClientIP != "" {
344- l .Request .RemoteIP = l .Request .ClientIP
360+ info , err := os .Stat (filePath )
361+ if err == nil {
362+ currentSize := info .Size ()
363+ // Si premier run ou fichier tronqué/vidé
364+ if lastParsedOffset == 0 || currentSize < lastParsedOffset {
365+ fetchCount := lCount * 10
366+ if all { fetchCount = totalLines }
367+ if fetchCount < 10 { fetchCount = 10 }
368+ rawLines , _ := getLastLines (filePath , fetchCount )
369+ newLogs := make ([]CaddyLog , 0 , len (rawLines ))
370+ for _ , line := range rawLines {
371+ if line == "" { continue }
372+ var l CaddyLog
373+ if err := json .Unmarshal ([]byte (line ), & l ); err == nil {
374+ if ! remoteIP && l .Request .ClientIP != "" {
375+ l .Request .RemoteIP = l .Request .ClientIP
376+ }
377+ newLogs = append (newLogs , l )
378+ }
379+ }
380+ cachedLogs = newLogs
381+ lastParsedOffset = currentSize
382+ parseLeftover = ""
383+ } else if currentSize > lastParsedOffset {
384+ // Lecture incrémentale
385+ f , err := os .Open (filePath )
386+ if err == nil {
387+ defer f .Close ()
388+ f .Seek (lastParsedOffset , io .SeekStart )
389+ buf := make ([]byte , 32 * 1024 )
390+ var content string
391+ for {
392+ c , err := f .Read (buf )
393+ if c > 0 { content += string (buf [:c ]) }
394+ if err != nil { break }
395+ }
396+
397+ content = parseLeftover + content
398+ lines := strings .Split (content , "\n " )
399+
400+ if len (lines ) > 0 {
401+ parseLeftover = lines [len (lines )- 1 ]
402+ lines = lines [:len (lines )- 1 ]
403+ }
404+
405+ for _ , line := range lines {
406+ if line == "" { continue }
407+ var l CaddyLog
408+ if err := json .Unmarshal ([]byte (line ), & l ); err == nil {
409+ if ! remoteIP && l .Request .ClientIP != "" {
410+ l .Request .RemoteIP = l .Request .ClientIP
411+ }
412+ cachedLogs = append (cachedLogs , l )
413+ }
414+ }
415+
416+ // Tronquer cachedLogs pour ne pas accumuler indéfiniment en mémoire
417+ limit := lCount * 10
418+ if all { limit = totalLines }
419+ if limit < 100 { limit = 100 }
420+ if len (cachedLogs ) > limit {
421+ cachedLogs = cachedLogs [len (cachedLogs )- limit :]
422+ }
423+
424+ lastParsedOffset = currentSize
345425 }
346- newLogs = append (newLogs , l )
347426 }
427+ cachedStats = calculateStats (cachedLogs )
348428 }
349- cachedLogs = newLogs
350- cachedStats = calculateStats (cachedLogs )
351429 }
352430
353431 if isTerminal || count {
0 commit comments