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