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

hedhyw / json-log-viewer / 14208689520

02 Apr 2025 12:20AM UTC coverage: 95.214%. Remained the same
14208689520

Pull #131

github

web-flow
chore(gomod): bump github.com/charmbracelet/lipgloss from 1.0.0 to 1.1.0

Bumps [github.com/charmbracelet/lipgloss](https://github.com/charmbracelet/lipgloss) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/charmbracelet/lipgloss/releases)
- [Changelog](https://github.com/charmbracelet/lipgloss/blob/master/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/lipgloss/compare/v1.0.0...v1.1.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/lipgloss
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #131: chore(gomod): bump github.com/charmbracelet/lipgloss from 1.0.0 to 1.1.0

1492 of 1567 relevant lines covered (95.21%)

5484.79 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 {
123✔
47
        errMulti := make([]error, 0, 2+len(s.temporaryFiles))
123✔
48

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

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

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

61
        return errors.Join(errMulti...)
123✔
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) {
69✔
94
        var err error
69✔
95

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

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

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

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

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

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

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

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

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

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

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

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

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

160
                        // Has the file size changed since we last looked?
161
                        info, err := os.Stat(s.name)
5✔
162
                        if err != nil || s.prevFollowSize == info.Size() {
8✔
163
                                return LazyLogEntry{}, io.EOF
3✔
164
                        }
3✔
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,339✔
179
                if err != nil {
4,461✔
180
                        if errors.Is(err, io.EOF) {
239✔
181
                                // Set the reader to nil so that we can recover from EOF.
117✔
182
                                s.reader = nil
117✔
183
                        }
117✔
184

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

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

4,217✔
192
                if len(bytes.TrimSpace(line)) != 0 {
8,415✔
193
                        return LazyLogEntry{
4,198✔
194
                                offset: offset,
4,198✔
195
                                length: length,
4,198✔
196
                        }, nil
4,198✔
197
                }
4,198✔
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