github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/events/journal_linux.go (about)

     1  // +build systemd
     2  
     3  package events
     4  
     5  import (
     6  	"fmt"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/coreos/go-systemd/v22/journal"
    11  	"github.com/coreos/go-systemd/v22/sdjournal"
    12  	"github.com/pkg/errors"
    13  	"github.com/sirupsen/logrus"
    14  )
    15  
    16  // DefaultEventerType is journald when systemd is available
    17  const DefaultEventerType = Journald
    18  
    19  // EventJournalD is the journald implementation of an eventer
    20  type EventJournalD struct {
    21  	options EventerOptions
    22  }
    23  
    24  // newEventJournalD creates a new journald Eventer
    25  func newEventJournalD(options EventerOptions) (Eventer, error) {
    26  	return EventJournalD{options}, nil
    27  }
    28  
    29  // Write to journald
    30  func (e EventJournalD) Write(ee Event) error {
    31  	m := make(map[string]string)
    32  	m["SYSLOG_IDENTIFIER"] = "podman"
    33  	m["PODMAN_EVENT"] = ee.Status.String()
    34  	m["PODMAN_TYPE"] = ee.Type.String()
    35  	m["PODMAN_TIME"] = ee.Time.Format(time.RFC3339Nano)
    36  
    37  	// Add specialized information based on the podman type
    38  	switch ee.Type {
    39  	case Image:
    40  		m["PODMAN_NAME"] = ee.Name
    41  		m["PODMAN_ID"] = ee.ID
    42  	case Container, Pod:
    43  		m["PODMAN_IMAGE"] = ee.Image
    44  		m["PODMAN_NAME"] = ee.Name
    45  		m["PODMAN_ID"] = ee.ID
    46  		if ee.ContainerExitCode != 0 {
    47  			m["PODMAN_EXIT_CODE"] = strconv.Itoa(ee.ContainerExitCode)
    48  		}
    49  	case Volume:
    50  		m["PODMAN_NAME"] = ee.Name
    51  	}
    52  	return journal.Send(fmt.Sprintf("%s", ee.ToHumanReadable()), journal.PriInfo, m)
    53  }
    54  
    55  // Read reads events from the journal and sends qualified events to the event channel
    56  func (e EventJournalD) Read(options ReadOptions) error {
    57  	defer close(options.EventChannel)
    58  	eventOptions, err := generateEventOptions(options.Filters, options.Since, options.Until)
    59  	if err != nil {
    60  		return errors.Wrapf(err, "failed to generate event options")
    61  	}
    62  	j, err := sdjournal.NewJournal() //nolint
    63  	if err != nil {
    64  		return err
    65  	}
    66  	// TODO AddMatch and Seek seem to conflict
    67  	// Issue filed upstream -> https://github.com/coreos/go-systemd/issues/315
    68  	// Leaving commented code in case upstream fixes things
    69  	//podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} //nolint
    70  	//if err := j.AddMatch(podmanJournal.String()); err != nil {
    71  	//	return errors.Wrap(err, "failed to add filter for event log")
    72  	//}
    73  	if len(options.Since) == 0 && len(options.Until) == 0 && options.Stream {
    74  		if err := j.SeekTail(); err != nil {
    75  			return errors.Wrap(err, "failed to seek end of journal")
    76  		}
    77  	} else {
    78  		podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} //nolint
    79  		if err := j.AddMatch(podmanJournal.String()); err != nil {
    80  			return errors.Wrap(err, "failed to add filter for event log")
    81  		}
    82  	}
    83  	// the api requires a next|prev before getting a cursor
    84  	if _, err := j.Next(); err != nil {
    85  		return err
    86  	}
    87  	prevCursor, err := j.GetCursor()
    88  	if err != nil {
    89  		return err
    90  	}
    91  	for {
    92  		if _, err := j.Next(); err != nil {
    93  			return err
    94  		}
    95  		newCursor, err := j.GetCursor()
    96  		if err != nil {
    97  			return err
    98  		}
    99  		if prevCursor == newCursor {
   100  			if len(options.Until) > 0 || !options.Stream {
   101  				break
   102  			}
   103  			_ = j.Wait(sdjournal.IndefiniteWait) //nolint
   104  			continue
   105  		}
   106  		prevCursor = newCursor
   107  		entry, err := j.GetEntry()
   108  		if err != nil {
   109  			return err
   110  		}
   111  		// TODO this keeps us from feeding the podman event parser with
   112  		// with regular journal content; it can be removed if the above
   113  		// problem with AddMatch is resolved.
   114  		if entry.Fields["PODMAN_EVENT"] == "" {
   115  			continue
   116  		}
   117  		newEvent, err := newEventFromJournalEntry(entry)
   118  		if err != nil {
   119  			// We can't decode this event.
   120  			// Don't fail hard - that would make events unusable.
   121  			// Instead, log and continue.
   122  			if errors.Cause(err) != ErrEventTypeBlank {
   123  				logrus.Errorf("Unable to decode event: %v", err)
   124  			}
   125  			continue
   126  		}
   127  		include := true
   128  		for _, filter := range eventOptions {
   129  			include = include && filter(newEvent)
   130  		}
   131  		if include {
   132  			options.EventChannel <- newEvent
   133  		}
   134  	}
   135  	return nil
   136  
   137  }
   138  
   139  func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { //nolint
   140  	newEvent := Event{}
   141  	eventType, err := StringToType(entry.Fields["PODMAN_TYPE"])
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	eventTime, err := time.Parse(time.RFC3339Nano, entry.Fields["PODMAN_TIME"])
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	eventStatus, err := StringToStatus(entry.Fields["PODMAN_EVENT"])
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	newEvent.Type = eventType
   154  	newEvent.Time = eventTime
   155  	newEvent.Status = eventStatus
   156  	newEvent.Name = entry.Fields["PODMAN_NAME"]
   157  
   158  	switch eventType {
   159  	case Container, Pod:
   160  		newEvent.ID = entry.Fields["PODMAN_ID"]
   161  		newEvent.Image = entry.Fields["PODMAN_IMAGE"]
   162  		if code, ok := entry.Fields["PODMAN_EXIT_CODE"]; ok {
   163  			intCode, err := strconv.Atoi(code)
   164  			if err != nil {
   165  				logrus.Errorf("Error parsing event exit code %s", code)
   166  			} else {
   167  				newEvent.ContainerExitCode = intCode
   168  			}
   169  		}
   170  	case Image:
   171  		newEvent.ID = entry.Fields["PODMAN_ID"]
   172  	}
   173  	return &newEvent, nil
   174  }
   175  
   176  // String returns a string representation of the logger
   177  func (e EventJournalD) String() string {
   178  	return Journald.String()
   179  }