mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
38c49d9987
This fix tries to address the issue raised in #22358 where syslog's message tag always starts with `docker/` and can not be removed by changing the log tag templates. The issue is that syslog driver hardcodes `path.Base(os.Args[0])` as the prefix, which is the binary file name of the daemon (`dockerd`). This could be an issue for certain situations (e.g., #22358) where user may prefer not to have a dedicated prefix in syslog messages. There is no way to override this behavior in the current verison of the docker. This fix tries to address this issue without making changes in the default behavior of the syslog driver. An additional `{{.DaemonName}}` has been introduced in the syslog tag. This is assigned as the `docker` when daemon starts. The default log tag template has also been changed from `path.Base(os.Args[0]) + "/{{.ID}}"` to `{{.DaemonName}}/{{.ID}}`. Therefore, there is no behavior changes when log-tag is not provided. In order to be consistent, the default log tag for fluentd has been changed from `docker.{{.ID}}` to `{{DaemonName}}.{{.ID}}` as well. The documentation for log-tag has been updated to reflect this change. Additional test cases have been added to cover changes in this fix. This fix fixes #22358. Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
258 lines
6.6 KiB
Go
258 lines
6.6 KiB
Go
// +build linux
|
|
|
|
// Package syslog provides the logdriver for forwarding server logs to syslog endpoints.
|
|
package syslog
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
syslog "github.com/RackSec/srslog"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/daemon/logger"
|
|
"github.com/docker/docker/daemon/logger/loggerutils"
|
|
"github.com/docker/docker/pkg/urlutil"
|
|
"github.com/docker/go-connections/tlsconfig"
|
|
)
|
|
|
|
const (
|
|
name = "syslog"
|
|
secureProto = "tcp+tls"
|
|
)
|
|
|
|
var facilities = map[string]syslog.Priority{
|
|
"kern": syslog.LOG_KERN,
|
|
"user": syslog.LOG_USER,
|
|
"mail": syslog.LOG_MAIL,
|
|
"daemon": syslog.LOG_DAEMON,
|
|
"auth": syslog.LOG_AUTH,
|
|
"syslog": syslog.LOG_SYSLOG,
|
|
"lpr": syslog.LOG_LPR,
|
|
"news": syslog.LOG_NEWS,
|
|
"uucp": syslog.LOG_UUCP,
|
|
"cron": syslog.LOG_CRON,
|
|
"authpriv": syslog.LOG_AUTHPRIV,
|
|
"ftp": syslog.LOG_FTP,
|
|
"local0": syslog.LOG_LOCAL0,
|
|
"local1": syslog.LOG_LOCAL1,
|
|
"local2": syslog.LOG_LOCAL2,
|
|
"local3": syslog.LOG_LOCAL3,
|
|
"local4": syslog.LOG_LOCAL4,
|
|
"local5": syslog.LOG_LOCAL5,
|
|
"local6": syslog.LOG_LOCAL6,
|
|
"local7": syslog.LOG_LOCAL7,
|
|
}
|
|
|
|
type syslogger struct {
|
|
writer *syslog.Writer
|
|
}
|
|
|
|
func init() {
|
|
if err := logger.RegisterLogDriver(name, New); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// rsyslog uses appname part of syslog message to fill in an %syslogtag% template
|
|
// attribute in rsyslog.conf. In order to be backward compatible to rfc3164
|
|
// tag will be also used as an appname
|
|
func rfc5424formatterWithAppNameAsTag(p syslog.Priority, hostname, tag, content string) string {
|
|
timestamp := time.Now().Format(time.RFC3339)
|
|
pid := os.Getpid()
|
|
msg := fmt.Sprintf("<%d>%d %s %s %s %d %s %s",
|
|
p, 1, timestamp, hostname, tag, pid, tag, content)
|
|
return msg
|
|
}
|
|
|
|
// The timestamp field in rfc5424 is derived from rfc3339. Whereas rfc3339 makes allowances
|
|
// for multiple syntaxes, there are further restrictions in rfc5424, i.e., the maximium
|
|
// resolution is limited to "TIME-SECFRAC" which is 6 (microsecond resolution)
|
|
func rfc5424microformatterWithAppNameAsTag(p syslog.Priority, hostname, tag, content string) string {
|
|
timestamp := time.Now().Format("2006-01-02T15:04:05.999999Z07:00")
|
|
pid := os.Getpid()
|
|
msg := fmt.Sprintf("<%d>%d %s %s %s %d %s %s",
|
|
p, 1, timestamp, hostname, tag, pid, tag, content)
|
|
return msg
|
|
}
|
|
|
|
// New creates a syslog logger using the configuration passed in on
|
|
// the context. Supported context configuration variables are
|
|
// syslog-address, syslog-facility, syslog-format.
|
|
func New(ctx logger.Context) (logger.Logger, error) {
|
|
tag, err := loggerutils.ParseLogTag(ctx, "{{.DaemonName}}/{{.ID}}")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
proto, address, err := parseAddress(ctx.Config["syslog-address"])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
facility, err := parseFacility(ctx.Config["syslog-facility"])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
syslogFormatter, syslogFramer, err := parseLogFormat(ctx.Config["syslog-format"])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var log *syslog.Writer
|
|
if proto == secureProto {
|
|
tlsConfig, tlsErr := parseTLSConfig(ctx.Config)
|
|
if tlsErr != nil {
|
|
return nil, tlsErr
|
|
}
|
|
log, err = syslog.DialWithTLSConfig(proto, address, facility, tag, tlsConfig)
|
|
} else {
|
|
log, err = syslog.Dial(proto, address, facility, tag)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.SetFormatter(syslogFormatter)
|
|
log.SetFramer(syslogFramer)
|
|
|
|
return &syslogger{
|
|
writer: log,
|
|
}, nil
|
|
}
|
|
|
|
func (s *syslogger) Log(msg *logger.Message) error {
|
|
if msg.Source == "stderr" {
|
|
return s.writer.Err(string(msg.Line))
|
|
}
|
|
return s.writer.Info(string(msg.Line))
|
|
}
|
|
|
|
func (s *syslogger) Close() error {
|
|
return s.writer.Close()
|
|
}
|
|
|
|
func (s *syslogger) Name() string {
|
|
return name
|
|
}
|
|
|
|
func parseAddress(address string) (string, string, error) {
|
|
if address == "" {
|
|
return "", "", nil
|
|
}
|
|
if !urlutil.IsTransportURL(address) {
|
|
return "", "", fmt.Errorf("syslog-address should be in form proto://address, got %v", address)
|
|
}
|
|
url, err := url.Parse(address)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
// unix and unixgram socket validation
|
|
if url.Scheme == "unix" || url.Scheme == "unixgram" {
|
|
if _, err := os.Stat(url.Path); err != nil {
|
|
return "", "", err
|
|
}
|
|
return url.Scheme, url.Path, nil
|
|
}
|
|
|
|
// here we process tcp|udp
|
|
host := url.Host
|
|
if _, _, err := net.SplitHostPort(host); err != nil {
|
|
if !strings.Contains(err.Error(), "missing port in address") {
|
|
return "", "", err
|
|
}
|
|
host = host + ":514"
|
|
}
|
|
|
|
return url.Scheme, host, nil
|
|
}
|
|
|
|
// ValidateLogOpt looks for syslog specific log options
|
|
// syslog-address, syslog-facility.
|
|
func ValidateLogOpt(cfg map[string]string) error {
|
|
for key := range cfg {
|
|
switch key {
|
|
case "env":
|
|
case "labels":
|
|
case "syslog-address":
|
|
case "syslog-facility":
|
|
case "syslog-tls-ca-cert":
|
|
case "syslog-tls-cert":
|
|
case "syslog-tls-key":
|
|
case "syslog-tls-skip-verify":
|
|
case "tag":
|
|
case "syslog-format":
|
|
default:
|
|
return fmt.Errorf("unknown log opt '%s' for syslog log driver", key)
|
|
}
|
|
}
|
|
if _, _, err := parseAddress(cfg["syslog-address"]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := parseFacility(cfg["syslog-facility"]); err != nil {
|
|
return err
|
|
}
|
|
if _, _, err := parseLogFormat(cfg["syslog-format"]); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseFacility(facility string) (syslog.Priority, error) {
|
|
if facility == "" {
|
|
return syslog.LOG_DAEMON, nil
|
|
}
|
|
|
|
if syslogFacility, valid := facilities[facility]; valid {
|
|
return syslogFacility, nil
|
|
}
|
|
|
|
fInt, err := strconv.Atoi(facility)
|
|
if err == nil && 0 <= fInt && fInt <= 23 {
|
|
return syslog.Priority(fInt << 3), nil
|
|
}
|
|
|
|
return syslog.Priority(0), errors.New("invalid syslog facility")
|
|
}
|
|
|
|
func parseTLSConfig(cfg map[string]string) (*tls.Config, error) {
|
|
_, skipVerify := cfg["syslog-tls-skip-verify"]
|
|
|
|
opts := tlsconfig.Options{
|
|
CAFile: cfg["syslog-tls-ca-cert"],
|
|
CertFile: cfg["syslog-tls-cert"],
|
|
KeyFile: cfg["syslog-tls-key"],
|
|
InsecureSkipVerify: skipVerify,
|
|
}
|
|
|
|
return tlsconfig.Client(opts)
|
|
}
|
|
|
|
func parseLogFormat(logFormat string) (syslog.Formatter, syslog.Framer, error) {
|
|
switch logFormat {
|
|
case "":
|
|
return syslog.UnixFormatter, syslog.DefaultFramer, nil
|
|
case "rfc3164":
|
|
return syslog.RFC3164Formatter, syslog.DefaultFramer, nil
|
|
case "rfc5424":
|
|
return rfc5424formatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
|
|
case "rfc5424micro":
|
|
return rfc5424microformatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
|
|
default:
|
|
return nil, nil, errors.New("Invalid syslog format")
|
|
}
|
|
|
|
}
|