github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/daemon/logger/journald/journald.go (about) 1 // +build linux 2 3 // Package journald provides the log driver for forwarding server logs 4 // to endpoints that receive the systemd format. 5 package journald // import "github.com/demonoid81/moby/daemon/logger/journald" 6 7 import ( 8 "fmt" 9 "sync" 10 "unicode" 11 12 "github.com/coreos/go-systemd/v22/journal" 13 "github.com/demonoid81/moby/daemon/logger" 14 "github.com/demonoid81/moby/daemon/logger/loggerutils" 15 "github.com/sirupsen/logrus" 16 ) 17 18 const name = "journald" 19 20 type journald struct { 21 mu sync.Mutex //nolint:structcheck,unused 22 vars map[string]string // additional variables and values to send to the journal along with the log message 23 readers map[*logger.LogWatcher]struct{} 24 } 25 26 func init() { 27 if err := logger.RegisterLogDriver(name, New); err != nil { 28 logrus.Fatal(err) 29 } 30 if err := logger.RegisterLogOptValidator(name, validateLogOpt); err != nil { 31 logrus.Fatal(err) 32 } 33 } 34 35 // sanitizeKeyMode returns the sanitized string so that it could be used in journald. 36 // In journald log, there are special requirements for fields. 37 // Fields must be composed of uppercase letters, numbers, and underscores, but must 38 // not start with an underscore. 39 func sanitizeKeyMod(s string) string { 40 n := "" 41 for _, v := range s { 42 if 'a' <= v && v <= 'z' { 43 v = unicode.ToUpper(v) 44 } else if ('Z' < v || v < 'A') && ('9' < v || v < '0') { 45 v = '_' 46 } 47 // If (n == "" && v == '_'), then we will skip as this is the beginning with '_' 48 if !(n == "" && v == '_') { 49 n += string(v) 50 } 51 } 52 return n 53 } 54 55 // New creates a journald logger using the configuration passed in on 56 // the context. 57 func New(info logger.Info) (logger.Logger, error) { 58 if !journal.Enabled() { 59 return nil, fmt.Errorf("journald is not enabled on this host") 60 } 61 62 // parse log tag 63 tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate) 64 if err != nil { 65 return nil, err 66 } 67 68 vars := map[string]string{ 69 "CONTAINER_ID": info.ContainerID[:12], 70 "CONTAINER_ID_FULL": info.ContainerID, 71 "CONTAINER_NAME": info.Name(), 72 "CONTAINER_TAG": tag, 73 "IMAGE_NAME": info.ImageName(), 74 "SYSLOG_IDENTIFIER": tag, 75 } 76 extraAttrs, err := info.ExtraAttributes(sanitizeKeyMod) 77 if err != nil { 78 return nil, err 79 } 80 for k, v := range extraAttrs { 81 vars[k] = v 82 } 83 return &journald{vars: vars, readers: make(map[*logger.LogWatcher]struct{})}, nil 84 } 85 86 // We don't actually accept any options, but we have to supply a callback for 87 // the factory to pass the (probably empty) configuration map to. 88 func validateLogOpt(cfg map[string]string) error { 89 for key := range cfg { 90 switch key { 91 case "labels": 92 case "labels-regex": 93 case "env": 94 case "env-regex": 95 case "tag": 96 default: 97 return fmt.Errorf("unknown log opt '%s' for journald log driver", key) 98 } 99 } 100 return nil 101 } 102 103 func (s *journald) Log(msg *logger.Message) error { 104 vars := map[string]string{} 105 for k, v := range s.vars { 106 vars[k] = v 107 } 108 if msg.PLogMetaData != nil && !msg.PLogMetaData.Last { 109 vars["CONTAINER_PARTIAL_MESSAGE"] = "true" 110 } 111 112 line := string(msg.Line) 113 source := msg.Source 114 logger.PutMessage(msg) 115 116 if source == "stderr" { 117 return journal.Send(line, journal.PriErr, vars) 118 } 119 return journal.Send(line, journal.PriInfo, vars) 120 } 121 122 func (s *journald) Name() string { 123 return name 124 }