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

umputun / dkll / 14460119915

15 Apr 2025 02:44AM UTC coverage: 82.857% (-0.2%) from 83.02%
14460119915

Pull #40

github

web-flow
Bump github.com/go-chi/chi/v5 from 5.1.0 to 5.2.1

Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.1.0 to 5.2.1.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.1.0...v5.2.1)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #40: Bump github.com/go-chi/chi/v5 from 5.1.0 to 5.2.1

1015 of 1225 relevant lines covered (82.86%)

10899.58 hits per line

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

86.29
/app/client/cli.go
1
// Package client implement remote client to get and print records
2
package client
3

4
import (
5
        "bytes"
6
        "context"
7
        "encoding/json"
8
        "fmt"
9
        "io"
10
        "net/http"
11
        "os"
12
        "strings"
13
        "time"
14

15
        "github.com/fatih/color"
16
        log "github.com/go-pkgz/lgr"
17
        "github.com/go-pkgz/repeater"
18
        "github.com/go-pkgz/repeater/strategy"
19
        "github.com/pkg/errors"
20

21
        "github.com/umputun/dkll/app/core"
22
)
23

24
// CLI is a client accessing remote dkll rest and printing output.
25
type CLI struct {
26
        DisplayParams
27
        APIParams
28
}
29

30
// APIParams define how and where access remote endpoint
31
type APIParams struct {
32
        UpdateInterval   time.Duration
33
        Client           *http.Client
34
        API              string
35
        RepeaterStrategy strategy.Interface
36
}
37

38
// DisplayParams customizes how records will be showed
39
type DisplayParams struct {
40
        ShowPid    bool           // include pid
41
        ShowTS     bool           // include time stamp as "2006-01-02 15:04:05.999999" in given TZ
42
        FollowMode bool           // follow mode, like -f in grep
43
        TailMode   bool           // tail mode, like -t in grep
44
        ShowSyslog bool           // show non-docker messages from syslog, off by default
45
        Grep       []string       // filter the final output line
46
        UnGrep     []string       // inverse filter for the final output line
47
        TimeZone   *time.Location // custom TZ, default is local
48
        Out        io.Writer      // custom out stream, default is stdout
49
}
50

51
var (
52
        green  = color.New(color.FgGreen).SprintFunc()
53
        red    = color.New(color.FgRed).SprintFunc()
54
        yellow = color.New(color.FgYellow).SprintFunc()
55
        white  = color.New(color.FgWhite).SprintFunc()
56
)
57

58
// NewCLI makes cli client
59
func NewCLI(apiParams APIParams, displayParams DisplayParams) *CLI {
12✔
60
        res := &CLI{DisplayParams: displayParams, APIParams: apiParams}
12✔
61
        if res.TimeZone == nil {
23✔
62
                res.TimeZone = time.Local
11✔
63
        }
11✔
64
        if res.Out == nil {
12✔
65
                res.Out = os.Stdout
×
66
        }
×
67
        return res
12✔
68
}
69

70
// Activate shows tail-like, colorized output. For FollowMode will run endless loop
71
// doesn't return error on context cancellation, but exit func.
72
func (c *CLI) Activate(ctx context.Context, request core.Request) (req core.Request, err error) {
11✔
73

11✔
74
        var items []core.LogEntry
11✔
75
        var id string
11✔
76

11✔
77
        if c.TailMode {
13✔
78
                if id, err = c.getLastID(ctx); err != nil {
3✔
79
                        return request, errors.Wrapf(c.resetCtxError(err), "can't get last ID for tail mode")
1✔
80
                }
1✔
81
                request.LastID = id
1✔
82
        }
83

84
        for {
1,519✔
85
                if items, id, err = c.getNext(ctx, request); err != nil {
1,510✔
86
                        return request, errors.Wrapf(c.resetCtxError(err), "can't get data for request %+v", request)
1✔
87
                }
1✔
88

89
                if len(items) == 0 && !c.FollowMode {
1,515✔
90
                        break
7✔
91
                }
92

93
                for _, e := range items {
1,547✔
94
                        line, ok := c.makeOutLine(e)
46✔
95
                        if !ok {
46✔
96
                                continue
×
97
                        }
98
                        if (len(c.Grep) > 0 && !contains(line, c.Grep)) || (len(c.UnGrep) > 0 && contains(line, c.UnGrep)) {
52✔
99
                                continue
6✔
100
                        }
101
                        _, _ = fmt.Fprint(c.Out, line)
40✔
102
                }
103
                request.LastID = id
1,501✔
104

1,501✔
105
                select {
1,501✔
106
                case <-ctx.Done():
2✔
107
                        log.Printf("[DEBUG] terminated, %v", ctx.Err())
2✔
108
                        return request, nil // don't return error, we don't want error status on ctrl-c in the client
2✔
109
                case <-time.After(c.UpdateInterval):
1,499✔
110
                        continue
1,499✔
111
                }
112
        }
113

114
        return request, nil
7✔
115
}
116

