Go언어 golang

golang Syslog server / Go언어 Syslog collector - 파싱 추가 RFC5424

미래의 고 2023. 3. 12. 23:50
이전 포스팅에 이어서 RFC5424 파싱을 추가 해 보았습니다.
 
RFC5424 generator는 아래 있는 걸 사용하였습니다.
 
정규식 검색해서 있는 거 편하게 가려고 했는데 지금 사용한 generator가 표준대로인지 아닌지 지금 확인하기가 어렵네요.
결국 직접 작성 해 보았습니다. 급조하게 작성해서 맹점이 있을지 모르니 선수님들의 고급스러운 정규식을 쓰시기를 바랍니다.
 
마무리가 급히 정리되어 죄송한 따름입니다.
그럼, 결과 확인을 해보실까요.
 
 
 
스크린숏을 보시면 2개의 generator에서 UDP 514를 송신하여 두 가지 포맷의 패킷을 수신할 수 있게 되었습니다.

 

 

package main

import (
   "errors"
   "fmt"
   "net"
   "regexp"
   "strconv"
   "time"
)

// mix RFC3164 and RFC5424
type syslog struct {
   Version  int // 0: RFC3164, 1: RFC5424, 2: RFC5424 // RFC5424 Version 2는 생략
   Facility int
   Datetime time.Time //hostname or IP address
   Hostname string
   Msg      string
   // RFC5424 only
   Priority       int
   Application    string
   PID            int
   MessageID      string
   StructuredData string
}

// RFC3164 https://www.rfc-editor.org/rfc/rfc3164.html
// * regex -> https://regex101.com/library/97phrb
// RFC5424 https://www.rfc-editor.org/rfc/rfc5424

var regRFC3164 *regexp.Regexp
var regRFC5424 *regexp.Regexp // is next time
var facilityMap = map[int]string{
   0:  "kernel messages",
   1:  "user-level messages",
   2:  "mail system",
   3:  "system daemons",
   4:  "security/authorization messages",
   5:  "messages generated internally by syslogd",
   6:  "line printer subsystem",
   7:  "network news subsystem",
   8:  "UUCP subsystem",
   9:  "clock daemon",
   10: "security/authorization messages",
   11: "FTP daemon",
   12: "NTP subsystem",
   13: "log audit",
   14: "log alert",
   15: "clock daemon (note 2)",
   16: "local use 0  (local0)",
   17: "local use 1  (local1)",
   18: "local use 2  (local2)",
   19: "local use 3  (local3)",
   20: "local use 4  (local4)",
   21: "local use 5  (local5)",
   22: "local use 6  (local6)",
   23: "local use 7  (local7)",
}

func syslogParser(r []byte) (syslog, error) {
   var log syslog
   var err error
   var rs [][]byte

   // try RFC3164
   rs = regRFC3164.FindSubmatch(r)
   if len(rs) > 1 {
      log.Version = 0
      log.Facility, err = strconv.Atoi(string(rs[1]))
      if err != nil {
         return log, err
      }
      t, err := time.Parse("Jan _2 15:04:05", string(rs[2]))
      if err != nil {
         return log, err
      }
      t = t.AddDate(time.Now().Year(), 0, 0) // 년 보정
      log.Datetime = t

      log.Hostname = string(rs[3])
      log.Msg = string(rs[4])
      return log, err
   }

   rs = regRFC5424.FindSubmatch(r)
   if len(rs) > 1 {
      log.Priority, err = strconv.Atoi(string(rs[1]))
      if err != nil {
         return log, err
      }
      log.Datetime, err = time.Parse("2006-01-02T15:04:05.999999-07:00", string(rs[3]))
      if err != nil {
         return log, err
      }
      log.Version, err = strconv.Atoi(string(rs[2]))
      log.Hostname = string(rs[4])
      log.Application = string(rs[5])
      log.PID, err = strconv.Atoi(string(rs[6]))
      log.MessageID = string(rs[7])
      log.StructuredData = string(rs[8])
      log.Msg = string(rs[9])
      return log, err
   }

   return log, errors.New("Not match")
}

func init() {
   var err error

   regRFC3164, err = regexp.Compile("<([0-9]{1,3})\\>([A-Za-z]{3} [0-9]{1,2} \\d{2}:\\d{2}:\\d{2}) ([\\S]+) ([\\S\\s]+)")
   if err != nil {
      fmt.Println(err.Error())
      panic(err)
   }

   // 대충 짜서 현업에선 결점 주의, 검증 없이 대충 짜서 누락 되는건 보충 해주세요. 죄송합니다. 포스팅 fin하려다 보니...
   regRFC5424, err = regexp.Compile("<([0-9]{1,3})\\>([0-9]) (([12]\\d{3}-0\\d|[1][012])-([012]\\d|3[01])T([01]\\d|2[0-4]):([0-5]\\d):([0-5]\\d|60)(?:\\.(\\d{1,6}))?(Z|[+-]\\d{2}:\\d{2})) [\\S]{1,255} [\\S]{1,255} [\\S]{1,255} [\\S]{1,255} [\\S]{1,255} ([\\S\\s]+)")
   if err != nil {
      fmt.Println(err.Error())
      panic(err)
   }
}

func main() {
   var ServerAddr *net.UDPAddr
   var err error

   ServerAddr, err = net.ResolveUDPAddr("udp", "0.0.0.0:514")
   if err != nil {
      fmt.Println(err.Error())
      panic(err)
   }

   ServerConn, err := net.ListenUDP("udp", ServerAddr)
   if err != nil {
      fmt.Println(err.Error())
      panic(err)
   }
   defer ServerConn.Close()

   serverConn := ServerConn

   buf := make([]byte, 65535)

   for {
      n, addr, _ := serverConn.ReadFromUDP(buf)
      if n != 0 {
         address := addr.IP.String()
         log, err := syslogParser(buf[:n])
         if err != nil {
            fmt.Println(err.Error())
            continue
         }

         if log.Version == 0 {
            fmt.Print(fmt.Sprintf(
               `Version       : RFC3164
IP Address    : %s
Facility      : %s
Datetime      : %s
Hostname      : %s
Message       : %s
`,
               address,
               facilityMap[log.Facility],
               log.Datetime.String(),
               log.Hostname,
               log.Msg,
            ))
         } else {
            fmt.Print(fmt.Sprintf(
               `Version       : RFC5424-%d
IP Address    : %s
Priority      : %d
Datetime      : %s
Hostname      : %s
Application   : %s
PID           : %d
MessageID     : %s
StructureData : %s
Message       : %s
`,
               log.Version,
               address,
               log.Priority,
               log.Datetime.String(),
               log.Hostname,
               log.Application,
               log.PID,
               log.MessageID,
               log.StructuredData,
               log.Msg,
            ))
         }
         fmt.Println("-----------------------------")
      }
   }
}