github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/daemon/logger/syslog/syslog.go (about) 1 // +build linux 2 3 // Package syslog provides the logdriver for forwarding server logs to syslog endpoints. 4 package syslog 5 6 import ( 7 "crypto/tls" 8 "errors" 9 "fmt" 10 "net" 11 "net/url" 12 "os" 13 "path" 14 "strconv" 15 "strings" 16 "time" 17 18 syslog "github.com/RackSec/srslog" 19 20 "github.com/Sirupsen/logrus" 21 "github.com/docker/docker/daemon/logger" 22 "github.com/docker/docker/daemon/logger/loggerutils" 23 "github.com/docker/docker/pkg/urlutil" 24 "github.com/docker/go-connections/tlsconfig" 25 ) 26 27 const ( 28 name = "syslog" 29 secureProto = "tcp+tls" 30 ) 31 32 var facilities = map[string]syslog.Priority{ 33 "kern": syslog.LOG_KERN, 34 "user": syslog.LOG_USER, 35 "mail": syslog.LOG_MAIL, 36 "daemon": syslog.LOG_DAEMON, 37 "auth": syslog.LOG_AUTH, 38 "syslog": syslog.LOG_SYSLOG, 39 "lpr": syslog.LOG_LPR, 40 "news": syslog.LOG_NEWS, 41 "uucp": syslog.LOG_UUCP, 42 "cron": syslog.LOG_CRON, 43 "authpriv": syslog.LOG_AUTHPRIV, 44 "ftp": syslog.LOG_FTP, 45 "local0": syslog.LOG_LOCAL0, 46 "local1": syslog.LOG_LOCAL1, 47 "local2": syslog.LOG_LOCAL2, 48 "local3": syslog.LOG_LOCAL3, 49 "local4": syslog.LOG_LOCAL4, 50 "local5": syslog.LOG_LOCAL5, 51 "local6": syslog.LOG_LOCAL6, 52 "local7": syslog.LOG_LOCAL7, 53 } 54 55 type syslogger struct { 56 writer *syslog.Writer 57 } 58 59 func init() { 60 if err := logger.RegisterLogDriver(name, New); err != nil { 61 logrus.Fatal(err) 62 } 63 if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil { 64 logrus.Fatal(err) 65 } 66 } 67 68 // rsyslog uses appname part of syslog message to fill in an %syslogtag% template 69 // attribute in rsyslog.conf. In order to be backward compatible to rfc3164 70 // tag will be also used as an appname 71 func rfc5424formatterWithAppNameAsTag(p syslog.Priority, hostname, tag, content string) string { 72 timestamp := time.Now().Format(time.RFC3339) 73 pid := os.Getpid() 74 msg := fmt.Sprintf("<%d>%d %s %s %s %d %s %s", 75 p, 1, timestamp, hostname, tag, pid, tag, content) 76 return msg 77 } 78 79 // The timestamp field in rfc5424 is derived from rfc3339. Whereas rfc3339 makes allowances 80 // for multiple syntaxes, there are further restrictions in rfc5424, i.e., the maximium 81 // resolution is limited to "TIME-SECFRAC" which is 6 (microsecond resolution) 82 func rfc5424microformatterWithAppNameAsTag(p syslog.Priority, hostname, tag, content string) string { 83 timestamp := time.Now().Format("2006-01-02T15:04:05.999999Z07:00") 84 pid := os.Getpid() 85 msg := fmt.Sprintf("<%d>%d %s %s %s %d %s %s", 86 p, 1, timestamp, hostname, tag, pid, tag, content) 87 return msg 88 } 89 90 // New creates a syslog logger using the configuration passed in on 91 // the context. Supported context configuration variables are 92 // syslog-address, syslog-facility, syslog-format, syslog-tag. 93 func New(ctx logger.Context) (logger.Logger, error) { 94 tag, err := loggerutils.ParseLogTag(ctx, "{{.ID}}") 95 if err != nil { 96 return nil, err 97 } 98 99 proto, address, err := parseAddress(ctx.Config["syslog-address"]) 100 if err != nil { 101 return nil, err 102 } 103 104 facility, err := parseFacility(ctx.Config["syslog-facility"]) 105 if err != nil { 106 return nil, err 107 } 108 109 syslogFormatter, syslogFramer, err := parseLogFormat(ctx.Config["syslog-format"]) 110 if err != nil { 111 return nil, err 112 } 113 114 logTag := path.Base(os.Args[0]) + "/" + tag 115 116 var log *syslog.Writer 117 if proto == secureProto { 118 tlsConfig, tlsErr := parseTLSConfig(ctx.Config) 119 if tlsErr != nil { 120 return nil, tlsErr 121 } 122 log, err = syslog.DialWithTLSConfig(proto, address, facility, logTag, tlsConfig) 123 } else { 124 log, err = syslog.Dial(proto, address, facility, logTag) 125 } 126 127 if err != nil { 128 return nil, err 129 } 130 131 log.SetFormatter(syslogFormatter) 132 log.SetFramer(syslogFramer) 133 134 return &syslogger{ 135 writer: log, 136 }, nil 137 } 138 139 func (s *syslogger) Log(msg *logger.Message) error { 140 if msg.Source == "stderr" { 141 return s.writer.Err(string(msg.Line)) 142 } 143 return s.writer.Info(string(msg.Line)) 144 } 145 146 func (s *syslogger) Close() error { 147 return s.writer.Close() 148 } 149 150 func (s *syslogger) Name() string { 151 return name 152 } 153 154 func parseAddress(address string) (string, string, error) { 155 if address == "" { 156 return "", "", nil 157 } 158 if !urlutil.IsTransportURL(address) { 159 return "", "", fmt.Errorf("syslog-address should be in form proto://address, got %v", address) 160 } 161 url, err := url.Parse(address) 162 if err != nil { 163 return "", "", err 164 } 165 166 // unix socket validation 167 if url.Scheme == "unix" { 168 if _, err := os.Stat(url.Path); err != nil { 169 return "", "", err 170 } 171 return url.Scheme, url.Path, nil 172 } 173 174 // here we process tcp|udp 175 host := url.Host 176 if _, _, err := net.SplitHostPort(host); err != nil { 177 if !strings.Contains(err.Error(), "missing port in address") { 178 return "", "", err 179 } 180 host = host + ":514" 181 } 182 183 return url.Scheme, host, nil 184 } 185 186 // ValidateLogOpt looks for syslog specific log options 187 // syslog-address, syslog-facility, & syslog-tag. 188 func ValidateLogOpt(cfg map[string]string) error { 189 for key := range cfg { 190 switch key { 191 case "env": 192 case "labels": 193 case "syslog-address": 194 case "syslog-facility": 195 case "syslog-tag": 196 case "syslog-tls-ca-cert": 197 case "syslog-tls-cert": 198 case "syslog-tls-key": 199 case "syslog-tls-skip-verify": 200 case "tag": 201 case "syslog-format": 202 default: 203 return fmt.Errorf("unknown log opt '%s' for syslog log driver", key) 204 } 205 } 206 if _, _, err := parseAddress(cfg["syslog-address"]); err != nil { 207 return err 208 } 209 if _, err := parseFacility(cfg["syslog-facility"]); err != nil { 210 return err 211 } 212 if _, _, err := parseLogFormat(cfg["syslog-format"]); err != nil { 213 return err 214 } 215 return nil 216 } 217 218 func parseFacility(facility string) (syslog.Priority, error) { 219 if facility == "" { 220 return syslog.LOG_DAEMON, nil 221 } 222 223 if syslogFacility, valid := facilities[facility]; valid { 224 return syslogFacility, nil 225 } 226 227 fInt, err := strconv.Atoi(facility) 228 if err == nil && 0 <= fInt && fInt <= 23 { 229 return syslog.Priority(fInt << 3), nil 230 } 231 232 return syslog.Priority(0), errors.New("invalid syslog facility") 233 } 234 235 func parseTLSConfig(cfg map[string]string) (*tls.Config, error) { 236 _, skipVerify := cfg["syslog-tls-skip-verify"] 237 238 opts := tlsconfig.Options{ 239 CAFile: cfg["syslog-tls-ca-cert"], 240 CertFile: cfg["syslog-tls-cert"], 241 KeyFile: cfg["syslog-tls-key"], 242 InsecureSkipVerify: skipVerify, 243 } 244 245 return tlsconfig.Client(opts) 246 } 247 248 func parseLogFormat(logFormat string) (syslog.Formatter, syslog.Framer, error) { 249 switch logFormat { 250 case "": 251 return syslog.UnixFormatter, syslog.DefaultFramer, nil 252 case "rfc3164": 253 return syslog.RFC3164Formatter, syslog.DefaultFramer, nil 254 case "rfc5424": 255 return rfc5424formatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil 256 case "rfc5424micro": 257 return rfc5424microformatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil 258 default: 259 return nil, nil, errors.New("Invalid syslog format") 260 } 261 262 }