github.com/olljanat/moby@v1.13.1/daemon/logger/syslog/syslog.go (about)

     1  // Package syslog provides the logdriver for forwarding server logs to syslog endpoints.
     2  package 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/Sirupsen/logrus"
    18  	"github.com/docker/docker/daemon/logger"
    19  	"github.com/docker/docker/daemon/logger/loggerutils"
    20  	"github.com/docker/docker/pkg/urlutil"
    21  	"github.com/docker/go-connections/tlsconfig"
    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 maximium
    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.999999Z07: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(ctx logger.Context) (logger.Logger, error) {
    91  	tag, err := loggerutils.ParseLogTag(ctx, loggerutils.DefaultTemplate)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	proto, address, err := parseAddress(ctx.Config["syslog-address"])
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	facility, err := parseFacility(ctx.Config["syslog-facility"])
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	syslogFormatter, syslogFramer, err := parseLogFormat(ctx.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(ctx.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 msg.Source == "stderr" {
   136  		return s.writer.Err(string(msg.Line))
   137  	}
   138  	return s.writer.Info(string(msg.Line))
   139  }
   140  
   141  func (s *syslogger) Close() error {
   142  	return s.writer.Close()
   143  }
   144  
   145  func (s *syslogger) Name() string {
   146  	return name
   147  }
   148  
   149  func parseAddress(address string) (string, string, error) {
   150  	if address == "" {
   151  		return "", "", nil
   152  	}
   153  	if !urlutil.IsTransportURL(address) {
   154  		return "", "", fmt.Errorf("syslog-address should be in form proto://address, got %v", address)
   155  	}
   156  	url, err := url.Parse(address)
   157  	if err != nil {
   158  		return "", "", err
   159  	}
   160  
   161  	// unix and unixgram socket validation
   162  	if url.Scheme == "unix" || url.Scheme == "unixgram" {
   163  		if _, err := os.Stat(url.Path); err != nil {
   164  			return "", "", err
   165  		}
   166  		return url.Scheme, url.Path, nil
   167  	}
   168  
   169  	// here we process tcp|udp
   170  	host := url.Host
   171  	if _, _, err := net.SplitHostPort(host); err != nil {
   172  		if !strings.Contains(err.Error(), "missing port in address") {
   173  			return "", "", err
   174  		}
   175  		host = host + ":514"
   176  	}
   177  
   178  	return url.Scheme, host, nil
   179  }
   180  
   181  // ValidateLogOpt looks for syslog specific log options
   182  // syslog-address, syslog-facility.
   183  func ValidateLogOpt(cfg map[string]string) error {
   184  	for key := range cfg {
   185  		switch key {
   186  		case "env":
   187  		case "labels":
   188  		case "syslog-address":
   189  		case "syslog-facility":
   190  		case "syslog-tls-ca-cert":
   191  		case "syslog-tls-cert":
   192  		case "syslog-tls-key":
   193  		case "syslog-tls-skip-verify":
   194  		case "tag":
   195  		case "syslog-format":
   196  		default:
   197  			return fmt.Errorf("unknown log opt '%s' for syslog log driver", key)
   198  		}
   199  	}
   200  	if _, _, err := parseAddress(cfg["syslog-address"]); err != nil {
   201  		return err
   202  	}
   203  	if _, err := parseFacility(cfg["syslog-facility"]); err != nil {
   204  		return err
   205  	}
   206  	if _, _, err := parseLogFormat(cfg["syslog-format"], ""); err != nil {
   207  		return err
   208  	}
   209  	return nil
   210  }
   211  
   212  func parseFacility(facility string) (syslog.Priority, error) {
   213  	if facility == "" {
   214  		return syslog.LOG_DAEMON, nil
   215  	}
   216  
   217  	if syslogFacility, valid := facilities[facility]; valid {
   218  		return syslogFacility, nil
   219  	}
   220  
   221  	fInt, err := strconv.Atoi(facility)
   222  	if err == nil && 0 <= fInt && fInt <= 23 {
   223  		return syslog.Priority(fInt << 3), nil
   224  	}
   225  
   226  	return syslog.Priority(0), errors.New("invalid syslog facility")
   227  }
   228  
   229  func parseTLSConfig(cfg map[string]string) (*tls.Config, error) {
   230  	_, skipVerify := cfg["syslog-tls-skip-verify"]
   231  
   232  	opts := tlsconfig.Options{
   233  		CAFile:             cfg["syslog-tls-ca-cert"],
   234  		CertFile:           cfg["syslog-tls-cert"],
   235  		KeyFile:            cfg["syslog-tls-key"],
   236  		InsecureSkipVerify: skipVerify,
   237  	}
   238  
   239  	return tlsconfig.Client(opts)
   240  }
   241  
   242  func parseLogFormat(logFormat, proto string) (syslog.Formatter, syslog.Framer, error) {
   243  	switch logFormat {
   244  	case "":
   245  		return syslog.UnixFormatter, syslog.DefaultFramer, nil
   246  	case "rfc3164":
   247  		return syslog.RFC3164Formatter, syslog.DefaultFramer, nil
   248  	case "rfc5424":
   249  		if proto == secureProto {
   250  			return rfc5424formatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
   251  		}
   252  		return rfc5424formatterWithAppNameAsTag, syslog.DefaultFramer, nil
   253  	case "rfc5424micro":
   254  		if proto == secureProto {
   255  			return rfc5424microformatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
   256  		}
   257  		return rfc5424microformatterWithAppNameAsTag, syslog.DefaultFramer, nil
   258  	default:
   259  		return nil, nil, errors.New("Invalid syslog format")
   260  	}
   261  
   262  }