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