117
func (c *CLI) resetCtxError(err error) error {
2✔
118
        if err == context.Canceled {
2✔
119
                return nil
×
120
        }
×
121
        return err
2✔
122
}
123

124
func (c *CLI) makeOutLine(e core.LogEntry) (string, bool) {
46✔
125
        if !c.ShowSyslog && e.Container == "syslog" {
46✔
126
                return "", false
×
127
        }
×
128

129
        pid := ""
46✔
130
        if c.ShowPid {
52✔
131
                pid = fmt.Sprintf(" [%d]", e.Pid)
6✔
132
        }
6✔
133

134
        ts := ""
46✔
135
        if c.ShowTS {
58✔
136
                ts = fmt.Sprintf(" - %s", e.TS.In(c.TimeZone).Format("2006-01-02 15:04:05.999999"))
12✔
137
        }
12✔
138
        line := fmt.Sprintf("%s:%s%s%s - %s\n", red(e.Host), green(e.Container), yellow(ts), yellow(pid), white(e.Msg))
46✔
139
        return line, true
46✔
140
}
141

142
func (c *CLI) getLastID(ctx context.Context) (string, error) {
4✔
143

4✔
144
        lastEntry := core.LogEntry{}
4✔
145
        err := repeater.New(c.RepeaterStrategy).Do(ctx, func() (e error) {
8✔
146
                resp, e := c.Client.Get(fmt.Sprintf("%s/last", c.API))
4✔
147
                if e != nil {
4✔
148
                        return errors.Wrapf(e, "can't get last id")
×
149
                }
×
150
                defer func() { _ = resp.Body.Close() }() // nolint
8✔
151
                if resp.StatusCode != http.StatusOK {
6✔
152
                        return errors.Errorf("http code %d", resp.StatusCode)
2✔
153
                }
2✔
154
                return json.NewDecoder(resp.Body).Decode(&lastEntry)
2✔
155
        })
156

157
        if err != nil {
6✔
158
                return "", err
2✔
159
        }
2✔
160

161
        return lastEntry.ID, nil
2✔
162
}
163

164
func (c *CLI) getNext(ctx context.Context, request core.Request) (items []core.LogEntry, lastID string, err error) {
1,509✔
165

1,509✔
166
        items = []core.LogEntry{}
1,509✔
167

1,509✔
168
        uri := fmt.Sprintf("%s/find", c.API)
1,509✔
169
        body := &bytes.Buffer{}
1,509✔
170
        if e := json.NewEncoder(body).Encode(request); e != nil {
1,509✔
171
                return items, "", e
×
172
        }
×
173
        req, e := http.NewRequest("POST", uri, body)
1,509✔
174
        if e != nil {
1,509✔
175
                return items, "", e
×
176
        }
×
177

178
        err = repeater.New(c.RepeaterStrategy).Do(ctx, func() error {
3,031✔
179
                var resp *http.Response
1,522✔
180
                resp, err = c.Client.Do(req)
1,522✔
181
                if err != nil {
1,522✔
182
                        return err
×
183
                }
×
184
                if resp.StatusCode != http.StatusOK {
1,526✔
185
                        _ = resp.Body.Close() // nolint
4✔
186
                        return errors.New("status")
4✔
187
                }
4✔
188
                defer func() { _ = resp.Body.Close() }() // nolint
3,036✔
189
                body, e := io.ReadAll(resp.Body)
1,518✔
190
                if e != nil {
1,518✔
191
                        return errors.Wrap(e, "can't read next response body")
×
192
                }
×
193

194
                if len(body) == 0 { // empty body shouldn't be an error
1,521✔
195
                        return nil
3✔
196
                }
3✔
197
                return json.Unmarshal(body, &items)
1,515✔
198
        })
199

200
        if err != nil {
1,510✔
201
                log.Printf("[DEBUG] failed to send request %s", uri)
1✔
202
                return items, "", err
1✔
203
        }
1✔
204

205
        lastID = request.LastID
1,508✔
206
        if len(items) > 0 {
1,524✔
207
                lastID = items[len(items)-1].ID
16✔
208
        }
16✔
209

210
        return items, lastID, nil
1,508✔
211
}
212

213
func contains(inp string, values []string) bool {
12✔
214
        for _, v := range values {
24✔
215
                if strings.Contains(inp, v) {
14✔
216
                        return true
2✔
217
                }
2✔
218
        }
219
        return false
10✔
220
}
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