github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/client/driver/logging/syslog_parser.go (about)

     1  // +build darwin dragonfly freebsd linux netbsd openbsd solaris windows
     2  
     3  package logging
     4  
     5  import (
     6  	"fmt"
     7  	"log"
     8  	"strconv"
     9  
    10  	syslog "github.com/RackSec/srslog"
    11  )
    12  
    13  // Errors related to parsing priority
    14  var (
    15  	ErrPriorityNoStart  = fmt.Errorf("No start char found for priority")
    16  	ErrPriorityEmpty    = fmt.Errorf("Priority field empty")
    17  	ErrPriorityNoEnd    = fmt.Errorf("No end char found for priority")
    18  	ErrPriorityTooShort = fmt.Errorf("Priority field too short")
    19  	ErrPriorityTooLong  = fmt.Errorf("Priority field too long")
    20  	ErrPriorityNonDigit = fmt.Errorf("Non digit found in priority")
    21  )
    22  
    23  // Priority header and ending characters
    24  const (
    25  	PRI_PART_START = '<'
    26  	PRI_PART_END   = '>'
    27  )
    28  
    29  // SyslogMessage represents a log line received
    30  type SyslogMessage struct {
    31  	Message  []byte
    32  	Severity syslog.Priority
    33  }
    34  
    35  // Priority holds all the priority bits in a syslog log line
    36  type Priority struct {
    37  	Pri      int
    38  	Facility syslog.Priority
    39  	Severity syslog.Priority
    40  }
    41  
    42  // DockerLogParser parses a line of log message that the docker daemon ships
    43  type DockerLogParser struct {
    44  	logger *log.Logger
    45  }
    46  
    47  // NewDockerLogParser creates a new DockerLogParser
    48  func NewDockerLogParser(logger *log.Logger) *DockerLogParser {
    49  	return &DockerLogParser{logger: logger}
    50  }
    51  
    52  // Parse parses a syslog log line
    53  func (d *DockerLogParser) Parse(line []byte) *SyslogMessage {
    54  	pri, _, _ := d.parsePriority(line)
    55  	msgIdx := d.logContentIndex(line)
    56  
    57  	// Create a copy of the line so that subsequent Scans do not override the
    58  	// message
    59  	lineCopy := make([]byte, len(line[msgIdx:]))
    60  	copy(lineCopy, line[msgIdx:])
    61  
    62  	return &SyslogMessage{
    63  		Severity: pri.Severity,
    64  		Message:  lineCopy,
    65  	}
    66  }
    67  
    68  // logContentIndex finds out the index of the start index of the content in a
    69  // syslog line
    70  func (d *DockerLogParser) logContentIndex(line []byte) int {
    71  	cursor := 0
    72  	numSpace := 0
    73  	numColons := 0
    74  	// first look for at least 2 colons. This matches into the date that has no more spaces in it
    75  	// DefaultFormatter log line look: '<30>2016-07-06T15:13:11Z00:00 hostname docker/9648c64f5037[16200]'
    76  	// UnixFormatter log line look: '<30>Jul  6 15:13:11 docker/9648c64f5037[16200]'
    77  	for i := 0; i < len(line); i++ {
    78  		if line[i] == ':' {
    79  			numColons += 1
    80  			if numColons == 2 {
    81  				cursor = i
    82  				break
    83  			}
    84  		}
    85  	}
    86  	// then look for the next space
    87  	for i := cursor; i < len(line); i++ {
    88  		if line[i] == ' ' {
    89  			numSpace += 1
    90  			if numSpace == 1 {
    91  				cursor = i
    92  				break
    93  			}
    94  		}
    95  	}
    96  	// then the colon is what separates it, followed by a space
    97  	for i := cursor; i < len(line); i++ {
    98  		if line[i] == ':' && i+1 < len(line) && line[i+1] == ' ' {
    99  			cursor = i + 1
   100  			break
   101  		}
   102  	}
   103  	// return the cursor to the next character
   104  	return cursor + 1
   105  }
   106  
   107  // parsePriority parses the priority in a syslog message
   108  func (d *DockerLogParser) parsePriority(line []byte) (Priority, int, error) {
   109  	cursor := 0
   110  	pri := d.newPriority(0)
   111  	if len(line) <= 0 {
   112  		return pri, cursor, ErrPriorityEmpty
   113  	}
   114  	if line[cursor] != PRI_PART_START {
   115  		return pri, cursor, ErrPriorityNoStart
   116  	}
   117  	i := 1
   118  	priDigit := 0
   119  	for i < len(line) {
   120  		if i >= 5 {
   121  			return pri, cursor, ErrPriorityTooLong
   122  		}
   123  		c := line[i]
   124  		if c == PRI_PART_END {
   125  			if i == 1 {
   126  				return pri, cursor, ErrPriorityTooShort
   127  			}
   128  			cursor = i + 1
   129  			return d.newPriority(priDigit), cursor, nil
   130  		}
   131  		if d.isDigit(c) {
   132  			v, e := strconv.Atoi(string(c))
   133  			if e != nil {
   134  				return pri, cursor, e
   135  			}
   136  			priDigit = (priDigit * 10) + v
   137  		} else {
   138  			return pri, cursor, ErrPriorityNonDigit
   139  		}
   140  		i++
   141  	}
   142  	return pri, cursor, ErrPriorityNoEnd
   143  }
   144  
   145  // isDigit checks if a byte is a numeric char
   146  func (d *DockerLogParser) isDigit(c byte) bool {
   147  	return c >= '0' && c <= '9'
   148  }
   149  
   150  // newPriority creates a new default priority
   151  func (d *DockerLogParser) newPriority(p int) Priority {
   152  	// The Priority value is calculated by first multiplying the Facility
   153  	// number by 8 and then adding the numerical value of the Severity.
   154  	return Priority{
   155  		Pri:      p,
   156  		Facility: syslog.Priority(p / 8),
   157  		Severity: syslog.Priority(p % 8),
   158  	}
   159  }