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