1
//go:build !remote && linux && systemd
13
"github.com/containers/podman/v5/libpod/define"
14
"github.com/containers/podman/v5/libpod/events"
15
"github.com/containers/podman/v5/libpod/logs"
16
"github.com/containers/podman/v5/pkg/rootless"
17
"github.com/coreos/go-systemd/v22/sdjournal"
18
"github.com/sirupsen/logrus"
22
// journaldLogOut is the journald priority signifying stdout
25
// journaldLogErr is the journald priority signifying stderr
30
logDrivers = append(logDrivers, define.JournaldLogging)
33
func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions,
34
logChannel chan *logs.LogLine, colorID int64, passthroughUnit string) error {
35
// We need the container's events in the same journal to guarantee
36
// consistency, see #10323.
37
if options.Follow && c.runtime.config.Engine.EventsLogger != "journald" {
38
return fmt.Errorf("using --follow with the journald --log-driver but without the journald --events-backend (%s) is not supported", c.runtime.config.Engine.EventsLogger)
41
journal, err := sdjournal.NewJournal()
45
// While logs are written to the `logChannel`, we inspect each event
46
// and stop once the container has died. Having logs and events in one
47
// stream prevents a race condition that we faced in #10323.
49
// Add the filters for events.
50
match := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"}
51
if err := journal.AddMatch(match.String()); err != nil {
52
return fmt.Errorf("adding filter to journald logger: %v: %w", match, err)
54
match = sdjournal.Match{Field: "PODMAN_ID", Value: c.ID()}
55
if err := journal.AddMatch(match.String()); err != nil {
56
return fmt.Errorf("adding filter to journald logger: %v: %w", match, err)
58
// Make sure we only read events for the current user, while it is unlikely that there
59
// is a container ID duplication for two users, it is better to have it just in case.
60
uidMatch := sdjournal.Match{Field: "_UID", Value: strconv.Itoa(rootless.GetRootlessUID())}
61
if err := journal.AddMatch(uidMatch.String()); err != nil {
62
return fmt.Errorf("adding filter to journald logger: %v: %w", uidMatch, err)
65
// Add the filter for logs. Note the disjunction so that we match
66
// either the events or the logs.
67
if err := journal.AddDisjunction(); err != nil {
68
return fmt.Errorf("adding filter disjunction to journald logger: %w", err)
71
if passthroughUnit != "" {
72
// Match based on systemd unit which is the container is cgroup
73
// so we get the exact logs for a single container even in the
74
// play kube case where a single unit starts more than one container.
75
unitTypeName := "_SYSTEMD_UNIT"
76
if rootless.IsRootless() {
77
unitTypeName = "_SYSTEMD_USER_UNIT"
79
// By default we will have our own systemd cgroup with the name libpod-<ID>.scope.
80
value := "libpod-" + c.ID() + ".scope"
81
if c.config.CgroupsMode == cgroupSplit {
82
// If cgroup split the container runs in the unit cgroup so we use this for logs,
83
// the good thing is we filter the podman events already out below.
84
// Thus we are left with the real container log and possibly podman output (e.g. logrus).
85
value = passthroughUnit
88
match = sdjournal.Match{Field: unitTypeName, Value: value}
89
if err := journal.AddMatch(match.String()); err != nil {
90
return fmt.Errorf("adding filter to journald logger: %v: %w", match, err)
93
match = sdjournal.Match{Field: "CONTAINER_ID_FULL", Value: c.ID()}
94
if err := journal.AddMatch(match.String()); err != nil {
95
return fmt.Errorf("adding filter to journald logger: %v: %w", match, err)
99
if err := journal.AddMatch(uidMatch.String()); err != nil {
100
return fmt.Errorf("adding filter to journald logger: %v: %w", uidMatch, err)
103
if options.Since.IsZero() {
104
if err := journal.SeekHead(); err != nil {
108
// seek based on time which helps to reduce unnecessary event reads
109
if err := journal.SeekRealtimeUsec(uint64(options.Since.UnixMicro())); err != nil {
115
if err := c.syncContainer(); err != nil {
119
// The initial "containerCouldBeLogging" state must be correct, we cannot rely on the start event being still in the journal.
120
// This can happen if the journal was rotated after the container was started or when --since is used.
121
// https://github.com/containers/podman/issues/16950
122
containerCouldBeLogging := c.ensureState(define.ContainerStateRunning, define.ContainerStateStopping)
125
options.WaitGroup.Add(1)
128
options.WaitGroup.Done()
129
if err := journal.Close(); err != nil {
130
logrus.Errorf("Unable to close journal: %v", err)
134
tailQueue := []*logs.LogLine{} // needed for options.Tail
135
doTail := options.Tail >= 0
136
doTailFunc := func() {
137
// Flush *once* we hit the end of the journal.
138
startIndex := int64(len(tailQueue))
139
outputLines := int64(0)
140
for startIndex > 0 && outputLines < options.Tail {
142
for startIndex > 0 && tailQueue[startIndex].Partial() {
147
for i := startIndex; i < int64(len(tailQueue)); i++ {
148
logChannel <- tailQueue[i]
154
entry, err := events.GetNextEntry(ctx, journal, !doTail && options.Follow && containerCouldBeLogging, options.Until)
156
logrus.Errorf("Failed to get journal entry: %v", err)
159
// entry nil == EOF in journal
168
entryTime := time.Unix(0, int64(entry.RealtimeTimestamp)*int64(time.Microsecond))
169
if (entryTime.Before(options.Since) && !options.Since.IsZero()) || (entryTime.After(options.Until) && !options.Until.IsZero()) {
172
// If we're reading an event and the container exited/died,
173
// then we're done and can return.
174
event, ok := entry.Fields["PODMAN_EVENT"]
176
status, err := events.StringToStatus(event)
178
logrus.Errorf("Failed to translate event: %v", err)
182
case events.History, events.Init, events.Start, events.Restart:
183
containerCouldBeLogging = true
185
containerCouldBeLogging = false
190
logLine, err := journalToLogLine(entry)
192
logrus.Errorf("Failed parse journal entry: %v", err)
200
logLine.ColorID = colorID
202
logLine.CName = c.Name()
205
tailQueue = append(tailQueue, logLine)
208
logChannel <- logLine
215
func journalToLogLine(entry *sdjournal.JournalEntry) (*logs.LogLine, error) {
216
line := &logs.LogLine{}
218
usec := entry.RealtimeTimestamp
219
line.Time = time.Unix(0, int64(usec)*int64(time.Microsecond))
221
priority, ok := entry.Fields["PRIORITY"]
223
return nil, errors.New("no PRIORITY field present in journal entry")
227
line.Device = "stdout"
229
line.Device = "stderr"
231
return nil, errors.New("unexpected PRIORITY field in journal entry")
234
// if CONTAINER_PARTIAL_MESSAGE is defined, the log type is "P"
235
if _, ok := entry.Fields["CONTAINER_PARTIAL_MESSAGE"]; ok {
236
line.ParseLogType = logs.PartialLogType
238
line.ParseLogType = logs.FullLogType
241
line.Msg, ok = entry.Fields["MESSAGE"]
243
return nil, errors.New("no MESSAGE field present in journal entry")
245
line.Msg = strings.TrimSuffix(line.Msg, "\n")