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