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 }