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  }