Skip to content

Commit 9fbaa18

Browse files
Highlight matched strings in the log lines with the include option (#231)
1 parent 52894f8 commit 9fbaa18

File tree

2 files changed

+97
-1
lines changed

2 files changed

+97
-1
lines changed

stern/tail.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"hash/fnv"
2424
"io"
2525
"regexp"
26+
"sort"
2627
"strings"
2728
"text/template"
2829
"time"
@@ -68,6 +69,9 @@ type TailOptions struct {
6869
TailLines *int64
6970
Follow bool
7071
OnlyLogLines bool
72+
73+
// regexp for highlighting the matched string
74+
reHightlight *regexp.Regexp
7175
}
7276

7377
type ResumeRequest struct {
@@ -99,6 +103,34 @@ func (o TailOptions) IsInclude(msg string) bool {
99103
return false
100104
}
101105

106+
var colorHighlight = color.New(color.FgRed, color.Bold).SprintFunc()
107+
108+
func (o TailOptions) HighlightMatchedString(msg string) string {
109+
if len(o.Include) == 0 {
110+
return msg
111+
}
112+
113+
if o.reHightlight == nil {
114+
ss := make([]string, len(o.Include))
115+
for i, rin := range o.Include {
116+
ss[i] = rin.String()
117+
}
118+
119+
// We expect a longer match
120+
sort.Slice(ss, func(i, j int) bool {
121+
return len(ss[i]) > len(ss[j])
122+
})
123+
124+
o.reHightlight = regexp.MustCompile("(" + strings.Join(ss, "|") + ")")
125+
}
126+
127+
msg = o.reHightlight.ReplaceAllStringFunc(msg, func(part string) string {
128+
return colorHighlight(part)
129+
})
130+
131+
return msg
132+
}
133+
102134
func (o TailOptions) UpdateTimezone(timestamp string) (string, error) {
103135
t, err := time.ParseInLocation(time.RFC3339Nano, timestamp, time.UTC)
104136
if err != nil {
@@ -292,7 +324,8 @@ func (t *Tail) consumeLine(line string) {
292324
return
293325
}
294326

295-
msg := content
327+
msg := t.Options.HighlightMatchedString(content)
328+
296329
if t.Options.Timestamps {
297330
updatedTs, err := t.Options.UpdateTimezone(rfc3339Nano)
298331
if err != nil {
@@ -301,6 +334,7 @@ func (t *Tail) consumeLine(line string) {
301334
}
302335
msg = updatedTs + " " + msg
303336
}
337+
304338
t.Print(msg)
305339
}
306340

stern/tail_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"text/template"
1212
"time"
1313

14+
"github.com/fatih/color"
1415
"k8s.io/client-go/kubernetes/fake"
1516
)
1617

@@ -365,3 +366,64 @@ func TestRemoveSubsecond(t *testing.T) {
365366
}
366367
}
367368
}
369+
370+
func TestHighlightMatchedString(t *testing.T) {
371+
tests := []struct {
372+
msg string
373+
include []*regexp.Regexp
374+
expected string
375+
}{
376+
{
377+
"test matched",
378+
[]*regexp.Regexp{
379+
regexp.MustCompile(`test`),
380+
},
381+
"\x1b[31;1mtest\x1b[0m matched",
382+
},
383+
{
384+
"test not-matched",
385+
[]*regexp.Regexp{
386+
regexp.MustCompile(`hoge`),
387+
},
388+
"test not-matched",
389+
},
390+
{
391+
"test matched",
392+
[]*regexp.Regexp{
393+
regexp.MustCompile(`not-matched`),
394+
regexp.MustCompile(`matched`),
395+
},
396+
"test \x1b[31;1mmatched\x1b[0m",
397+
},
398+
{
399+
"test multiple matched",
400+
[]*regexp.Regexp{
401+
regexp.MustCompile(`multiple`),
402+
regexp.MustCompile(`matched`),
403+
},
404+
"test \x1b[31;1mmultiple\x1b[0m \x1b[31;1mmatched\x1b[0m",
405+
},
406+
{
407+
"test match on the longer one",
408+
[]*regexp.Regexp{
409+
regexp.MustCompile(`match`),
410+
regexp.MustCompile(`match on the longer one`),
411+
},
412+
"test \x1b[31;1mmatch on the longer one\x1b[0m",
413+
},
414+
}
415+
416+
orig := color.NoColor
417+
color.NoColor = false
418+
defer func() {
419+
color.NoColor = orig
420+
}()
421+
422+
for i, tt := range tests {
423+
o := &TailOptions{Include: tt.include}
424+
actual := o.HighlightMatchedString(tt.msg)
425+
if actual != tt.expected {
426+
t.Errorf("%d: expected %q, but actual %q", i, tt.expected, actual)
427+
}
428+
}
429+
}

0 commit comments

Comments
 (0)