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  }