From 7bf7c177ab3f67d5de1689842204c258fca083e4 Mon Sep 17 00:00:00 2001 From: baude Date: Wed, 27 Mar 2019 13:50:54 -0500 Subject: journald event logging add the ability for podman to read and write events to journald instead of just a logfile. This can be controlled in libpod.conf with the `events_logger` attribute of `journald` or `file`. The default will be set to `journald`. Signed-off-by: baude --- libpod/events.go | 69 +++++------------ libpod/events/config.go | 149 ++++++++++++++++++++++++++++++++++++ libpod/events/events.go | 148 ++++++----------------------------- libpod/events/events_linux.go | 20 +++++ libpod/events/events_unsupported.go | 10 +++ libpod/events/filters.go | 114 +++++++++++++++++++++++++++ libpod/events/journal_linux.go | 131 +++++++++++++++++++++++++++++++ libpod/events/logfile.go | 65 ++++++++++++++++ libpod/events/nullout.go | 23 ++++++ libpod/image/image.go | 6 +- libpod/image/image_test.go | 4 + libpod/runtime.go | 22 +++++- 12 files changed, 582 insertions(+), 179 deletions(-) create mode 100644 libpod/events/config.go create mode 100644 libpod/events/events_linux.go create mode 100644 libpod/events/events_unsupported.go create mode 100644 libpod/events/filters.go create mode 100644 libpod/events/journal_linux.go create mode 100644 libpod/events/logfile.go create mode 100644 libpod/events/nullout.go (limited to 'libpod') diff --git a/libpod/events.go b/libpod/events.go index b6a277789..1b5c3bd99 100644 --- a/libpod/events.go +++ b/libpod/events.go @@ -1,14 +1,19 @@ package libpod import ( - "os" - "github.com/containers/libpod/libpod/events" - "github.com/hpcloud/tail" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) +// newEventer returns an eventer that can be used to read/write events +func (r *Runtime) newEventer() (events.Eventer, error) { + options := events.EventerOptions{ + EventerType: r.config.EventsLogger, + LogFilePath: r.config.EventsLogFilePath, + } + return events.NewEventer(options) +} + // newContainerEvent creates a new event based on a container func (c *Container) newContainerEvent(status events.Status) { e := events.NewEvent(status) @@ -16,8 +21,8 @@ func (c *Container) newContainerEvent(status events.Status) { e.Name = c.Name() e.Image = c.config.RootfsImageName e.Type = events.Container - if err := e.Write(c.runtime.config.EventsLogFilePath); err != nil { - logrus.Errorf("unable to write event to %s", c.runtime.config.EventsLogFilePath) + if err := c.runtime.eventer.Write(e); err != nil { + logrus.Errorf("unable to write pod event: %q", err) } } @@ -29,8 +34,8 @@ func (c *Container) newContainerExitedEvent(exitCode int32) { e.Image = c.config.RootfsImageName e.Type = events.Container e.ContainerExitCode = int(exitCode) - if err := e.Write(c.runtime.config.EventsLogFilePath); err != nil { - logrus.Errorf("unable to write event to %s", c.runtime.config.EventsLogFilePath) + if err := c.runtime.eventer.Write(e); err != nil { + logrus.Errorf("unable to write pod event: %q", err) } } @@ -40,8 +45,8 @@ func (p *Pod) newPodEvent(status events.Status) { e.ID = p.ID() e.Name = p.Name() e.Type = events.Pod - if err := e.Write(p.runtime.config.EventsLogFilePath); err != nil { - logrus.Errorf("unable to write event to %s", p.runtime.config.EventsLogFilePath) + if err := p.runtime.eventer.Write(e); err != nil { + logrus.Errorf("unable to write pod event: %q", err) } } @@ -50,51 +55,17 @@ func (v *Volume) newVolumeEvent(status events.Status) { e := events.NewEvent(status) e.Name = v.Name() e.Type = events.Volume - if err := e.Write(v.runtime.config.EventsLogFilePath); err != nil { - logrus.Errorf("unable to write event to %s", v.runtime.config.EventsLogFilePath) + if err := v.runtime.eventer.Write(e); err != nil { + logrus.Errorf("unable to write volume event: %q", err) } } // Events is a wrapper function for everyone to begin tailing the events log // with options -func (r *Runtime) Events(fromStart, stream bool, options []events.EventFilter, eventChannel chan *events.Event) error { - if !r.valid { - return ErrRuntimeStopped - } - - t, err := r.getTail(fromStart, stream) +func (r *Runtime) Events(options events.ReadOptions) error { + eventer, err := r.newEventer() if err != nil { return err } - for line := range t.Lines { - event, err := events.NewEventFromString(line.Text) - if err != nil { - return err - } - switch event.Type { - case events.Image, events.Volume, events.Pod, events.Container: - // no-op - default: - return errors.Errorf("event type %s is not valid in %s", event.Type.String(), r.config.EventsLogFilePath) - } - include := true - for _, filter := range options { - include = include && filter(event) - } - if include { - eventChannel <- event - } - } - close(eventChannel) - return nil -} - -func (r *Runtime) getTail(fromStart, stream bool) (*tail.Tail, error) { - reopen := true - seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} - if fromStart || !stream { - seek.Whence = 0 - reopen = false - } - return tail.TailFile(r.config.EventsLogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger}) + return eventer.Read(options) } diff --git a/libpod/events/config.go b/libpod/events/config.go new file mode 100644 index 000000000..d3b6d8c50 --- /dev/null +++ b/libpod/events/config.go @@ -0,0 +1,149 @@ +package events + +import ( + "time" +) + +// EventerType ... +type EventerType int + +const ( + // LogFile indicates the event logger will be a logfile + LogFile EventerType = iota + // Journald indicates journald should be used to log events + Journald EventerType = iota +) + +// Event describes the attributes of a libpod event +type Event struct { + // ContainerExitCode is for storing the exit code of a container which can + // be used for "internal" event notification + ContainerExitCode int + // ID can be for the container, image, volume, etc + ID string + // Image used where applicable + Image string + // Name where applicable + Name string + // Status describes the event that occurred + Status Status + // Time the event occurred + Time time.Time + // Type of event that occurred + Type Type +} + +// EventerOptions describe options that need to be passed to create +// an eventer +type EventerOptions struct { + // EventerType describes whether to use journald or a file + EventerType string + // LogFilePath is the path to where the log file should reside if using + // the file logger + LogFilePath string +} + +// Eventer is the interface for journald or file event logging +type Eventer interface { + // Write an event to a backend + Write(event Event) error + // Read an event from the backend + Read(options ReadOptions) error +} + +// ReadOptions describe the attributes needed to read event logs +type ReadOptions struct { + // EventChannel is the comm path back to user + EventChannel chan *Event + // Filters are key/value pairs that describe to limit output + Filters []string + // FromStart means you start reading from the start of the logs + FromStart bool + // Since reads "since" the given time + Since string + // Stream is follow + Stream bool + // Until reads "until" the given time + Until string +} + +// Type of event that occurred (container, volume, image, pod, etc) +type Type string + +// Status describes the actual event action (stop, start, create, kill) +type Status string + +const ( + // If you add or subtract any values to the following lists, make sure you also update + // the switch statements below and the enums for EventType or EventStatus in the + // varlink description file. + + // Container - event is related to containers + Container Type = "container" + // Image - event is related to images + Image Type = "image" + // Pod - event is related to pods + Pod Type = "pod" + // Volume - event is related to volumes + Volume Type = "volume" + + // Attach ... + Attach Status = "attach" + // Checkpoint ... + Checkpoint Status = "checkpoint" + // Cleanup ... + Cleanup Status = "cleanup" + // Commit ... + Commit Status = "commit" + // Create ... + Create Status = "create" + // Exec ... + Exec Status = "exec" + // Exited indicates that a container's process died + Exited Status = "died" + // Export ... + Export Status = "export" + // History ... + History Status = "history" + // Import ... + Import Status = "import" + // Init ... + Init Status = "init" + // Kill ... + Kill Status = "kill" + // LoadFromArchive ... + LoadFromArchive Status = "loadfromarchive" + // Mount ... + Mount Status = "mount" + // Pause ... + Pause Status = "pause" + // Prune ... + Prune Status = "prune" + // Pull ... + Pull Status = "pull" + // Push ... + Push Status = "push" + // Remove ... + Remove Status = "remove" + // Restore ... + Restore Status = "restore" + // Save ... + Save Status = "save" + // Start ... + Start Status = "start" + // Stop ... + Stop Status = "stop" + // Sync ... + Sync Status = "sync" + // Tag ... + Tag Status = "tag" + // Unmount ... + Unmount Status = "unmount" + // Unpause ... + Unpause Status = "unpause" + // Untag ... + Untag Status = "untag" +) + +// EventFilter for filtering events +type EventFilter func(*Event) bool diff --git a/libpod/events/events.go b/libpod/events/events.go index 074a3ba5b..e8c61faa0 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -6,109 +6,18 @@ import ( "os" "time" - "github.com/containers/storage" + "github.com/hpcloud/tail" "github.com/pkg/errors" ) -// Event describes the attributes of a libpod event -type Event struct { - // ContainerExitCode is for storing the exit code of a container which can - // be used for "internal" event notification - ContainerExitCode int - // ID can be for the container, image, volume, etc - ID string - // Image used where applicable - Image string - // Name where applicable - Name string - // Status describes the event that occurred - Status Status - // Time the event occurred - Time time.Time - // Type of event that occurred - Type Type -} - -// Type of event that occurred (container, volume, image, pod, etc) -type Type string - -// Status describes the actual event action (stop, start, create, kill) -type Status string - -const ( - // If you add or subtract any values to the following lists, make sure you also update - // the switch statements below and the enums for EventType or EventStatus in the - // varlink description file. - - // Container - event is related to containers - Container Type = "container" - // Image - event is related to images - Image Type = "image" - // Pod - event is related to pods - Pod Type = "pod" - // Volume - event is related to volumes - Volume Type = "volume" +// String returns a string representation of EventerType +func (et EventerType) String() string { + if et == LogFile { + return "file" - // Attach ... - Attach Status = "attach" - // Checkpoint ... - Checkpoint Status = "checkpoint" - // Cleanup ... - Cleanup Status = "cleanup" - // Commit ... - Commit Status = "commit" - // Create ... - Create Status = "create" - // Exec ... - Exec Status = "exec" - // Exited indicates that a container's process died - Exited Status = "died" - // Export ... - Export Status = "export" - // History ... - History Status = "history" - // Import ... - Import Status = "import" - // Init ... - Init Status = "init" - // Kill ... - Kill Status = "kill" - // LoadFromArchive ... - LoadFromArchive Status = "status" - // Mount ... - Mount Status = "mount" - // Pause ... - Pause Status = "pause" - // Prune ... - Prune Status = "prune" - // Pull ... - Pull Status = "pull" - // Push ... - Push Status = "push" - // Remove ... - Remove Status = "remove" - // Restore ... - Restore Status = "restore" - // Save ... - Save Status = "save" - // Start ... - Start Status = "start" - // Stop ... - Stop Status = "stop" - // Sync ... - Sync Status = "sync" - // Tag ... - Tag Status = "tag" - // Unmount ... - Unmount Status = "unmount" - // Unpause ... - Unpause Status = "unpause" - // Untag ... - Untag Status = "untag" -) - -// EventFilter for filtering events -type EventFilter func(*Event) bool + } + return "journald" +} // NewEvent creates a event struct and populates with // the given status and time. @@ -119,30 +28,6 @@ func NewEvent(status Status) Event { } } -// Write will record the event to the given path -func (e *Event) Write(path string) error { - // We need to lock events file - lock, err := storage.GetLockfile(path + ".lock") - if err != nil { - return err - } - lock.Lock() - defer lock.Unlock() - f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700) - if err != nil { - return err - } - defer f.Close() - eventJSONString, err := e.ToJSONString() - if err != nil { - return err - } - if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil { - return err - } - return nil -} - // Recycle checks if the event log has reach a limit and if so // renames the current log and starts a new one. The remove bool // indicates the old log file should be deleted. @@ -172,7 +57,7 @@ func (e *Event) ToHumanReadable() string { // NewEventFromString takes stringified json and converts // it to an event -func NewEventFromString(event string) (*Event, error) { +func newEventFromJSONString(event string) (*Event, error) { e := Event{} if err := json.Unmarshal([]byte(event), &e); err != nil { return nil, err @@ -222,6 +107,7 @@ func StringToStatus(name string) (Status, error) { case Commit.String(): return Commit, nil case Create.String(): + return Create, nil case Exec.String(): return Exec, nil @@ -270,3 +156,17 @@ func StringToStatus(name string) (Status, error) { } return "", errors.Errorf("unknown event status %s", name) } + +func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) { + reopen := true + seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} + if options.FromStart || !options.Stream { + seek.Whence = 0 + reopen = false + } + stream := options.Stream + if len(options.Until) > 0 { + stream = false + } + return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger}) +} diff --git a/libpod/events/events_linux.go b/libpod/events/events_linux.go new file mode 100644 index 000000000..d6898145c --- /dev/null +++ b/libpod/events/events_linux.go @@ -0,0 +1,20 @@ +package events + +import ( + "github.com/pkg/errors" + "strings" +) + +// NewEventer creates an eventer based on the eventer type +func NewEventer(options EventerOptions) (Eventer, error) { + var eventer Eventer + switch strings.ToUpper(options.EventerType) { + case strings.ToUpper(Journald.String()): + eventer = EventJournalD{options} + case strings.ToUpper(LogFile.String()): + eventer = EventLogFile{options} + default: + return eventer, errors.Errorf("unknown event logger type: %s", strings.ToUpper(options.EventerType)) + } + return eventer, nil +} diff --git a/libpod/events/events_unsupported.go b/libpod/events/events_unsupported.go new file mode 100644 index 000000000..5b32a1b4b --- /dev/null +++ b/libpod/events/events_unsupported.go @@ -0,0 +1,10 @@ +// +build !linux + +package events + +import "github.com/pkg/errors" + +// NewEventer creates an eventer based on the eventer type +func NewEventer(options EventerOptions) (Eventer, error) { + return nil, errors.New("this function is not available for your platform") +} diff --git a/libpod/events/filters.go b/libpod/events/filters.go new file mode 100644 index 000000000..9a64082d1 --- /dev/null +++ b/libpod/events/filters.go @@ -0,0 +1,114 @@ +package events + +import ( + "fmt" + "strings" + "time" + + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" +) + +func generateEventFilter(filter, filterValue string) (func(e *Event) bool, error) { + switch strings.ToUpper(filter) { + case "CONTAINER": + return func(e *Event) bool { + if e.Type != Container { + return false + } + if e.Name == filterValue { + return true + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "EVENT", "STATUS": + return func(e *Event) bool { + return fmt.Sprintf("%s", e.Status) == filterValue + }, nil + case "IMAGE": + return func(e *Event) bool { + if e.Type != Image { + return false + } + if e.Name == filterValue { + return true + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "POD": + return func(e *Event) bool { + if e.Type != Pod { + return false + } + if e.Name == filterValue { + return true + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "VOLUME": + return func(e *Event) bool { + if e.Type != Volume { + return false + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "TYPE": + return func(e *Event) bool { + return fmt.Sprintf("%s", e.Type) == filterValue + }, nil + } + return nil, errors.Errorf("%s is an invalid filter", filter) +} + +func generateEventSinceOption(timeSince time.Time) func(e *Event) bool { + return func(e *Event) bool { + return e.Time.After(timeSince) + } +} + +func generateEventUntilOption(timeUntil time.Time) func(e *Event) bool { + return func(e *Event) bool { + return e.Time.Before(timeUntil) + + } +} + +func parseFilter(filter string) (string, string, error) { + filterSplit := strings.Split(filter, "=") + if len(filterSplit) != 2 { + return "", "", errors.Errorf("%s is an invalid filter", filter) + } + return filterSplit[0], filterSplit[1], nil +} + +func generateEventOptions(filters []string, since, until string) ([]EventFilter, error) { + var options []EventFilter + for _, filter := range filters { + key, val, err := parseFilter(filter) + if err != nil { + return nil, err + } + funcFilter, err := generateEventFilter(key, val) + if err != nil { + return nil, err + } + options = append(options, funcFilter) + } + + if len(since) > 0 { + timeSince, err := util.ParseInputTime(since) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert since time of %s", since) + } + options = append(options, generateEventSinceOption(timeSince)) + } + + if len(until) > 0 { + timeUntil, err := util.ParseInputTime(until) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert until time of %s", until) + } + options = append(options, generateEventUntilOption(timeUntil)) + } + return options, nil +} diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go new file mode 100644 index 000000000..e6b54db1d --- /dev/null +++ b/libpod/events/journal_linux.go @@ -0,0 +1,131 @@ +package events + +import ( + "fmt" + "time" + + "github.com/coreos/go-systemd/journal" + "github.com/coreos/go-systemd/sdjournal" + "github.com/pkg/errors" +) + +// EventJournalD is the journald implementation of an eventer +type EventJournalD struct { + options EventerOptions +} + +// Write to journald +func (e EventJournalD) Write(ee Event) error { + m := make(map[string]string) + m["SYSLOG_IDENTIFIER"] = "podman" + m["PODMAN_EVENT"] = ee.Status.String() + m["PODMAN_TYPE"] = ee.Type.String() + m["PODMAN_TIME"] = ee.Time.Format(time.RFC3339Nano) + + // Add specialized information based on the podman type + switch ee.Type { + case Image: + m["PODMAN_NAME"] = ee.Name + m["PODMAN_ID"] = ee.ID + case Container, Pod: + m["PODMAN_IMAGE"] = ee.Image + m["PODMAN_NAME"] = ee.Name + m["PODMAN_ID"] = ee.ID + case Volume: + m["PODMAN_NAME"] = ee.Name + } + return journal.Send(fmt.Sprintf("%s", ee.ToHumanReadable()), journal.PriInfo, m) +} + +// Read reads events from the journal and sends qualified events to the event channel +func (e EventJournalD) Read(options ReadOptions) error { + eventOptions, err := generateEventOptions(options.Filters, options.Since, options.Until) + if err != nil { + return errors.Wrapf(err, "failed to generate event options") + } + podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} //nolint + j, err := sdjournal.NewJournal() //nolint + if err != nil { + return err + } + if err := j.AddMatch(podmanJournal.String()); err != nil { + return errors.Wrap(err, "failed to add filter for event log") + } + if len(options.Since) == 0 && len(options.Until) == 0 && options.Stream { + if err := j.SeekTail(); err != nil { + return errors.Wrap(err, "failed to seek end of journal") + } + } + // the api requires a next|prev before getting a cursor + if _, err := j.Next(); err != nil { + return err + } + prevCursor, err := j.GetCursor() + if err != nil { + return err + } + defer close(options.EventChannel) + for { + if _, err := j.Next(); err != nil { + return err + } + newCursor, err := j.GetCursor() + if err != nil { + return err + } + if prevCursor == newCursor { + if len(options.Until) > 0 || !options.Stream { + break + } + _ = j.Wait(sdjournal.IndefiniteWait) //nolint + continue + } + prevCursor = newCursor + entry, err := j.GetEntry() + if err != nil { + return err + } + newEvent, err := newEventFromJournalEntry(entry) + if err != nil { + return err + } + include := true + for _, filter := range eventOptions { + include = include && filter(newEvent) + } + if include { + options.EventChannel <- newEvent + } + } + return nil + +} + +func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { //nolint + newEvent := Event{} + eventType, err := StringToType(entry.Fields["PODMAN_TYPE"]) + if err != nil { + return nil, err + } + eventTime, err := time.Parse(time.RFC3339Nano, entry.Fields["PODMAN_TIME"]) + if err != nil { + return nil, err + } + eventStatus, err := StringToStatus(entry.Fields["PODMAN_EVENT"]) + if err != nil { + return nil, err + } + newEvent.Type = eventType + newEvent.Time = eventTime + newEvent.Status = eventStatus + newEvent.Name = entry.Fields["PODMAN_NAME"] + + switch eventType { + case Container, Pod: + newEvent.ID = entry.Fields["PODMAN_ID"] + newEvent.Image = entry.Fields["PODMAN_IMAGE"] + case Image: + newEvent.ID = entry.Fields["PODMAN_ID"] + } + return &newEvent, nil +} diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go new file mode 100644 index 000000000..3232b86d0 --- /dev/null +++ b/libpod/events/logfile.go @@ -0,0 +1,65 @@ +package events + +import ( + "fmt" + "os" + + "github.com/pkg/errors" +) + +// EventLogFile is the structure for event writing to a logfile. It contains the eventer +// options and the event itself. Methods for reading and writing are also defined from it. +type EventLogFile struct { + options EventerOptions +} + +// Writes to the log file +func (e EventLogFile) Write(ee Event) error { + f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700) + if err != nil { + return err + } + defer f.Close() + eventJSONString, err := ee.ToJSONString() + if err != nil { + return err + } + if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil { + return err + } + return nil + +} + +// Reads from the log file +func (e EventLogFile) Read(options ReadOptions) error { + eventOptions, err := generateEventOptions(options.Filters, options.Since, options.Until) + if err != nil { + return errors.Wrapf(err, "unable to generate event options") + } + t, err := e.getTail(options) + if err != nil { + return err + } + for line := range t.Lines { + event, err := newEventFromJSONString(line.Text) + if err != nil { + return err + } + switch event.Type { + case Image, Volume, Pod, Container: + // no-op + default: + return errors.Errorf("event type %s is not valid in %s", event.Type.String(), e.options.LogFilePath) + } + include := true + for _, filter := range eventOptions { + include = include && filter(event) + } + if include { + options.EventChannel <- event + } + } + close(options.EventChannel) + return nil +} diff --git a/libpod/events/nullout.go b/libpod/events/nullout.go new file mode 100644 index 000000000..7d811a9c7 --- /dev/null +++ b/libpod/events/nullout.go @@ -0,0 +1,23 @@ +package events + +// EventToNull is an eventer type that only performs write operations +// and only writes to /dev/null. It is meant for unittests only +type EventToNull struct{} + +// Write eats the event and always returns nil +func (e EventToNull) Write(ee Event) error { + return nil +} + +// Read does nothing. Do not use it. +func (e EventToNull) Read(options ReadOptions) error { + return nil +} + +// NewNullEventer returns a new null eventer. You should only do this for +// the purposes on internal libpod testing. +func NewNullEventer() Eventer { + var e Eventer + e = EventToNull{} + return e +} diff --git a/libpod/image/image.go b/libpod/image/image.go index 757d034a2..27773edcf 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -66,6 +66,8 @@ type Runtime struct { store storage.Store SignaturePolicyPath string EventsLogFilePath string + EventsLogger string + Eventer events.Eventer } // InfoImage keep information of Image along with all associated layers @@ -1203,7 +1205,7 @@ func (ir *Runtime) newImageEvent(status events.Status, name string) { e := events.NewEvent(status) e.Type = events.Image e.Name = name - if err := e.Write(ir.EventsLogFilePath); err != nil { + if err := ir.Eventer.Write(e); err != nil { logrus.Infof("unable to write event to %s", ir.EventsLogFilePath) } } @@ -1216,7 +1218,7 @@ func (i *Image) newImageEvent(status events.Status) { if len(i.Names()) > 0 { e.Name = i.Names()[0] } - if err := e.Write(i.imageruntime.EventsLogFilePath); err != nil { + if err := i.imageruntime.Eventer.Write(e); err != nil { logrus.Infof("unable to write event to %s", i.imageruntime.EventsLogFilePath) } } diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index 075ba119d..dfe817c90 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -3,6 +3,7 @@ package image import ( "context" "fmt" + "github.com/containers/libpod/libpod/events" "io" "io/ioutil" "os" @@ -87,6 +88,7 @@ func TestImage_NewFromLocal(t *testing.T) { // Need images to be present for this test ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) + ir.Eventer = events.NewNullEventer() bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, false, nil) assert.NoError(t, err) bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, false, nil) @@ -127,6 +129,7 @@ func TestImage_New(t *testing.T) { } ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) + ir.Eventer = events.NewNullEventer() // Build the list of pull names names = append(names, bbNames...) names = append(names, fedoraNames...) @@ -164,6 +167,7 @@ func TestImage_MatchRepoTag(t *testing.T) { } ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) + ir.Eventer = events.NewNullEventer() newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, false, nil) assert.NoError(t, err) err = newImage.TagImage("foo:latest") diff --git a/libpod/runtime.go b/libpod/runtime.go index 3b1c2be98..38e0e0ff6 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -2,6 +2,7 @@ package libpod import ( "fmt" + "github.com/containers/libpod/libpod/events" "io/ioutil" "os" "path/filepath" @@ -105,6 +106,9 @@ type Runtime struct { // storage unusable). When valid is false, the runtime cannot be used. valid bool lock sync.RWMutex + + // mechanism to read and write even logs + eventer events.Eventer } // OCIRuntimePath contains information about an OCI runtime. @@ -222,6 +226,8 @@ type RuntimeConfig struct { // pods. NumLocks uint32 `toml:"num_locks,omitempty"` + // EventsLogger determines where events should be logged + EventsLogger string `toml:"events_logger"` // EventsLogFilePath is where the events log is stored. EventsLogFilePath string `toml:-"events_logfile_path"` } @@ -252,7 +258,6 @@ func defaultRuntimeConfig() (RuntimeConfig, error) { if err != nil { return RuntimeConfig{}, err } - return RuntimeConfig{ // Leave this empty so containers/storage will use its defaults StorageConfig: storage.StoreOptions{}, @@ -296,6 +301,7 @@ func defaultRuntimeConfig() (RuntimeConfig, error) { EnablePortReservation: true, EnableLabeling: true, NumLocks: 2048, + EventsLogger: "journald", }, nil } @@ -755,16 +761,24 @@ func makeRuntime(runtime *Runtime) (err error) { // Set up image runtime and store in runtime ir := image.NewImageRuntimeFromStore(runtime.store) - if err != nil { - return err - } runtime.imageRuntime = ir // Setting signaturepolicypath ir.SignaturePolicyPath = runtime.config.SignaturePolicyPath + // Set logfile path for events ir.EventsLogFilePath = runtime.config.EventsLogFilePath + // Set logger type + ir.EventsLogger = runtime.config.EventsLogger + + // Setup the eventer + eventer, err := runtime.newEventer() + if err != nil { + return err + } + runtime.eventer = eventer + ir.Eventer = eventer defer func() { if err != nil && store != nil { -- cgit v1.2.3-54-g00ecf