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 }