github.com/rumpl/bof@v23.0.0-rc.2+incompatible/daemon/logger/journald/read.go (about) 1 //go:build linux && cgo && !static_build && journald 2 // +build linux,cgo,!static_build,journald 3 4 package journald // import "github.com/docker/docker/daemon/logger/journald" 5 6 // #include <sys/types.h> 7 // #include <sys/poll.h> 8 // #include <systemd/sd-journal.h> 9 // #include <errno.h> 10 // #include <stdio.h> 11 // #include <stdlib.h> 12 // #include <string.h> 13 // #include <time.h> 14 // #include <unistd.h> 15 // 16 //static int get_message(sd_journal *j, const char **msg, size_t *length, int *partial) 17 //{ 18 // int rc; 19 // size_t plength; 20 // *msg = NULL; 21 // *length = 0; 22 // plength = strlen("CONTAINER_PARTIAL_MESSAGE=true"); 23 // rc = sd_journal_get_data(j, "CONTAINER_PARTIAL_MESSAGE", (const void **) msg, length); 24 // *partial = ((rc == 0) && (*length == plength) && (memcmp(*msg, "CONTAINER_PARTIAL_MESSAGE=true", plength) == 0)); 25 // rc = sd_journal_get_data(j, "MESSAGE", (const void **) msg, length); 26 // if (rc == 0) { 27 // if (*length > 8) { 28 // (*msg) += 8; 29 // *length -= 8; 30 // } else { 31 // *msg = NULL; 32 // *length = 0; 33 // rc = -ENOENT; 34 // } 35 // } 36 // return rc; 37 //} 38 //static int get_priority(sd_journal *j, int *priority) 39 //{ 40 // const void *data; 41 // size_t i, length; 42 // int rc; 43 // *priority = -1; 44 // rc = sd_journal_get_data(j, "PRIORITY", &data, &length); 45 // if (rc == 0) { 46 // if ((length > 9) && (strncmp(data, "PRIORITY=", 9) == 0)) { 47 // *priority = 0; 48 // for (i = 9; i < length; i++) { 49 // *priority = *priority * 10 + ((const char *)data)[i] - '0'; 50 // } 51 // if (length > 9) { 52 // rc = 0; 53 // } 54 // } 55 // } 56 // return rc; 57 //} 58 //static int is_attribute_field(const char *msg, size_t length) 59 //{ 60 // static const struct known_field { 61 // const char *name; 62 // size_t length; 63 // } fields[] = { 64 // {"MESSAGE", sizeof("MESSAGE") - 1}, 65 // {"MESSAGE_ID", sizeof("MESSAGE_ID") - 1}, 66 // {"PRIORITY", sizeof("PRIORITY") - 1}, 67 // {"CODE_FILE", sizeof("CODE_FILE") - 1}, 68 // {"CODE_LINE", sizeof("CODE_LINE") - 1}, 69 // {"CODE_FUNC", sizeof("CODE_FUNC") - 1}, 70 // {"ERRNO", sizeof("ERRNO") - 1}, 71 // {"SYSLOG_FACILITY", sizeof("SYSLOG_FACILITY") - 1}, 72 // {"SYSLOG_IDENTIFIER", sizeof("SYSLOG_IDENTIFIER") - 1}, 73 // {"SYSLOG_PID", sizeof("SYSLOG_PID") - 1}, 74 // {"CONTAINER_NAME", sizeof("CONTAINER_NAME") - 1}, 75 // {"CONTAINER_ID", sizeof("CONTAINER_ID") - 1}, 76 // {"CONTAINER_ID_FULL", sizeof("CONTAINER_ID_FULL") - 1}, 77 // {"CONTAINER_TAG", sizeof("CONTAINER_TAG") - 1}, 78 // }; 79 // unsigned int i; 80 // void *p; 81 // if ((length < 1) || (msg[0] == '_') || ((p = memchr(msg, '=', length)) == NULL)) { 82 // return -1; 83 // } 84 // length = ((const char *) p) - msg; 85 // for (i = 0; i < sizeof(fields) / sizeof(fields[0]); i++) { 86 // if ((fields[i].length == length) && (memcmp(fields[i].name, msg, length) == 0)) { 87 // return -1; 88 // } 89 // } 90 // return 0; 91 //} 92 //static int get_attribute_field(sd_journal *j, const char **msg, size_t *length) 93 //{ 94 // int rc; 95 // *msg = NULL; 96 // *length = 0; 97 // while ((rc = sd_journal_enumerate_data(j, (const void **) msg, length)) > 0) { 98 // if (is_attribute_field(*msg, *length) == 0) { 99 // break; 100 // } 101 // rc = -ENOENT; 102 // } 103 // return rc; 104 //} 105 import "C" 106 107 import ( 108 "errors" 109 "strings" 110 "time" 111 "unsafe" 112 113 "github.com/coreos/go-systemd/v22/journal" 114 "github.com/docker/docker/api/types/backend" 115 "github.com/docker/docker/daemon/logger" 116 "github.com/sirupsen/logrus" 117 ) 118 119 // CErr converts error code returned from a sd_journal_* function 120 // (which returns -errno) to a string 121 func CErr(ret C.int) string { 122 return C.GoString(C.strerror(-ret)) 123 } 124 125 func (s *journald) drainJournal(logWatcher *logger.LogWatcher, j *C.sd_journal, oldCursor *C.char, untilUnixMicro uint64) (*C.char, bool, int) { 126 var ( 127 msg, data, cursor *C.char 128 length C.size_t 129 stamp C.uint64_t 130 priority, partial C.int 131 done bool 132 shown int 133 ) 134 135 // Walk the journal from here forward until we run out of new entries 136 // or we reach the until value (if provided). 137 drain: 138 for { 139 // Try not to send a given entry twice. 140 if oldCursor != nil { 141 for C.sd_journal_test_cursor(j, oldCursor) > 0 { 142 if C.sd_journal_next(j) <= 0 { 143 break drain 144 } 145 } 146 } 147 // Read and send the logged message, if there is one to read. 148 i := C.get_message(j, &msg, &length, &partial) 149 if i != -C.ENOENT && i != -C.EADDRNOTAVAIL { 150 // Read the entry's timestamp. 151 if C.sd_journal_get_realtime_usec(j, &stamp) != 0 { 152 break 153 } 154 // Break if the timestamp exceeds any provided until flag. 155 if untilUnixMicro != 0 && untilUnixMicro < uint64(stamp) { 156 done = true 157 break 158 } 159 160 // Set up the time and text of the entry. 161 timestamp := time.Unix(int64(stamp)/1000000, (int64(stamp)%1000000)*1000) 162 line := C.GoBytes(unsafe.Pointer(msg), C.int(length)) 163 if partial == 0 { 164 line = append(line, "\n"...) 165 } 166 // Recover the stream name by mapping 167 // from the journal priority back to 168 // the stream that we would have 169 // assigned that value. 170 source := "" 171 if C.get_priority(j, &priority) == 0 { 172 if priority == C.int(journal.PriErr) { 173 source = "stderr" 174 } else if priority == C.int(journal.PriInfo) { 175 source = "stdout" 176 } 177 } 178 // Retrieve the values of any variables we're adding to the journal. 179 var attrs []backend.LogAttr 180 C.sd_journal_restart_data(j) 181 for C.get_attribute_field(j, &data, &length) > C.int(0) { 182 kv := strings.SplitN(C.GoStringN(data, C.int(length)), "=", 2) 183 attrs = append(attrs, backend.LogAttr{Key: kv[0], Value: kv[1]}) 184 } 185 186 // Send the log message, unless the consumer is gone 187 select { 188 case <-logWatcher.WatchConsumerGone(): 189 done = true // we won't be able to write anything anymore 190 break drain 191 case logWatcher.Msg <- &logger.Message{ 192 Line: line, 193 Source: source, 194 Timestamp: timestamp.In(time.UTC), 195 Attrs: attrs, 196 }: 197 shown++ 198 } 199 // Call sd_journal_process() periodically during the processing loop 200 // to close any opened file descriptors for rotated (deleted) journal files. 201 if shown%1024 == 0 { 202 if ret := C.sd_journal_process(j); ret < 0 { 203 // log a warning but ignore it for now 204 logrus.WithField("container", s.vars["CONTAINER_ID_FULL"]). 205 WithField("error", CErr(ret)). 206 Warn("journald: error processing journal") 207 } 208 } 209 } 210 // If we're at the end of the journal, we're done (for now). 211 if C.sd_journal_next(j) <= 0 { 212 break 213 } 214 } 215 216 // free(NULL) is safe 217 C.free(unsafe.Pointer(oldCursor)) 218 if C.sd_journal_get_cursor(j, &cursor) != 0 { 219 // ensure that we won't be freeing an address that's invalid 220 cursor = nil 221 } 222 return cursor, done, shown 223 } 224 225 func (s *journald) followJournal(logWatcher *logger.LogWatcher, j *C.sd_journal, cursor *C.char, untilUnixMicro uint64) *C.char { 226 defer close(logWatcher.Msg) 227 228 waitTimeout := C.uint64_t(250000) // 0.25s 229 230 LOOP: 231 for { 232 status := C.sd_journal_wait(j, waitTimeout) 233 if status < 0 { 234 logWatcher.Err <- errors.New("error waiting for journal: " + CErr(status)) 235 break 236 } 237 select { 238 case <-logWatcher.WatchConsumerGone(): 239 break LOOP // won't be able to write anything anymore 240 case <-s.closed: 241 // container is gone, drain journal 242 default: 243 // container is still alive 244 if status == C.SD_JOURNAL_NOP { 245 // no new data -- keep waiting 246 continue 247 } 248 } 249 newCursor, done, recv := s.drainJournal(logWatcher, j, cursor, untilUnixMicro) 250 cursor = newCursor 251 if done || (status == C.SD_JOURNAL_NOP && recv == 0) { 252 break 253 } 254 } 255 256 return cursor 257 } 258 259 func (s *journald) readLogs(logWatcher *logger.LogWatcher, config logger.ReadConfig) { 260 var ( 261 j *C.sd_journal 262 cmatch, cursor *C.char 263 stamp C.uint64_t 264 sinceUnixMicro uint64 265 untilUnixMicro uint64 266 ) 267 268 // Get a handle to the journal. 269 if rc := C.sd_journal_open(&j, C.int(0)); rc != 0 { 270 logWatcher.Err <- errors.New("error opening journal: " + CErr(rc)) 271 close(logWatcher.Msg) 272 return 273 } 274 if config.Follow { 275 // Initialize library inotify watches early 276 if rc := C.sd_journal_get_fd(j); rc < 0 { 277 logWatcher.Err <- errors.New("error getting journald fd: " + CErr(rc)) 278 close(logWatcher.Msg) 279 return 280 } 281 } 282 // If we end up following the log, we can set the journal context 283 // pointer and the channel pointer to nil so that we won't close them 284 // here, potentially while the goroutine that uses them is still 285 // running. Otherwise, close them when we return from this function. 286 following := false 287 defer func() { 288 if !following { 289 close(logWatcher.Msg) 290 } 291 C.sd_journal_close(j) 292 }() 293 // Remove limits on the size of data items that we'll retrieve. 294 if rc := C.sd_journal_set_data_threshold(j, C.size_t(0)); rc != 0 { 295 logWatcher.Err <- errors.New("error setting journal data threshold: " + CErr(rc)) 296 return 297 } 298 // Add a match to have the library do the searching for us. 299 cmatch = C.CString("CONTAINER_ID_FULL=" + s.vars["CONTAINER_ID_FULL"]) 300 defer C.free(unsafe.Pointer(cmatch)) 301 if rc := C.sd_journal_add_match(j, unsafe.Pointer(cmatch), C.strlen(cmatch)); rc != 0 { 302 logWatcher.Err <- errors.New("error setting journal match: " + CErr(rc)) 303 return 304 } 305 // If we have a cutoff time, convert it to Unix time once. 306 if !config.Since.IsZero() { 307 nano := config.Since.UnixNano() 308 sinceUnixMicro = uint64(nano / 1000) 309 } 310 // If we have an until value, convert it too 311 if !config.Until.IsZero() { 312 nano := config.Until.UnixNano() 313 untilUnixMicro = uint64(nano / 1000) 314 } 315 if config.Tail >= 0 { 316 // If until time provided, start from there. 317 // Otherwise start at the end of the journal. 318 if untilUnixMicro != 0 { 319 if rc := C.sd_journal_seek_realtime_usec(j, C.uint64_t(untilUnixMicro)); rc != 0 { 320 logWatcher.Err <- errors.New("error seeking provided until value: " + CErr(rc)) 321 return 322 } 323 } else if rc := C.sd_journal_seek_tail(j); rc != 0 { 324 logWatcher.Err <- errors.New("error seeking to end of journal: " + CErr(rc)) 325 return 326 } 327 // (Try to) skip backwards by the requested number of lines... 328 if C.sd_journal_previous_skip(j, C.uint64_t(config.Tail)) >= 0 { 329 // ...but not before "since" 330 if sinceUnixMicro != 0 && 331 C.sd_journal_get_realtime_usec(j, &stamp) == 0 && 332 uint64(stamp) < sinceUnixMicro { 333 C.sd_journal_seek_realtime_usec(j, C.uint64_t(sinceUnixMicro)) 334 } 335 } 336 } else { 337 // Start at the beginning of the journal. 338 if rc := C.sd_journal_seek_head(j); rc != 0 { 339 logWatcher.Err <- errors.New("error seeking to start of journal: " + CErr(rc)) 340 return 341 } 342 // If we have a cutoff date, fast-forward to it. 343 if sinceUnixMicro != 0 { 344 if rc := C.sd_journal_seek_realtime_usec(j, C.uint64_t(sinceUnixMicro)); rc != 0 { 345 logWatcher.Err <- errors.New("error seeking to start time in journal: " + CErr(rc)) 346 return 347 } 348 } 349 if rc := C.sd_journal_next(j); rc < 0 { 350 logWatcher.Err <- errors.New("error skipping to next journal entry: " + CErr(rc)) 351 return 352 } 353 } 354 if config.Tail != 0 { // special case for --tail 0 355 cursor, _, _ = s.drainJournal(logWatcher, j, nil, untilUnixMicro) 356 } 357 if config.Follow { 358 cursor = s.followJournal(logWatcher, j, cursor, untilUnixMicro) 359 // Let followJournal handle freeing the journal context 360 // object and closing the channel. 361 following = true 362 } 363 364 C.free(unsafe.Pointer(cursor)) 365 } 366 367 func (s *journald) ReadLogs(config logger.ReadConfig) *logger.LogWatcher { 368 logWatcher := logger.NewLogWatcher() 369 go s.readLogs(logWatcher, config) 370 return logWatcher 371 }