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