@@ -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
1819type 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
157108func 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- }
0 commit comments