github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/libpod/events/journal_linux.go (about)

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