Files
docker-cli/components/engine/daemon/logger/syslog/syslog.go
Brian Goff c238856aa4 Use sync.Pool for logger Messages
This reduces allocs and bytes used per log entry significantly as well
as some improvement to time per log operation.

Each log driver, however, must put messages back in the pool once they
are finished with the message.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Upstream-commit: 3f4fccb65f0ef286c9c4e0f01c4ae7bb09a6ad89
Component: engine
2017-02-01 13:52:37 -05:00

265 lines
6.8 KiB
Go

// 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(info logger.Info) (logger.Logger, error) {
tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate)
if err != nil {
return nil, err
}
proto, address, err := parseAddress(info.Config["syslog-address"])
if err != nil {
return nil, err
}
facility, err := parseFacility(info.Config["syslog-facility"])
if err != nil {
return nil, err
}
syslogFormatter, syslogFramer, err := parseLogFormat(info.Config["syslog-format"], proto)
if err != nil {
return nil, err
}
var log *syslog.Writer
if proto == secureProto {
tlsConfig, tlsErr := parseTLSConfig(info.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 {
line := string(msg.Line)
logger.PutMessage(msg)
if msg.Source == "stderr" {
return s.writer.Err(line)
}
return s.writer.Info(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, proto 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":
if proto == secureProto {
return rfc5424formatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
}
return rfc5424formatterWithAppNameAsTag, syslog.DefaultFramer, nil
case "rfc5424micro":
if proto == secureProto {
return rfc5424microformatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
}
return rfc5424microformatterWithAppNameAsTag, syslog.DefaultFramer, nil
default:
return nil, nil, errors.New("Invalid syslog format")
}
}