From f61fa28d39298def261dded2644b8dcf45366415 Mon Sep 17 00:00:00 2001 From: Peter Hunt Date: Sat, 18 May 2019 19:39:11 -0400 Subject: Added --log-driver and journald logging Signed-off-by: Peter Hunt --- libpod/container_log.go | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'libpod/container_log.go') diff --git a/libpod/container_log.go b/libpod/container_log.go index e998ad316..f11db8014 100644 --- a/libpod/container_log.go +++ b/libpod/container_log.go @@ -53,6 +53,11 @@ func (r *Runtime) Log(containers []*Container, options *LogOptions, logChannel c // ReadLog reads a containers log based on the input options and returns loglines over a channel func (c *Container) ReadLog(options *LogOptions, logChannel chan *LogLine) error { + // TODO Skip sending logs until journald logs can be read + // TODO make this not a magic string + if c.LogDriver() == "journald" { + return ErrNotImplemented + } t, tailLog, err := getLogFile(c.LogPath(), options) if err != nil { // If the log file does not exist, this is not fatal. -- cgit v1.2.3-54-g00ecf From 02f971131a3bb71615d15a45df808edd0fa88266 Mon Sep 17 00:00:00 2001 From: Peter Hunt Date: Sat, 18 May 2019 22:17:37 -0400 Subject: Implement podman logs with log-driver journald Add a journald reader that translates the journald entry to a k8s-file formatted line, to be added as a log line Note: --follow with journald hasn't been implemented. It's going to be a larger undertaking that can wait. Signed-off-by: Peter Hunt --- cmd/podman/shared/create.go | 2 +- docs/podman-logs.1.md | 2 + libpod/container_log.go | 25 ++++++-- libpod/container_log_linux.go | 114 ++++++++++++++++++++++++++++++++++++ libpod/container_log_unsupported.go | 7 +++ libpod/oci.go | 6 ++ libpod/oci_linux.go | 2 +- test/e2e/logs_test.go | 78 ++++++++++++++++++++++++ 8 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 libpod/container_log_linux.go create mode 100644 libpod/container_log_unsupported.go (limited to 'libpod/container_log.go') diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index 4e105cad4..7cf230605 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -603,7 +603,7 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod. memorySwappiness := c.Int64("memory-swappiness") - logDriver := "k8s-file" + logDriver := libpod.KubernetesLogging if c.Changed("log-driver") { logDriver = c.String("log-driver") } diff --git a/docs/podman-logs.1.md b/docs/podman-logs.1.md index ce5d890ce..6d5be373b 100644 --- a/docs/podman-logs.1.md +++ b/docs/podman-logs.1.md @@ -23,6 +23,8 @@ Note: If you are following a container which is removed `podman container rm` or removed on exit `podman run --rm ...`, then there is a chance the the log file will be removed before `podman logs` reads the final content. +Also note: **--follow** is not currently supported when the container's log driver is journald + **--latest, -l** Instead of providing the container name or ID, use the last created container. If you use methods other than Podman diff --git a/libpod/container_log.go b/libpod/container_log.go index f11db8014..9276b52f4 100644 --- a/libpod/container_log.go +++ b/libpod/container_log.go @@ -19,6 +19,16 @@ const ( // zeroes are not trimmed, taken from // https://github.com/golang/go/issues/19635 logTimeFormat = "2006-01-02T15:04:05.000000000Z07:00" + + // readLogTimeFormat is the format the log lines will be read in + readLogTimeFormat = time.RFC3339Nano + + // partialLogType signifies a log line that exceeded the buffer + // length and needed to spill into a new line + partialLogType = "P" + + // fullLogType signifies a log line is full + fullLogType = "F" ) // LogOptions is the options you can use for logs @@ -55,9 +65,16 @@ func (r *Runtime) Log(containers []*Container, options *LogOptions, logChannel c func (c *Container) ReadLog(options *LogOptions, logChannel chan *LogLine) error { // TODO Skip sending logs until journald logs can be read // TODO make this not a magic string - if c.LogDriver() == "journald" { - return ErrNotImplemented + if c.LogDriver() == JournaldLogging { + if options.Follow { + return errors.Errorf("The follow option with journald logging is not currently supported") + } + return c.readFromJournal(options, logChannel) } + return c.readFromLogFile(options, logChannel) +} + +func (c *Container) readFromLogFile(options *LogOptions, logChannel chan *LogLine) error { t, tailLog, err := getLogFile(c.LogPath(), options) if err != nil { // If the log file does not exist, this is not fatal. @@ -196,7 +213,7 @@ func newLogLine(line string) (*LogLine, error) { if len(splitLine) < 4 { return nil, errors.Errorf("'%s' is not a valid container log line", line) } - logTime, err := time.Parse(time.RFC3339Nano, splitLine[0]) + logTime, err := time.Parse(readLogTimeFormat, splitLine[0]) if err != nil { return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0]) } @@ -211,7 +228,7 @@ func newLogLine(line string) (*LogLine, error) { // Partial returns a bool if the log line is a partial log type func (l *LogLine) Partial() bool { - if l.ParseLogType == "P" { + if l.ParseLogType == partialLogType { return true } return false diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go new file mode 100644 index 000000000..47dc44b8f --- /dev/null +++ b/libpod/container_log_linux.go @@ -0,0 +1,114 @@ +//+build linux + +package libpod + +import ( + "fmt" + "io" + "strings" + "time" + + journal "github.com/coreos/go-systemd/sdjournal" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + // journaldLogOut is the journald priority signifying stdout + journaldLogOut = "6" + + // journaldLogErr is the journald priority signifying stderr + journaldLogErr = "3" + + // bufLen is the length of the buffer to read from a k8s-file + // formatted log line + // It consists of conmon.TSBUFLEN+2+conmon.STDIOBUFSIZE+'\0' + bufLen = 44 + 2 + 8192 + 1 +) + +func (c *Container) readFromJournal(options *LogOptions, logChannel chan *LogLine) error { + var config journal.JournalReaderConfig + config.NumFromTail = options.Tail + config.Formatter = journalFormatter + defaultTime := time.Time{} + if options.Since != defaultTime { + // coreos/go-systemd/sdjournal doesn't correctly handle requests for data in the future + // return nothing instead of fasely printing + if time.Now().Before(options.Since) { + return nil + } + config.Since = time.Since(options.Since) + } + config.Matches = append(config.Matches, journal.Match{ + Field: "CONTAINER_ID_FULL", + Value: c.ID(), + }) + options.WaitGroup.Add(1) + + r, err := journal.NewJournalReader(config) + if err != nil { + return err + } + if r == nil { + return errors.Errorf("journal reader creation failed") + } + if options.Tail == 0 { + r.Rewind() + } + + go func() { + bytes := make([]byte, bufLen) + // /me complains about no do-while in go + ec, err := r.Read(bytes) + for ec != 0 && err == nil { + // because we are reusing bytes, we need to make + // sure the old data doesn't get into the new line + bytestr := string(bytes[:ec]) + logLine, err2 := newLogLine(bytestr) + if err2 != nil { + logrus.Error(err2) + continue + } + logChannel <- logLine + ec, err = r.Read(bytes) + } + if err != nil && err != io.EOF { + logrus.Error(err) + } + r.Close() + options.WaitGroup.Done() + }() + return nil +} + +func journalFormatter(entry *journal.JournalEntry) (string, error) { + usec := entry.RealtimeTimestamp + timestamp := time.Unix(0, int64(usec)*int64(time.Microsecond)) + output := timestamp.Format(readLogTimeFormat) + " " + priority, ok := entry.Fields["PRIORITY"] + if !ok { + return "", errors.Errorf("no PRIORITY field present in journal entry") + } + if priority == journaldLogOut { + output += "stdout " + } else if priority == journaldLogErr { + output += "stderr " + } else { + return "", errors.Errorf("unexpected PRIORITY field in journal entry") + } + + // if CONTAINER_PARTIAL_MESSAGE is defined, the log type is "P" + if _, ok := entry.Fields["CONTAINER_PARTIAL_MESSAGE"]; ok { + output += fmt.Sprintf("%s ", partialLogType) + } else { + output += fmt.Sprintf("%s ", fullLogType) + } + + // Finally, append the message + msg, ok := entry.Fields["MESSAGE"] + if !ok { + return "", fmt.Errorf("no MESSAGE field present in journal entry") + } + output += strings.TrimSpace(msg) + return output, nil +} diff --git a/libpod/container_log_unsupported.go b/libpod/container_log_unsupported.go new file mode 100644 index 000000000..0c3d41620 --- /dev/null +++ b/libpod/container_log_unsupported.go @@ -0,0 +1,7 @@ +//+build !linux + +package libpod + +func (c *Container) readFromJournal(options *LogOptions, logChannel chan *LogLine) error { + return ErrOSNotSupported +} diff --git a/libpod/oci.go b/libpod/oci.go index 152c8e73e..a3e44bcce 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -42,6 +42,12 @@ const ( // NsRunDir is the default directory in which running network namespaces // are stored NsRunDir = "/var/run/netns" + + // JournaldLogging is the string conmon expects to specify journald logging + JournaldLogging = "journald" + + // KubernetesLogging is the string conmon expects when specifying to use the kubernetes logging format + KubernetesLogging = "k8s-file" ) // OCIRuntime represents an OCI-compatible runtime that libpod can call into diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go index b3a21948e..5d66e6d82 100644 --- a/libpod/oci_linux.go +++ b/libpod/oci_linux.go @@ -237,7 +237,7 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res args = append(args, "--log-size-max", fmt.Sprintf("%v", r.logSizeMax)) } - logDriver := "k8s-file" + logDriver := KubernetesLogging if ctr.LogDriver() != "" { logDriver = ctr.LogDriver() } diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index 2c82182cf..20d02efcb 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -140,4 +140,82 @@ var _ = Describe("Podman logs", func() { results.WaitWithDefaultTimeout() Expect(results.ExitCode()).To(BeZero()) }) + + It("podman journald logs for container", func() { + Skip("need to verify images have correct packages for journald") + logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).To(Equal(0)) + cid := logc.OutputToString() + + results := podmanTest.Podman([]string{"logs", cid}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(0)) + Expect(len(results.OutputToStringArray())).To(Equal(3)) + }) + + It("podman journald logs tail two lines", func() { + Skip("need to verify images have correct packages for journald") + logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).To(Equal(0)) + cid := logc.OutputToString() + + results := podmanTest.Podman([]string{"logs", "--tail", "2", cid}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(0)) + Expect(len(results.OutputToStringArray())).To(Equal(2)) + }) + + It("podman journald logs tail 99 lines", func() { + Skip("need to verify images have correct packages for journald") + logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).To(Equal(0)) + cid := logc.OutputToString() + + results := podmanTest.Podman([]string{"logs", "--tail", "99", cid}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(0)) + Expect(len(results.OutputToStringArray())).To(Equal(3)) + }) + + It("podman journald logs tail 2 lines with timestamps", func() { + Skip("need to verify images have correct packages for journald") + logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).To(Equal(0)) + cid := logc.OutputToString() + + results := podmanTest.Podman([]string{"logs", "--tail", "2", "-t", cid}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(0)) + Expect(len(results.OutputToStringArray())).To(Equal(2)) + }) + + It("podman journald logs latest with since time", func() { + Skip("need to verify images have correct packages for journald") + logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).To(Equal(0)) + cid := logc.OutputToString() + + results := podmanTest.Podman([]string{"logs", "--since", "2017-08-07T10:10:09.056611202-04:00", cid}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(0)) + Expect(len(results.OutputToStringArray())).To(Equal(3)) + }) + + It("podman journald logs latest with since duration", func() { + Skip("need to verify images have correct packages for journald") + logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).To(Equal(0)) + cid := logc.OutputToString() + + results := podmanTest.Podman([]string{"logs", "--since", "10m", cid}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(0)) + Expect(len(results.OutputToStringArray())).To(Equal(3)) + }) }) -- cgit v1.2.3-54-g00ecf From 51bdf29f0493827ce3eb278a193d0a7402add896 Mon Sep 17 00:00:00 2001 From: Peter Hunt Date: Mon, 20 May 2019 13:58:31 -0400 Subject: Address comments Signed-off-by: Peter Hunt --- libpod/container.go | 9 +++++++++ libpod/container_log.go | 5 +---- libpod/container_log_linux.go | 7 ++++--- libpod/container_log_unsupported.go | 8 ++++++-- libpod/oci.go | 7 ------- libpod/oci_linux.go | 1 + libpod/options.go | 8 +++++--- libpod/runtime_ctr.go | 3 +-- test/e2e/.logs_test.go.swp | Bin 0 -> 20480 bytes 9 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 test/e2e/.logs_test.go.swp (limited to 'libpod/container_log.go') diff --git a/libpod/container.go b/libpod/container.go index 9ac08cba0..c8ab42fc3 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -51,6 +51,15 @@ const CgroupfsDefaultCgroupParent = "/libpod_parent" // manager in libpod const SystemdDefaultCgroupParent = "machine.slice" +// JournaldLogging is the string conmon expects to specify journald logging +const JournaldLogging = "journald" + +// KubernetesLogging is the string conmon expects when specifying to use the kubernetes logging format +const KubernetesLogging = "k8s-file" + +// JSONLogging is the string conmon expects when specifying to use the json logging format +const JSONLogging = "json-file" + // DefaultWaitInterval is the default interval between container status checks // while waiting. const DefaultWaitInterval = 250 * time.Millisecond diff --git a/libpod/container_log.go b/libpod/container_log.go index 9276b52f4..c893ccad9 100644 --- a/libpod/container_log.go +++ b/libpod/container_log.go @@ -20,9 +20,6 @@ const ( // https://github.com/golang/go/issues/19635 logTimeFormat = "2006-01-02T15:04:05.000000000Z07:00" - // readLogTimeFormat is the format the log lines will be read in - readLogTimeFormat = time.RFC3339Nano - // partialLogType signifies a log line that exceeded the buffer // length and needed to spill into a new line partialLogType = "P" @@ -213,7 +210,7 @@ func newLogLine(line string) (*LogLine, error) { if len(splitLine) < 4 { return nil, errors.Errorf("'%s' is not a valid container log line", line) } - logTime, err := time.Parse(readLogTimeFormat, splitLine[0]) + logTime, err := time.Parse(logTimeFormat, splitLine[0]) if err != nil { return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0]) } diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go index 47dc44b8f..3b7945e0c 100644 --- a/libpod/container_log_linux.go +++ b/libpod/container_log_linux.go @@ -1,4 +1,5 @@ //+build linux +//+build systemd package libpod @@ -22,8 +23,8 @@ const ( // bufLen is the length of the buffer to read from a k8s-file // formatted log line - // It consists of conmon.TSBUFLEN+2+conmon.STDIOBUFSIZE+'\0' - bufLen = 44 + 2 + 8192 + 1 + // let's set it as 2k just to be safe if k8s-file format ever changes + bufLen = 16384 ) func (c *Container) readFromJournal(options *LogOptions, logChannel chan *LogLine) error { @@ -84,7 +85,7 @@ func (c *Container) readFromJournal(options *LogOptions, logChannel chan *LogLin func journalFormatter(entry *journal.JournalEntry) (string, error) { usec := entry.RealtimeTimestamp timestamp := time.Unix(0, int64(usec)*int64(time.Microsecond)) - output := timestamp.Format(readLogTimeFormat) + " " + output := timestamp.Format(logTimeFormat) + " " priority, ok := entry.Fields["PRIORITY"] if !ok { return "", errors.Errorf("no PRIORITY field present in journal entry") diff --git a/libpod/container_log_unsupported.go b/libpod/container_log_unsupported.go index 0c3d41620..0ec5740e2 100644 --- a/libpod/container_log_unsupported.go +++ b/libpod/container_log_unsupported.go @@ -1,7 +1,11 @@ -//+build !linux +//+build !linux !systemd package libpod +import ( + "github.com/pkg/errors" +) + func (c *Container) readFromJournal(options *LogOptions, logChannel chan *LogLine) error { - return ErrOSNotSupported + return errors.Wrapf(ErrOSNotSupported, "Journald logging only enabled with systemd on linux") } diff --git a/libpod/oci.go b/libpod/oci.go index a3e44bcce..7138108c5 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -42,12 +42,6 @@ const ( // NsRunDir is the default directory in which running network namespaces // are stored NsRunDir = "/var/run/netns" - - // JournaldLogging is the string conmon expects to specify journald logging - JournaldLogging = "journald" - - // KubernetesLogging is the string conmon expects when specifying to use the kubernetes logging format - KubernetesLogging = "k8s-file" ) // OCIRuntime represents an OCI-compatible runtime that libpod can call into @@ -409,7 +403,6 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty // Append container ID, name and command args = append(args, c.ID()) args = append(args, cmd...) - args = append(args, c.Name()) logrus.Debugf("Starting runtime %s with following arguments: %v", r.path, args) diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go index 5d66e6d82..7c1c18052 100644 --- a/libpod/oci_linux.go +++ b/libpod/oci_linux.go @@ -214,6 +214,7 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res } args = append(args, "-c", ctr.ID()) args = append(args, "-u", ctr.ID()) + args = append(args, "-n", ctr.Name()) args = append(args, "-r", r.path) args = append(args, "-b", ctr.bundlePath()) args = append(args, "-p", filepath.Join(ctr.state.RunDir, "pidfile")) diff --git a/libpod/options.go b/libpod/options.go index d6eb97609..20aa51981 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -985,10 +985,12 @@ func WithLogDriver(driver string) CtrCreateOption { if ctr.valid { return ErrCtrFinalized } - if driver == "" { + switch driver { + case "": return errors.Wrapf(ErrInvalidArg, "log driver must be set") - } - if driver != "journald" && driver != "k8s-file" && driver != "json-file" { + case JournaldLogging, KubernetesLogging, JSONLogging: + break + default: return errors.Wrapf(ErrInvalidArg, "invalid log driver") } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 25db10d33..db7a5e5c3 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -196,8 +196,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. } } - // TODO magic string - if ctr.config.LogPath == "" && ctr.config.LogDriver != "journald" { + if ctr.config.LogPath == "" && ctr.config.LogDriver != JournaldLogging { ctr.config.LogPath = filepath.Join(ctr.config.StaticDir, "ctr.log") } diff --git a/test/e2e/.logs_test.go.swp b/test/e2e/.logs_test.go.swp new file mode 100644 index 000000000..4a4788beb Binary files /dev/null and b/test/e2e/.logs_test.go.swp differ -- cgit v1.2.3-54-g00ecf From 88429242ddf82c03509ca66a687d9fb1534d2446 Mon Sep 17 00:00:00 2001 From: Peter Hunt Date: Wed, 22 May 2019 11:46:26 -0400 Subject: Add --follow to journald ctr logging Signed-off-by: Peter Hunt --- docs/podman-logs.1.md | 2 -- libpod/container_log.go | 3 --- libpod/container_log_linux.go | 32 ++++++++++++++++++++++++++++++-- test/e2e/.logs_test.go.swp | Bin 20480 -> 0 bytes 4 files changed, 30 insertions(+), 7 deletions(-) delete mode 100644 test/e2e/.logs_test.go.swp (limited to 'libpod/container_log.go') diff --git a/docs/podman-logs.1.md b/docs/podman-logs.1.md index 6d5be373b..ce5d890ce 100644 --- a/docs/podman-logs.1.md +++ b/docs/podman-logs.1.md @@ -23,8 +23,6 @@ Note: If you are following a container which is removed `podman container rm` or removed on exit `podman run --rm ...`, then there is a chance the the log file will be removed before `podman logs` reads the final content. -Also note: **--follow** is not currently supported when the container's log driver is journald - **--latest, -l** Instead of providing the container name or ID, use the last created container. If you use methods other than Podman diff --git a/libpod/container_log.go b/libpod/container_log.go index c893ccad9..374e5a1fc 100644 --- a/libpod/container_log.go +++ b/libpod/container_log.go @@ -63,9 +63,6 @@ func (c *Container) ReadLog(options *LogOptions, logChannel chan *LogLine) error // TODO Skip sending logs until journald logs can be read // TODO make this not a magic string if c.LogDriver() == JournaldLogging { - if options.Follow { - return errors.Errorf("The follow option with journald logging is not currently supported") - } return c.readFromJournal(options, logChannel) } return c.readFromLogFile(options, logChannel) diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go index 3b7945e0c..e549673a6 100644 --- a/libpod/container_log_linux.go +++ b/libpod/container_log_linux.go @@ -57,6 +57,20 @@ func (c *Container) readFromJournal(options *LogOptions, logChannel chan *LogLin r.Rewind() } + if options.Follow { + go func() { + follower := FollowBuffer{logChannel} + err := r.Follow(nil, follower) + if err != nil { + logrus.Debugf(err.Error()) + } + r.Close() + options.WaitGroup.Done() + return + }() + return nil + } + go func() { bytes := make([]byte, bufLen) // /me complains about no do-while in go @@ -84,8 +98,8 @@ func (c *Container) readFromJournal(options *LogOptions, logChannel chan *LogLin func journalFormatter(entry *journal.JournalEntry) (string, error) { usec := entry.RealtimeTimestamp - timestamp := time.Unix(0, int64(usec)*int64(time.Microsecond)) - output := timestamp.Format(logTimeFormat) + " " + tsString := time.Unix(0, int64(usec)*int64(time.Microsecond)).Format(logTimeFormat) + output := fmt.Sprintf("%s ", tsString) priority, ok := entry.Fields["PRIORITY"] if !ok { return "", errors.Errorf("no PRIORITY field present in journal entry") @@ -113,3 +127,17 @@ func journalFormatter(entry *journal.JournalEntry) (string, error) { output += strings.TrimSpace(msg) return output, nil } + +type FollowBuffer struct { + logChannel chan *LogLine +} + +func (f FollowBuffer) Write(p []byte) (int, error) { + bytestr := string(p) + logLine, err := newLogLine(bytestr) + if err != nil { + return -1, err + } + f.logChannel <- logLine + return len(p), nil +} diff --git a/test/e2e/.logs_test.go.swp b/test/e2e/.logs_test.go.swp deleted file mode 100644 index 4a4788beb..000000000 Binary files a/test/e2e/.logs_test.go.swp and /dev/null differ -- cgit v1.2.3-54-g00ecf