aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/logs.go181
-rw-r--r--completions/bash/podman2
-rw-r--r--contrib/spec/podman.spec1
-rw-r--r--docs/podman-logs.1.md35
-rw-r--r--test/e2e/logs_test.go69
-rw-r--r--vendor.conf1
-rw-r--r--vendor/github.com/hpcloud/tail/LICENSE.txt21
-rw-r--r--vendor/github.com/hpcloud/tail/README.md28
-rw-r--r--vendor/github.com/hpcloud/tail/ratelimiter/leakybucket.go97
-rw-r--r--vendor/github.com/hpcloud/tail/ratelimiter/memory.go58
-rw-r--r--vendor/github.com/hpcloud/tail/ratelimiter/storage.go6
-rw-r--r--vendor/github.com/hpcloud/tail/tail.go438
-rw-r--r--vendor/github.com/hpcloud/tail/tail_posix.go11
-rw-r--r--vendor/github.com/hpcloud/tail/tail_windows.go12
-rw-r--r--vendor/github.com/hpcloud/tail/util/util.go48
-rw-r--r--vendor/github.com/hpcloud/tail/watch/filechanges.go36
-rw-r--r--vendor/github.com/hpcloud/tail/watch/inotify.go128
-rw-r--r--vendor/github.com/hpcloud/tail/watch/inotify_tracker.go260
-rw-r--r--vendor/github.com/hpcloud/tail/watch/polling.go118
-rw-r--r--vendor/github.com/hpcloud/tail/watch/watch.go20
-rw-r--r--vendor/github.com/hpcloud/tail/winfile/winfile.go92
21 files changed, 230 insertions, 1432 deletions
diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go
index b31dce2d3..5551d6157 100644
--- a/cmd/podman/logs.go
+++ b/cmd/podman/logs.go
@@ -2,20 +2,23 @@ package main
import (
"fmt"
+ "io"
+ "os"
"strings"
"time"
- "github.com/hpcloud/tail"
+ "bufio"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/libpod"
"github.com/urfave/cli"
)
type logOptions struct {
- details bool
- follow bool
- sinceTime time.Time
- tail uint64
+ details bool
+ follow bool
+ sinceTime time.Time
+ tail uint64
+ showTimestamps bool
}
var (
@@ -37,17 +40,22 @@ var (
Name: "tail",
Usage: "Output the specified number of LINES at the end of the logs. Defaults to 0, which prints all lines",
},
+ cli.BoolFlag{
+ Name: "timestamps, t",
+ Usage: "Output the timestamps in the log",
+ },
LatestFlag,
}
logsDescription = "The podman logs command batch-retrieves whatever logs are present for a container at the time of execution. This does not guarantee execution" +
"order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs"
logsCommand = cli.Command{
- Name: "logs",
- Usage: "Fetch the logs of a container",
- Description: logsDescription,
- Flags: logsFlags,
- Action: logsCmd,
- ArgsUsage: "CONTAINER",
+ Name: "logs",
+ Usage: "Fetch the logs of a container",
+ Description: logsDescription,
+ Flags: logsFlags,
+ Action: logsCmd,
+ ArgsUsage: "CONTAINER",
+ SkipArgReorder: true,
}
)
@@ -72,7 +80,7 @@ func logsCmd(c *cli.Context) error {
sinceTime := time.Time{}
if c.IsSet("since") {
// parse time, error out if something is wrong
- since, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", c.String("since"))
+ since, err := parseInputTime(c.String("since"))
if err != nil {
return errors.Wrapf(err, "could not parse time: %q", c.String("since"))
}
@@ -80,10 +88,11 @@ func logsCmd(c *cli.Context) error {
}
opts := logOptions{
- details: c.Bool("details"),
- follow: c.Bool("follow"),
- sinceTime: sinceTime,
- tail: c.Uint64("tail"),
+ details: c.Bool("details"),
+ follow: c.Bool("follow"),
+ sinceTime: sinceTime,
+ tail: c.Uint64("tail"),
+ showTimestamps: c.Bool("timestamps"),
}
if c.Bool("latest") {
@@ -95,52 +104,116 @@ func logsCmd(c *cli.Context) error {
return err
}
- logs := make(chan string)
- go func() {
- err = getLogs(ctr, logs, opts)
- }()
- printLogs(logs)
+ file, err := os.Open(ctr.LogPath())
+ if err != nil {
+ return errors.Wrapf(err, "unable to read container log file")
+ }
+ defer file.Close()
+ reader := bufio.NewReader(file)
+ if opts.follow {
+ followLog(reader, opts)
+ } else {
+ dumpLog(reader, opts)
+ }
return err
}
-// getLogs returns the logs of a container from the log file
-// log file is created when the container is started/ran
-func getLogs(container *libpod.Container, logChan chan string, opts logOptions) error {
- defer close(logChan)
-
- seekInfo := &tail.SeekInfo{Offset: 0, Whence: 0}
+func followLog(reader *bufio.Reader, opts logOptions) error {
+ var cacheOutput []string
+ firstPass := false
if opts.tail > 0 {
- // seek to correct position in log files
- seekInfo.Offset = int64(opts.tail)
- seekInfo.Whence = 2
+ firstPass = true
}
-
- t, err := tail.TailFile(container.LogPath(), tail.Config{Follow: opts.follow, ReOpen: false, Location: seekInfo})
- for line := range t.Lines {
- if since, err := logSinceTime(opts.sinceTime, line.Text); err != nil || !since {
+ // We need to read the entire file in here until we reach EOF
+ // and then dump it out in the case that the user also wants
+ // tail output
+ for {
+ line, err := reader.ReadString('\n')
+ if err == io.EOF && opts.follow {
+ if firstPass {
+ firstPass = false
+ cacheLen := int64(len(cacheOutput))
+ start := int64(0)
+ if cacheLen > int64(opts.tail) {
+ start = cacheLen - int64(opts.tail)
+ }
+ for i := start; i < cacheLen; i++ {
+ printLine(cacheOutput[i], opts)
+ }
+ continue
+ }
+ time.Sleep(1 * time.Second)
continue
}
- logMessage := line.Text[secondSpaceIndex(line.Text):]
- logChan <- logMessage
+ // exits
+ if err != nil {
+ break
+ }
+ if firstPass {
+ cacheOutput = append(cacheOutput, line)
+ continue
+ }
+ printLine(line, opts)
}
- return err
+ return nil
}
-func printLogs(logs chan string) {
- for line := range logs {
- fmt.Println(line)
+func dumpLog(reader *bufio.Reader, opts logOptions) error {
+ output := readLog(reader, opts)
+ for _, line := range output {
+ printLine(line, opts)
}
+
+ return nil
+}
+
+func readLog(reader *bufio.Reader, opts logOptions) []string {
+ var output []string
+ for {
+ line, err := reader.ReadString('\n')
+ if err != nil {
+ break
+ }
+ output = append(output, line)
+ }
+ start := 0
+ if opts.tail > 0 {
+ if len(output) > int(opts.tail) {
+ start = len(output) - int(opts.tail)
+ }
+ }
+ return output[start:]
+}
+
+func printLine(line string, opts logOptions) {
+ start := 3
+ fields := strings.Fields(line)
+ if opts.showTimestamps || !isStringTimestamp(fields[0]) {
+ start = 0
+ }
+ if opts.sinceTime.IsZero() || logSinceTime(opts.sinceTime, fields[0]) {
+ output := strings.Join(fields[start:], " ")
+ fmt.Printf("%s\n", output)
+ }
+}
+
+func isStringTimestamp(t string) bool {
+ _, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", t)
+ if err != nil {
+ return false
+ }
+ return true
}
// returns true if the time stamps of the logs are equal to or after the
// timestamp comparing to
-func logSinceTime(sinceTime time.Time, logStr string) (bool, error) {
+func logSinceTime(sinceTime time.Time, logStr string) bool {
timestamp := strings.Split(logStr, " ")[0]
logTime, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", timestamp)
if err != nil {
- return false, err
+ return false
}
- return logTime.After(sinceTime) || logTime.Equal(sinceTime), nil
+ return logTime.After(sinceTime) || logTime.Equal(sinceTime)
}
// secondSpaceIndex returns the index of the second space in a string
@@ -158,3 +231,25 @@ func secondSpaceIndex(line string) int {
}
return index
}
+
+// parseInputTime takes the users input and to determine if it is valid and
+// returns a time format and error. The input is compared to known time formats
+// or a duration which implies no-duration
+func parseInputTime(inputTime string) (time.Time, error) {
+ timeFormats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05", "2006-01-02T15:04:05.999999999",
+ "2006-01-02Z07:00", "2006-01-02"}
+ // iterate the supported time formats
+ for _, tf := range timeFormats {
+ t, err := time.Parse(tf, inputTime)
+ if err == nil {
+ return t, nil
+ }
+ }
+
+ // input might be a duration
+ duration, err := time.ParseDuration(inputTime)
+ if err != nil {
+ return time.Time{}, errors.Errorf("unable to interpret time value")
+ }
+ return time.Now().Add(-duration), nil
+}
diff --git a/completions/bash/podman b/completions/bash/podman
index 925f8fca5..905792a9b 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -923,6 +923,8 @@ _podman_logs() {
-f
--latest
-l
+ --timestamps
+ -t
"
_complete_ "$options_with_args" "$boolean_options"
diff --git a/contrib/spec/podman.spec b/contrib/spec/podman.spec
index 977a62590..15d841a6d 100644
--- a/contrib/spec/podman.spec
+++ b/contrib/spec/podman.spec
@@ -124,7 +124,6 @@ Provides: bundled(golang(github.com/gorilla/mux)) = v1.3.0
Provides: bundled(golang(github.com/hashicorp/errwrap)) = 7554cd9344cec97297fa6649b055a8c98c2a1e55
Provides: bundled(golang(github.com/hashicorp/golang-lru)) = 0a025b7e63adc15a622f29b0b2c4c3848243bbf6
Provides: bundled(golang(github.com/hashicorp/go-multierror)) = 83588e72410abfbe4df460eeb6f30841ae47d4c4
-Provides: bundled(golang(github.com/hpcloud/tail)) = v1.0.0
Provides: bundled(golang(github.com/imdario/mergo)) = 0.2.2
Provides: bundled(golang(github.com/juju/ratelimit)) = 5b9ff866471762aa2ab2dced63c9fb6f53921342
Provides: bundled(golang(github.com/kr/pty)) = v1.0.0
diff --git a/docs/podman-logs.1.md b/docs/podman-logs.1.md
index 414b98b8c..98dc7b180 100644
--- a/docs/podman-logs.1.md
+++ b/docs/podman-logs.1.md
@@ -9,7 +9,9 @@ podman logs - Fetch the logs of a container
**podman** **logs** [*options* [...]] container
## DESCRIPTION
-The podman logs command batch-retrieves whatever logs are present for a container at the time of execution. This does not guarantee execution order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs
+The podman logs command batch-retrieves whatever logs are present for a container at the time of execution.
+This does not guarantee execution order when combined with podman run (i.e. your run may not have generated
+any logs at the time you execute podman logs
## OPTIONS
@@ -19,14 +21,24 @@ Follow log output. Default is false
**--since=TIMESTAMP**
-Show logs since TIMESTAMP
+Show logs since TIMESTAMP. The --since option can be Unix timestamps, date formatted timestamps, or Go duration
+strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
+time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
+and 2006-01-02.
**--tail=LINES**
-Output the specified number of LINES at the end of the logs. LINES must be a positive integer. Defaults to 0, which prints all lines
+Output the specified number of LINES at the end of the logs. LINES must be a positive integer. Defaults to 0,
+which prints all lines
+
+**--timestamps, -t**
+
+Show timestamps in the log outputs. The default is false
## EXAMPLE
+To view a container's logs:
+```
podman logs b3f2436bdb978c1d33b1387afb5d7ba7e3243ed2ce908db431ac0069da86cb45
2017/08/07 10:16:21 Seeked /var/log/crio/pods/eb296bd56fab164d4d3cc46e5776b54414af3bf543d138746b25832c816b933b/c49f49788da14f776b7aa93fb97a2a71f9912f4e5a3e30397fca7dfe0ee0367b.log - &{Offset:0 Whence:0}
@@ -39,13 +51,18 @@ podman logs b3f2436bdb978c1d33b1387afb5d7ba7e3243ed2ce908db431ac0069da86cb45
1:M 07 Aug 14:10:09.056 * Running mode=standalone, port=6379.
1:M 07 Aug 14:10:09.056 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 07 Aug 14:10:09.056 # Server initialized
+```
-
+To view only the last two lines in container's log:
+```
podman logs --tail 2 b3f2436bdb97
1:M 07 Aug 14:10:09.056 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 07 Aug 14:10:09.056 # Server initialized
+```
+To view a containers logs since a certain time:
+```
podman logs 224c375f27cd --since 2017-08-07T10:10:09.055837383-04:00 myserver
1:M 07 Aug 14:10:09.055 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
@@ -53,9 +70,19 @@ podman logs 224c375f27cd --since 2017-08-07T10:10:09.055837383-04:00 myserver
1:M 07 Aug 14:10:09.056 * Running mode=standalone, port=6379.
1:M 07 Aug 14:10:09.056 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 07 Aug 14:10:09.056 # Server initialized
+```
+
+To view a container's logs generated in the last 10 minutes:
+```
+podman logs 224c375f27cd --since 10m myserver
+
+1:M 07 Aug 14:10:09.055 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
+1:M 07 Aug 14:10:09.055 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
+```
## SEE ALSO
podman(1)
## HISTORY
August 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
+February 2018, Updated by Brent Baude <bbaude@redhat.com>
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index d8fc440c0..74e31016c 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -28,33 +28,82 @@ var _ = Describe("Podman logs", func() {
})
+ //sudo bin/podman run -it --rm fedora-minimal bash -c 'for a in `seq 5`; do echo hello; done'
It("podman logs for container", func() {
- _, ec, cid := podmanTest.RunLsContainer("")
- Expect(ec).To(Equal(0))
+ podmanTest.RestoreArtifact(fedoraMinimal)
+ logc := podmanTest.Podman([]string{"run", "-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(4))
})
- It("podman logs tail three lines", func() {
- Skip("Tail is not working correctly")
- _, ec, cid := podmanTest.RunLsContainer("")
- Expect(ec).To(Equal(0))
+ It("podman logs tail two lines", func() {
+ podmanTest.RestoreArtifact(fedoraMinimal)
+ logc := podmanTest.Podman([]string{"run", "-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", "3", cid})
+ results := podmanTest.Podman([]string{"logs", "--tail", "2", cid})
results.WaitWithDefaultTimeout()
Expect(results.ExitCode()).To(Equal(0))
Expect(len(results.OutputToStringArray())).To(Equal(3))
})
- It("podman logs since a given time", func() {
- _, ec, cid := podmanTest.RunLsContainer("")
- Expect(ec).To(Equal(0))
+ It("podman logs tail 99 lines", func() {
+ podmanTest.RestoreArtifact(fedoraMinimal)
+ logc := podmanTest.Podman([]string{"run", "-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(4))
+ })
+
+ It("podman logs tail 2 lines with timestamps", func() {
+ podmanTest.RestoreArtifact(fedoraMinimal)
+ logc := podmanTest.Podman([]string{"run", "-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(3))
+ })
+
+ It("podman logs latest with since time", func() {
+ podmanTest.RestoreArtifact(fedoraMinimal)
+ logc := podmanTest.Podman([]string{"run", "-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(4))
})
+ It("podman logs latest with since duration", func() {
+ podmanTest.RestoreArtifact(fedoraMinimal)
+ logc := podmanTest.Podman([]string{"run", "-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(4))
+ })
})
diff --git a/vendor.conf b/vendor.conf
index ea659eab8..7df5040ff 100644
--- a/vendor.conf
+++ b/vendor.conf
@@ -69,7 +69,6 @@ github.com/godbus/dbus a389bdde4dd695d414e47b755e95e72b7826432c
github.com/urfave/cli 39908eb08fee7c10d842622a114a5c133fb0a3c6
github.com/vbatts/tar-split v0.10.2
github.com/renstrom/dedent v1.0.0
-github.com/hpcloud/tail v1.0.0
gopkg.in/fsnotify.v1 v1.4.2
gopkg.in/tomb.v1 v1
github.com/fatih/camelcase f6a740d52f961c60348ebb109adde9f4635d7540
diff --git a/vendor/github.com/hpcloud/tail/LICENSE.txt b/vendor/github.com/hpcloud/tail/LICENSE.txt
deleted file mode 100644
index 818d802a5..000000000
--- a/vendor/github.com/hpcloud/tail/LICENSE.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-# The MIT License (MIT)
-
-# © Copyright 2015 Hewlett Packard Enterprise Development LP
-Copyright (c) 2014 ActiveState
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/vendor/github.com/hpcloud/tail/README.md b/vendor/github.com/hpcloud/tail/README.md
deleted file mode 100644
index fb7fbc26c..000000000
--- a/vendor/github.com/hpcloud/tail/README.md
+++ /dev/null
@@ -1,28 +0,0 @@
-[![Build Status](https://travis-ci.org/hpcloud/tail.svg)](https://travis-ci.org/hpcloud/tail)
-[![Build status](https://ci.appveyor.com/api/projects/status/kohpsf3rvhjhrox6?svg=true)](https://ci.appveyor.com/project/HelionCloudFoundry/tail)
-
-# Go package for tail-ing files
-
-A Go package striving to emulate the features of the BSD `tail` program.
-
-```Go
-t, err := tail.TailFile("/var/log/nginx.log", tail.Config{Follow: true})
-for line := range t.Lines {
- fmt.Println(line.Text)
-}
-```
-
-See [API documentation](http://godoc.org/github.com/hpcloud/tail).
-
-## Log rotation
-
-Tail comes with full support for truncation/move detection as it is
-designed to work with log rotation tools.
-
-## Installing
-
- go get github.com/hpcloud/tail/...
-
-## Windows support
-
-This package [needs assistance](https://github.com/hpcloud/tail/labels/Windows) for full Windows support.
diff --git a/vendor/github.com/hpcloud/tail/ratelimiter/leakybucket.go b/vendor/github.com/hpcloud/tail/ratelimiter/leakybucket.go
deleted file mode 100644
index 358b69e7f..000000000
--- a/vendor/github.com/hpcloud/tail/ratelimiter/leakybucket.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Package ratelimiter implements the Leaky Bucket ratelimiting algorithm with memcached and in-memory backends.
-package ratelimiter
-
-import (
- "time"
-)
-
-type LeakyBucket struct {
- Size uint16
- Fill float64
- LeakInterval time.Duration // time.Duration for 1 unit of size to leak
- Lastupdate time.Time
- Now func() time.Time
-}
-
-func NewLeakyBucket(size uint16, leakInterval time.Duration) *LeakyBucket {
- bucket := LeakyBucket{
- Size: size,
- Fill: 0,
- LeakInterval: leakInterval,
- Now: time.Now,
- Lastupdate: time.Now(),
- }
-
- return &bucket
-}
-
-func (b *LeakyBucket) updateFill() {
- now := b.Now()
- if b.Fill > 0 {
- elapsed := now.Sub(b.Lastupdate)
-
- b.Fill -= float64(elapsed) / float64(b.LeakInterval)
- if b.Fill < 0 {
- b.Fill = 0
- }
- }
- b.Lastupdate = now
-}
-
-func (b *LeakyBucket) Pour(amount uint16) bool {
- b.updateFill()
-
- var newfill float64 = b.Fill + float64(amount)
-
- if newfill > float64(b.Size) {
- return false
- }
-
- b.Fill = newfill
-
- return true
-}
-
-// The time at which this bucket will be completely drained
-func (b *LeakyBucket) DrainedAt() time.Time {
- return b.Lastupdate.Add(time.Duration(b.Fill * float64(b.LeakInterval)))
-}
-
-// The duration until this bucket is completely drained
-func (b *LeakyBucket) TimeToDrain() time.Duration {
- return b.DrainedAt().Sub(b.Now())
-}
-
-func (b *LeakyBucket) TimeSinceLastUpdate() time.Duration {
- return b.Now().Sub(b.Lastupdate)
-}
-
-type LeakyBucketSer struct {
- Size uint16
- Fill float64
- LeakInterval time.Duration // time.Duration for 1 unit of size to leak
- Lastupdate time.Time
-}
-
-func (b *LeakyBucket) Serialise() *LeakyBucketSer {
- bucket := LeakyBucketSer{
- Size: b.Size,
- Fill: b.Fill,
- LeakInterval: b.LeakInterval,
- Lastupdate: b.Lastupdate,
- }
-
- return &bucket
-}
-
-func (b *LeakyBucketSer) DeSerialise() *LeakyBucket {
- bucket := LeakyBucket{
- Size: b.Size,
- Fill: b.Fill,
- LeakInterval: b.LeakInterval,
- Lastupdate: b.Lastupdate,
- Now: time.Now,
- }
-
- return &bucket
-}
diff --git a/vendor/github.com/hpcloud/tail/ratelimiter/memory.go b/vendor/github.com/hpcloud/tail/ratelimiter/memory.go
deleted file mode 100644
index 8f6a5784a..000000000
--- a/vendor/github.com/hpcloud/tail/ratelimiter/memory.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package ratelimiter
-
-import (
- "errors"
- "time"
-)
-
-const GC_SIZE int = 100
-
-type Memory struct {
- store map[string]LeakyBucket
- lastGCCollected time.Time
-}
-
-func NewMemory() *Memory {
- m := new(Memory)
- m.store = make(map[string]LeakyBucket)
- m.lastGCCollected = time.Now()
- return m
-}
-
-func (m *Memory) GetBucketFor(key string) (*LeakyBucket, error) {
-
- bucket, ok := m.store[key]
- if !ok {
- return nil, errors.New("miss")
- }
-
- return &bucket, nil
-}
-
-func (m *Memory) SetBucketFor(key string, bucket LeakyBucket) error {
-
- if len(m.store) > GC_SIZE {
- m.GarbageCollect()
- }
-
- m.store[key] = bucket
-
- return nil
-}
-
-func (m *Memory) GarbageCollect() {
- now := time.Now()
-
- // rate limit GC to once per minute
- if now.Add(60*time.Second).Unix() > m.lastGCCollected.Unix() {
-
- for key, bucket := range m.store {
- // if the bucket is drained, then GC
- if bucket.DrainedAt().Unix() > now.Unix() {
- delete(m.store, key)
- }
- }
-
- m.lastGCCollected = now
- }
-}
diff --git a/vendor/github.com/hpcloud/tail/ratelimiter/storage.go b/vendor/github.com/hpcloud/tail/ratelimiter/storage.go
deleted file mode 100644
index 89b2fe882..000000000
--- a/vendor/github.com/hpcloud/tail/ratelimiter/storage.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package ratelimiter
-
-type Storage interface {
- GetBucketFor(string) (*LeakyBucket, error)
- SetBucketFor(string, LeakyBucket) error
-}
diff --git a/vendor/github.com/hpcloud/tail/tail.go b/vendor/github.com/hpcloud/tail/tail.go
deleted file mode 100644
index 2d252d605..000000000
--- a/vendor/github.com/hpcloud/tail/tail.go
+++ /dev/null
@@ -1,438 +0,0 @@
-// Copyright (c) 2015 HPE Software Inc. All rights reserved.
-// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
-
-package tail
-
-import (
- "bufio"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "os"
- "strings"
- "sync"
- "time"
-
- "github.com/hpcloud/tail/ratelimiter"
- "github.com/hpcloud/tail/util"
- "github.com/hpcloud/tail/watch"
- "gopkg.in/tomb.v1"
-)
-
-var (
- ErrStop = fmt.Errorf("tail should now stop")
-)
-
-type Line struct {
- Text string
- Time time.Time
- Err error // Error from tail
-}
-
-// NewLine returns a Line with present time.
-func NewLine(text string) *Line {
- return &Line{text, time.Now(), nil}
-}
-
-// SeekInfo represents arguments to `os.Seek`
-type SeekInfo struct {
- Offset int64
- Whence int // os.SEEK_*
-}
-
-type logger interface {
- Fatal(v ...interface{})
- Fatalf(format string, v ...interface{})
- Fatalln(v ...interface{})
- Panic(v ...interface{})
- Panicf(format string, v ...interface{})
- Panicln(v ...interface{})
- Print(v ...interface{})
- Printf(format string, v ...interface{})
- Println(v ...interface{})
-}
-
-// Config is used to specify how a file must be tailed.
-type Config struct {
- // File-specifc
- Location *SeekInfo // Seek to this location before tailing
- ReOpen bool // Reopen recreated files (tail -F)
- MustExist bool // Fail early if the file does not exist
- Poll bool // Poll for file changes instead of using inotify
- Pipe bool // Is a named pipe (mkfifo)
- RateLimiter *ratelimiter.LeakyBucket
-
- // Generic IO
- Follow bool // Continue looking for new lines (tail -f)
- MaxLineSize int // If non-zero, split longer lines into multiple lines
-
- // Logger, when nil, is set to tail.DefaultLogger
- // To disable logging: set field to tail.DiscardingLogger
- Logger logger
-}
-
-type Tail struct {
- Filename string
- Lines chan *Line
- Config
-
- file *os.File
- reader *bufio.Reader
-
- watcher watch.FileWatcher
- changes *watch.FileChanges
-
- tomb.Tomb // provides: Done, Kill, Dying
-
- lk sync.Mutex
-}
-
-var (
- // DefaultLogger is used when Config.Logger == nil
- DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
- // DiscardingLogger can be used to disable logging output
- DiscardingLogger = log.New(ioutil.Discard, "", 0)
-)
-
-// TailFile begins tailing the file. Output stream is made available
-// via the `Tail.Lines` channel. To handle errors during tailing,
-// invoke the `Wait` or `Err` method after finishing reading from the
-// `Lines` channel.
-func TailFile(filename string, config Config) (*Tail, error) {
- if config.ReOpen && !config.Follow {
- util.Fatal("cannot set ReOpen without Follow.")
- }
-
- t := &Tail{
- Filename: filename,
- Lines: make(chan *Line),
- Config: config,
- }
-
- // when Logger was not specified in config, use default logger
- if t.Logger == nil {
- t.Logger = log.New(os.Stderr, "", log.LstdFlags)
- }
-
- if t.Poll {
- t.watcher = watch.NewPollingFileWatcher(filename)
- } else {
- t.watcher = watch.NewInotifyFileWatcher(filename)
- }
-
- if t.MustExist {
- var err error
- t.file, err = OpenFile(t.Filename)
- if err != nil {
- return nil, err
- }
- }
-
- go t.tailFileSync()
-
- return t, nil
-}
-
-// Return the file's current position, like stdio's ftell().
-// But this value is not very accurate.
-// it may readed one line in the chan(tail.Lines),
-// so it may lost one line.
-func (tail *Tail) Tell() (offset int64, err error) {
- if tail.file == nil {
- return
- }
- offset, err = tail.file.Seek(0, os.SEEK_CUR)
- if err != nil {
- return
- }
-
- tail.lk.Lock()
- defer tail.lk.Unlock()
- if tail.reader == nil {
- return
- }
-
- offset -= int64(tail.reader.Buffered())
- return
-}
-
-// Stop stops the tailing activity.
-func (tail *Tail) Stop() error {
- tail.Kill(nil)
- return tail.Wait()
-}
-
-// StopAtEOF stops tailing as soon as the end of the file is reached.
-func (tail *Tail) StopAtEOF() error {
- tail.Kill(errStopAtEOF)
- return tail.Wait()
-}
-
-var errStopAtEOF = errors.New("tail: stop at eof")
-
-func (tail *Tail) close() {
- close(tail.Lines)
- tail.closeFile()
-}
-
-func (tail *Tail) closeFile() {
- if tail.file != nil {
- tail.file.Close()
- tail.file = nil
- }
-}
-
-func (tail *Tail) reopen() error {
- tail.closeFile()
- for {
- var err error
- tail.file, err = OpenFile(tail.Filename)
- if err != nil {
- if os.IsNotExist(err) {
- tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
- if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
- if err == tomb.ErrDying {
- return err
- }
- return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
- }
- continue
- }
- return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
- }
- break
- }
- return nil
-}
-
-func (tail *Tail) readLine() (string, error) {
- tail.lk.Lock()
- line, err := tail.reader.ReadString('\n')
- tail.lk.Unlock()
- if err != nil {
- // Note ReadString "returns the data read before the error" in
- // case of an error, including EOF, so we return it as is. The
- // caller is expected to process it if err is EOF.
- return line, err
- }
-
- line = strings.TrimRight(line, "\n")
-
- return line, err
-}
-
-func (tail *Tail) tailFileSync() {
- defer tail.Done()
- defer tail.close()
-
- if !tail.MustExist {
- // deferred first open.
- err := tail.reopen()
- if err != nil {
- if err != tomb.ErrDying {
- tail.Kill(err)
- }
- return
- }
- }
-
- // Seek to requested location on first open of the file.
- if tail.Location != nil {
- _, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
- tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location)
- if err != nil {
- tail.Killf("Seek error on %s: %s", tail.Filename, err)
- return
- }
- }
-
- tail.openReader()
-
- var offset int64 = 0
- var err error
-
- // Read line by line.
- for {
- // do not seek in named pipes
- if !tail.Pipe {
- // grab the position in case we need to back up in the event of a half-line
- offset, err = tail.Tell()
- if err != nil {
- tail.Kill(err)
- return
- }
- }
-
- line, err := tail.readLine()
-
- // Process `line` even if err is EOF.
- if err == nil {
- cooloff := !tail.sendLine(line)
- if cooloff {
- // Wait a second before seeking till the end of
- // file when rate limit is reached.
- msg := fmt.Sprintf(
- "Too much log activity; waiting a second " +
- "before resuming tailing")
- tail.Lines <- &Line{msg, time.Now(), fmt.Errorf(msg)}
- select {
- case <-time.After(time.Second):
- case <-tail.Dying():
- return
- }
- if err := tail.seekEnd(); err != nil {
- tail.Kill(err)
- return
- }
- }
- } else if err == io.EOF {
- if !tail.Follow {
- if line != "" {
- tail.sendLine(line)
- }
- return
- }
-
- if tail.Follow && line != "" {
- // this has the potential to never return the last line if
- // it's not followed by a newline; seems a fair trade here
- err := tail.seekTo(SeekInfo{Offset: offset, Whence: 0})
- if err != nil {
- tail.Kill(err)
- return
- }
- }
-
- // When EOF is reached, wait for more data to become
- // available. Wait strategy is based on the `tail.watcher`
- // implementation (inotify or polling).
- err := tail.waitForChanges()
- if err != nil {
- if err != ErrStop {
- tail.Kill(err)
- }
- return
- }
- } else {
- // non-EOF error
- tail.Killf("Error reading %s: %s", tail.Filename, err)
- return
- }
-
- select {
- case <-tail.Dying():
- if tail.Err() == errStopAtEOF {
- continue
- }
- return
- default:
- }
- }
-}
-
-// waitForChanges waits until the file has been appended, deleted,
-// moved or truncated. When moved or deleted - the file will be
-// reopened if ReOpen is true. Truncated files are always reopened.
-func (tail *Tail) waitForChanges() error {
- if tail.changes == nil {
- pos, err := tail.file.Seek(0, os.SEEK_CUR)
- if err != nil {
- return err
- }
- tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos)
- if err != nil {
- return err
- }
- }
-
- select {
- case <-tail.changes.Modified:
- return nil
- case <-tail.changes.Deleted:
- tail.changes = nil
- if tail.ReOpen {
- // XXX: we must not log from a library.
- tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
- if err := tail.reopen(); err != nil {
- return err
- }
- tail.Logger.Printf("Successfully reopened %s", tail.Filename)
- tail.openReader()
- return nil
- } else {
- tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
- return ErrStop
- }
- case <-tail.changes.Truncated:
- // Always reopen truncated files (Follow is true)
- tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
- if err := tail.reopen(); err != nil {
- return err
- }
- tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
- tail.openReader()
- return nil
- case <-tail.Dying():
- return ErrStop
- }
- panic("unreachable")
-}
-
-func (tail *Tail) openReader() {
- if tail.MaxLineSize > 0 {
- // add 2 to account for newline characters
- tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
- } else {
- tail.reader = bufio.NewReader(tail.file)
- }
-}
-
-func (tail *Tail) seekEnd() error {
- return tail.seekTo(SeekInfo{Offset: 0, Whence: os.SEEK_END})
-}
-
-func (tail *Tail) seekTo(pos SeekInfo) error {
- _, err := tail.file.Seek(pos.Offset, pos.Whence)
- if err != nil {
- return fmt.Errorf("Seek error on %s: %s", tail.Filename, err)
- }
- // Reset the read buffer whenever the file is re-seek'ed
- tail.reader.Reset(tail.file)
- return nil
-}
-
-// sendLine sends the line(s) to Lines channel, splitting longer lines
-// if necessary. Return false if rate limit is reached.
-func (tail *Tail) sendLine(line string) bool {
- now := time.Now()
- lines := []string{line}
-
- // Split longer lines
- if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
- lines = util.PartitionString(line, tail.MaxLineSize)
- }
-
- for _, line := range lines {
- tail.Lines <- &Line{line, now, nil}
- }
-
- if tail.Config.RateLimiter != nil {
- ok := tail.Config.RateLimiter.Pour(uint16(len(lines)))
- if !ok {
- tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.\n",
- tail.Filename)
- return false
- }
- }
-
- return true
-}
-
-// Cleanup removes inotify watches added by the tail package. This function is
-// meant to be invoked from a process's exit handler. Linux kernel may not
-// automatically remove inotify watches after the process exits.
-func (tail *Tail) Cleanup() {
- watch.Cleanup(tail.Filename)
-}
diff --git a/vendor/github.com/hpcloud/tail/tail_posix.go b/vendor/github.com/hpcloud/tail/tail_posix.go
deleted file mode 100644
index bc4dc3357..000000000
--- a/vendor/github.com/hpcloud/tail/tail_posix.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// +build linux darwin freebsd netbsd openbsd
-
-package tail
-
-import (
- "os"
-)
-
-func OpenFile(name string) (file *os.File, err error) {
- return os.Open(name)
-}
diff --git a/vendor/github.com/hpcloud/tail/tail_windows.go b/vendor/github.com/hpcloud/tail/tail_windows.go
deleted file mode 100644
index ef2cfca1b..000000000
--- a/vendor/github.com/hpcloud/tail/tail_windows.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// +build windows
-
-package tail
-
-import (
- "github.com/hpcloud/tail/winfile"
- "os"
-)
-
-func OpenFile(name string) (file *os.File, err error) {
- return winfile.OpenFile(name, os.O_RDONLY, 0)
-}
diff --git a/vendor/github.com/hpcloud/tail/util/util.go b/vendor/github.com/hpcloud/tail/util/util.go
deleted file mode 100644
index 54151fe39..000000000
--- a/vendor/github.com/hpcloud/tail/util/util.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2015 HPE Software Inc. All rights reserved.
-// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
-
-package util
-
-import (
- "fmt"
- "log"
- "os"
- "runtime/debug"
-)
-
-type Logger struct {
- *log.Logger
-}
-
-var LOGGER = &Logger{log.New(os.Stderr, "", log.LstdFlags)}
-
-// fatal is like panic except it displays only the current goroutine's stack.
-func Fatal(format string, v ...interface{}) {
- // https://github.com/hpcloud/log/blob/master/log.go#L45
- LOGGER.Output(2, fmt.Sprintf("FATAL -- "+format, v...)+"\n"+string(debug.Stack()))
- os.Exit(1)
-}
-
-// partitionString partitions the string into chunks of given size,
-// with the last chunk of variable size.
-func PartitionString(s string, chunkSize int) []string {
- if chunkSize <= 0 {
- panic("invalid chunkSize")
- }
- length := len(s)
- chunks := 1 + length/chunkSize
- start := 0
- end := chunkSize
- parts := make([]string, 0, chunks)
- for {
- if end > length {
- end = length
- }
- parts = append(parts, s[start:end])
- if end == length {
- break
- }
- start, end = end, end+chunkSize
- }
- return parts
-}
diff --git a/vendor/github.com/hpcloud/tail/watch/filechanges.go b/vendor/github.com/hpcloud/tail/watch/filechanges.go
deleted file mode 100644
index 3ce5dcecb..000000000
--- a/vendor/github.com/hpcloud/tail/watch/filechanges.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package watch
-
-type FileChanges struct {
- Modified chan bool // Channel to get notified of modifications
- Truncated chan bool // Channel to get notified of truncations
- Deleted chan bool // Channel to get notified of deletions/renames
-}
-
-func NewFileChanges() *FileChanges {
- return &FileChanges{
- make(chan bool), make(chan bool), make(chan bool)}
-}
-
-func (fc *FileChanges) NotifyModified() {
- sendOnlyIfEmpty(fc.Modified)
-}
-
-func (fc *FileChanges) NotifyTruncated() {
- sendOnlyIfEmpty(fc.Truncated)
-}
-
-func (fc *FileChanges) NotifyDeleted() {
- sendOnlyIfEmpty(fc.Deleted)
-}
-
-// sendOnlyIfEmpty sends on a bool channel only if the channel has no
-// backlog to be read by other goroutines. This concurrency pattern
-// can be used to notify other goroutines if and only if they are
-// looking for it (i.e., subsequent notifications can be compressed
-// into one).
-func sendOnlyIfEmpty(ch chan bool) {
- select {
- case ch <- true:
- default:
- }
-}
diff --git a/vendor/github.com/hpcloud/tail/watch/inotify.go b/vendor/github.com/hpcloud/tail/watch/inotify.go
deleted file mode 100644
index 4478f1e1a..000000000
--- a/vendor/github.com/hpcloud/tail/watch/inotify.go
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright (c) 2015 HPE Software Inc. All rights reserved.
-// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
-
-package watch
-
-import (
- "fmt"
- "os"
- "path/filepath"
-
- "github.com/hpcloud/tail/util"
-
- "gopkg.in/fsnotify.v1"
- "gopkg.in/tomb.v1"
-)
-
-// InotifyFileWatcher uses inotify to monitor file changes.
-type InotifyFileWatcher struct {
- Filename string
- Size int64
-}
-
-func NewInotifyFileWatcher(filename string) *InotifyFileWatcher {
- fw := &InotifyFileWatcher{filepath.Clean(filename), 0}
- return fw
-}
-
-func (fw *InotifyFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
- err := WatchCreate(fw.Filename)
- if err != nil {
- return err
- }
- defer RemoveWatchCreate(fw.Filename)
-
- // Do a real check now as the file might have been created before
- // calling `WatchFlags` above.
- if _, err = os.Stat(fw.Filename); !os.IsNotExist(err) {
- // file exists, or stat returned an error.
- return err
- }
-
- events := Events(fw.Filename)
-
- for {
- select {
- case evt, ok := <-events:
- if !ok {
- return fmt.Errorf("inotify watcher has been closed")
- }
- evtName, err := filepath.Abs(evt.Name)
- if err != nil {
- return err
- }
- fwFilename, err := filepath.Abs(fw.Filename)
- if err != nil {
- return err
- }
- if evtName == fwFilename {
- return nil
- }
- case <-t.Dying():
- return tomb.ErrDying
- }
- }
- panic("unreachable")
-}
-
-func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) {
- err := Watch(fw.Filename)
- if err != nil {
- return nil, err
- }
-
- changes := NewFileChanges()
- fw.Size = pos
-
- go func() {
- defer RemoveWatch(fw.Filename)
-
- events := Events(fw.Filename)
-
- for {
- prevSize := fw.Size
-
- var evt fsnotify.Event
- var ok bool
-
- select {
- case evt, ok = <-events:
- if !ok {
- return
- }
- case <-t.Dying():
- return
- }
-
- switch {
- case evt.Op&fsnotify.Remove == fsnotify.Remove:
- fallthrough
-
- case evt.Op&fsnotify.Rename == fsnotify.Rename:
- changes.NotifyDeleted()
- return
-
- case evt.Op&fsnotify.Write == fsnotify.Write:
- fi, err := os.Stat(fw.Filename)
- if err != nil {
- if os.IsNotExist(err) {
- changes.NotifyDeleted()
- return
- }
- // XXX: report this error back to the user
- util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
- }
- fw.Size = fi.Size()
-
- if prevSize > 0 && prevSize > fw.Size {
- changes.NotifyTruncated()
- } else {
- changes.NotifyModified()
- }
- prevSize = fw.Size
- }
- }
- }()
-
- return changes, nil
-}
diff --git a/vendor/github.com/hpcloud/tail/watch/inotify_tracker.go b/vendor/github.com/hpcloud/tail/watch/inotify_tracker.go
deleted file mode 100644
index 03be4275c..000000000
--- a/vendor/github.com/hpcloud/tail/watch/inotify_tracker.go
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright (c) 2015 HPE Software Inc. All rights reserved.
-// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
-
-package watch
-
-import (
- "log"
- "os"
- "path/filepath"
- "sync"
- "syscall"
-
- "github.com/hpcloud/tail/util"
-
- "gopkg.in/fsnotify.v1"
-)
-
-type InotifyTracker struct {
- mux sync.Mutex
- watcher *fsnotify.Watcher
- chans map[string]chan fsnotify.Event
- done map[string]chan bool
- watchNums map[string]int
- watch chan *watchInfo
- remove chan *watchInfo
- error chan error
-}
-
-type watchInfo struct {
- op fsnotify.Op
- fname string
-}
-
-func (this *watchInfo) isCreate() bool {
- return this.op == fsnotify.Create
-}
-
-var (
- // globally shared InotifyTracker; ensures only one fsnotify.Watcher is used
- shared *InotifyTracker
-
- // these are used to ensure the shared InotifyTracker is run exactly once
- once = sync.Once{}
- goRun = func() {
- shared = &InotifyTracker{
- mux: sync.Mutex{},
- chans: make(map[string]chan fsnotify.Event),
- done: make(map[string]chan bool),
- watchNums: make(map[string]int),
- watch: make(chan *watchInfo),
- remove: make(chan *watchInfo),
- error: make(chan error),
- }
- go shared.run()
- }
-
- logger = log.New(os.Stderr, "", log.LstdFlags)
-)
-
-// Watch signals the run goroutine to begin watching the input filename
-func Watch(fname string) error {
- return watch(&watchInfo{
- fname: fname,
- })
-}
-
-// Watch create signals the run goroutine to begin watching the input filename
-// if call the WatchCreate function, don't call the Cleanup, call the RemoveWatchCreate
-func WatchCreate(fname string) error {
- return watch(&watchInfo{
- op: fsnotify.Create,
- fname: fname,
- })
-}
-
-func watch(winfo *watchInfo) error {
- // start running the shared InotifyTracker if not already running
- once.Do(goRun)
-
- winfo.fname = filepath.Clean(winfo.fname)
- shared.watch <- winfo
- return <-shared.error
-}
-
-// RemoveWatch signals the run goroutine to remove the watch for the input filename
-func RemoveWatch(fname string) {
- remove(&watchInfo{
- fname: fname,
- })
-}
-
-// RemoveWatch create signals the run goroutine to remove the watch for the input filename
-func RemoveWatchCreate(fname string) {
- remove(&watchInfo{
- op: fsnotify.Create,
- fname: fname,
- })
-}
-
-func remove(winfo *watchInfo) {
- // start running the shared InotifyTracker if not already running
- once.Do(goRun)
-
- winfo.fname = filepath.Clean(winfo.fname)
- shared.mux.Lock()
- done := shared.done[winfo.fname]
- if done != nil {
- delete(shared.done, winfo.fname)
- close(done)
- }
-
- fname := winfo.fname
- if winfo.isCreate() {
- // Watch for new files to be created in the parent directory.
- fname = filepath.Dir(fname)
- }
- shared.watchNums[fname]--
- watchNum := shared.watchNums[fname]
- if watchNum == 0 {
- delete(shared.watchNums, fname)
- }
- shared.mux.Unlock()
-
- // If we were the last ones to watch this file, unsubscribe from inotify.
- // This needs to happen after releasing the lock because fsnotify waits
- // synchronously for the kernel to acknowledge the removal of the watch
- // for this file, which causes us to deadlock if we still held the lock.
- if watchNum == 0 {
- shared.watcher.Remove(fname)
- }
- shared.remove <- winfo
-}
-
-// Events returns a channel to which FileEvents corresponding to the input filename
-// will be sent. This channel will be closed when removeWatch is called on this
-// filename.
-func Events(fname string) <-chan fsnotify.Event {
- shared.mux.Lock()
- defer shared.mux.Unlock()
-
- return shared.chans[fname]
-}
-
-// Cleanup removes the watch for the input filename if necessary.
-func Cleanup(fname string) {
- RemoveWatch(fname)
-}
-
-// watchFlags calls fsnotify.WatchFlags for the input filename and flags, creating
-// a new Watcher if the previous Watcher was closed.
-func (shared *InotifyTracker) addWatch(winfo *watchInfo) error {
- shared.mux.Lock()
- defer shared.mux.Unlock()
-
- if shared.chans[winfo.fname] == nil {
- shared.chans[winfo.fname] = make(chan fsnotify.Event)
- shared.done[winfo.fname] = make(chan bool)
- }
-
- fname := winfo.fname
- if winfo.isCreate() {
- // Watch for new files to be created in the parent directory.
- fname = filepath.Dir(fname)
- }
-
- // already in inotify watch
- if shared.watchNums[fname] > 0 {
- shared.watchNums[fname]++
- if winfo.isCreate() {
- shared.watchNums[winfo.fname]++
- }
- return nil
- }
-
- err := shared.watcher.Add(fname)
- if err == nil {
- shared.watchNums[fname]++
- if winfo.isCreate() {
- shared.watchNums[winfo.fname]++
- }
- }
- return err
-}
-
-// removeWatch calls fsnotify.RemoveWatch for the input filename and closes the
-// corresponding events channel.
-func (shared *InotifyTracker) removeWatch(winfo *watchInfo) {
- shared.mux.Lock()
- defer shared.mux.Unlock()
-
- ch := shared.chans[winfo.fname]
- if ch == nil {
- return
- }
-
- delete(shared.chans, winfo.fname)
- close(ch)
-
- if !winfo.isCreate() {
- return
- }
-
- shared.watchNums[winfo.fname]--
- if shared.watchNums[winfo.fname] == 0 {
- delete(shared.watchNums, winfo.fname)
- }
-}
-
-// sendEvent sends the input event to the appropriate Tail.
-func (shared *InotifyTracker) sendEvent(event fsnotify.Event) {
- name := filepath.Clean(event.Name)
-
- shared.mux.Lock()
- ch := shared.chans[name]
- done := shared.done[name]
- shared.mux.Unlock()
-
- if ch != nil && done != nil {
- select {
- case ch <- event:
- case <-done:
- }
- }
-}
-
-// run starts the goroutine in which the shared struct reads events from its
-// Watcher's Event channel and sends the events to the appropriate Tail.
-func (shared *InotifyTracker) run() {
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- util.Fatal("failed to create Watcher")
- }
- shared.watcher = watcher
-
- for {
- select {
- case winfo := <-shared.watch:
- shared.error <- shared.addWatch(winfo)
-
- case winfo := <-shared.remove:
- shared.removeWatch(winfo)
-
- case event, open := <-shared.watcher.Events:
- if !open {
- return
- }
- shared.sendEvent(event)
-
- case err, open := <-shared.watcher.Errors:
- if !open {
- return
- } else if err != nil {
- sysErr, ok := err.(*os.SyscallError)
- if !ok || sysErr.Err != syscall.EINTR {
- logger.Printf("Error in Watcher Error channel: %s", err)
- }
- }
- }
- }
-}
diff --git a/vendor/github.com/hpcloud/tail/watch/polling.go b/vendor/github.com/hpcloud/tail/watch/polling.go
deleted file mode 100644
index 49491f21d..000000000
--- a/vendor/github.com/hpcloud/tail/watch/polling.go
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright (c) 2015 HPE Software Inc. All rights reserved.
-// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
-
-package watch
-
-import (
- "os"
- "runtime"
- "time"
-
- "github.com/hpcloud/tail/util"
- "gopkg.in/tomb.v1"
-)
-
-// PollingFileWatcher polls the file for changes.
-type PollingFileWatcher struct {
- Filename string
- Size int64
-}
-
-func NewPollingFileWatcher(filename string) *PollingFileWatcher {
- fw := &PollingFileWatcher{filename, 0}
- return fw
-}
-
-var POLL_DURATION time.Duration
-
-func (fw *PollingFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
- for {
- if _, err := os.Stat(fw.Filename); err == nil {
- return nil
- } else if !os.IsNotExist(err) {
- return err
- }
- select {
- case <-time.After(POLL_DURATION):
- continue
- case <-t.Dying():
- return tomb.ErrDying
- }
- }
- panic("unreachable")
-}
-
-func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) {
- origFi, err := os.Stat(fw.Filename)
- if err != nil {
- return nil, err
- }
-
- changes := NewFileChanges()
- var prevModTime time.Time
-
- // XXX: use tomb.Tomb to cleanly manage these goroutines. replace
- // the fatal (below) with tomb's Kill.
-
- fw.Size = pos
-
- go func() {
- prevSize := fw.Size
- for {
- select {
- case <-t.Dying():
- return
- default:
- }
-
- time.Sleep(POLL_DURATION)
- fi, err := os.Stat(fw.Filename)
- if err != nil {
- // Windows cannot delete a file if a handle is still open (tail keeps one open)
- // so it gives access denied to anything trying to read it until all handles are released.
- if os.IsNotExist(err) || (runtime.GOOS == "windows" && os.IsPermission(err)) {
- // File does not exist (has been deleted).
- changes.NotifyDeleted()
- return
- }
-
- // XXX: report this error back to the user
- util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
- }
-
- // File got moved/renamed?
- if !os.SameFile(origFi, fi) {
- changes.NotifyDeleted()
- return
- }
-
- // File got truncated?
- fw.Size = fi.Size()
- if prevSize > 0 && prevSize > fw.Size {
- changes.NotifyTruncated()
- prevSize = fw.Size
- continue
- }
- // File got bigger?
- if prevSize > 0 && prevSize < fw.Size {
- changes.NotifyModified()
- prevSize = fw.Size
- continue
- }
- prevSize = fw.Size
-
- // File was appended to (changed)?
- modTime := fi.ModTime()
- if modTime != prevModTime {
- prevModTime = modTime
- changes.NotifyModified()
- }
- }
- }()
-
- return changes, nil
-}
-
-func init() {
- POLL_DURATION = 250 * time.Millisecond
-}
diff --git a/vendor/github.com/hpcloud/tail/watch/watch.go b/vendor/github.com/hpcloud/tail/watch/watch.go
deleted file mode 100644
index 2e1783ef0..000000000
--- a/vendor/github.com/hpcloud/tail/watch/watch.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2015 HPE Software Inc. All rights reserved.
-// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
-
-package watch
-
-import "gopkg.in/tomb.v1"
-
-// FileWatcher monitors file-level events.
-type FileWatcher interface {
- // BlockUntilExists blocks until the file comes into existence.
- BlockUntilExists(*tomb.Tomb) error
-
- // ChangeEvents reports on changes to a file, be it modification,
- // deletion, renames or truncations. Returned FileChanges group of
- // channels will be closed, thus become unusable, after a deletion
- // or truncation event.
- // In order to properly report truncations, ChangeEvents requires
- // the caller to pass their current offset in the file.
- ChangeEvents(*tomb.Tomb, int64) (*FileChanges, error)
-}
diff --git a/vendor/github.com/hpcloud/tail/winfile/winfile.go b/vendor/github.com/hpcloud/tail/winfile/winfile.go
deleted file mode 100644
index aa7e7bc5d..000000000
--- a/vendor/github.com/hpcloud/tail/winfile/winfile.go
+++ /dev/null
@@ -1,92 +0,0 @@
-// +build windows
-
-package winfile
-
-import (
- "os"
- "syscall"
- "unsafe"
-)
-
-// issue also described here
-//https://codereview.appspot.com/8203043/
-
-// https://github.com/jnwhiteh/golang/blob/master/src/pkg/syscall/syscall_windows.go#L218
-func Open(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
- if len(path) == 0 {
- return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
- }
- pathp, err := syscall.UTF16PtrFromString(path)
- if err != nil {
- return syscall.InvalidHandle, err
- }
- var access uint32
- switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
- case syscall.O_RDONLY:
- access = syscall.GENERIC_READ
- case syscall.O_WRONLY:
- access = syscall.GENERIC_WRITE
- case syscall.O_RDWR:
- access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
- }
- if mode&syscall.O_CREAT != 0 {
- access |= syscall.GENERIC_WRITE
- }
- if mode&syscall.O_APPEND != 0 {
- access &^= syscall.GENERIC_WRITE
- access |= syscall.FILE_APPEND_DATA
- }
- sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
- var sa *syscall.SecurityAttributes
- if mode&syscall.O_CLOEXEC == 0 {
- sa = makeInheritSa()
- }
- var createmode uint32
- switch {
- case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
- createmode = syscall.CREATE_NEW
- case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
- createmode = syscall.CREATE_ALWAYS
- case mode&syscall.O_CREAT == syscall.O_CREAT:
- createmode = syscall.OPEN_ALWAYS
- case mode&syscall.O_TRUNC == syscall.O_TRUNC:
- createmode = syscall.TRUNCATE_EXISTING
- default:
- createmode = syscall.OPEN_EXISTING
- }
- h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, syscall.FILE_ATTRIBUTE_NORMAL, 0)
- return h, e
-}
-
-// https://github.com/jnwhiteh/golang/blob/master/src/pkg/syscall/syscall_windows.go#L211
-func makeInheritSa() *syscall.SecurityAttributes {
- var sa syscall.SecurityAttributes
- sa.Length = uint32(unsafe.Sizeof(sa))
- sa.InheritHandle = 1
- return &sa
-}
-
-// https://github.com/jnwhiteh/golang/blob/master/src/pkg/os/file_windows.go#L133
-func OpenFile(name string, flag int, perm os.FileMode) (file *os.File, err error) {
- r, e := Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
- if e != nil {
- return nil, e
- }
- return os.NewFile(uintptr(r), name), nil
-}
-
-// https://github.com/jnwhiteh/golang/blob/master/src/pkg/os/file_posix.go#L61
-func syscallMode(i os.FileMode) (o uint32) {
- o |= uint32(i.Perm())
- if i&os.ModeSetuid != 0 {
- o |= syscall.S_ISUID
- }
- if i&os.ModeSetgid != 0 {
- o |= syscall.S_ISGID
- }
- if i&os.ModeSticky != 0 {
- o |= syscall.S_ISVTX
- }
- // No mapping for Go's ModeTemporary (plan9 only).
- return
-}