github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/libpod/events/journal_linux.go (about)

     1  // +build systemd
     2  
     3  package events
     4  
     5  import (
     6  	"context"
     7  	"encoding/json"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/coreos/go-systemd/v22/journal"
    12  	"github.com/coreos/go-systemd/v22/sdjournal"
    13  	"github.com/pkg/errors"
    14  	"github.com/sirupsen/logrus"
    15  )
    16  
    17  // DefaultEventerType is journald when systemd is available
    18  const DefaultEventerType = Journald
    19  
    20  // EventJournalD is the journald implementation of an eventer
    21  type EventJournalD struct {
    22  	options EventerOptions
    23  }
    24  
    25  // newEventJournalD creates a new journald Eventer
    26  func newEventJournalD(options EventerOptions) (Eventer, error) {
    27  	return EventJournalD{options}, nil
    28  }
    29  
    30  // Write to journald
    31  func (e EventJournalD) Write(ee Event) error {
    32  	m := make(map[string]string)
    33  	m["SYSLOG_IDENTIFIER"] = "podman"
    34  	m["PODMAN_EVENT"] = ee.Status.String()
    35  	m["PODMAN_TYPE"] = ee.Type.String()
    36  	m["PODMAN_TIME"] = ee.Time.Format(time.RFC3339Nano)
    37  
    38  	// Add specialized information based on the podman type
    39  	switch ee.Type {
    40  	case Image:
    41  		m["PODMAN_NAME"] = ee.Name
    42  		m["PODMAN_ID"] = ee.ID
    43  	case Container, Pod:
    44  		m["PODMAN_IMAGE"] = ee.Image
    45  		m["PODMAN_NAME"] = ee.Name
    46  		m["PODMAN_ID"] = ee.ID
    47  		if ee.ContainerExitCode != 0 {
    48  			m["PODMAN_EXIT_CODE"] = strconv.Itoa(ee.ContainerExitCode)
    49  		}
    50  		// If we have container labels, we need to convert them to a string so they
    51  		// can be recorded with the event
    52  		if len(ee.Details.Attributes) > 0 {
    53  			b, err := json.Marshal(ee.Details.Attributes)
    54  			if err != nil {
    55  				return err
    56  			}
    57  			m["PODMAN_LABELS"] = string(b)
    58  		}
    59  	case Network:
    60  		m["PODMAN_ID"] = ee.ID
    61  		m["PODMAN_NETWORK_NAME"] = ee.Network
    62  	case Volume:
    63  		m["PODMAN_NAME"] = ee.Name
    64  	}
    65  	return journal.Send(string(ee.ToHumanReadable()), journal.PriInfo, m)
    66  }
    67  
    68  // Read reads events from the journal and sends qualified events to the event channel
    69  func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error {
    70  	defer close(options.EventChannel)
    71  	eventOptions, err := generateEventOptions(options.Filters, options.Since, options.Until)
    72  	if err != nil {
    73  		return errors.Wrapf(err, "failed to generate event options")
    74  	}
    75  	j, err := sdjournal.NewJournal()
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	// match only podman journal entries
    81  	podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"}
    82  	if err := j.AddMatch(podmanJournal.String()); err != nil {
    83  		return errors.Wrap(err, "failed to add journal filter for event log")
    84  	}
    85  
    86  	if len(options.Since) == 0 && len(options.Until) == 0 && options.Stream {
    87  		if err := j.SeekTail(); err != nil {
    88  			return errors.Wrap(err, "failed to seek end of journal")
    89  		}
    90  		// After SeekTail calling Next moves to a random entry.
    91  		// To prevent this we have to call Previous first.
    92  		// see: https://bugs.freedesktop.org/show_bug.cgi?id=64614
    93  		if _, err := j.Previous(); err != nil {
    94  			return errors.Wrap(err, "failed to move journal cursor to previous entry")
    95  		}
    96  	}
    97  
    98  	// the api requires a next|prev before getting a cursor
    99  	if _, err := j.Next(); err != nil {
   100  		return errors.Wrap(err, "failed to move journal cursor to next entry")
   101  	}
   102  
   103  	prevCursor, err := j.GetCursor()
   104  	if err != nil {
   105  		return errors.Wrap(err, "failed to get journal cursor")
   106  	}
   107  
   108  	for {
   109  		select {
   110  		case <-ctx.Done():
   111  			// the consumer has cancelled
   112  			return nil
   113  		default:
   114  			// fallthrough
   115  		}
   116  
   117  		if _, err := j.Next(); err != nil {
   118  			return errors.Wrap(err, "failed to move journal cursor to next entry")
   119  		}
   120  		newCursor, err := j.GetCursor()
   121  		if err != nil {
   122  			return errors.Wrap(err, "failed to get journal cursor")
   123  		}
   124  		if prevCursor == newCursor {
   125  			if len(options.Until) > 0 || !options.Stream {
   126  				break
   127  			}
   128  			_ = j.Wait(sdjournal.IndefiniteWait)
   129  			continue
   130  		}
   131  		prevCursor = newCursor
   132  
   133  		entry, err := j.GetEntry()
   134  		if err != nil {
   135  			return errors.Wrap(err, "failed to read journal entry")
   136  		}
   137  		newEvent, err := newEventFromJournalEntry(entry)
   138  		if err != nil {
   139  			// We can't decode this event.
   140  			// Don't fail hard - that would make events unusable.
   141  			// Instead, log and continue.
   142  			if errors.Cause(err) != ErrEventTypeBlank {
   143  				logrus.Errorf("Unable to decode event: %v", err)
   144  			}
   145  			continue
   146  		}
   147  		include := true
   148  		for _, filter := range eventOptions {
   149  			include = include && filter(newEvent)
   150  		}
   151  		if include {
   152  			options.EventChannel <- newEvent
   153  		}
   154  	}
   155  	return nil
   156  
   157  }
   158  
   159  func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { //nolint
   160  	newEvent := Event{}
   161  	eventType, err := StringToType(entry.Fields["PODMAN_TYPE"])
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	eventTime, err := time.Parse(time.RFC3339Nano, entry.Fields["PODMAN_TIME"])
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	eventStatus, err := StringToStatus(entry.Fields["PODMAN_EVENT"])
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	newEvent.Type = eventType
   174  	newEvent.Time = eventTime
   175  	newEvent.Status = eventStatus
   176  	newEvent.Name = entry.Fields["PODMAN_NAME"]
   177  
   178  	switch eventType {
   179  	case Container, Pod:
   180  		newEvent.ID = entry.Fields["PODMAN_ID"]
   181  		newEvent.Image = entry.Fields["PODMAN_IMAGE"]
   182  		if code, ok := entry.Fields["PODMAN_EXIT_CODE"]; ok {
   183  			intCode, err := strconv.Atoi(code)
   184  			if err != nil {
   185  				logrus.Errorf("Error parsing event exit code %s", code)
   186  			} else {
   187  				newEvent.ContainerExitCode = intCode
   188  			}
   189  		}
   190  
   191  		// we need to check for the presence of labels recorded to a container event
   192  		if stringLabels, ok := entry.Fields["PODMAN_LABELS"]; ok && len(stringLabels) > 0 {
   193  			labels := make(map[string]string, 0)
   194  			if err := json.Unmarshal([]byte(stringLabels), &labels); err != nil {
   195  				return nil, err
   196  			}
   197  
   198  			// if we have labels, add them to the event
   199  			if len(labels) > 0 {
   200  				newEvent.Details = Details{Attributes: labels}
   201  			}
   202  		}
   203  	case Network:
   204  		newEvent.ID = entry.Fields["PODMAN_ID"]
   205  		newEvent.Network = entry.Fields["PODMAN_NETWORK_NAME"]
   206  	case Image:
   207  		newEvent.ID = entry.Fields["PODMAN_ID"]
   208  	}
   209  	return &newEvent, nil
   210  }
   211  
   212  // String returns a string representation of the logger
   213  func (e EventJournalD) String() string {
   214  	return Journald.String()
   215  }