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  }