Signed-off-by: wlan0 <sidharthamn@gmail.com>
| ... | ... |
@@ -19,7 +19,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
| 19 | 19 |
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
| 20 | 20 |
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
| 21 | 21 |
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
| 22 |
- tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
|
|
| 22 |
+ tail := cmd.String([]string{"-tail"}, "latest", "Number of lines to show from the end of the logs")
|
|
| 23 | 23 |
cmd.Require(flag.Exact, 1) |
| 24 | 24 |
|
| 25 | 25 |
cmd.ParseFlags(args, true) |
| ... | ... |
@@ -710,7 +710,10 @@ func (container *Container) SetHostConfig(hostConfig *runconfig.HostConfig) {
|
| 710 | 710 |
|
| 711 | 711 |
func (container *Container) getLogConfig() runconfig.LogConfig {
|
| 712 | 712 |
cfg := container.hostConfig.LogConfig |
| 713 |
- if cfg.Type != "" { // container has log driver configured
|
|
| 713 |
+ if cfg.Type != "" || len(cfg.Config) > 0 { // container has log driver configured
|
|
| 714 |
+ if cfg.Type == "" {
|
|
| 715 |
+ cfg.Type = jsonfilelog.Name |
|
| 716 |
+ } |
|
| 714 | 717 |
return cfg |
| 715 | 718 |
} |
| 716 | 719 |
// Use daemon's default log config for containers |
| ... | ... |
@@ -719,6 +722,9 @@ func (container *Container) getLogConfig() runconfig.LogConfig {
|
| 719 | 719 |
|
| 720 | 720 |
func (container *Container) getLogger() (logger.Logger, error) {
|
| 721 | 721 |
cfg := container.getLogConfig() |
| 722 |
+ if err := logger.ValidateLogOpts(cfg.Type, cfg.Config); err != nil {
|
|
| 723 |
+ return nil, err |
|
| 724 |
+ } |
|
| 722 | 725 |
c, err := logger.GetLogDriver(cfg.Type) |
| 723 | 726 |
if err != nil {
|
| 724 | 727 |
return nil, fmt.Errorf("Failed to get logging factory: %v", err)
|
| ... | ... |
@@ -891,28 +897,32 @@ func (c *Container) AttachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer |
| 891 | 891 |
|
| 892 | 892 |
if logs {
|
| 893 | 893 |
logDriver, err := c.getLogger() |
| 894 |
- cLog, err := logDriver.GetReader() |
|
| 895 |
- |
|
| 896 | 894 |
if err != nil {
|
| 897 |
- logrus.Errorf("Error reading logs: %s", err)
|
|
| 898 |
- } else if c.LogDriverType() != jsonfilelog.Name {
|
|
| 899 |
- logrus.Errorf("Reading logs not implemented for driver %s", c.LogDriverType())
|
|
| 895 |
+ logrus.Errorf("Error obtaining the logger %v", err)
|
|
| 896 |
+ return err |
|
| 897 |
+ } |
|
| 898 |
+ if _, ok := logDriver.(logger.Reader); !ok {
|
|
| 899 |
+ logrus.Errorf("cannot read logs for [%s] driver", logDriver.Name())
|
|
| 900 | 900 |
} else {
|
| 901 |
- dec := json.NewDecoder(cLog) |
|
| 902 |
- for {
|
|
| 903 |
- l := &jsonlog.JSONLog{}
|
|
| 904 |
- |
|
| 905 |
- if err := dec.Decode(l); err == io.EOF {
|
|
| 906 |
- break |
|
| 907 |
- } else if err != nil {
|
|
| 908 |
- logrus.Errorf("Error streaming logs: %s", err)
|
|
| 909 |
- break |
|
| 910 |
- } |
|
| 911 |
- if l.Stream == "stdout" && stdout != nil {
|
|
| 912 |
- io.WriteString(stdout, l.Log) |
|
| 913 |
- } |
|
| 914 |
- if l.Stream == "stderr" && stderr != nil {
|
|
| 915 |
- io.WriteString(stderr, l.Log) |
|
| 901 |
+ if cLog, err := logDriver.(logger.Reader).ReadLog(); err != nil {
|
|
| 902 |
+ logrus.Errorf("Error reading logs %v", err)
|
|
| 903 |
+ } else {
|
|
| 904 |
+ dec := json.NewDecoder(cLog) |
|
| 905 |
+ for {
|
|
| 906 |
+ l := &jsonlog.JSONLog{}
|
|
| 907 |
+ |
|
| 908 |
+ if err := dec.Decode(l); err == io.EOF {
|
|
| 909 |
+ break |
|
| 910 |
+ } else if err != nil {
|
|
| 911 |
+ logrus.Errorf("Error streaming logs: %s", err)
|
|
| 912 |
+ break |
|
| 913 |
+ } |
|
| 914 |
+ if l.Stream == "stdout" && stdout != nil {
|
|
| 915 |
+ io.WriteString(stdout, l.Log) |
|
| 916 |
+ } |
|
| 917 |
+ if l.Stream == "stderr" && stderr != nil {
|
|
| 918 |
+ io.WriteString(stderr, l.Log) |
|
| 919 |
+ } |
|
| 916 | 920 |
} |
| 917 | 921 |
} |
| 918 | 922 |
} |
| ... | ... |
@@ -3,7 +3,6 @@ package logger |
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"encoding/json" |
| 6 |
- "errors" |
|
| 7 | 6 |
"io" |
| 8 | 7 |
"testing" |
| 9 | 8 |
"time" |
| ... | ... |
@@ -19,10 +18,6 @@ func (l *TestLoggerJSON) Close() error { return nil }
|
| 19 | 19 |
|
| 20 | 20 |
func (l *TestLoggerJSON) Name() string { return "json" }
|
| 21 | 21 |
|
| 22 |
-func (l *TestLoggerJSON) GetReader() (io.Reader, error) {
|
|
| 23 |
- return nil, errors.New("not used in the test")
|
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 | 22 |
type TestLoggerText struct {
|
| 27 | 23 |
*bytes.Buffer |
| 28 | 24 |
} |
| ... | ... |
@@ -36,10 +31,6 @@ func (l *TestLoggerText) Close() error { return nil }
|
| 36 | 36 |
|
| 37 | 37 |
func (l *TestLoggerText) Name() string { return "text" }
|
| 38 | 38 |
|
| 39 |
-func (l *TestLoggerText) GetReader() (io.Reader, error) {
|
|
| 40 |
- return nil, errors.New("not used in the test")
|
|
| 41 |
-} |
|
| 42 |
- |
|
| 43 | 39 |
func TestCopier(t *testing.T) {
|
| 44 | 40 |
stdoutLine := "Line that thinks that it is log line from docker stdout" |
| 45 | 41 |
stderrLine := "Line that thinks that it is log line from docker stderr" |
| ... | ... |
@@ -11,6 +11,9 @@ import ( |
| 11 | 11 |
// Creator is a method that builds a logging driver instance with given context |
| 12 | 12 |
type Creator func(Context) (Logger, error) |
| 13 | 13 |
|
| 14 |
+//LogOptValidator is a method that validates the log opts provided |
|
| 15 |
+type LogOptValidator func(cfg map[string]string) error |
|
| 16 |
+ |
|
| 14 | 17 |
// Context provides enough information for a logging driver to do its function |
| 15 | 18 |
type Context struct {
|
| 16 | 19 |
Config map[string]string |
| ... | ... |
@@ -42,8 +45,9 @@ func (ctx *Context) Command() string {
|
| 42 | 42 |
} |
| 43 | 43 |
|
| 44 | 44 |
type logdriverFactory struct {
|
| 45 |
- registry map[string]Creator |
|
| 46 |
- m sync.Mutex |
|
| 45 |
+ registry map[string]Creator |
|
| 46 |
+ optValidator map[string]LogOptValidator |
|
| 47 |
+ m sync.Mutex |
|
| 47 | 48 |
} |
| 48 | 49 |
|
| 49 | 50 |
func (lf *logdriverFactory) register(name string, c Creator) error {
|
| ... | ... |
@@ -57,6 +61,17 @@ func (lf *logdriverFactory) register(name string, c Creator) error {
|
| 57 | 57 |
return nil |
| 58 | 58 |
} |
| 59 | 59 |
|
| 60 |
+func (lf *logdriverFactory) registerLogOptValidator(name string, l LogOptValidator) error {
|
|
| 61 |
+ lf.m.Lock() |
|
| 62 |
+ defer lf.m.Unlock() |
|
| 63 |
+ |
|
| 64 |
+ if _, ok := lf.optValidator[name]; ok {
|
|
| 65 |
+ return fmt.Errorf("logger: log driver named '%s' is already registered", name)
|
|
| 66 |
+ } |
|
| 67 |
+ lf.optValidator[name] = l |
|
| 68 |
+ return nil |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 60 | 71 |
func (lf *logdriverFactory) get(name string) (Creator, error) {
|
| 61 | 72 |
lf.m.Lock() |
| 62 | 73 |
defer lf.m.Unlock() |
| ... | ... |
@@ -68,7 +83,15 @@ func (lf *logdriverFactory) get(name string) (Creator, error) {
|
| 68 | 68 |
return c, nil |
| 69 | 69 |
} |
| 70 | 70 |
|
| 71 |
-var factory = &logdriverFactory{registry: make(map[string]Creator)} // global factory instance
|
|
| 71 |
+func (lf *logdriverFactory) getLogOptValidator(name string) LogOptValidator {
|
|
| 72 |
+ lf.m.Lock() |
|
| 73 |
+ defer lf.m.Unlock() |
|
| 74 |
+ |
|
| 75 |
+ c, _ := lf.optValidator[name] |
|
| 76 |
+ return c |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+var factory = &logdriverFactory{registry: make(map[string]Creator), optValidator: make(map[string]LogOptValidator)} // global factory instance
|
|
| 72 | 80 |
|
| 73 | 81 |
// RegisterLogDriver registers the given logging driver builder with given logging |
| 74 | 82 |
// driver name. |
| ... | ... |
@@ -76,7 +99,19 @@ func RegisterLogDriver(name string, c Creator) error {
|
| 76 | 76 |
return factory.register(name, c) |
| 77 | 77 |
} |
| 78 | 78 |
|
| 79 |
+func RegisterLogOptValidator(name string, l LogOptValidator) error {
|
|
| 80 |
+ return factory.registerLogOptValidator(name, l) |
|
| 81 |
+} |
|
| 82 |
+ |
|
| 79 | 83 |
// GetLogDriver provides the logging driver builder for a logging driver name. |
| 80 | 84 |
func GetLogDriver(name string) (Creator, error) {
|
| 81 | 85 |
return factory.get(name) |
| 82 | 86 |
} |
| 87 |
+ |
|
| 88 |
+func ValidateLogOpts(name string, cfg map[string]string) error {
|
|
| 89 |
+ l := factory.getLogOptValidator(name) |
|
| 90 |
+ if l != nil {
|
|
| 91 |
+ return l(cfg) |
|
| 92 |
+ } |
|
| 93 |
+ return fmt.Errorf("Log Opts are not valid for [%s] driver", name)
|
|
| 94 |
+} |
| ... | ... |
@@ -2,7 +2,7 @@ package fluentd |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 |
- "io" |
|
| 5 |
+ "fmt" |
|
| 6 | 6 |
"math" |
| 7 | 7 |
"net" |
| 8 | 8 |
"strconv" |
| ... | ... |
@@ -38,6 +38,9 @@ func init() {
|
| 38 | 38 |
if err := logger.RegisterLogDriver(name, New); err != nil {
|
| 39 | 39 |
logrus.Fatal(err) |
| 40 | 40 |
} |
| 41 |
+ if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
|
|
| 42 |
+ logrus.Fatal(err) |
|
| 43 |
+ } |
|
| 41 | 44 |
} |
| 42 | 45 |
|
| 43 | 46 |
func parseConfig(ctx logger.Context) (string, int, string, error) {
|
| ... | ... |
@@ -116,6 +119,18 @@ func (f *Fluentd) Log(msg *logger.Message) error {
|
| 116 | 116 |
return f.writer.PostWithTime(f.tag, msg.Timestamp, data) |
| 117 | 117 |
} |
| 118 | 118 |
|
| 119 |
+func ValidateLogOpt(cfg map[string]string) error {
|
|
| 120 |
+ for key := range cfg {
|
|
| 121 |
+ switch key {
|
|
| 122 |
+ case "fluentd-address": |
|
| 123 |
+ case "fluentd-tag": |
|
| 124 |
+ default: |
|
| 125 |
+ return fmt.Errorf("unknown log opt '%s' for fluentd log driver", key)
|
|
| 126 |
+ } |
|
| 127 |
+ } |
|
| 128 |
+ return nil |
|
| 129 |
+} |
|
| 130 |
+ |
|
| 119 | 131 |
func (f *Fluentd) Close() error {
|
| 120 | 132 |
return f.writer.Close() |
| 121 | 133 |
} |
| ... | ... |
@@ -123,7 +138,3 @@ func (f *Fluentd) Close() error {
|
| 123 | 123 |
func (f *Fluentd) Name() string {
|
| 124 | 124 |
return name |
| 125 | 125 |
} |
| 126 |
- |
|
| 127 |
-func (s *Fluentd) GetReader() (io.Reader, error) {
|
|
| 128 |
- return nil, logger.ReadLogsNotSupported |
|
| 129 |
-} |
| ... | ... |
@@ -5,7 +5,6 @@ package gelf |
| 5 | 5 |
import ( |
| 6 | 6 |
"bytes" |
| 7 | 7 |
"fmt" |
| 8 |
- "io" |
|
| 9 | 8 |
"net" |
| 10 | 9 |
"net/url" |
| 11 | 10 |
"time" |
| ... | ... |
@@ -39,6 +38,9 @@ func init() {
|
| 39 | 39 |
if err := logger.RegisterLogDriver(name, New); err != nil {
|
| 40 | 40 |
logrus.Fatal(err) |
| 41 | 41 |
} |
| 42 |
+ if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
|
|
| 43 |
+ logrus.Fatal(err) |
|
| 44 |
+ } |
|
| 42 | 45 |
} |
| 43 | 46 |
|
| 44 | 47 |
func New(ctx logger.Context) (logger.Logger, error) {
|
| ... | ... |
@@ -113,10 +115,6 @@ func (s *GelfLogger) Log(msg *logger.Message) error {
|
| 113 | 113 |
return nil |
| 114 | 114 |
} |
| 115 | 115 |
|
| 116 |
-func (s *GelfLogger) GetReader() (io.Reader, error) {
|
|
| 117 |
- return nil, logger.ReadLogsNotSupported |
|
| 118 |
-} |
|
| 119 |
- |
|
| 120 | 116 |
func (s *GelfLogger) Close() error {
|
| 121 | 117 |
return s.writer.Close() |
| 122 | 118 |
} |
| ... | ... |
@@ -125,6 +123,18 @@ func (s *GelfLogger) Name() string {
|
| 125 | 125 |
return name |
| 126 | 126 |
} |
| 127 | 127 |
|
| 128 |
+func ValidateLogOpt(cfg map[string]string) error {
|
|
| 129 |
+ for key := range cfg {
|
|
| 130 |
+ switch key {
|
|
| 131 |
+ case "gelf-address": |
|
| 132 |
+ case "gelf-tag": |
|
| 133 |
+ default: |
|
| 134 |
+ return fmt.Errorf("unknown log opt '%s' for gelf log driver", key)
|
|
| 135 |
+ } |
|
| 136 |
+ } |
|
| 137 |
+ return nil |
|
| 138 |
+} |
|
| 139 |
+ |
|
| 128 | 140 |
func parseAddress(address string) (string, error) {
|
| 129 | 141 |
if urlutil.IsTransportURL(address) {
|
| 130 | 142 |
url, err := url.Parse(address) |
| ... | ... |
@@ -4,7 +4,6 @@ package journald |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 | 6 |
"fmt" |
| 7 |
- "io" |
|
| 8 | 7 |
|
| 9 | 8 |
"github.com/Sirupsen/logrus" |
| 10 | 9 |
"github.com/coreos/go-systemd/journal" |
| ... | ... |
@@ -54,7 +53,3 @@ func (s *Journald) Close() error {
|
| 54 | 54 |
func (s *Journald) Name() string {
|
| 55 | 55 |
return name |
| 56 | 56 |
} |
| 57 |
- |
|
| 58 |
-func (s *Journald) GetReader() (io.Reader, error) {
|
|
| 59 |
- return nil, logger.ReadLogsNotSupported |
|
| 60 |
-} |
| ... | ... |
@@ -2,14 +2,17 @@ package jsonfilelog |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 |
+ "fmt" |
|
| 5 | 6 |
"io" |
| 6 | 7 |
"os" |
| 8 |
+ "strconv" |
|
| 7 | 9 |
"sync" |
| 8 | 10 |
|
| 9 | 11 |
"github.com/Sirupsen/logrus" |
| 10 | 12 |
"github.com/docker/docker/daemon/logger" |
| 11 | 13 |
"github.com/docker/docker/pkg/jsonlog" |
| 12 | 14 |
"github.com/docker/docker/pkg/timeutils" |
| 15 |
+ "github.com/docker/docker/pkg/units" |
|
| 13 | 16 |
) |
| 14 | 17 |
|
| 15 | 18 |
const ( |
| ... | ... |
@@ -19,17 +22,21 @@ const ( |
| 19 | 19 |
// JSONFileLogger is Logger implementation for default docker logging: |
| 20 | 20 |
// JSON objects to file |
| 21 | 21 |
type JSONFileLogger struct {
|
| 22 |
- buf *bytes.Buffer |
|
| 23 |
- f *os.File // store for closing |
|
| 24 |
- mu sync.Mutex // protects buffer |
|
| 25 |
- |
|
| 26 |
- ctx logger.Context |
|
| 22 |
+ buf *bytes.Buffer |
|
| 23 |
+ f *os.File // store for closing |
|
| 24 |
+ mu sync.Mutex // protects buffer |
|
| 25 |
+ capacity int64 //maximum size of each file |
|
| 26 |
+ n int //maximum number of files |
|
| 27 |
+ ctx logger.Context |
|
| 27 | 28 |
} |
| 28 | 29 |
|
| 29 | 30 |
func init() {
|
| 30 | 31 |
if err := logger.RegisterLogDriver(Name, New); err != nil {
|
| 31 | 32 |
logrus.Fatal(err) |
| 32 | 33 |
} |
| 34 |
+ if err := logger.RegisterLogOptValidator(Name, ValidateLogOpt); err != nil {
|
|
| 35 |
+ logrus.Fatal(err) |
|
| 36 |
+ } |
|
| 33 | 37 |
} |
| 34 | 38 |
|
| 35 | 39 |
// New creates new JSONFileLogger which writes to filename |
| ... | ... |
@@ -38,10 +45,30 @@ func New(ctx logger.Context) (logger.Logger, error) {
|
| 38 | 38 |
if err != nil {
|
| 39 | 39 |
return nil, err |
| 40 | 40 |
} |
| 41 |
+ var capval int64 = -1 |
|
| 42 |
+ if capacity, ok := ctx.Config["max-size"]; ok {
|
|
| 43 |
+ var err error |
|
| 44 |
+ capval, err = units.FromHumanSize(capacity) |
|
| 45 |
+ if err != nil {
|
|
| 46 |
+ return nil, err |
|
| 47 |
+ } |
|
| 48 |
+ } |
|
| 49 |
+ var maxFiles int = 1 |
|
| 50 |
+ if maxFileString, ok := ctx.Config["max-file"]; ok {
|
|
| 51 |
+ maxFiles, err = strconv.Atoi(maxFileString) |
|
| 52 |
+ if err != nil {
|
|
| 53 |
+ return nil, err |
|
| 54 |
+ } |
|
| 55 |
+ if maxFiles < 1 {
|
|
| 56 |
+ return nil, fmt.Errorf("max-files cannot be less than 1.")
|
|
| 57 |
+ } |
|
| 58 |
+ } |
|
| 41 | 59 |
return &JSONFileLogger{
|
| 42 |
- f: log, |
|
| 43 |
- buf: bytes.NewBuffer(nil), |
|
| 44 |
- ctx: ctx, |
|
| 60 |
+ f: log, |
|
| 61 |
+ buf: bytes.NewBuffer(nil), |
|
| 62 |
+ ctx: ctx, |
|
| 63 |
+ capacity: capval, |
|
| 64 |
+ n: maxFiles, |
|
| 45 | 65 |
}, nil |
| 46 | 66 |
} |
| 47 | 67 |
|
| ... | ... |
@@ -59,17 +86,102 @@ func (l *JSONFileLogger) Log(msg *logger.Message) error {
|
| 59 | 59 |
return err |
| 60 | 60 |
} |
| 61 | 61 |
l.buf.WriteByte('\n')
|
| 62 |
- _, err = l.buf.WriteTo(l.f) |
|
| 62 |
+ _, err = writeLog(l) |
|
| 63 |
+ return err |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+func writeLog(l *JSONFileLogger) (int64, error) {
|
|
| 67 |
+ if l.capacity == -1 {
|
|
| 68 |
+ return writeToBuf(l) |
|
| 69 |
+ } |
|
| 70 |
+ meta, err := l.f.Stat() |
|
| 71 |
+ if err != nil {
|
|
| 72 |
+ return -1, err |
|
| 73 |
+ } |
|
| 74 |
+ if meta.Size() >= l.capacity {
|
|
| 75 |
+ name := l.f.Name() |
|
| 76 |
+ if err := l.f.Close(); err != nil {
|
|
| 77 |
+ return -1, err |
|
| 78 |
+ } |
|
| 79 |
+ if err := rotate(name, l.n); err != nil {
|
|
| 80 |
+ return -1, err |
|
| 81 |
+ } |
|
| 82 |
+ file, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) |
|
| 83 |
+ if err != nil {
|
|
| 84 |
+ return -1, err |
|
| 85 |
+ } |
|
| 86 |
+ l.f = file |
|
| 87 |
+ } |
|
| 88 |
+ return writeToBuf(l) |
|
| 89 |
+} |
|
| 90 |
+ |
|
| 91 |
+func writeToBuf(l *JSONFileLogger) (int64, error) {
|
|
| 92 |
+ i, err := l.buf.WriteTo(l.f) |
|
| 63 | 93 |
if err != nil {
|
| 64 |
- // this buffer is screwed, replace it with another to avoid races |
|
| 65 | 94 |
l.buf = bytes.NewBuffer(nil) |
| 95 |
+ } |
|
| 96 |
+ return i, err |
|
| 97 |
+} |
|
| 98 |
+ |
|
| 99 |
+func rotate(name string, n int) error {
|
|
| 100 |
+ if n < 2 {
|
|
| 101 |
+ return nil |
|
| 102 |
+ } |
|
| 103 |
+ for i := n - 1; i > 1; i-- {
|
|
| 104 |
+ oldFile := name + "." + strconv.Itoa(i) |
|
| 105 |
+ replacingFile := name + "." + strconv.Itoa(i-1) |
|
| 106 |
+ if err := backup(oldFile, replacingFile); err != nil {
|
|
| 107 |
+ return err |
|
| 108 |
+ } |
|
| 109 |
+ } |
|
| 110 |
+ if err := backup(name+".1", name); err != nil {
|
|
| 66 | 111 |
return err |
| 67 | 112 |
} |
| 68 | 113 |
return nil |
| 69 | 114 |
} |
| 70 | 115 |
|
| 71 |
-func (l *JSONFileLogger) GetReader() (io.Reader, error) {
|
|
| 72 |
- return os.Open(l.ctx.LogPath) |
|
| 116 |
+func backup(old, curr string) error {
|
|
| 117 |
+ if _, err := os.Stat(old); !os.IsNotExist(err) {
|
|
| 118 |
+ err := os.Remove(old) |
|
| 119 |
+ if err != nil {
|
|
| 120 |
+ return err |
|
| 121 |
+ } |
|
| 122 |
+ } |
|
| 123 |
+ if _, err := os.Stat(curr); os.IsNotExist(err) {
|
|
| 124 |
+ if f, err := os.Create(curr); err != nil {
|
|
| 125 |
+ return err |
|
| 126 |
+ } else {
|
|
| 127 |
+ f.Close() |
|
| 128 |
+ } |
|
| 129 |
+ } |
|
| 130 |
+ return os.Rename(curr, old) |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+func ValidateLogOpt(cfg map[string]string) error {
|
|
| 134 |
+ for key := range cfg {
|
|
| 135 |
+ switch key {
|
|
| 136 |
+ case "max-file": |
|
| 137 |
+ case "max-size": |
|
| 138 |
+ default: |
|
| 139 |
+ return fmt.Errorf("unknown log opt '%s' for json-file log driver", key)
|
|
| 140 |
+ } |
|
| 141 |
+ } |
|
| 142 |
+ return nil |
|
| 143 |
+} |
|
| 144 |
+ |
|
| 145 |
+func (l *JSONFileLogger) ReadLog(args ...string) (io.Reader, error) {
|
|
| 146 |
+ pth := l.ctx.LogPath |
|
| 147 |
+ if len(args) > 0 {
|
|
| 148 |
+ //check if args[0] is an integer index |
|
| 149 |
+ index, err := strconv.ParseInt(args[0], 0, 0) |
|
| 150 |
+ if err != nil {
|
|
| 151 |
+ return nil, err |
|
| 152 |
+ } |
|
| 153 |
+ if index > 0 {
|
|
| 154 |
+ pth = pth + "." + args[0] |
|
| 155 |
+ } |
|
| 156 |
+ } |
|
| 157 |
+ return os.Open(pth) |
|
| 73 | 158 |
} |
| 74 | 159 |
|
| 75 | 160 |
func (l *JSONFileLogger) LogPath() string {
|
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"io/ioutil" |
| 5 | 5 |
"os" |
| 6 | 6 |
"path/filepath" |
| 7 |
+ "strconv" |
|
| 7 | 8 |
"testing" |
| 8 | 9 |
"time" |
| 9 | 10 |
|
| ... | ... |
@@ -84,3 +85,67 @@ func BenchmarkJSONFileLogger(b *testing.B) {
|
| 84 | 84 |
} |
| 85 | 85 |
} |
| 86 | 86 |
} |
| 87 |
+ |
|
| 88 |
+func TestJSONFileLoggerWithOpts(t *testing.T) {
|
|
| 89 |
+ cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657" |
|
| 90 |
+ tmp, err := ioutil.TempDir("", "docker-logger-")
|
|
| 91 |
+ if err != nil {
|
|
| 92 |
+ t.Fatal(err) |
|
| 93 |
+ } |
|
| 94 |
+ defer os.RemoveAll(tmp) |
|
| 95 |
+ filename := filepath.Join(tmp, "container.log") |
|
| 96 |
+ config := map[string]string{"max-file": "2", "max-size": "1k"}
|
|
| 97 |
+ l, err := New(logger.Context{
|
|
| 98 |
+ ContainerID: cid, |
|
| 99 |
+ LogPath: filename, |
|
| 100 |
+ Config: config, |
|
| 101 |
+ }) |
|
| 102 |
+ if err != nil {
|
|
| 103 |
+ t.Fatal(err) |
|
| 104 |
+ } |
|
| 105 |
+ defer l.Close() |
|
| 106 |
+ for i := 0; i < 20; i++ {
|
|
| 107 |
+ if err := l.Log(&logger.Message{ContainerID: cid, Line: []byte("line" + strconv.Itoa(i)), Source: "src1"}); err != nil {
|
|
| 108 |
+ t.Fatal(err) |
|
| 109 |
+ } |
|
| 110 |
+ } |
|
| 111 |
+ res, err := ioutil.ReadFile(filename) |
|
| 112 |
+ if err != nil {
|
|
| 113 |
+ t.Fatal(err) |
|
| 114 |
+ } |
|
| 115 |
+ penUlt, err := ioutil.ReadFile(filename + ".1") |
|
| 116 |
+ if err != nil {
|
|
| 117 |
+ t.Fatal(err) |
|
| 118 |
+ } |
|
| 119 |
+ |
|
| 120 |
+ expectedPenultimate := `{"log":"line0\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 121 |
+{"log":"line1\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 122 |
+{"log":"line2\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 123 |
+{"log":"line3\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 124 |
+{"log":"line4\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 125 |
+{"log":"line5\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 126 |
+{"log":"line6\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 127 |
+{"log":"line7\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 128 |
+{"log":"line8\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 129 |
+{"log":"line9\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 130 |
+{"log":"line10\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 131 |
+{"log":"line11\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 132 |
+{"log":"line12\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 133 |
+{"log":"line13\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 134 |
+{"log":"line14\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 135 |
+{"log":"line15\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 136 |
+` |
|
| 137 |
+ expected := `{"log":"line16\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 138 |
+{"log":"line17\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 139 |
+{"log":"line18\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 140 |
+{"log":"line19\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
|
|
| 141 |
+` |
|
| 142 |
+ |
|
| 143 |
+ if string(res) != expected {
|
|
| 144 |
+ t.Fatalf("Wrong log content: %q, expected %q", res, expected)
|
|
| 145 |
+ } |
|
| 146 |
+ if string(penUlt) != expectedPenultimate {
|
|
| 147 |
+ t.Fatalf("Wrong log content: %q, expected %q", penUlt, expectedPenultimate)
|
|
| 148 |
+ } |
|
| 149 |
+ |
|
| 150 |
+} |
| ... | ... |
@@ -21,5 +21,9 @@ type Logger interface {
|
| 21 | 21 |
Log(*Message) error |
| 22 | 22 |
Name() string |
| 23 | 23 |
Close() error |
| 24 |
- GetReader() (io.Reader, error) |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+//Reader is an interface for docker logging drivers that support reading |
|
| 27 |
+type Reader interface {
|
|
| 28 |
+ ReadLog(args ...string) (io.Reader, error) |
|
| 25 | 29 |
} |
| ... | ... |
@@ -4,7 +4,7 @@ package syslog |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 | 6 |
"errors" |
| 7 |
- "io" |
|
| 7 |
+ "fmt" |
|
| 8 | 8 |
"log/syslog" |
| 9 | 9 |
"net" |
| 10 | 10 |
"net/url" |
| ... | ... |
@@ -51,6 +51,9 @@ func init() {
|
| 51 | 51 |
if err := logger.RegisterLogDriver(name, New); err != nil {
|
| 52 | 52 |
logrus.Fatal(err) |
| 53 | 53 |
} |
| 54 |
+ if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
|
|
| 55 |
+ logrus.Fatal(err) |
|
| 56 |
+ } |
|
| 54 | 57 |
} |
| 55 | 58 |
|
| 56 | 59 |
func New(ctx logger.Context) (logger.Logger, error) {
|
| ... | ... |
@@ -99,10 +102,6 @@ func (s *Syslog) Name() string {
|
| 99 | 99 |
return name |
| 100 | 100 |
} |
| 101 | 101 |
|
| 102 |
-func (s *Syslog) GetReader() (io.Reader, error) {
|
|
| 103 |
- return nil, logger.ReadLogsNotSupported |
|
| 104 |
-} |
|
| 105 |
- |
|
| 106 | 102 |
func parseAddress(address string) (string, string, error) {
|
| 107 | 103 |
if urlutil.IsTransportURL(address) {
|
| 108 | 104 |
url, err := url.Parse(address) |
| ... | ... |
@@ -133,6 +132,19 @@ func parseAddress(address string) (string, string, error) {
|
| 133 | 133 |
return "", "", nil |
| 134 | 134 |
} |
| 135 | 135 |
|
| 136 |
+func ValidateLogOpt(cfg map[string]string) error {
|
|
| 137 |
+ for key := range cfg {
|
|
| 138 |
+ switch key {
|
|
| 139 |
+ case "syslog-address": |
|
| 140 |
+ case "syslog-tag": |
|
| 141 |
+ case "syslog-facility": |
|
| 142 |
+ default: |
|
| 143 |
+ return fmt.Errorf("unknown log opt '%s' for syslog log driver", key)
|
|
| 144 |
+ } |
|
| 145 |
+ } |
|
| 146 |
+ return nil |
|
| 147 |
+} |
|
| 148 |
+ |
|
| 136 | 149 |
func parseFacility(facility string) (syslog.Priority, error) {
|
| 137 | 150 |
if facility == "" {
|
| 138 | 151 |
return syslog.LOG_DAEMON, nil |
| ... | ... |
@@ -12,6 +12,7 @@ import ( |
| 12 | 12 |
"time" |
| 13 | 13 |
|
| 14 | 14 |
"github.com/Sirupsen/logrus" |
| 15 |
+ "github.com/docker/docker/daemon/logger" |
|
| 15 | 16 |
"github.com/docker/docker/daemon/logger/jsonfilelog" |
| 16 | 17 |
"github.com/docker/docker/pkg/jsonlog" |
| 17 | 18 |
"github.com/docker/docker/pkg/stdcopy" |
| ... | ... |
@@ -40,7 +41,7 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er |
| 40 | 40 |
format = timeutils.RFC3339NanoFixed |
| 41 | 41 |
} |
| 42 | 42 |
if config.Tail == "" {
|
| 43 |
- config.Tail = "all" |
|
| 43 |
+ config.Tail = "latest" |
|
| 44 | 44 |
} |
| 45 | 45 |
|
| 46 | 46 |
container, err := daemon.Get(name) |
| ... | ... |
@@ -62,13 +63,29 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er |
| 62 | 62 |
if container.LogDriverType() != jsonfilelog.Name {
|
| 63 | 63 |
return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
|
| 64 | 64 |
} |
| 65 |
+ |
|
| 66 |
+ maxFile := 1 |
|
| 67 |
+ container.readHostConfig() |
|
| 68 |
+ cfg := container.getLogConfig() |
|
| 69 |
+ conf := cfg.Config |
|
| 70 |
+ if val, ok := conf["max-file"]; ok {
|
|
| 71 |
+ var err error |
|
| 72 |
+ maxFile, err = strconv.Atoi(val) |
|
| 73 |
+ if err != nil {
|
|
| 74 |
+ return fmt.Errorf("Error reading max-file value: %s", err)
|
|
| 75 |
+ } |
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 65 | 78 |
logDriver, err := container.getLogger() |
| 66 |
- cLog, err := logDriver.GetReader() |
|
| 67 | 79 |
if err != nil {
|
| 68 |
- logrus.Errorf("Error reading logs: %s", err)
|
|
| 80 |
+ return err |
|
| 81 |
+ } |
|
| 82 |
+ _, ok := logDriver.(logger.Reader) |
|
| 83 |
+ if !ok {
|
|
| 84 |
+ logrus.Errorf("Cannot read logs of the [%s] driver", logDriver.Name())
|
|
| 69 | 85 |
} else {
|
| 70 | 86 |
// json-file driver |
| 71 |
- if config.Tail != "all" {
|
|
| 87 |
+ if config.Tail != "all" && config.Tail != "latest" {
|
|
| 72 | 88 |
var err error |
| 73 | 89 |
lines, err = strconv.Atoi(config.Tail) |
| 74 | 90 |
if err != nil {
|
| ... | ... |
@@ -78,42 +95,50 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er |
| 78 | 78 |
} |
| 79 | 79 |
|
| 80 | 80 |
if lines != 0 {
|
| 81 |
- if lines > 0 {
|
|
| 82 |
- f := cLog.(*os.File) |
|
| 83 |
- ls, err := tailfile.TailFile(f, lines) |
|
| 84 |
- if err != nil {
|
|
| 85 |
- return err |
|
| 86 |
- } |
|
| 87 |
- tmp := bytes.NewBuffer([]byte{})
|
|
| 88 |
- for _, l := range ls {
|
|
| 89 |
- fmt.Fprintf(tmp, "%s\n", l) |
|
| 90 |
- } |
|
| 91 |
- cLog = tmp |
|
| 81 |
+ n := maxFile |
|
| 82 |
+ if config.Tail == "latest" && config.Since.IsZero() {
|
|
| 83 |
+ n = 1 |
|
| 92 | 84 |
} |
| 93 |
- |
|
| 94 |
- dec := json.NewDecoder(cLog) |
|
| 95 |
- l := &jsonlog.JSONLog{}
|
|
| 96 |
- for {
|
|
| 97 |
- l.Reset() |
|
| 98 |
- if err := dec.Decode(l); err == io.EOF {
|
|
| 99 |
- break |
|
| 100 |
- } else if err != nil {
|
|
| 101 |
- logrus.Errorf("Error streaming logs: %s", err)
|
|
| 85 |
+ before := false |
|
| 86 |
+ for i := n; i > 0; i-- {
|
|
| 87 |
+ if before {
|
|
| 102 | 88 |
break |
| 103 | 89 |
} |
| 104 |
- logLine := l.Log |
|
| 105 |
- if !config.Since.IsZero() && l.Created.Before(config.Since) {
|
|
| 90 |
+ cLog, err := getReader(logDriver, i, n, lines) |
|
| 91 |
+ if err != nil {
|
|
| 92 |
+ logrus.Debugf("Error reading %d log file: %v", i-1, err)
|
|
| 106 | 93 |
continue |
| 107 | 94 |
} |
| 108 |
- if config.Timestamps {
|
|
| 109 |
- // format can be "" or time format, so here can't be error |
|
| 110 |
- logLine, _ = l.Format(format) |
|
| 111 |
- } |
|
| 112 |
- if l.Stream == "stdout" && config.UseStdout {
|
|
| 113 |
- io.WriteString(outStream, logLine) |
|
| 95 |
+ //if lines are specified, then iterate only once |
|
| 96 |
+ if lines > 0 {
|
|
| 97 |
+ i = 1 |
|
| 98 |
+ } else { // if lines are not specified, cLog is a file, It needs to be closed
|
|
| 99 |
+ defer cLog.(*os.File).Close() |
|
| 114 | 100 |
} |
| 115 |
- if l.Stream == "stderr" && config.UseStderr {
|
|
| 116 |
- io.WriteString(errStream, logLine) |
|
| 101 |
+ dec := json.NewDecoder(cLog) |
|
| 102 |
+ l := &jsonlog.JSONLog{}
|
|
| 103 |
+ for {
|
|
| 104 |
+ l.Reset() |
|
| 105 |
+ if err := dec.Decode(l); err == io.EOF {
|
|
| 106 |
+ break |
|
| 107 |
+ } else if err != nil {
|
|
| 108 |
+ logrus.Errorf("Error streaming logs: %s", err)
|
|
| 109 |
+ break |
|
| 110 |
+ } |
|
| 111 |
+ logLine := l.Log |
|
| 112 |
+ if !config.Since.IsZero() && l.Created.Before(config.Since) {
|
|
| 113 |
+ continue |
|
| 114 |
+ } |
|
| 115 |
+ if config.Timestamps {
|
|
| 116 |
+ // format can be "" or time format, so here can't be error |
|
| 117 |
+ logLine, _ = l.Format(format) |
|
| 118 |
+ } |
|
| 119 |
+ if l.Stream == "stdout" && config.UseStdout {
|
|
| 120 |
+ io.WriteString(outStream, logLine) |
|
| 121 |
+ } |
|
| 122 |
+ if l.Stream == "stderr" && config.UseStderr {
|
|
| 123 |
+ io.WriteString(errStream, logLine) |
|
| 124 |
+ } |
|
| 117 | 125 |
} |
| 118 | 126 |
} |
| 119 | 127 |
} |
| ... | ... |
@@ -177,3 +202,36 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er |
| 177 | 177 |
} |
| 178 | 178 |
return nil |
| 179 | 179 |
} |
| 180 |
+ |
|
| 181 |
+func getReader(logDriver logger.Logger, fileIndex, maxFiles, lines int) (io.Reader, error) {
|
|
| 182 |
+ if lines <= 0 {
|
|
| 183 |
+ index := strconv.Itoa(fileIndex - 1) |
|
| 184 |
+ cLog, err := logDriver.(logger.Reader).ReadLog(index) |
|
| 185 |
+ return cLog, err |
|
| 186 |
+ } |
|
| 187 |
+ buf := bytes.NewBuffer([]byte{})
|
|
| 188 |
+ remaining := lines |
|
| 189 |
+ for i := 0; i < maxFiles; i++ {
|
|
| 190 |
+ index := strconv.Itoa(i) |
|
| 191 |
+ cLog, err := logDriver.(logger.Reader).ReadLog(index) |
|
| 192 |
+ if err != nil {
|
|
| 193 |
+ return buf, err |
|
| 194 |
+ } |
|
| 195 |
+ f := cLog.(*os.File) |
|
| 196 |
+ ls, err := tailfile.TailFile(f, remaining) |
|
| 197 |
+ if err != nil {
|
|
| 198 |
+ return buf, err |
|
| 199 |
+ } |
|
| 200 |
+ tmp := bytes.NewBuffer([]byte{})
|
|
| 201 |
+ for _, l := range ls {
|
|
| 202 |
+ fmt.Fprintf(tmp, "%s\n", l) |
|
| 203 |
+ } |
|
| 204 |
+ tmp.ReadFrom(buf) |
|
| 205 |
+ buf = tmp |
|
| 206 |
+ if len(ls) == remaining {
|
|
| 207 |
+ return buf, nil |
|
| 208 |
+ } |
|
| 209 |
+ remaining = remaining - len(ls) |
|
| 210 |
+ } |
|
| 211 |
+ return buf, nil |
|
| 212 |
+} |
| ... | ... |
@@ -15,6 +15,7 @@ import ( |
| 15 | 15 |
"github.com/docker/docker/autogen/dockerversion" |
| 16 | 16 |
"github.com/docker/docker/cliconfig" |
| 17 | 17 |
"github.com/docker/docker/daemon" |
| 18 |
+ "github.com/docker/docker/daemon/logger" |
|
| 18 | 19 |
flag "github.com/docker/docker/pkg/mflag" |
| 19 | 20 |
"github.com/docker/docker/pkg/pidfile" |
| 20 | 21 |
"github.com/docker/docker/pkg/signal" |
| ... | ... |
@@ -94,6 +95,12 @@ func mainDaemon() {
|
| 94 | 94 |
logrus.Fatalf("Failed to set umask: %v", err)
|
| 95 | 95 |
} |
| 96 | 96 |
|
| 97 |
+ if len(daemonCfg.LogConfig.Config) > 0 {
|
|
| 98 |
+ if err := logger.ValidateLogOpts(daemonCfg.LogConfig.Type, daemonCfg.LogConfig.Config); err != nil {
|
|
| 99 |
+ logrus.Fatalf("Failed to set log opts: %v", err)
|
|
| 100 |
+ } |
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 97 | 103 |
var pfile *pidfile.PidFile |
| 98 | 104 |
if daemonCfg.Pidfile != "" {
|
| 99 | 105 |
pf, err := pidfile.New(daemonCfg.Pidfile) |
| ... | ... |
@@ -29,7 +29,7 @@ The `docker logs --follow` command will continue streaming the new output from |
| 29 | 29 |
the container's `STDOUT` and `STDERR`. |
| 30 | 30 |
|
| 31 | 31 |
Passing a negative number or a non-integer to `--tail` is invalid and the |
| 32 |
-value is set to `all` in that case. This behavior may change in the future. |
|
| 32 |
+value is set to `latest` in that case. |
|
| 33 | 33 |
|
| 34 | 34 |
The `docker logs --timestamp` commands will add an RFC3339Nano |
| 35 | 35 |
timestamp, for example `2014-09-16T06:17:46.000000000Z`, to each |
| ... | ... |
@@ -17,7 +17,7 @@ container's logging driver. The following options are supported: |
| 17 | 17 |
|
| 18 | 18 |
| `none` | Disables any logging for the container. `docker logs` won't be available with this driver. | |
| 19 | 19 |
|-------------|-------------------------------------------------------------------------------------------------------------------------------| |
| 20 |
-| `json-file` | Default logging driver for Docker. Writes JSON messages to file. No logging options are supported for this driver. | |
|
| 20 |
+| `json-file` | Default logging driver for Docker. Writes JSON messages to file. | |
|
| 21 | 21 |
| `syslog` | Syslog logging driver for Docker. Writes log messages to syslog. | |
| 22 | 22 |
| `journald` | Journald logging driver for Docker. Writes log messages to `journald`. | |
| 23 | 23 |
| `gelf` | Graylog Extended Log Format (GELF) logging driver for Docker. Writes log messages to a GELF endpoint likeGraylog or Logstash. | |
| ... | ... |
@@ -25,6 +25,20 @@ container's logging driver. The following options are supported: |
| 25 | 25 |
|
| 26 | 26 |
The `docker logs`command is available only for the `json-file` logging driver. |
| 27 | 27 |
|
| 28 |
+### The json-file options |
|
| 29 |
+ |
|
| 30 |
+The following logging options are supported for the `json-file` logging driver: |
|
| 31 |
+ |
|
| 32 |
+ --log-opt max-size=[0-9+][k|m|g] |
|
| 33 |
+ --log-opt max-file=[0-9+] |
|
| 34 |
+ |
|
| 35 |
+Logs that reach `max-size` are rolled over. You can set the size in kilobytes(k), megabytes(m), or gigabytes(g). eg `--log-opt max-size=50m`. If `max-size` is not set, then logs are not rolled over. |
|
| 36 |
+ |
|
| 37 |
+ |
|
| 38 |
+`max-file` specifies the maximum number of files that a log is rolled over before being discarded. eg `--log-opt max-file=100`. If `max-size` is not set, then `max-file` is not honored. |
|
| 39 |
+ |
|
| 40 |
+If `max-size` and `max-file` are set, `docker logs` only returns the log lines from the newest log file. |
|
| 41 |
+ |
|
| 28 | 42 |
### The syslog options |
| 29 | 43 |
|
| 30 | 44 |
The following logging options are supported for the `syslog` logging driver: |
| ... | ... |
@@ -422,7 +422,6 @@ func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]st |
| 422 | 422 |
if loggingDriver == "none" && len(loggingOpts) > 0 {
|
| 423 | 423 |
return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
|
| 424 | 424 |
} |
| 425 |
- //TODO - validation step |
|
| 426 | 425 |
return loggingOptsMap, nil |
| 427 | 426 |
} |
| 428 | 427 |
|