Browse code

daemon: Logging drivers refactoring

- noplog driver pkg for '--log-driver=none' (null object pattern)
- centralized factory for log drivers (instead of case/switch)
- logging drivers registers themselves to factory upon import
(easy plug/unplug of drivers in daemon/logdrivers.go)
- daemon now doesn't start with an invalid log driver
- Name() method of loggers is actually now their cli names (made it useful)
- generalized Read() logic, made it unsupported except json-file (preserves
existing behavior)

Spotted some duplication code around processing of legacy json-file
format, didn't touch that and refactored in both places.

Signed-off-by: Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>

Ahmet Alp Balkan authored on 2015/04/09 13:23:30
Showing 12 changed files
... ...
@@ -37,8 +37,8 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
37 37
 		return err
38 38
 	}
39 39
 
40
-	if c.HostConfig.LogConfig.Type != "json-file" {
41
-		return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver")
40
+	if logType := c.HostConfig.LogConfig.Type; logType != "json-file" {
41
+		return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver (got: %s)", logType)
42 42
 	}
43 43
 
44 44
 	v := url.Values{}
... ...
@@ -22,9 +22,7 @@ import (
22 22
 	"github.com/Sirupsen/logrus"
23 23
 	"github.com/docker/docker/daemon/execdriver"
24 24
 	"github.com/docker/docker/daemon/logger"
25
-	"github.com/docker/docker/daemon/logger/journald"
26 25
 	"github.com/docker/docker/daemon/logger/jsonfilelog"
27
-	"github.com/docker/docker/daemon/logger/syslog"
28 26
 	"github.com/docker/docker/daemon/network"
29 27
 	"github.com/docker/docker/daemon/networkdriver/bridge"
30 28
 	"github.com/docker/docker/image"
... ...
@@ -947,18 +945,6 @@ func (container *Container) Unmount() error {
947 947
 	return container.daemon.Unmount(container)
948 948
 }
949 949
 
950
-func (container *Container) logPath(name string) (string, error) {
951
-	return container.GetRootResourcePath(fmt.Sprintf("%s-%s.log", container.ID, name))
952
-}
953
-
954
-func (container *Container) ReadLog(name string) (io.Reader, error) {
955
-	pth, err := container.logPath(name)
956
-	if err != nil {
957
-		return nil, err
958
-	}
959
-	return os.Open(pth)
960
-}
961
-
962 950
 func (container *Container) hostConfigPath() (string, error) {
963 951
 	return container.GetRootResourcePath("hostconfig.json")
964 952
 }
... ...
@@ -1445,41 +1431,45 @@ func (container *Container) setupWorkingDirectory() error {
1445 1445
 	return nil
1446 1446
 }
1447 1447
 
1448
-func (container *Container) startLogging() error {
1448
+func (container *Container) getLogConfig() runconfig.LogConfig {
1449 1449
 	cfg := container.hostConfig.LogConfig
1450
-	if cfg.Type == "" {
1451
-		cfg = container.daemon.defaultLogConfig
1450
+	if cfg.Type != "" { // container has log driver configured
1451
+		return cfg
1452 1452
 	}
1453
-	var l logger.Logger
1454
-	switch cfg.Type {
1455
-	case "json-file":
1456
-		pth, err := container.logPath("json")
1457
-		if err != nil {
1458
-			return err
1459
-		}
1460
-		container.LogPath = pth
1453
+	// Use daemon's default log config for containers
1454
+	return container.daemon.defaultLogConfig
1455
+}
1461 1456
 
1462
-		dl, err := jsonfilelog.New(pth)
1463
-		if err != nil {
1464
-			return err
1465
-		}
1466
-		l = dl
1467
-	case "syslog":
1468
-		dl, err := syslog.New(container.ID[:12])
1469
-		if err != nil {
1470
-			return err
1471
-		}
1472
-		l = dl
1473
-	case "journald":
1474
-		dl, err := journald.New(container.ID, container.Name)
1457
+func (container *Container) getLogger() (logger.Logger, error) {
1458
+	cfg := container.getLogConfig()
1459
+	c, err := logger.GetLogDriver(cfg.Type)
1460
+	if err != nil {
1461
+		return nil, fmt.Errorf("Failed to get logging factory: %v", err)
1462
+	}
1463
+	ctx := logger.Context{
1464
+		ContainerID:   container.ID,
1465
+		ContainerName: container.Name,
1466
+	}
1467
+
1468
+	// Set logging file for "json-logger"
1469
+	if cfg.Type == jsonfilelog.Name {
1470
+		ctx.LogPath, err = container.GetRootResourcePath(fmt.Sprintf("%s-json.log", container.ID))
1475 1471
 		if err != nil {
1476
-			return err
1472
+			return nil, err
1477 1473
 		}
1478
-		l = dl
1479
-	case "none":
1480
-		return nil
1481
-	default:
1482
-		return fmt.Errorf("Unknown logging driver: %s", cfg.Type)
1474
+	}
1475
+	return c(ctx)
1476
+}
1477
+
1478
+func (container *Container) startLogging() error {
1479
+	cfg := container.getLogConfig()
1480
+	if cfg.Type == "none" {
1481
+		return nil // do not start logging routines
1482
+	}
1483
+
1484
+	l, err := container.getLogger()
1485
+	if err != nil {
1486
+		return fmt.Errorf("Failed to initialize logging driver: %v", err)
1483 1487
 	}
1484 1488
 
1485 1489
 	copier, err := logger.NewCopier(container.ID, map[string]io.Reader{"stdout": container.StdoutPipe(), "stderr": container.StderrPipe()}, l)
... ...
@@ -1490,6 +1480,11 @@ func (container *Container) startLogging() error {
1490 1490
 	copier.Run()
1491 1491
 	container.logDriver = l
1492 1492
 
1493
+	// set LogPath field only for json-file logdriver
1494
+	if jl, ok := l.(*jsonfilelog.JSONFileLogger); ok {
1495
+		container.LogPath = jl.LogPath()
1496
+	}
1497
+
1493 1498
 	return nil
1494 1499
 }
1495 1500
 
... ...
@@ -1663,28 +1658,13 @@ func (c *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writ
1663 1663
 
1664 1664
 func (c *Container) AttachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool) error {
1665 1665
 	if logs {
1666
-		cLog, err := c.ReadLog("json")
1667
-		if err != nil && os.IsNotExist(err) {
1668
-			// Legacy logs
1669
-			logrus.Debugf("Old logs format")
1670
-			if stdout != nil {
1671
-				cLog, err := c.ReadLog("stdout")
1672
-				if err != nil {
1673
-					logrus.Errorf("Error reading logs (stdout): %s", err)
1674
-				} else if _, err := io.Copy(stdout, cLog); err != nil {
1675
-					logrus.Errorf("Error streaming logs (stdout): %s", err)
1676
-				}
1677
-			}
1678
-			if stderr != nil {
1679
-				cLog, err := c.ReadLog("stderr")
1680
-				if err != nil {
1681
-					logrus.Errorf("Error reading logs (stderr): %s", err)
1682
-				} else if _, err := io.Copy(stderr, cLog); err != nil {
1683
-					logrus.Errorf("Error streaming logs (stderr): %s", err)
1684
-				}
1685
-			}
1686
-		} else if err != nil {
1687
-			logrus.Errorf("Error reading logs (json): %s", err)
1666
+		logDriver, err := c.getLogger()
1667
+		cLog, err := logDriver.GetReader()
1668
+
1669
+		if err != nil {
1670
+			logrus.Errorf("Error reading logs: %s", err)
1671
+		} else if c.LogDriverType() != jsonfilelog.Name {
1672
+			logrus.Errorf("Reading logs not implemented for driver %s", c.LogDriverType())
1688 1673
 		} else {
1689 1674
 			dec := json.NewDecoder(cLog)
1690 1675
 			for {
... ...
@@ -24,6 +24,7 @@ import (
24 24
 	"github.com/docker/docker/daemon/execdriver/execdrivers"
25 25
 	"github.com/docker/docker/daemon/graphdriver"
26 26
 	_ "github.com/docker/docker/daemon/graphdriver/vfs"
27
+	"github.com/docker/docker/daemon/logger"
27 28
 	"github.com/docker/docker/daemon/network"
28 29
 	"github.com/docker/docker/daemon/networkdriver/bridge"
29 30
 	"github.com/docker/docker/graph"
... ...
@@ -798,6 +799,14 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
798 798
 		}
799 799
 	}()
800 800
 
801
+	// Verify logging driver type
802
+	if config.LogConfig.Type != "none" {
803
+		if _, err := logger.GetLogDriver(config.LogConfig.Type); err != nil {
804
+			return nil, fmt.Errorf("error finding the logging driver: %v", err)
805
+		}
806
+	}
807
+	logrus.Debugf("Using default logging driver %s", config.LogConfig.Type)
808
+
801 809
 	if config.EnableSelinuxSupport {
802 810
 		if selinuxEnabled() {
803 811
 			// As Docker on btrfs and SELinux are incompatible at present, error on both being enabled
804 812
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+package daemon
1
+
2
+// Importing packages here only to make sure their init gets called and
3
+// therefore they register themselves to the logdriver factory.
4
+import (
5
+	_ "github.com/docker/docker/daemon/logger/journald"
6
+	_ "github.com/docker/docker/daemon/logger/jsonfilelog"
7
+	_ "github.com/docker/docker/daemon/logger/syslog"
8
+)
... ...
@@ -3,6 +3,7 @@ package logger
3 3
 import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6
+	"errors"
6 7
 	"io"
7 8
 	"testing"
8 9
 	"time"
... ...
@@ -12,16 +13,14 @@ type TestLoggerJSON struct {
12 12
 	*json.Encoder
13 13
 }
14 14
 
15
-func (l *TestLoggerJSON) Log(m *Message) error {
16
-	return l.Encode(m)
17
-}
15
+func (l *TestLoggerJSON) Log(m *Message) error { return l.Encode(m) }
18 16
 
19
-func (l *TestLoggerJSON) Close() error {
20
-	return nil
21
-}
17
+func (l *TestLoggerJSON) Close() error { return nil }
18
+
19
+func (l *TestLoggerJSON) Name() string { return "json" }
22 20
 
23
-func (l *TestLoggerJSON) Name() string {
24
-	return "json"
21
+func (l *TestLoggerJSON) GetReader() (io.Reader, error) {
22
+	return nil, errors.New("not used in the test")
25 23
 }
26 24
 
27 25
 type TestLoggerText struct {
... ...
@@ -33,12 +32,12 @@ func (l *TestLoggerText) Log(m *Message) error {
33 33
 	return err
34 34
 }
35 35
 
36
-func (l *TestLoggerText) Close() error {
37
-	return nil
38
-}
36
+func (l *TestLoggerText) Close() error { return nil }
37
+
38
+func (l *TestLoggerText) Name() string { return "text" }
39 39
 
40
-func (l *TestLoggerText) Name() string {
41
-	return "text"
40
+func (l *TestLoggerText) GetReader() (io.Reader, error) {
41
+	return nil, errors.New("not used in the test")
42 42
 }
43 43
 
44 44
 func TestCopier(t *testing.T) {
45 45
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package logger
1
+
2
+import (
3
+	"fmt"
4
+	"sync"
5
+)
6
+
7
+// Creator is a method that builds a logging driver instance with given context
8
+type Creator func(Context) (Logger, error)
9
+
10
+// Context provides enough information for a logging driver to do its function
11
+type Context struct {
12
+	ContainerID   string
13
+	ContainerName string
14
+	LogPath       string
15
+}
16
+
17
+type logdriverFactory struct {
18
+	registry map[string]Creator
19
+	m        sync.Mutex
20
+}
21
+
22
+func (lf *logdriverFactory) register(name string, c Creator) error {
23
+	lf.m.Lock()
24
+	defer lf.m.Unlock()
25
+
26
+	if _, ok := lf.registry[name]; ok {
27
+		return fmt.Errorf("logger: log driver named '%s' is already registered", name)
28
+	}
29
+	lf.registry[name] = c
30
+	return nil
31
+}
32
+
33
+func (lf *logdriverFactory) get(name string) (Creator, error) {
34
+	lf.m.Lock()
35
+	defer lf.m.Unlock()
36
+
37
+	c, ok := lf.registry[name]
38
+	if !ok {
39
+		return c, fmt.Errorf("logger: no log driver named '%s' is registered", name)
40
+	}
41
+	return c, nil
42
+}
43
+
44
+var factory = &logdriverFactory{registry: make(map[string]Creator)} // global factory instance
45
+
46
+// RegisterLogDriver registers the given logging driver builder with given logging
47
+// driver name.
48
+func RegisterLogDriver(name string, c Creator) error {
49
+	return factory.register(name, c)
50
+}
51
+
52
+// GetLogDriver provides the logging driver builder for a logging driver name.
53
+func GetLogDriver(name string) (Creator, error) {
54
+	return factory.get(name)
55
+}
... ...
@@ -2,27 +2,38 @@ package journald
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"io"
5 6
 
7
+	"github.com/Sirupsen/logrus"
6 8
 	"github.com/coreos/go-systemd/journal"
7 9
 	"github.com/docker/docker/daemon/logger"
8 10
 )
9 11
 
12
+const name = "journald"
13
+
10 14
 type Journald struct {
11 15
 	Jmap map[string]string
12 16
 }
13 17
 
14
-func New(id string, name string) (logger.Logger, error) {
18
+func init() {
19
+	if err := logger.RegisterLogDriver(name, New); err != nil {
20
+		logrus.Fatal(err)
21
+	}
22
+}
23
+
24
+func New(ctx logger.Context) (logger.Logger, error) {
15 25
 	if !journal.Enabled() {
16 26
 		return nil, fmt.Errorf("journald is not enabled on this host")
17 27
 	}
18 28
 	// Strip a leading slash so that people can search for
19 29
 	// CONTAINER_NAME=foo rather than CONTAINER_NAME=/foo.
30
+	name := ctx.ContainerName
20 31
 	if name[0] == '/' {
21 32
 		name = name[1:]
22 33
 	}
23 34
 	jmap := map[string]string{
24
-		"CONTAINER_ID":      id[:12],
25
-		"CONTAINER_ID_FULL": id,
35
+		"CONTAINER_ID":      ctx.ContainerID[:12],
36
+		"CONTAINER_ID_FULL": ctx.ContainerID,
26 37
 		"CONTAINER_NAME":    name}
27 38
 	return &Journald{Jmap: jmap}, nil
28 39
 }
... ...
@@ -39,5 +50,9 @@ func (s *Journald) Close() error {
39 39
 }
40 40
 
41 41
 func (s *Journald) Name() string {
42
-	return "Journald"
42
+	return name
43
+}
44
+
45
+func (s *Journald) GetReader() (io.Reader, error) {
46
+	return nil, logger.ReadLogsNotSupported
43 47
 }
... ...
@@ -2,31 +2,46 @@ package jsonfilelog
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"io"
5 6
 	"os"
6 7
 	"sync"
7 8
 
9
+	"github.com/Sirupsen/logrus"
8 10
 	"github.com/docker/docker/daemon/logger"
9 11
 	"github.com/docker/docker/pkg/jsonlog"
10 12
 	"github.com/docker/docker/pkg/timeutils"
11 13
 )
12 14
 
15
+const (
16
+	Name = "json-file"
17
+)
18
+
13 19
 // JSONFileLogger is Logger implementation for default docker logging:
14 20
 // JSON objects to file
15 21
 type JSONFileLogger struct {
16 22
 	buf *bytes.Buffer
17 23
 	f   *os.File   // store for closing
18 24
 	mu  sync.Mutex // protects buffer
25
+
26
+	ctx logger.Context
27
+}
28
+
29
+func init() {
30
+	if err := logger.RegisterLogDriver(Name, New); err != nil {
31
+		logrus.Fatal(err)
32
+	}
19 33
 }
20 34
 
21 35
 // New creates new JSONFileLogger which writes to filename
22
-func New(filename string) (logger.Logger, error) {
23
-	log, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
36
+func New(ctx logger.Context) (logger.Logger, error) {
37
+	log, err := os.OpenFile(ctx.LogPath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
24 38
 	if err != nil {
25 39
 		return nil, err
26 40
 	}
27 41
 	return &JSONFileLogger{
28 42
 		f:   log,
29 43
 		buf: bytes.NewBuffer(nil),
44
+		ctx: ctx,
30 45
 	}, nil
31 46
 }
32 47
 
... ...
@@ -34,6 +49,7 @@ func New(filename string) (logger.Logger, error) {
34 34
 func (l *JSONFileLogger) Log(msg *logger.Message) error {
35 35
 	l.mu.Lock()
36 36
 	defer l.mu.Unlock()
37
+
37 38
 	timestamp, err := timeutils.FastMarshalJSON(msg.Timestamp)
38 39
 	if err != nil {
39 40
 		return err
... ...
@@ -52,6 +68,14 @@ func (l *JSONFileLogger) Log(msg *logger.Message) error {
52 52
 	return nil
53 53
 }
54 54
 
55
+func (l *JSONFileLogger) GetReader() (io.Reader, error) {
56
+	return os.Open(l.ctx.LogPath)
57
+}
58
+
59
+func (l *JSONFileLogger) LogPath() string {
60
+	return l.ctx.LogPath
61
+}
62
+
55 63
 // Close closes underlying file
56 64
 func (l *JSONFileLogger) Close() error {
57 65
 	return l.f.Close()
... ...
@@ -59,5 +83,5 @@ func (l *JSONFileLogger) Close() error {
59 59
 
60 60
 // Name returns name of this logger
61 61
 func (l *JSONFileLogger) Name() string {
62
-	return "JSONFile"
62
+	return Name
63 63
 }
... ...
@@ -12,18 +12,22 @@ import (
12 12
 )
13 13
 
14 14
 func TestJSONFileLogger(t *testing.T) {
15
+	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
15 16
 	tmp, err := ioutil.TempDir("", "docker-logger-")
16 17
 	if err != nil {
17 18
 		t.Fatal(err)
18 19
 	}
19 20
 	defer os.RemoveAll(tmp)
20 21
 	filename := filepath.Join(tmp, "container.log")
21
-	l, err := New(filename)
22
+	l, err := New(logger.Context{
23
+		ContainerID: cid,
24
+		LogPath:     filename,
25
+	})
22 26
 	if err != nil {
23 27
 		t.Fatal(err)
24 28
 	}
25 29
 	defer l.Close()
26
-	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
30
+
27 31
 	if err := l.Log(&logger.Message{ContainerID: cid, Line: []byte("line1"), Source: "src1"}); err != nil {
28 32
 		t.Fatal(err)
29 33
 	}
... ...
@@ -48,18 +52,22 @@ func TestJSONFileLogger(t *testing.T) {
48 48
 }
49 49
 
50 50
 func BenchmarkJSONFileLogger(b *testing.B) {
51
+	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
51 52
 	tmp, err := ioutil.TempDir("", "docker-logger-")
52 53
 	if err != nil {
53 54
 		b.Fatal(err)
54 55
 	}
55 56
 	defer os.RemoveAll(tmp)
56 57
 	filename := filepath.Join(tmp, "container.log")
57
-	l, err := New(filename)
58
+	l, err := New(logger.Context{
59
+		ContainerID: cid,
60
+		LogPath:     filename,
61
+	})
58 62
 	if err != nil {
59 63
 		b.Fatal(err)
60 64
 	}
61 65
 	defer l.Close()
62
-	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
66
+
63 67
 	testLine := "Line that thinks that it is log line from docker\n"
64 68
 	msg := &logger.Message{ContainerID: cid, Line: []byte(testLine), Source: "stderr", Timestamp: time.Now().UTC()}
65 69
 	jsonlog, err := (&jsonlog.JSONLog{Log: string(msg.Line) + "\n", Stream: msg.Source, Created: msg.Timestamp}).MarshalJSON()
... ...
@@ -1,6 +1,12 @@
1 1
 package logger
2 2
 
3
-import "time"
3
+import (
4
+	"errors"
5
+	"io"
6
+	"time"
7
+)
8
+
9
+var ReadLogsNotSupported = errors.New("configured logging reader does not support reading")
4 10
 
5 11
 // Message is datastructure that represents record from some container
6 12
 type Message struct {
... ...
@@ -15,4 +21,5 @@ type Logger interface {
15 15
 	Log(*Message) error
16 16
 	Name() string
17 17
 	Close() error
18
+	GetReader() (io.Reader, error)
18 19
 }
... ...
@@ -2,22 +2,34 @@ package syslog
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"io"
5 6
 	"log/syslog"
6 7
 	"os"
7 8
 	"path"
8 9
 
10
+	"github.com/Sirupsen/logrus"
9 11
 	"github.com/docker/docker/daemon/logger"
10 12
 )
11 13
 
14
+const name = "syslog"
15
+
12 16
 type Syslog struct {
13 17
 	writer *syslog.Writer
14 18
 }
15 19
 
16
-func New(tag string) (logger.Logger, error) {
20
+func init() {
21
+	if err := logger.RegisterLogDriver(name, New); err != nil {
22
+		logrus.Fatal(err)
23
+	}
24
+}
25
+
26
+func New(ctx logger.Context) (logger.Logger, error) {
27
+	tag := ctx.ContainerID[:12]
17 28
 	log, err := syslog.New(syslog.LOG_DAEMON, fmt.Sprintf("%s/%s", path.Base(os.Args[0]), tag))
18 29
 	if err != nil {
19 30
 		return nil, err
20 31
 	}
32
+
21 33
 	return &Syslog{
22 34
 		writer: log,
23 35
 	}, nil
... ...
@@ -35,5 +47,9 @@ func (s *Syslog) Close() error {
35 35
 }
36 36
 
37 37
 func (s *Syslog) Name() string {
38
-	return "Syslog"
38
+	return name
39
+}
40
+
41
+func (s *Syslog) GetReader() (io.Reader, error) {
42
+	return nil, logger.ReadLogsNotSupported
39 43
 }
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"time"
12 12
 
13 13
 	"github.com/Sirupsen/logrus"
14
+	"github.com/docker/docker/daemon/logger/jsonfilelog"
14 15
 	"github.com/docker/docker/pkg/jsonlog"
15 16
 	"github.com/docker/docker/pkg/stdcopy"
16 17
 	"github.com/docker/docker/pkg/tailfile"
... ...
@@ -56,32 +57,15 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
56 56
 		errStream = outStream
57 57
 	}
58 58
 
59
-	if container.LogDriverType() != "json-file" {
59
+	if container.LogDriverType() != jsonfilelog.Name {
60 60
 		return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
61 61
 	}
62
-	cLog, err := container.ReadLog("json")
63
-	if err != nil && os.IsNotExist(err) {
64
-		// Legacy logs
65
-		logrus.Debugf("Old logs format")
66
-		if config.UseStdout {
67
-			cLog, err := container.ReadLog("stdout")
68
-			if err != nil {
69
-				logrus.Errorf("Error reading logs (stdout): %s", err)
70
-			} else if _, err := io.Copy(outStream, cLog); err != nil {
71
-				logrus.Errorf("Error streaming logs (stdout): %s", err)
72
-			}
73
-		}
74
-		if config.UseStderr {
75
-			cLog, err := container.ReadLog("stderr")
76
-			if err != nil {
77
-				logrus.Errorf("Error reading logs (stderr): %s", err)
78
-			} else if _, err := io.Copy(errStream, cLog); err != nil {
79
-				logrus.Errorf("Error streaming logs (stderr): %s", err)
80
-			}
81
-		}
82
-	} else if err != nil {
83
-		logrus.Errorf("Error reading logs (json): %s", err)
62
+	logDriver, err := container.getLogger()
63
+	cLog, err := logDriver.GetReader()
64
+	if err != nil {
65
+		logrus.Errorf("Error reading logs: %s", err)
84 66
 	} else {
67
+		// json-file driver
85 68
 		if config.Tail != "all" {
86 69
 			var err error
87 70
 			lines, err = strconv.Atoi(config.Tail)