github.com/boynux/docker@v1.11.0-rc4/daemon/logger/journald/read.go (about) 1 // +build linux,cgo,!static_build,journald 2 3 package journald 4 5 // #include <sys/types.h> 6 // #include <sys/poll.h> 7 // #include <systemd/sd-journal.h> 8 // #include <errno.h> 9 // #include <stdio.h> 10 // #include <stdlib.h> 11 // #include <string.h> 12 // #include <time.h> 13 // #include <unistd.h> 14 // 15 //static int get_message(sd_journal *j, const char **msg, size_t *length) 16 //{ 17 // int rc; 18 // *msg = NULL; 19 // *length = 0; 20 // rc = sd_journal_get_data(j, "MESSAGE", (const void **) msg, length); 21 // if (rc == 0) { 22 // if (*length > 8) { 23 // (*msg) += 8; 24 // *length -= 8; 25 // } else { 26 // *msg = NULL; 27 // *length = 0; 28 // rc = -ENOENT; 29 // } 30 // } 31 // return rc; 32 //} 33 //static int get_priority(sd_journal *j, int *priority) 34 //{ 35 // const void *data; 36 // size_t i, length; 37 // int rc; 38 // *priority = -1; 39 // rc = sd_journal_get_data(j, "PRIORITY", &data, &length); 40 // if (rc == 0) { 41 // if ((length > 9) && (strncmp(data, "PRIORITY=", 9) == 0)) { 42 // *priority = 0; 43 // for (i = 9; i < length; i++) { 44 // *priority = *priority * 10 + ((const char *)data)[i] - '0'; 45 // } 46 // if (length > 9) { 47 // rc = 0; 48 // } 49 // } 50 // } 51 // return rc; 52 //} 53 //static int wait_for_data_or_close(sd_journal *j, int pipefd) 54 //{ 55 // struct pollfd fds[2]; 56 // uint64_t when = 0; 57 // int timeout, jevents, i; 58 // struct timespec ts; 59 // uint64_t now; 60 // do { 61 // memset(&fds, 0, sizeof(fds)); 62 // fds[0].fd = pipefd; 63 // fds[0].events = POLLHUP; 64 // fds[1].fd = sd_journal_get_fd(j); 65 // if (fds[1].fd < 0) { 66 // return fds[1].fd; 67 // } 68 // jevents = sd_journal_get_events(j); 69 // if (jevents < 0) { 70 // return jevents; 71 // } 72 // fds[1].events = jevents; 73 // sd_journal_get_timeout(j, &when); 74 // if (when == -1) { 75 // timeout = -1; 76 // } else { 77 // clock_gettime(CLOCK_MONOTONIC, &ts); 78 // now = (uint64_t) ts.tv_sec * 1000000 + ts.tv_nsec / 1000; 79 // timeout = when > now ? (int) ((when - now + 999) / 1000) : 0; 80 // } 81 // i = poll(fds, 2, timeout); 82 // if ((i == -1) && (errno != EINTR)) { 83 // /* An unexpected error. */ 84 // return (errno != 0) ? -errno : -EINTR; 85 // } 86 // if (fds[0].revents & POLLHUP) { 87 // /* The close notification pipe was closed. */ 88 // return 0; 89 // } 90 // if (sd_journal_process(j) == SD_JOURNAL_APPEND) { 91 // /* Data, which we might care about, was appended. */ 92 // return 1; 93 // } 94 // } while ((fds[0].revents & POLLHUP) == 0); 95 // return 0; 96 //} 97 import "C" 98 99 import ( 100 "fmt" 101 "time" 102 "unsafe" 103 104 "github.com/Sirupsen/logrus" 105 "github.com/coreos/go-systemd/journal" 106 "github.com/docker/docker/daemon/logger" 107 ) 108 109 func (s *journald) Close() error { 110 s.readers.mu.Lock() 111 for reader := range s.readers.readers { 112 reader.Close() 113 } 114 s.readers.mu.Unlock() 115 return nil 116 } 117 118 func (s *journald) drainJournal(logWatcher *logger.LogWatcher, config logger.ReadConfig, j *C.sd_journal, oldCursor string) string { 119 var msg, cursor *C.char 120 var length C.size_t 121 var stamp C.uint64_t 122 var priority C.int 123 124 // Walk the journal from here forward until we run out of new entries. 125 drain: 126 for { 127 // Try not to send a given entry twice. 128 if oldCursor != "" { 129 ccursor := C.CString(oldCursor) 130 defer C.free(unsafe.Pointer(ccursor)) 131 for C.sd_journal_test_cursor(j, ccursor) > 0 { 132 if C.sd_journal_next(j) <= 0 { 133 break drain 134 } 135 } 136 } 137 // Read and send the logged message, if there is one to read. 138 i := C.get_message(j, &msg, &length) 139 if i != -C.ENOENT && i != -C.EADDRNOTAVAIL { 140 // Read the entry's timestamp. 141 if C.sd_journal_get_realtime_usec(j, &stamp) != 0 { 142 break 143 } 144 // Set up the time and text of the entry. 145 timestamp := time.Unix(int64(stamp)/1000000, (int64(stamp)%1000000)*1000) 146 line := append(C.GoBytes(unsafe.Pointer(msg), C.int(length)), "\n"...) 147 // Recover the stream name by mapping 148 // from the journal priority back to 149 // the stream that we would have 150 // assigned that value. 151 source := "" 152 if C.get_priority(j, &priority) != 0 { 153 source = "" 154 } else if priority == C.int(journal.PriErr) { 155 source = "stderr" 156 } else if priority == C.int(journal.PriInfo) { 157 source = "stdout" 158 } 159 // Send the log message. 160 cid := s.vars["CONTAINER_ID_FULL"] 161 logWatcher.Msg <- &logger.Message{ContainerID: cid, Line: line, Source: source, Timestamp: timestamp} 162 } 163 // If we're at the end of the journal, we're done (for now). 164 if C.sd_journal_next(j) <= 0 { 165 break 166 } 167 } 168 retCursor := "" 169 if C.sd_journal_get_cursor(j, &cursor) == 0 { 170 retCursor = C.GoString(cursor) 171 C.free(unsafe.Pointer(cursor)) 172 } 173 return retCursor 174 } 175 176 func (s *journald) followJournal(logWatcher *logger.LogWatcher, config logger.ReadConfig, j *C.sd_journal, pfd [2]C.int, cursor string) { 177 s.readers.mu.Lock() 178 s.readers.readers[logWatcher] = logWatcher 179 s.readers.mu.Unlock() 180 go func() { 181 // Keep copying journal data out until we're notified to stop 182 // or we hit an error. 183 status := C.wait_for_data_or_close(j, pfd[0]) 184 for status == 1 { 185 cursor = s.drainJournal(logWatcher, config, j, cursor) 186 status = C.wait_for_data_or_close(j, pfd[0]) 187 } 188 if status < 0 { 189 cerrstr := C.strerror(C.int(-status)) 190 errstr := C.GoString(cerrstr) 191 fmtstr := "error %q while attempting to follow journal for container %q" 192 logrus.Errorf(fmtstr, errstr, s.vars["CONTAINER_ID_FULL"]) 193 } 194 // Clean up. 195 C.close(pfd[0]) 196 s.readers.mu.Lock() 197 delete(s.readers.readers, logWatcher) 198 s.readers.mu.Unlock() 199 C.sd_journal_close(j) 200 close(logWatcher.Msg) 201 }() 202 // Wait until we're told to stop. 203 select { 204 case <-logWatcher.WatchClose(): 205 // Notify the other goroutine that its work is done. 206 C.close(pfd[1]) 207 } 208 } 209 210 func (s *journald) readLogs(logWatcher *logger.LogWatcher, config logger.ReadConfig) { 211 var j *C.sd_journal 212 var cmatch *C.char 213 var stamp C.uint64_t 214 var sinceUnixMicro uint64 215 var pipes [2]C.int 216 cursor := "" 217 218 // Get a handle to the journal. 219 rc := C.sd_journal_open(&j, C.int(0)) 220 if rc != 0 { 221 logWatcher.Err <- fmt.Errorf("error opening journal") 222 close(logWatcher.Msg) 223 return 224 } 225 // If we end up following the log, we can set the journal context 226 // pointer and the channel pointer to nil so that we won't close them 227 // here, potentially while the goroutine that uses them is still 228 // running. Otherwise, close them when we return from this function. 229 following := false 230 defer func(pfollowing *bool) { 231 if !*pfollowing { 232 C.sd_journal_close(j) 233 close(logWatcher.Msg) 234 } 235 }(&following) 236 // Remove limits on the size of data items that we'll retrieve. 237 rc = C.sd_journal_set_data_threshold(j, C.size_t(0)) 238 if rc != 0 { 239 logWatcher.Err <- fmt.Errorf("error setting journal data threshold") 240 return 241 } 242 // Add a match to have the library do the searching for us. 243 cmatch = C.CString("CONTAINER_ID_FULL=" + s.vars["CONTAINER_ID_FULL"]) 244 defer C.free(unsafe.Pointer(cmatch)) 245 rc = C.sd_journal_add_match(j, unsafe.Pointer(cmatch), C.strlen(cmatch)) 246 if rc != 0 { 247 logWatcher.Err <- fmt.Errorf("error setting journal match") 248 return 249 } 250 // If we have a cutoff time, convert it to Unix time once. 251 if !config.Since.IsZero() { 252 nano := config.Since.UnixNano() 253 sinceUnixMicro = uint64(nano / 1000) 254 } 255 if config.Tail > 0 { 256 lines := config.Tail 257 // Start at the end of the journal. 258 if C.sd_journal_seek_tail(j) < 0 { 259 logWatcher.Err <- fmt.Errorf("error seeking to end of journal") 260 return 261 } 262 if C.sd_journal_previous(j) < 0 { 263 logWatcher.Err <- fmt.Errorf("error backtracking to previous journal entry") 264 return 265 } 266 // Walk backward. 267 for lines > 0 { 268 // Stop if the entry time is before our cutoff. 269 // We'll need the entry time if it isn't, so go 270 // ahead and parse it now. 271 if C.sd_journal_get_realtime_usec(j, &stamp) != 0 { 272 break 273 } else { 274 // Compare the timestamp on the entry 275 // to our threshold value. 276 if sinceUnixMicro != 0 && sinceUnixMicro > uint64(stamp) { 277 break 278 } 279 } 280 lines-- 281 // If we're at the start of the journal, or 282 // don't need to back up past any more entries, 283 // stop. 284 if lines == 0 || C.sd_journal_previous(j) <= 0 { 285 break 286 } 287 } 288 } else { 289 // Start at the beginning of the journal. 290 if C.sd_journal_seek_head(j) < 0 { 291 logWatcher.Err <- fmt.Errorf("error seeking to start of journal") 292 return 293 } 294 // If we have a cutoff date, fast-forward to it. 295 if sinceUnixMicro != 0 && C.sd_journal_seek_realtime_usec(j, C.uint64_t(sinceUnixMicro)) != 0 { 296 logWatcher.Err <- fmt.Errorf("error seeking to start time in journal") 297 return 298 } 299 if C.sd_journal_next(j) < 0 { 300 logWatcher.Err <- fmt.Errorf("error skipping to next journal entry") 301 return 302 } 303 } 304 cursor = s.drainJournal(logWatcher, config, j, "") 305 if config.Follow { 306 // Allocate a descriptor for following the journal, if we'll 307 // need one. Do it here so that we can report if it fails. 308 if fd := C.sd_journal_get_fd(j); fd < C.int(0) { 309 logWatcher.Err <- fmt.Errorf("error opening journald follow descriptor: %q", C.GoString(C.strerror(-fd))) 310 } else { 311 // Create a pipe that we can poll at the same time as 312 // the journald descriptor. 313 if C.pipe(&pipes[0]) == C.int(-1) { 314 logWatcher.Err <- fmt.Errorf("error opening journald close notification pipe") 315 } else { 316 s.followJournal(logWatcher, config, j, pipes, cursor) 317 // Let followJournal handle freeing the journal context 318 // object and closing the channel. 319 following = true 320 } 321 } 322 } 323 return 324 } 325 326 func (s *journald) ReadLogs(config logger.ReadConfig) *logger.LogWatcher { 327 logWatcher := logger.NewLogWatcher() 328 go s.readLogs(logWatcher, config) 329 return logWatcher 330 }