github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/logger/journald/journald.go (about) 1 //go:build linux 2 // +build linux 3 4 package journald // import "github.com/docker/docker/daemon/logger/journald" 5 6 import ( 7 "fmt" 8 "strconv" 9 "sync/atomic" 10 "time" 11 "unicode" 12 13 "github.com/coreos/go-systemd/v22/journal" 14 15 "github.com/docker/docker/daemon/logger" 16 "github.com/docker/docker/daemon/logger/loggerutils" 17 "github.com/docker/docker/pkg/stringid" 18 ) 19 20 const name = "journald" 21 22 // Well-known user journal fields. 23 // https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html 24 const ( 25 fieldSyslogIdentifier = "SYSLOG_IDENTIFIER" 26 fieldSyslogTimestamp = "SYSLOG_TIMESTAMP" 27 ) 28 29 // User journal fields used by the log driver. 30 const ( 31 fieldContainerID = "CONTAINER_ID" 32 fieldContainerIDFull = "CONTAINER_ID_FULL" 33 fieldContainerName = "CONTAINER_NAME" 34 fieldContainerTag = "CONTAINER_TAG" 35 fieldImageName = "IMAGE_NAME" 36 37 // Fields used to serialize PLogMetaData. 38 39 fieldPLogID = "CONTAINER_PARTIAL_ID" 40 fieldPLogOrdinal = "CONTAINER_PARTIAL_ORDINAL" 41 fieldPLogLast = "CONTAINER_PARTIAL_LAST" 42 fieldPartialMessage = "CONTAINER_PARTIAL_MESSAGE" 43 44 fieldLogEpoch = "CONTAINER_LOG_EPOCH" 45 fieldLogOrdinal = "CONTAINER_LOG_ORDINAL" 46 ) 47 48 var waitUntilFlushed func(*journald) error 49 50 type journald struct { 51 // Sequence number of the most recent message sent by this instance of 52 // the log driver, starting from 1. Corollary: ordinal == 0 implies no 53 // messages have been sent by this instance. 54 ordinal uint64 // Placed first in struct to ensure 8-byte alignment for atomic ops. 55 // Epoch identifier to distinguish sequence numbers from this instance 56 // vs. other instances. 57 epoch string 58 59 vars map[string]string // additional variables and values to send to the journal along with the log message 60 61 closed chan struct{} 62 63 // Overrides for unit tests. 64 65 sendToJournal func(message string, priority journal.Priority, vars map[string]string) error 66 journalReadDir string //nolint:unused // Referenced in read.go, which has more restrictive build constraints. 67 readSyncTimeout time.Duration 68 } 69 70 func init() { 71 if err := logger.RegisterLogDriver(name, New); err != nil { 72 panic(err) 73 } 74 if err := logger.RegisterLogOptValidator(name, validateLogOpt); err != nil { 75 panic(err) 76 } 77 } 78 79 // sanitizeKeyMode returns the sanitized string so that it could be used in journald. 80 // In journald log, there are special requirements for fields. 81 // Fields must be composed of uppercase letters, numbers, and underscores, but must 82 // not start with an underscore. 83 func sanitizeKeyMod(s string) string { 84 n := "" 85 for _, v := range s { 86 if 'a' <= v && v <= 'z' { 87 v = unicode.ToUpper(v) 88 } else if ('Z' < v || v < 'A') && ('9' < v || v < '0') { 89 v = '_' 90 } 91 // If (n == "" && v == '_'), then we will skip as this is the beginning with '_' 92 if !(n == "" && v == '_') { 93 n += string(v) 94 } 95 } 96 return n 97 } 98 99 // New creates a journald logger using the configuration passed in on 100 // the context. 101 func New(info logger.Info) (logger.Logger, error) { 102 if !journal.Enabled() { 103 return nil, fmt.Errorf("journald is not enabled on this host") 104 } 105 106 return new(info) 107 } 108 109 func new(info logger.Info) (*journald, error) { 110 // parse log tag 111 tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate) 112 if err != nil { 113 return nil, err 114 } 115 116 epoch := stringid.GenerateRandomID() 117 118 vars := map[string]string{ 119 fieldContainerID: info.ContainerID[:12], 120 fieldContainerIDFull: info.ContainerID, 121 fieldContainerName: info.Name(), 122 fieldContainerTag: tag, 123 fieldImageName: info.ImageName(), 124 fieldSyslogIdentifier: tag, 125 fieldLogEpoch: epoch, 126 } 127 extraAttrs, err := info.ExtraAttributes(sanitizeKeyMod) 128 if err != nil { 129 return nil, err 130 } 131 for k, v := range extraAttrs { 132 vars[k] = v 133 } 134 return &journald{ 135 epoch: epoch, 136 vars: vars, 137 closed: make(chan struct{}), 138 sendToJournal: journal.Send, 139 }, nil 140 } 141 142 // We don't actually accept any options, but we have to supply a callback for 143 // the factory to pass the (probably empty) configuration map to. 144 func validateLogOpt(cfg map[string]string) error { 145 for key := range cfg { 146 switch key { 147 case "labels": 148 case "labels-regex": 149 case "env": 150 case "env-regex": 151 case "tag": 152 default: 153 return fmt.Errorf("unknown log opt '%s' for journald log driver", key) 154 } 155 } 156 return nil 157 } 158 159 func (s *journald) Log(msg *logger.Message) error { 160 vars := map[string]string{} 161 for k, v := range s.vars { 162 vars[k] = v 163 } 164 if !msg.Timestamp.IsZero() { 165 vars[fieldSyslogTimestamp] = msg.Timestamp.Format(time.RFC3339Nano) 166 } 167 if msg.PLogMetaData != nil { 168 vars[fieldPLogID] = msg.PLogMetaData.ID 169 vars[fieldPLogOrdinal] = strconv.Itoa(msg.PLogMetaData.Ordinal) 170 vars[fieldPLogLast] = strconv.FormatBool(msg.PLogMetaData.Last) 171 if !msg.PLogMetaData.Last { 172 vars[fieldPartialMessage] = "true" 173 } 174 } 175 176 line := string(msg.Line) 177 source := msg.Source 178 logger.PutMessage(msg) 179 180 seq := atomic.AddUint64(&s.ordinal, 1) 181 vars[fieldLogOrdinal] = strconv.FormatUint(seq, 10) 182 183 if source == "stderr" { 184 return s.sendToJournal(line, journal.PriErr, vars) 185 } 186 return s.sendToJournal(line, journal.PriInfo, vars) 187 } 188 189 func (s *journald) Name() string { 190 return name 191 } 192 193 func (s *journald) Close() error { 194 close(s.closed) 195 if waitUntilFlushed != nil { 196 return waitUntilFlushed(s) 197 } 198 return nil 199 }