github.com/honeycombio/honeytail@v1.9.0/parsers/syslog/syslog.go (about) 1 package syslog 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 "time" 8 9 "github.com/sirupsen/logrus" 10 11 "github.com/honeycombio/honeytail/event" 12 "github.com/honeycombio/honeytail/parsers" 13 14 "github.com/jeromer/syslogparser" 15 "github.com/jeromer/syslogparser/rfc3164" 16 "github.com/jeromer/syslogparser/rfc5424" 17 ) 18 19 // Options defines the options relevant to the syslog parser 20 type Options struct { 21 Mode string `long:"mode" description:"Syslog mode. Supported values are rfc3164 and rfc5424"` 22 ProcessList string `long:"processes" description:"comma separated list of processes to filter for. example: 'sshd,sudo' - by default all are consumed"` 23 NumParsers int `hidden:"true" description:"number of parsers to spin up"` 24 } 25 26 // Parser implements the Parser interface 27 type Parser struct { 28 conf Options 29 lineParser parsers.LineParser 30 } 31 32 // Init constructs our parser from the provided options 33 func (p *Parser) Init(options interface{}) error { 34 p.conf = *options.(*Options) 35 lineParser, err := NewSyslogLineParser(p.conf.Mode, p.conf.ProcessList) 36 if err != nil { 37 return err 38 } 39 p.lineParser = lineParser 40 return nil 41 } 42 43 type SyslogLineParser struct { 44 mode string 45 supportedProcesses map[string]struct{} 46 } 47 48 func normalizeLogFields(fields map[string]interface{}) { 49 // The RFC3164 and RFC5424 parsers use different fields to refer to the 50 // process - normalize to "process" for consistency and clarity 51 // RFC3164 52 if process, ok := fields["tag"].(string); ok { 53 fields["process"] = process 54 delete(fields, "tag") 55 } 56 // RFC5424 57 if process, ok := fields["app_name"].(string); ok { 58 fields["process"] = process 59 delete(fields, "app_name") 60 } 61 62 // clean up whitespace in the message 63 if message, ok := fields["message"].(string); ok { 64 fields["message"] = strings.TrimSpace(message) 65 } 66 } 67 68 // NewSyslogLineParser factory 69 func NewSyslogLineParser(mode string, processList string) (*SyslogLineParser, error) { 70 var supportedProcesses map[string]struct{} 71 // if a list of process 72 if processList != "" { 73 supportedProcesses = make(map[string]struct{}) 74 for _, process := range strings.Split(processList, ",") { 75 supportedProcesses[strings.TrimSpace(process)] = struct{}{} 76 } 77 } 78 if mode == "rfc3164" || mode == "rfc5424" { 79 return &SyslogLineParser{ 80 mode: mode, 81 supportedProcesses: supportedProcesses, 82 }, nil 83 } 84 85 return nil, fmt.Errorf("unsupported mode %s, see --help", mode) 86 } 87 88 func (p *SyslogLineParser) ParseLine(line string) (map[string]interface{}, error) { 89 var parser syslogparser.LogParser 90 if p.mode == "rfc3164" { 91 parser = rfc3164.NewParser([]byte(line)) 92 } else if p.mode == "rfc5424" { 93 parser = rfc5424.NewParser([]byte(line)) 94 } 95 96 if err := parser.Parse(); err != nil { 97 return nil, err 98 } 99 logFields := parser.Dump() 100 normalizeLogFields(logFields) 101 // if someone set --processes, this will not be nil 102 if p.supportedProcesses != nil { 103 if process, ok := logFields["process"].(string); ok { 104 // if the process is not in the whitelist, skip it 105 if _, match := p.supportedProcesses[process]; !match { 106 return nil, nil 107 } 108 } 109 } 110 return logFields, nil 111 } 112 113 func (p *Parser) ProcessLines(lines <-chan string, send chan<- event.Event, prefixRegex *parsers.ExtRegexp) { 114 // parse lines one by one 115 wg := sync.WaitGroup{} 116 numParsers := 1 117 if p.conf.NumParsers > 0 { 118 numParsers = p.conf.NumParsers 119 } 120 for i := 0; i < numParsers; i++ { 121 wg.Add(1) 122 go func() { 123 for line := range lines { 124 logrus.WithFields(logrus.Fields{ 125 "line": line, 126 }).Debug("attempting to process line") 127 128 // take care of any headers on the line 129 var prefixFields map[string]string 130 if prefixRegex != nil { 131 var prefix string 132 prefix, prefixFields = prefixRegex.FindStringSubmatchMap(line) 133 line = strings.TrimPrefix(line, prefix) 134 } 135 136 parsedLine, err := p.lineParser.ParseLine(line) 137 if parsedLine == nil || err != nil { 138 continue 139 } 140 141 if len(parsedLine) == 0 { 142 logrus.WithFields(logrus.Fields{ 143 "line": line, 144 }).Info("skipping line, no values found") 145 continue 146 } 147 148 // merge the prefix fields and the parsed line contents 149 for k, v := range prefixFields { 150 parsedLine[k] = v 151 } 152 153 // the timestamp should be in the log file if we're following either rfc 3164 or 5424 154 var timestamp time.Time 155 if t, ok := parsedLine["timestamp"].(time.Time); ok { 156 timestamp = t 157 } 158 159 // send an event to Transmission 160 e := event.Event{ 161 Timestamp: timestamp, 162 Data: parsedLine, 163 } 164 send <- e 165 } 166 wg.Done() 167 }() 168 } 169 wg.Wait() 170 logrus.Debug("lines channel is closed, ending syslog processor") 171 }