Merge branch '21366-subprocess-output-loading-bug' into main. Closes #21366
[arvados.git] / lib / crunchrun / logscanner.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package crunchrun
6
7 import (
8         "bytes"
9         "strings"
10 )
11
12 // logScanner is an io.Writer that calls ReportFunc(pattern) the first
13 // time one of the Patterns appears in the data. Patterns must not
14 // contain newlines.
15 type logScanner struct {
16         Patterns   []string
17         ReportFunc func(pattern, text string)
18         reported   bool
19         buf        bytes.Buffer
20 }
21
22 func (s *logScanner) Write(p []byte) (int, error) {
23         if s.reported {
24                 // We only call reportFunc once. Once we've called it
25                 // there's no need to buffer/search subsequent writes.
26                 return len(p), nil
27         }
28         split := bytes.LastIndexByte(p, '\n')
29         if split < 0 {
30                 return s.buf.Write(p)
31         }
32         s.buf.Write(p[:split+1])
33         txt := s.buf.String()
34         for _, pattern := range s.Patterns {
35                 if found := strings.Index(txt, pattern); found >= 0 {
36                         // Report the entire line where the pattern
37                         // was found.
38                         txt = txt[strings.LastIndexByte(txt[:found], '\n')+1:]
39                         if end := strings.IndexByte(txt, '\n'); end >= 0 {
40                                 txt = txt[:end]
41                         }
42                         s.ReportFunc(pattern, txt)
43                         s.reported = true
44                         return len(p), nil
45                 }
46         }
47         s.buf.Reset()
48         if split == len(p) {
49                 return len(p), nil
50         }
51         n, err := s.buf.Write(p[split+1:])
52         return n + split + 1, err
53 }