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