Skip to content

Commit f156d12

Browse files
authored
Carptenter: multiple renderers, filters, fixes. (#754)
1 parent d67d1db commit f156d12

File tree

17 files changed

+833
-357
lines changed

17 files changed

+833
-357
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
# Test binary, built with `go test -c`
1010
*.test
11-
carpenter
11+
/cmd/carpenter/carpenter
1212

1313
# Test & linter reports
1414
*report.xml

cmd/carpenter/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,11 @@ lint-fix:
1414
golangci-lint run -c ../../.golangci.yml --fix
1515

1616
checks: test lint
17+
18+
$(GOPATH)/bin/go-enum:
19+
go get -u github.com/abice/go-enum
20+
go install github.com/abice/go-enum
21+
22+
.PHONY: generate
23+
generate: $(GOPATH)/bin/go-enum
24+
go generate ./...

cmd/carpenter/command.go

Lines changed: 103 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -5,153 +5,104 @@ import (
55
"context"
66
"fmt"
77
"os"
8+
"slices"
89
"strings"
9-
"time"
1010

11-
"github.com/charmbracelet/lipgloss"
1211
"github.com/urfave/cli/v3"
1312

13+
"github.com/smartcontractkit/chainlink-ccip/cmd/carpenter/internal/filter"
1414
"github.com/smartcontractkit/chainlink-ccip/cmd/carpenter/internal/parse"
15+
"github.com/smartcontractkit/chainlink-ccip/cmd/carpenter/internal/render"
1516
"github.com/smartcontractkit/chainlink-ccip/cmd/carpenter/internal/stream"
1617
)
1718

1819
type arguments struct {
19-
files []string
20-
logType string
21-
disableFilters bool
22-
}
23-
24-
// renderData
25-
/*
26-
27-
2024-12-04T20:15:35Z | 1.1.1 | Commit(MerkleRoot) | <processor details>
28-
| | | | |-- processor
29-
| | | |-- OCR Plugin
30-
| | |-- sequence number
31-
| |-- DON ID
32-
-- oracleID
33-
34-
*/
35-
func renderData(data *parse.Data) {
36-
// simple color selection algorithm
37-
withColor := func(in interface{}, i int) string {
38-
color := fmt.Sprintf("%d", i%7+1)
39-
str := fmt.Sprintf("%v", in)
40-
41-
return lipgloss.NewStyle().Foreground(lipgloss.Color(color)).Render(str)
42-
}
43-
44-
var timeStyle = lipgloss.NewStyle().Width(10).Height(1).MaxHeight(1).
45-
Align(lipgloss.Center)
46-
var uidStyle = lipgloss.NewStyle().Width(25).Height(1).MaxHeight(1).
47-
Align(lipgloss.Left).PaddingLeft(1).Bold(true)
48-
var levelStyle = lipgloss.NewStyle().Width(4).Height(1).MaxHeight(1).
49-
Align(lipgloss.Left).PaddingLeft(1).Italic(true)
50-
var messageStyle = lipgloss.NewStyle().Width(60).Height(1).MaxHeight(1).
51-
Align(lipgloss.Left).PaddingLeft(1)
52-
var fieldsStyle = lipgloss.NewStyle().Width(100).Height(1).MaxHeight(1).
53-
Align(lipgloss.Left).PaddingLeft(1)
20+
files []string
21+
logType parse.LogType
22+
rendererName string
5423

55-
uid := fmt.Sprintf("%s.%s.%s.%s.%s",
56-
withColor(data.OracleID, data.OracleID),
57-
withColor(data.DONID, data.DONID),
58-
withColor(data.SequenceNumber, data.SequenceNumber),
59-
withColor(data.Component, 0),
60-
withColor(data.OCRPhase, ocrPhaseToColor(data.OCRPhase)),
61-
)
62-
63-
fmt.Printf("%s|%s|%s|%s|%s\n",
64-
timeStyle.Render(data.Timestamp.Format(time.TimeOnly)),
65-
uidStyle.Render(uid),
66-
levelStyle.Render(truncateLevel(data.Level)),
67-
messageStyle.Render(data.Message),
68-
fieldsStyle.Render(getRelevantFieldsForMessage(data)),
69-
)
70-
}
71-
72-
func ocrPhaseToColor(phase string) int {
73-
switch phase {
74-
case "qry":
75-
return 1
76-
case "obs":
77-
return 2
78-
case "otcm":
79-
return 3
80-
case "rprt":
81-
return 4
82-
case "sacc":
83-
return 5
84-
case "strn":
85-
return 6
86-
default:
87-
return 0
88-
}
24+
filter.CompiledFilterFields
25+
filterOP filter.FilterOP
8926
}
9027

91-
func truncateLevel(level string) string {
92-
switch lv := strings.ToLower(level); lv {
93-
case "info":
94-
return "ifo"
95-
case "debug":
96-
return "dbg"
97-
case "warn":
98-
return "wrn"
99-
case "error":
100-
return "err"
101-
case "critical":
102-
return "crt"
103-
default:
104-
return "unk"
105-
}
106-
}
107-
108-
func getRelevantFieldsForMessage(data *parse.Data) string {
109-
var fields string
110-
111-
if strings.ToLower(data.Level) == "error" {
112-
fields = fmt.Sprintf("err=%v", data.RawLoggerFields["err"])
113-
}
114-
115-
if strings.HasPrefix(data.Message, "failed to get token prices outcome") {
116-
return fmt.Sprintf("err=%v", data.RawLoggerFields["err"])
117-
}
118-
119-
if strings.HasPrefix(data.Message, "Get consensus observation failed, empty outcome") {
120-
return fmt.Sprintf("err=%v", data.RawLoggerFields["err"])
121-
}
122-
123-
if strings.HasPrefix(data.Message, "Sending Outcome") {
124-
return fmt.Sprintf("nextState=%v outcome=%v",
125-
data.RawLoggerFields["nextState"], data.RawLoggerFields["outcome"])
126-
}
127-
128-
if strings.HasPrefix(data.Message, "sending merkle root processor observation") {
129-
return fmt.Sprintf("observation=%v", data.RawLoggerFields["observation"])
130-
}
131-
132-
if strings.HasPrefix(data.Message, "call to MsgsBetweenSeqNums returned unexpected") {
133-
return fmt.Sprintf(
134-
"%s expected=%v actual=%v chain=%v",
135-
fields,
136-
data.RawLoggerFields["expected"],
137-
data.RawLoggerFields["actual"],
138-
data.RawLoggerFields["chain"],
139-
)
140-
}
141-
if strings.HasPrefix(data.Message, "queried messages between sequence numbers") {
142-
return fmt.Sprintf("%s numMsgs=%v sourceChain=%v seqNumRange=%v",
143-
fields,
144-
data.RawLoggerFields["numMsgs"],
145-
data.RawLoggerFields["sourceChainSelector"],
146-
data.RawLoggerFields["seqNumRange"],
147-
)
148-
}
149-
if strings.HasPrefix(data.Message, "decoded messages between sequence numbers") {
150-
return fmt.Sprintf("%s sourceChain=%v seqNumRange=%v",
151-
fields, data.RawLoggerFields["sourceChainSelector"], data.RawLoggerFields["seqNumRange"])
28+
func makeCommand() *cli.Command {
29+
var args arguments
30+
return &cli.Command{
31+
Name: "carpenter",
32+
Usage: "A tool for parsing and displaying logs",
33+
Flags: []cli.Flag{
34+
&cli.StringSliceFlag{
35+
Name: "filename",
36+
Usage: "Provide one or more files to read. If not provided, reads from stdin.",
37+
Destination: &args.files,
38+
},
39+
&cli.StringFlag{
40+
Name: "logType",
41+
Usage: "Specify the type of log to parse, valid options: json, mixed, ci",
42+
Value: "json",
43+
Validator: func(s string) error {
44+
var err error
45+
args.logType, err = parse.ParseLogType(s)
46+
if err != nil {
47+
return fmt.Errorf("expected one of [%s]",
48+
strings.Join(parse.LogTypeNames(), ", "))
49+
}
50+
return nil
51+
},
52+
},
53+
&cli.StringFlag{
54+
OnlyOnce: true,
55+
Name: "renderer",
56+
Usage: fmt.Sprintf("Select which rendering algorithm to use: [%s]", strings.Join(render.GetRenderers(), ", ")),
57+
Value: "basic",
58+
Destination: &args.rendererName,
59+
Validator: func(s string) error {
60+
choices := render.GetRenderers()
61+
if !slices.Contains(choices, s) {
62+
return fmt.Errorf("expected one of [%s]",
63+
s, strings.Join(choices, ", "))
64+
}
65+
return nil
66+
},
67+
},
68+
&cli.StringSliceFlag{
69+
Name: "filter",
70+
Usage: fmt.Sprintf(
71+
"Line selection filters. Format as 'FieldName:Regexp', valid fields: [%s]",
72+
strings.Join(filter.FieldNames(), ", ")),
73+
//Destination: &args.FilterFields.Filters,
74+
Category: "filters",
75+
Validator: func(fields []string) error {
76+
var err error
77+
args.CompiledFilterFields, err = filter.NewFilterFields(fields)
78+
if err != nil {
79+
return err
80+
}
81+
return nil
82+
},
83+
},
84+
&cli.StringFlag{
85+
Name: "filter-op",
86+
Usage: fmt.Sprintf(
87+
"Operation to use when combining filters. Valid options: [%s]",
88+
strings.Join(filter.FilterOPNames(), ", ")),
89+
Category: "filters",
90+
Value: string(filter.FilterOPAND),
91+
Validator: func(s string) error {
92+
var err error
93+
args.filterOP, err = filter.ParseFilterOP(s)
94+
if err != nil {
95+
return fmt.Errorf("expected one of %s", err,
96+
strings.Join(filter.FilterOPNames(), ", "))
97+
}
98+
return nil
99+
},
100+
},
101+
},
102+
Action: func(ctx context.Context, cmd *cli.Command) error {
103+
return run(args)
104+
},
152105
}
153-
154-
return ""
155106
}
156107

157108
func run(args arguments) error {
@@ -162,6 +113,11 @@ func run(args arguments) error {
162113
io.Filenames = args.files
163114
}
164115

116+
renderer, err := render.GetRenderer(args.rendererName, render.Options{})
117+
if err != nil {
118+
return fmt.Errorf("failed to get renderer: %w", err)
119+
}
120+
165121
inputStream, err := stream.InitializeInputStream(io)
166122
if err != nil {
167123
return fmt.Errorf("failed to initialize input stream: %w", err)
@@ -170,59 +126,26 @@ func run(args arguments) error {
170126
scanner := bufio.NewScanner(inputStream)
171127
for scanner.Scan() {
172128
line := scanner.Text()
173-
data, err := parse.Filter(line, args.logType, args.disableFilters)
129+
data, err := parse.ParseLine(line, args.logType)
130+
if err != nil {
131+
return fmt.Errorf("ParseLine: %w", err)
132+
}
133+
134+
include, err := filter.Filter(data, args.CompiledFilterFields, args.filterOP)
174135
if err != nil {
175-
fmt.Fprintf(os.Stderr, "Unable to get data: %s\n", err)
136+
msg := fmt.Sprintf("Unable to get data: %s\n", err)
137+
_, err2 := fmt.Fprintf(os.Stderr, msg)
138+
if err2 != nil {
139+
panic(msg)
140+
}
176141
return err
177142
}
178-
if data == nil {
143+
if !include {
179144
// no data to display.
180145
continue
181146
}
182147

183-
renderData(data)
184-
//fmt.Println(data)
148+
renderer(data)
185149
}
186150
return nil
187151
}
188-
189-
func makeCommand() *cli.Command {
190-
var args arguments
191-
return &cli.Command{
192-
Name: "carpenter",
193-
Usage: "A tool for parsing and displaying logs",
194-
Flags: []cli.Flag{
195-
&cli.StringSliceFlag{
196-
Name: "filename",
197-
Usage: "Provide one or more files to read. If not provided, reads from stdin.",
198-
Destination: &args.files,
199-
},
200-
&cli.StringFlag{
201-
Name: "logType",
202-
Usage: "Specify the type of log to parse, valid options: json, mixed, ci",
203-
Destination: &args.logType,
204-
Required: true,
205-
Validator: func(s string) error {
206-
if !parse.IsValidLogType(s) {
207-
return fmt.Errorf("invalid log type: %s, expected either %s or %s or %s",
208-
s,
209-
parse.LogTypeJSON,
210-
parse.LogTypeMixed,
211-
parse.LogTypeMixedGoTestJSON,
212-
)
213-
}
214-
return nil
215-
},
216-
},
217-
&cli.BoolFlag{
218-
Name: "disableFilters",
219-
Usage: "Set to disable filter application on the logs. Defaults to false.",
220-
Destination: &args.disableFilters,
221-
Required: false,
222-
},
223-
},
224-
Action: func(ctx context.Context, cmd *cli.Command) error {
225-
return run(args)
226-
},
227-
}
228-
}

cmd/carpenter/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/smartcontractkit/chainlink-ccip/cmd/carpenter
22

3-
go 1.23.3
3+
go 1.24.0
44

55
require (
66
github.com/charmbracelet/lipgloss v1.0.0
@@ -11,6 +11,7 @@ require (
1111
require (
1212
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
1313
github.com/charmbracelet/x/ansi v0.4.2 // indirect
14+
github.com/charmbracelet/x/term v0.2.1 // indirect
1415
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1516
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
1617
github.com/mattn/go-isatty v0.0.20 // indirect

cmd/carpenter/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O
44
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
55
github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk=
66
github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
7+
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
8+
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
79
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
810
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
911
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=

0 commit comments

Comments
 (0)