github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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 func (s *journald) Close() error { 120 s.mu.Lock() 121 for r := range s.readers { 122 r.ProducerGone() 123 delete(s.readers, r) 124 } 125 s.mu.Unlock() 126 return nil 127 } 128 129 // CErr converts error code returned from a sd_journal_* function 130 // (which returns -errno) to a string 131 func CErr(ret C.int) string { 132 return C.GoString(C.strerror(-ret)) 133 } 134 135 func (s *journald) drainJournal(logWatcher *logger.LogWatcher, j *C.sd_journal, oldCursor *C.char, untilUnixMicro uint64) (*C.char, bool, int) { 136 var ( 137 msg, data, cursor *C.char 138 length C.size_t 139 stamp C.uint64_t 140 priority, partial C.int 141 done bool 142 shown int 143 ) 144 145 // Walk the journal from here forward until we run out of new entries 146 // or we reach the until value (if provided). 147 drain: 148 for { 149 // Try not to send a given entry twice. 150 if oldCursor != nil { 151 for C.sd_journal_test_cursor(j, oldCursor) > 0 { 152 if C.sd_journal_next(j) <= 0 { 153 break drain 154 } 155 } 156 } 157 // Read and send the logged message, if there is one to read. 158 i := C.get_message(j, &msg, &length, &partial) 159 if i != -C.ENOENT && i != -C.EADDRNOTAVAIL { 160 // Read the entry's timestamp. 161 if C.sd_journal_get_realtime_usec(j, &stamp) != 0 { 162 break 163 } 164 // Break if the timestamp exceeds any provided until flag. 165 if untilUnixMicro != 0 && untilUnixMicro < uint64(stamp) { 166 done = true 167 break 168 } 169 170 // Set up the time and text of the entry. 171 timestamp := time.Unix(int64(stamp)/1000000, (int64(stamp)%1000000)*1000) 172 line := C.GoBytes(unsafe.Pointer(msg), C.int(length)) 173 if partial == 0 { 174 line = append(line, "\n"...) 175 } 176 // Recover the stream name by mapping 177 // from the journal priority back to 178 // the stream that we would have 179 // assigned that value. 180 source := "" 181 if C.get_priority(j, &priority) == 0 { 182 if priority == C.int(journal.PriErr) { 183 source = "stderr" 184 } else if priority == C.int(journal.PriInfo) { 185 source = "stdout" 186 } 187 } 188 // Retrieve the values of any variables we're adding to the journal. 189 var attrs []backend.LogAttr 190 C.sd_journal_restart_data(j) 191 for C.get_attribute_field(j, &data, &length) > C.int(0) { 192 kv := strings.SplitN(C.GoStringN(data, C.int(length)), "=", 2) 193 attrs = append(attrs, backend.LogAttr{Key: kv[0], Value: kv[1]}) 194 } 195 196 // Send the log message, unless the consumer is gone 197 select { 198 case <-logWatcher.WatchConsumerGone(): 199 done = true // we won't be able to write anything anymore 200 break drain 201 case logWatcher.Msg <- &logger.Message{ 202 Line: line, 203 Source: source, 204 Timestamp: timestamp.In(time.UTC), 205 Attrs: attrs, 206 }: 207 shown++ 208 } 209 // Call sd_journal_process() periodically during the processing loop 210 // to close any opened file descriptors for rotated (deleted) journal files. 211 if shown%1024 == 0 { 212 if ret := C.sd_journal_process(j); ret < 0 { 213 // log a warning but ignore it for now 214 logrus.WithField("container", s.vars["CONTAINER_ID_FULL"]). 215 WithField("error", CErr(ret)). 216 Warn("journald: error processing journal") 217 } 218 } 219 } 220 // If we're at the end of the journal, we're done (for now). 221 if C.sd_journal_next(j) <= 0 { 222 break 223 } 224 } 225 226 // free(NULL) is safe 227 C.free(unsafe.Pointer(oldCursor)) 228 if C.sd_journal_get_cursor(j, &cursor) != 0 { 229 // ensure that we won't be freeing an address that's invalid 230 cursor = nil 231 } 232 return cursor, done, shown 233 } 234 235 func (s *journald) followJournal(logWatcher *logger.LogWatcher, j *C.sd_journal, cursor *C.char, untilUnixMicro uint64) *C.char { 236 s.mu.Lock() 237 s.readers[logWatcher] = struct{}{} 238 s.mu.Unlock() 239 240 waitTimeout := C.uint64_t(250000) // 0.25s 241 242 for { 243 status := C.sd_journal_wait(j, waitTimeout) 244 if status < 0 { 245 logWatcher.Err <- errors.New("error waiting for journal: " + CErr(status)) 246 goto cleanup 247 } 248 select { 249 case <-logWatcher.WatchConsumerGone(): 250 goto cleanup // won't be able to write anything anymore 251 case <-logWatcher.WatchProducerGone(): 252 // container is gone, drain journal 253 default: 254 // container is still alive 255 if status == C.SD_JOURNAL_NOP { 256 // no new data -- keep waiting 257 continue 258 } 259 } 260 newCursor, done, recv := s.drainJournal(logWatcher, j, cursor, untilUnixMicro) 261 cursor = newCursor 262 if done || (status == C.SD_JOURNAL_NOP && recv == 0) { 263 break 264 } 265 } 266 267 cleanup: 268 s.mu.Lock() 269 delete(s.readers, logWatcher) 270 s.mu.Unlock() 271 close(logWatcher.Msg) 272 return cursor 273 } 274 275 func (s *journald) readLogs(logWatcher *logger.LogWatcher, config logger.ReadConfig) { 276 var ( 277 j *C.sd_journal 278 cmatch, cursor *C.char 279 stamp C.uint64_t 280 sinceUnixMicro uint64 281 untilUnixMicro uint64 282 ) 283 284 // Get a handle to the journal. 285 if rc := C.sd_journal_open(&j, C.int(0)); rc != 0 { 286 logWatcher.Err <- errors.New("error opening journal: " + CErr(rc)) 287 close(logWatcher.Msg) 288 return 289 } 290 if config.Follow { 291 // Initialize library inotify watches early 292 if rc := C.sd_journal_get_fd(j); rc < 0 { 293 logWatcher.Err <- errors.New("error getting journald fd: " + CErr(rc)) 294 close(logWatcher.Msg) 295 return 296 } 297 } 298 // If we end up following the log, we can set the journal context 299 // pointer and the channel pointer to nil so that we won't close them 300 // here, potentially while the goroutine that uses them is still 301 // running. Otherwise, close them when we return from this function. 302 following := false 303 defer func() { 304 if !following { 305 close(logWatcher.Msg) 306 } 307 C.sd_journal_close(j) 308 }() 309 // Remove limits on the size of data items that we'll retrieve. 310 if rc := C.sd_journal_set_data_threshold(j, C.size_t(0)); rc != 0 { 311 logWatcher.Err <- errors.New("error setting journal data threshold: " + CErr(rc)) 312 return 313 } 314 // Add a match to have the library do the searching for us. 315 cmatch = C.CString("CONTAINER_ID_FULL=" + s.vars["CONTAINER_ID_FULL"]) 316 defer C.free(unsafe.Pointer(cmatch)) 317 if rc := C.sd_journal_add_match(j, unsafe.Pointer(cmatch), C.strlen(cmatch)); rc != 0 { 318 logWatcher.Err <- errors.New("error setting journal match: " + CErr(rc)) 319 return 320 } 321 // If we have a cutoff time, convert it to Unix time once. 322 if !config.Since.IsZero() { 323 nano := config.Since.UnixNano() 324 sinceUnixMicro = uint64(nano / 1000) 325 } 326 // If we have an until value, convert it too 327 if !config.Until.IsZero() { 328 nano := config.Until.UnixNano() 329 untilUnixMicro = uint64(nano / 1000) 330 } 331 if config.Tail >= 0 { 332 // If until time provided, start from there. 333 // Otherwise start at the end of the journal. 334 if untilUnixMicro != 0 { 335 if rc := C.sd_journal_seek_realtime_usec(j, C.uint64_t(untilUnixMicro)); rc != 0 { 336 logWatcher.Err <- errors.New("error seeking provided until value: " + CErr(rc)) 337 return 338 } 339 } else if rc := C.sd_journal_seek_tail(j); rc != 0 { 340 logWatcher.Err <- errors.New("error seeking to end of journal: " + CErr(rc)) 341 return 342 } 343 // (Try to) skip backwards by the requested number of lines... 344 if C.sd_journal_previous_skip(j, C.uint64_t(config.Tail)) >= 0 { 345 // ...but not before "since" 346 if sinceUnixMicro != 0 && 347 C.sd_journal_get_realtime_usec(j, &stamp) == 0 && 348 uint64(stamp) < sinceUnixMicro { 349 C.sd_journal_seek_realtime_usec(j, C.uint64_t(sinceUnixMicro)) 350 } 351 } 352 } else { 353 // Start at the beginning of the journal. 354 if rc := C.sd_journal_seek_head(j); rc != 0 { 355 logWatcher.Err <- errors.New("error seeking to start of journal: " + CErr(rc)) 356 return 357 } 358 // If we have a cutoff date, fast-forward to it. 359 if sinceUnixMicro != 0 { 360 if rc := C.sd_journal_seek_realtime_usec(j, C.uint64_t(sinceUnixMicro)); rc != 0 { 361 logWatcher.Err <- errors.New("error seeking to start time in journal: " + CErr(rc)) 362 return 363 } 364 } 365 if rc := C.sd_journal_next(j); rc < 0 { 366 logWatcher.Err <- errors.New("error skipping to next journal entry: " + CErr(rc)) 367 return 368 } 369 } 370 if config.Tail != 0 { // special case for --tail 0 371 cursor, _, _ = s.drainJournal(logWatcher, j, nil, untilUnixMicro) 372 } 373 if config.Follow { 374 cursor = s.followJournal(logWatcher, j, cursor, untilUnixMicro) 375 // Let followJournal handle freeing the journal context 376 // object and closing the channel. 377 following = true 378 } 379 380 C.free(unsafe.Pointer(cursor)) 381 } 382 383 func (s *journald) ReadLogs(config logger.ReadConfig) *logger.LogWatcher { 384 logWatcher := logger.NewLogWatcher() 385 go s.readLogs(logWatcher, config) 386 return logWatcher 387 }