2022-10-17 07:24:07 +00:00
package hubtest
2021-10-04 15:14:52 +00:00
import (
"bufio"
"fmt"
2022-09-06 11:55:03 +00:00
"io"
2021-10-04 15:14:52 +00:00
"os"
"sort"
"strings"
"time"
"github.com/antonmedv/expr"
"github.com/enescakir/emoji"
2021-11-02 11:06:01 +00:00
"github.com/fatih/color"
diff "github.com/r3labs/diff/v2"
2021-10-04 15:14:52 +00:00
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
2023-06-29 09:34:59 +00:00
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
"github.com/crowdsecurity/crowdsec/pkg/types"
2021-10-04 15:14:52 +00:00
)
type AssertFail struct {
File string
Line int
Expression string
Debug map [ string ] string
}
type ParserAssert struct {
File string
AutoGenAssert bool
AutoGenAssertData string
NbAssert int
Fails [ ] AssertFail
Success bool
TestData * ParserResults
}
type ParserResult struct {
Evt types . Event
Success bool
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
type ParserResults map [ string ] map [ string ] [ ] ParserResult
func NewParserAssert ( file string ) * ParserAssert {
ParserAssert := & ParserAssert {
File : file ,
NbAssert : 0 ,
Success : false ,
Fails : make ( [ ] AssertFail , 0 ) ,
AutoGenAssert : false ,
TestData : & ParserResults { } ,
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
return ParserAssert
}
func ( p * ParserAssert ) AutoGenFromFile ( filename string ) ( string , error ) {
err := p . LoadTest ( filename )
if err != nil {
return "" , err
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
ret := p . AutoGenParserAssert ( )
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
return ret , nil
}
func ( p * ParserAssert ) LoadTest ( filename string ) error {
parserDump , err := LoadParserDump ( filename )
if err != nil {
return fmt . Errorf ( "loading parser dump file: %+v" , err )
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
p . TestData = parserDump
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
return nil
}
func ( p * ParserAssert ) AssertFile ( testFile string ) error {
file , err := os . Open ( p . File )
if err != nil {
return fmt . Errorf ( "failed to open" )
}
if err := p . LoadTest ( testFile ) ; err != nil {
return fmt . Errorf ( "unable to load parser dump file '%s': %s" , testFile , err )
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
scanner := bufio . NewScanner ( file )
scanner . Split ( bufio . ScanLines )
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
nbLine := 0
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
for scanner . Scan ( ) {
2023-11-07 13:02:02 +00:00
nbLine ++
2021-10-04 15:14:52 +00:00
if scanner . Text ( ) == "" {
continue
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
ok , err := p . Run ( scanner . Text ( ) )
if err != nil {
return fmt . Errorf ( "unable to run assert '%s': %+v" , scanner . Text ( ) , err )
}
2023-11-07 13:02:02 +00:00
p . NbAssert ++
2021-10-04 15:14:52 +00:00
if ! ok {
log . Debugf ( "%s is FALSE" , scanner . Text ( ) )
failedAssert := & AssertFail {
File : p . File ,
Line : nbLine ,
Expression : scanner . Text ( ) ,
Debug : make ( map [ string ] string ) ,
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
match := variableRE . FindStringSubmatch ( scanner . Text ( ) )
2023-09-28 15:22:00 +00:00
variable := ""
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
if len ( match ) == 0 {
log . Infof ( "Couldn't get variable of line '%s'" , scanner . Text ( ) )
2023-09-28 15:22:00 +00:00
variable = scanner . Text ( )
} else {
variable = match [ 1 ]
2021-10-04 15:14:52 +00:00
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
result , err := p . EvalExpression ( variable )
if err != nil {
log . Errorf ( "unable to evaluate variable '%s': %s" , variable , err )
continue
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
failedAssert . Debug [ variable ] = result
p . Fails = append ( p . Fails , * failedAssert )
2023-09-28 15:22:00 +00:00
2021-10-04 15:14:52 +00:00
continue
}
//fmt.Printf(" %s '%s'\n", emoji.GreenSquare, scanner.Text())
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
file . Close ( )
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
if p . NbAssert == 0 {
assertData , err := p . AutoGenFromFile ( testFile )
if err != nil {
2022-06-22 13:53:53 +00:00
return fmt . Errorf ( "couldn't generate assertion: %s" , err )
2021-10-04 15:14:52 +00:00
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
p . AutoGenAssertData = assertData
p . AutoGenAssert = true
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
if len ( p . Fails ) == 0 {
p . Success = true
}
return nil
}
func ( p * ParserAssert ) RunExpression ( expression string ) ( interface { } , error ) {
//debug doesn't make much sense with the ability to evaluate "on the fly"
//var debugFilter *exprhelpers.ExprDebugger
var output interface { }
env := map [ string ] interface { } { "results" : * p . TestData }
2023-11-07 13:02:02 +00:00
runtimeFilter , err := expr . Compile ( expression , exprhelpers . GetExprOptions ( env ) ... )
if err != nil {
2023-09-28 15:22:00 +00:00
log . Errorf ( "failed to compile '%s' : %s" , expression , err )
2021-10-04 15:14:52 +00:00
return output , err
}
//dump opcode in trace level
log . Tracef ( "%s" , runtimeFilter . Disassemble ( ) )
2023-09-28 15:22:00 +00:00
output , err = expr . Run ( runtimeFilter , env )
2021-10-04 15:14:52 +00:00
if err != nil {
log . Warningf ( "running : %s" , expression )
log . Warningf ( "runtime error : %s" , err )
2023-11-07 13:02:02 +00:00
2023-06-29 09:34:59 +00:00
return output , fmt . Errorf ( "while running expression %s: %w" , expression , err )
2021-10-04 15:14:52 +00:00
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
return output , nil
}
func ( p * ParserAssert ) EvalExpression ( expression string ) ( string , error ) {
output , err := p . RunExpression ( expression )
if err != nil {
return "" , err
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
ret , err := yaml . Marshal ( output )
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
if err != nil {
return "" , err
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
return string ( ret ) , nil
}
func ( p * ParserAssert ) Run ( assert string ) ( bool , error ) {
output , err := p . RunExpression ( assert )
if err != nil {
return false , err
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
switch out := output . ( type ) {
case bool :
return out , nil
default :
return false , fmt . Errorf ( "assertion '%s' is not a condition" , assert )
}
}
func Escape ( val string ) string {
val = strings . ReplaceAll ( val , ` \ ` , ` \\ ` )
val = strings . ReplaceAll ( val , ` " ` , ` \" ` )
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
return val
}
func ( p * ParserAssert ) AutoGenParserAssert ( ) string {
//attempt to autogen parser asserts
2023-11-07 13:02:02 +00:00
ret := fmt . Sprintf ( "len(results) == %d\n" , len ( * p . TestData ) )
//sort map keys for consistent order
stages := sortedMapKeys ( * p . TestData )
2021-10-04 15:14:52 +00:00
for _ , stage := range stages {
parsers := ( * p . TestData ) [ stage ]
2023-11-07 13:02:02 +00:00
//sort map keys for consistent order
pnames := sortedMapKeys ( parsers )
2021-10-04 15:14:52 +00:00
for _ , parser := range pnames {
presults := parsers [ parser ]
ret += fmt . Sprintf ( ` len(results["%s"]["%s"]) == %d ` + "\n" , stage , parser , len ( presults ) )
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
for pidx , result := range presults {
ret += fmt . Sprintf ( ` results["%s"]["%s"][%d].Success == %t ` + "\n" , stage , parser , pidx , result . Success )
if ! result . Success {
continue
}
2023-11-07 13:02:02 +00:00
2023-10-16 07:54:19 +00:00
for _ , pkey := range sortedMapKeys ( result . Evt . Parsed ) {
pval := result . Evt . Parsed [ pkey ]
2021-10-04 15:14:52 +00:00
if pval == "" {
continue
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
ret += fmt . Sprintf ( ` results["%s"]["%s"][%d].Evt.Parsed["%s"] == "%s" ` + "\n" , stage , parser , pidx , pkey , Escape ( pval ) )
}
2023-11-07 13:02:02 +00:00
2023-10-16 07:54:19 +00:00
for _ , mkey := range sortedMapKeys ( result . Evt . Meta ) {
mval := result . Evt . Meta [ mkey ]
2021-10-04 15:14:52 +00:00
if mval == "" {
continue
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
ret += fmt . Sprintf ( ` results["%s"]["%s"][%d].Evt.Meta["%s"] == "%s" ` + "\n" , stage , parser , pidx , mkey , Escape ( mval ) )
}
2023-11-07 13:02:02 +00:00
2023-10-16 07:54:19 +00:00
for _ , ekey := range sortedMapKeys ( result . Evt . Enriched ) {
eval := result . Evt . Enriched [ ekey ]
2021-10-04 15:14:52 +00:00
if eval == "" {
continue
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
ret += fmt . Sprintf ( ` results["%s"]["%s"][%d].Evt.Enriched["%s"] == "%s" ` + "\n" , stage , parser , pidx , ekey , Escape ( eval ) )
}
2023-11-07 13:02:02 +00:00
2023-10-16 07:54:19 +00:00
for _ , ukey := range sortedMapKeys ( result . Evt . Unmarshaled ) {
uval := result . Evt . Unmarshaled [ ukey ]
if uval == "" {
2023-05-26 09:44:58 +00:00
continue
}
2023-11-07 13:02:02 +00:00
2023-10-16 07:54:19 +00:00
base := fmt . Sprintf ( ` results["%s"]["%s"][%d].Evt.Unmarshaled["%s"] ` , stage , parser , pidx , ukey )
2023-11-07 13:02:02 +00:00
2023-10-16 07:54:19 +00:00
for _ , line := range p . buildUnmarshaledAssert ( base , uval ) {
2023-09-28 15:22:00 +00:00
ret += line
2023-05-26 09:44:58 +00:00
}
}
2023-11-07 13:02:02 +00:00
2023-09-20 15:42:19 +00:00
ret += fmt . Sprintf ( ` results["%s"]["%s"][%d].Evt.Whitelisted == %t ` + "\n" , stage , parser , pidx , result . Evt . Whitelisted )
2023-11-07 13:02:02 +00:00
2023-09-20 15:42:19 +00:00
if result . Evt . WhitelistReason != "" {
ret += fmt . Sprintf ( ` results["%s"]["%s"][%d].Evt.WhitelistReason == "%s" ` + "\n" , stage , parser , pidx , Escape ( result . Evt . WhitelistReason ) )
}
2021-10-04 15:14:52 +00:00
}
}
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
return ret
}
2023-05-26 09:44:58 +00:00
func ( p * ParserAssert ) buildUnmarshaledAssert ( ekey string , eval interface { } ) [ ] string {
ret := make ( [ ] string , 0 )
2023-11-07 13:02:02 +00:00
2023-05-26 09:44:58 +00:00
switch val := eval . ( type ) {
case map [ string ] interface { } :
for k , v := range val {
ret = append ( ret , p . buildUnmarshaledAssert ( fmt . Sprintf ( ` %s["%s"] ` , ekey , k ) , v ) ... )
}
case map [ interface { } ] interface { } :
for k , v := range val {
ret = append ( ret , p . buildUnmarshaledAssert ( fmt . Sprintf ( ` %s["%s"] ` , ekey , k ) , v ) ... )
}
case [ ] interface { } :
case string :
ret = append ( ret , fmt . Sprintf ( ` %s == "%s" ` + "\n" , ekey , Escape ( val ) ) )
case bool :
ret = append ( ret , fmt . Sprintf ( ` %s == %t ` + "\n" , ekey , val ) )
case int :
ret = append ( ret , fmt . Sprintf ( ` %s == %d ` + "\n" , ekey , val ) )
case float64 :
2023-09-28 15:22:00 +00:00
ret = append ( ret , fmt . Sprintf ( ` FloatApproxEqual(%s, %f) ` + "\n" ,
ekey , val ) )
2023-05-26 09:44:58 +00:00
default :
log . Warningf ( "unknown type '%T' for key '%s'" , val , ekey )
}
2023-11-07 13:02:02 +00:00
2023-05-26 09:44:58 +00:00
return ret
}
2021-10-04 15:14:52 +00:00
func LoadParserDump ( filepath string ) ( * ParserResults , error ) {
dumpData , err := os . Open ( filepath )
if err != nil {
return nil , err
}
defer dumpData . Close ( )
2022-09-06 11:55:03 +00:00
results , err := io . ReadAll ( dumpData )
2021-10-04 15:14:52 +00:00
if err != nil {
return nil , err
}
2023-11-07 13:02:02 +00:00
pdump := ParserResults { }
2021-10-04 15:14:52 +00:00
if err := yaml . Unmarshal ( results , & pdump ) ; err != nil {
return nil , err
}
2022-12-29 14:03:32 +00:00
/ * we know that some variables should always be set ,
let ' s check if they ' re present in last parser output of last stage * /
2023-11-07 13:02:02 +00:00
stages := sortedMapKeys ( pdump )
2023-09-29 11:03:56 +00:00
var lastStage string
2023-11-07 13:02:02 +00:00
2023-09-29 11:03:56 +00:00
//Loop over stages to find last successful one with at least one parser
for i := len ( stages ) - 2 ; i >= 0 ; i -- {
if len ( pdump [ stages [ i ] ] ) != 0 {
lastStage = stages [ i ]
break
}
}
2023-11-07 13:02:02 +00:00
2022-12-29 14:03:32 +00:00
parsers := make ( [ ] string , 0 , len ( pdump [ lastStage ] ) )
2023-11-07 13:02:02 +00:00
2022-12-29 14:03:32 +00:00
for k := range pdump [ lastStage ] {
parsers = append ( parsers , k )
}
2023-11-07 13:02:02 +00:00
2022-12-29 14:03:32 +00:00
sort . Strings ( parsers )
2023-11-14 13:58:36 +00:00
if len ( parsers ) == 0 {
return nil , fmt . Errorf ( "no parser found. Please install the appropriate parser and retry" )
}
2022-12-29 14:03:32 +00:00
lastParser := parsers [ len ( parsers ) - 1 ]
for idx , result := range pdump [ lastStage ] [ lastParser ] {
if result . Evt . StrTime == "" {
log . Warningf ( "Line %d/%d is missing evt.StrTime. It is most likely a mistake as it will prevent your logs to be processed in time-machine/forensic mode." , idx , len ( pdump [ lastStage ] [ lastParser ] ) )
} else {
log . Debugf ( "Line %d/%d has evt.StrTime set to '%s'" , idx , len ( pdump [ lastStage ] [ lastParser ] ) , result . Evt . StrTime )
}
}
2021-10-04 15:14:52 +00:00
return & pdump , nil
}
2021-11-08 17:01:43 +00:00
type DumpOpts struct {
2023-02-24 13:49:17 +00:00
Details bool
SkipOk bool
ShowNotOkParsers bool
2021-11-08 17:01:43 +00:00
}
2023-11-07 13:02:02 +00:00
func DumpTree ( parserResults ParserResults , bucketPour BucketPourInfo , opts DumpOpts ) {
2021-10-04 15:14:52 +00:00
//note : we can use line -> time as the unique identifier (of acquisition)
2021-11-02 11:06:01 +00:00
state := make ( map [ time . Time ] map [ string ] map [ string ] ParserResult )
2021-10-04 15:14:52 +00:00
assoc := make ( map [ time . Time ] string , 0 )
2023-11-07 13:02:02 +00:00
for stage , parsers := range parserResults {
2021-10-04 15:14:52 +00:00
for parser , results := range parsers {
2023-11-07 13:02:02 +00:00
for _ , parserRes := range results {
evt := parserRes . Evt
2021-10-04 15:14:52 +00:00
if _ , ok := state [ evt . Line . Time ] ; ! ok {
2021-11-02 11:06:01 +00:00
state [ evt . Line . Time ] = make ( map [ string ] map [ string ] ParserResult )
2021-10-04 15:14:52 +00:00
assoc [ evt . Line . Time ] = evt . Line . Raw
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
if _ , ok := state [ evt . Line . Time ] [ stage ] ; ! ok {
2021-11-02 11:06:01 +00:00
state [ evt . Line . Time ] [ stage ] = make ( map [ string ] ParserResult )
2021-10-04 15:14:52 +00:00
}
2021-11-02 11:06:01 +00:00
2023-11-07 13:02:02 +00:00
state [ evt . Line . Time ] [ stage ] [ parser ] = ParserResult { Evt : evt , Success : parserRes . Success }
}
2021-10-04 15:14:52 +00:00
}
}
2023-11-07 13:02:02 +00:00
for bname , evtlist := range bucketPour {
2021-10-04 15:14:52 +00:00
for _ , evt := range evtlist {
if evt . Line . Raw == "" {
continue
}
2023-11-07 13:02:02 +00:00
2022-04-27 09:04:12 +00:00
//it might be bucket overflow being reprocessed, skip this
2021-10-04 15:14:52 +00:00
if _ , ok := state [ evt . Line . Time ] ; ! ok {
2021-11-02 11:06:01 +00:00
state [ evt . Line . Time ] = make ( map [ string ] map [ string ] ParserResult )
2021-10-04 15:14:52 +00:00
assoc [ evt . Line . Time ] = evt . Line . Raw
}
2023-11-07 13:02:02 +00:00
2022-04-19 09:25:27 +00:00
//there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
2021-10-04 15:14:52 +00:00
//we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
if _ , ok := state [ evt . Line . Time ] [ "buckets" ] ; ! ok {
2021-11-02 11:06:01 +00:00
state [ evt . Line . Time ] [ "buckets" ] = make ( map [ string ] ParserResult )
2021-10-04 15:14:52 +00:00
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
state [ evt . Line . Time ] [ "buckets" ] [ bname ] = ParserResult { Success : true }
2021-10-04 15:14:52 +00:00
}
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
yellow := color . New ( color . FgYellow ) . SprintFunc ( )
red := color . New ( color . FgRed ) . SprintFunc ( )
green := color . New ( color . FgGreen ) . SprintFunc ( )
2023-04-12 09:48:42 +00:00
whitelistReason := ""
2021-10-04 15:14:52 +00:00
//get each line
for tstamp , rawstr := range assoc {
2021-11-08 17:01:43 +00:00
if opts . SkipOk {
if _ , ok := state [ tstamp ] [ "buckets" ] [ "OK" ] ; ok {
continue
}
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
fmt . Printf ( "line: %s\n" , rawstr )
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
skeys := make ( [ ] string , 0 , len ( state [ tstamp ] ) )
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
for k := range state [ tstamp ] {
2022-04-19 09:25:27 +00:00
//there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
2021-10-04 15:14:52 +00:00
//we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
if k == "buckets" {
continue
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
skeys = append ( skeys , k )
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
sort . Strings ( skeys )
2023-11-07 13:02:02 +00:00
// iterate stage
var prevItem types . Event
2021-11-02 11:06:01 +00:00
2021-10-04 15:14:52 +00:00
for _ , stage := range skeys {
parsers := state [ tstamp ] [ stage ]
sep := "├"
presep := "|"
fmt . Printf ( "\t%s %s\n" , sep , stage )
2023-11-07 13:02:02 +00:00
pkeys := sortedMapKeys ( parsers )
2021-10-04 15:14:52 +00:00
for idx , parser := range pkeys {
2021-11-02 11:06:01 +00:00
res := parsers [ parser ] . Success
2021-10-04 15:14:52 +00:00
sep := "├"
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
if idx == len ( pkeys ) - 1 {
sep = "└"
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
created := 0
updated := 0
deleted := 0
whitelisted := false
changeStr := ""
detailsDisplay := ""
2021-10-04 15:14:52 +00:00
if res {
2023-11-07 13:02:02 +00:00
changelog , _ := diff . Diff ( prevItem , parsers [ parser ] . Evt )
2023-02-14 14:36:08 +00:00
for _ , change := range changelog {
switch change . Type {
case "create" :
created ++
2023-11-07 13:02:02 +00:00
2023-02-14 14:36:08 +00:00
detailsDisplay += fmt . Sprintf ( "\t%s\t\t%s %s evt.%s : %s\n" , presep , sep , change . Type , strings . Join ( change . Path , "." ) , green ( change . To ) )
case "update" :
detailsDisplay += fmt . Sprintf ( "\t%s\t\t%s %s evt.%s : %s -> %s\n" , presep , sep , change . Type , strings . Join ( change . Path , "." ) , change . From , yellow ( change . To ) )
2023-11-07 13:02:02 +00:00
2023-02-14 14:36:08 +00:00
if change . Path [ 0 ] == "Whitelisted" && change . To == true {
whitelisted = true
2023-11-07 13:02:02 +00:00
2023-04-12 09:48:42 +00:00
if whitelistReason == "" {
whitelistReason = parsers [ parser ] . Evt . WhitelistReason
}
2021-11-02 11:06:01 +00:00
}
2023-02-14 14:36:08 +00:00
updated ++
case "delete" :
deleted ++
2023-11-07 13:02:02 +00:00
2023-02-14 14:36:08 +00:00
detailsDisplay += fmt . Sprintf ( "\t%s\t\t%s %s evt.%s\n" , presep , sep , change . Type , red ( strings . Join ( change . Path , "." ) ) )
2021-11-02 11:06:01 +00:00
}
}
2023-11-07 13:02:02 +00:00
prevItem = parsers [ parser ] . Evt
2021-11-02 11:06:01 +00:00
}
if created > 0 {
changeStr += green ( fmt . Sprintf ( "+%d" , created ) )
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
if updated > 0 {
if len ( changeStr ) > 0 {
changeStr += " "
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
changeStr += yellow ( fmt . Sprintf ( "~%d" , updated ) )
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
if deleted > 0 {
if len ( changeStr ) > 0 {
changeStr += " "
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
changeStr += red ( fmt . Sprintf ( "-%d" , deleted ) )
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
if whitelisted {
if len ( changeStr ) > 0 {
changeStr += " "
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
changeStr += red ( "[whitelisted]" )
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
if changeStr == "" {
changeStr = yellow ( "unchanged" )
}
2023-11-07 13:02:02 +00:00
2021-11-02 11:06:01 +00:00
if res {
fmt . Printf ( "\t%s\t%s %s %s (%s)\n" , presep , sep , emoji . GreenCircle , parser , changeStr )
2023-11-07 13:02:02 +00:00
2021-11-08 17:01:43 +00:00
if opts . Details {
2021-11-02 11:06:01 +00:00
fmt . Print ( detailsDisplay )
}
2023-02-24 13:49:17 +00:00
} else if opts . ShowNotOkParsers {
2021-10-04 15:14:52 +00:00
fmt . Printf ( "\t%s\t%s %s %s\n" , presep , sep , emoji . RedCircle , parser )
}
}
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
sep := "└"
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
if len ( state [ tstamp ] [ "buckets" ] ) > 0 {
sep = "├"
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
//did the event enter the bucket pour phase ?
if _ , ok := state [ tstamp ] [ "buckets" ] [ "OK" ] ; ok {
fmt . Printf ( "\t%s-------- parser success %s\n" , sep , emoji . GreenCircle )
2023-04-12 09:48:42 +00:00
} else if whitelistReason != "" {
fmt . Printf ( "\t%s-------- parser success, ignored by whitelist (%s) %s\n" , sep , whitelistReason , emoji . GreenCircle )
2021-10-04 15:14:52 +00:00
} else {
fmt . Printf ( "\t%s-------- parser failure %s\n" , sep , emoji . RedCircle )
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
//now print bucket info
if len ( state [ tstamp ] [ "buckets" ] ) > 0 {
fmt . Printf ( "\t├ Scenarios\n" )
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
bnames := make ( [ ] string , 0 , len ( state [ tstamp ] [ "buckets" ] ) )
2023-11-07 13:02:02 +00:00
2022-02-01 21:08:06 +00:00
for k := range state [ tstamp ] [ "buckets" ] {
2022-04-19 09:25:27 +00:00
//there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
2021-10-04 15:14:52 +00:00
//we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
if k == "OK" {
continue
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
bnames = append ( bnames , k )
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
sort . Strings ( bnames )
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
for idx , bname := range bnames {
sep := "├"
if idx == len ( bnames ) - 1 {
sep = "└"
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
fmt . Printf ( "\t\t%s %s %s\n" , sep , emoji . GreenCircle , bname )
}
2023-11-07 13:02:02 +00:00
2021-10-04 15:14:52 +00:00
fmt . Println ( )
}
}