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 }