• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hedhyw / json-log-viewer / 13087545808

01 Feb 2025 10:03AM UTC coverage: 95.133%. Remained the same
13087545808

Pull #122

github

web-flow
chore(gomod): bump github.com/go-playground/validator/v10

Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.23.0 to 10.24.0.
- [Release notes](https://github.com/go-playground/validator/releases)
- [Commits](https://github.com/go-playground/validator/compare/v10.23.0...v10.24.0)

---
updated-dependencies:
- dependency-name: github.com/go-playground/validator/v10
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #122: chore(gomod): bump github.com/go-playground/validator/v10 from 10.23.0 to 10.24.0

1466 of 1541 relevant lines covered (95.13%)

7442.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.74
/internal/pkg/source/source.go
1
package source
2

3
import (
4
        "bufio"
5
        "bytes"
6
        "errors"
7
        "fmt"
8
        "io"
9
        "os"
10

11
        "github.com/hedhyw/semerr/pkg/v1/semerr"
12

13
        "github.com/hedhyw/json-log-viewer/internal/pkg/config"
14
)
15

16
const (
17
        maxLineSize = 8 * 1024 * 1024
18

19
        temporaryFilePattern = "jlv-*.log"
20

21
        ErrFileTruncated semerr.Error = "file truncated"
22
)
23

24
type Source struct {
25
        // Seeker is used to do random access reads from the file.
26
        Seeker *os.File
27
        // Reader is used to read the file sequentially.
28
        reader *bufio.Reader
29
        // The log file we are reading from, or a temp file we are writing to (depending on if created with File or Reader func).
30
        file *os.File
31
        // offset is the next offset a long entry will be read from.
32
        offset int64
33
        // prevFollowSize is the size of the file the last time we checked
34
        prevFollowSize int64
35
        // name is the name of the file we are reading.
36
        name string
37
        // maxSize is the maximum size of the file we will read.
38
        maxSize int64
39
        // temporaryFiles to remove at the end.
40
        temporaryFiles []string
41
}
42

43
// Close implements io.Closer.
44
//
45
// It closes and removes temporary files.
46
func (s *Source) Close() error {
122✔
47
        errMulti := make([]error, 0, 2+len(s.temporaryFiles))
122✔
48

122✔
49
        if s.file != nil {
244✔
50
                errMulti = append(errMulti, s.file.Close())
122✔
51
        }
122✔
52

53
        if s.Seeker != nil {
244✔
54
                errMulti = append(errMulti, s.Seeker.Close())
122✔
55
        }
122✔
56

57
        for _, f := range s.temporaryFiles {
190✔
58
                errMulti = append(errMulti, os.Remove(f))
68✔
59
        }
68✔
60

61
        return errors.Join(errMulti...)
122✔
62
}
63

64
// File creates a new Source for reading log entries from a file.
65
func File(name string, cfg *config.Config) (*Source, error) {
56✔
66
        var err error
56✔
67

56✔
68
        source := &Source{
56✔
69
                maxSize: int64(cfg.MaxFileSizeBytes),
56✔
70
                name:    name,
56✔
71
        }
56✔
72

56✔
73
        source.file, err = os.Open(name)
56✔
74
        if err != nil {
58✔
75
                return nil, fmt.Errorf("opening: %w", err)
2✔
76
        }
2✔
77

78
        source.Seeker, err = os.Open(name)
54✔
79
        if err != nil {
54✔
80
                return nil, errors.Join(err, source.Close())
×
81
        }
×
82

83
        source.reader = bufio.NewReaderSize(
54✔
84
                io.LimitReader(source.file, source.maxSize),
54✔
85
                maxLineSize,
54✔
86
        )
54✔
87

54✔
88
        return source, nil
54✔
89
}
90

91
// Reader creates a new Source for reading log entries from an io.Reader.
92
// This will write the input to a temp file, which will be used to seek against.
93
func Reader(input io.Reader, cfg *config.Config) (*Source, error) {
68✔
94
        var err error
68✔
95

68✔
96
        source := &Source{
68✔
97
                maxSize: int64(cfg.MaxFileSizeBytes),
68✔
98
        }
68✔
99

68✔
100
        // We will write the as read to a temp file.  Seek against the temp file.
68✔
101
        source.file, err = os.CreateTemp(
68✔
102
                "", // Default directory for temporary files.
68✔
103
                temporaryFilePattern,
68✔
104
        )
68✔
105
        if err != nil {
68✔
106
                return nil, fmt.Errorf("creating temporary file: %w", err)
×
107
        }
×
108

109
        source.temporaryFiles = append(source.temporaryFiles, source.file.Name())
68✔
110

68✔
111
        // The io.TeeReader will write the input to the is.file as it is read.
68✔
112
        reader := io.TeeReader(input, source.file)
68✔
113

68✔
114
        // We can now seek against the data that is read in the input io.Reader.
68✔
115
        source.Seeker, err = os.Open(source.file.Name())
68✔
116
        if err != nil {
68✔
117
                return nil, errors.Join(err, source.Close())
×
118
        }
×
119

120
        reader = io.LimitReader(reader, source.maxSize)
68✔
121
        source.reader = bufio.NewReaderSize(reader, maxLineSize)
68✔
122

68✔
123
        return source, nil
68✔
124
}
125

126
func (s *Source) ParseLogEntries() (LazyLogEntries, error) {
113✔
127
        logEntries := make([]LazyLogEntry, 0, initialLogSize)
113✔
128
        for {
4,322✔
129
                entry, err := s.readLogEntry()
4,209✔
130
                if err != nil {
4,322✔
131
                        if errors.Is(err, io.EOF) {
225✔
132
                                break
112✔
133
                        }
134

135
                        return LazyLogEntries{}, err
1✔
136
                }
137

138
                logEntries = append(logEntries, entry)
4,096✔
139
        }
140

141
        return LazyLogEntries{
112✔
142
                Seeker:  s.Seeker,
112✔
143
                Entries: logEntries,
112✔
144
        }, nil
112✔
145
}
146

147
func (s *Source) CanFollow() bool {
15✔
148
        return len(s.name) != 0
15✔
149
}
15✔
150

151
// readLogEntry reads the next LazyLogEntry from the file.
152
func (s *Source) readLogEntry() (LazyLogEntry, error) {
4,226✔
153
        for {
8,471✔
154
                if s.reader == nil {
4,251✔
155
                        // If we can't follow the file, or we have reached the max size, we are done.
6✔
156
                        if !s.CanFollow() || s.offset >= s.maxSize {
6✔
157
                                return LazyLogEntry{}, io.EOF
×
158
                        }
×
159

160
                        // Has the file size changed since we last looked?
161
                        info, err := os.Stat(s.name)
6✔
162
                        if err != nil || s.prevFollowSize == info.Size() {
10✔
163
                                return LazyLogEntry{}, io.EOF
4✔
164
                        }
4✔
165

166
                        if info.Size() < s.offset {
2✔
167
                                // The file has been truncated or rolled over, all previous line
×
168
                                // offsets are invalid. We can't recover from this.
×
169
                                return LazyLogEntry{}, ErrFileTruncated
×
170
                        }
×
171

172
                        s.prevFollowSize = info.Size()
2✔
173
                        // Reset the reader and try to read the file again.
2✔
174
                        _, _ = s.file.Seek(s.offset, io.SeekStart)
2✔
175
                        s.reader = bufio.NewReaderSize(io.LimitReader(s.file, s.maxSize-s.offset), maxLineSize)
2✔
176
                }
177

178
                line, err := s.reader.ReadBytes('\n')
4,241✔
179
                if err != nil {
4,362✔
180
                        if errors.Is(err, io.EOF) {
237✔
181
                                // Set the reader to nil so that we can recover from EOF.
116✔
182
                                s.reader = nil
116✔
183
                        }
116✔
184

185
                        return LazyLogEntry{}, err
121✔
186
                }
187

188
                length := len(line)
4,120✔
189
                offset := s.offset
4,120✔
190
                s.offset += int64(length)
4,120✔
191

4,120✔
192
                if len(bytes.TrimSpace(line)) != 0 {
8,221✔
193
                        return LazyLogEntry{
4,101✔
194
                                offset: offset,
4,101✔
195
                                length: length,
4,101✔
196
                        }, nil
4,101✔
197
                }
4,101✔
198
        }
199
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc