syslog.go 7.0 KB

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