4f0d95fa |
package daemon // import "github.com/docker/docker/daemon" |
066330fc |
import ( |
7d62e40f |
"context" |
066330fc |
"strconv" |
06d8f504 |
"time" |
066330fc |
|
1044093b |
"github.com/docker/docker/api/types" |
06d8f504 |
"github.com/docker/docker/api/types/backend" |
91e197d6 |
containertypes "github.com/docker/docker/api/types/container"
timetypes "github.com/docker/docker/api/types/time" |
6bb0d181 |
"github.com/docker/docker/container" |
9b782d3a |
"github.com/docker/docker/daemon/logger" |
d453fe35 |
"github.com/docker/docker/errdefs" |
b0b9a25e |
"github.com/pkg/errors" |
1009e6a4 |
"github.com/sirupsen/logrus" |
066330fc |
)
|
1044093b |
// ContainerLogs copies the container's log channel to the channel provided in
// the config. If ContainerLogs returns an error, no messages have been copied.
// and the channel will be closed without data.
//
// if it returns nil, the config channel will be active and return log
// messages until it runs out or the context is canceled. |
2c252a48 |
func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *types.ContainerLogsOptions) (messages <-chan *backend.LogMessage, isTTY bool, retErr error) { |
1044093b |
lg := logrus.WithFields(logrus.Fields{
"module": "daemon",
"method": "(*Daemon).ContainerLogs",
"container": containerName,
})
|
05dc9846 |
if !(config.ShowStdout || config.ShowStderr) { |
87a12421 |
return nil, false, errdefs.InvalidParameter(errors.New("You must choose at least one stream")) |
05dc9846 |
} |
d7d512bb |
container, err := daemon.GetContainer(containerName) |
1eecc1e7 |
if err != nil { |
ebcb7d6b |
return nil, false, err |
1eecc1e7 |
}
|
238ad8c3 |
if container.RemovalInProgress || container.Dead { |
87a12421 |
return nil, false, errdefs.Conflict(errors.New("can not get logs from container which is dead or marked for removal")) |
238ad8c3 |
}
|
05dc9846 |
if container.HostConfig.LogConfig.Type == "none" { |
ebcb7d6b |
return nil, false, logger.ErrReadLogsNotSupported{} |
066330fc |
} |
91bfed60 |
|
68e71aa3 |
cLog, cLogCreated, err := daemon.getLogger(container) |
3a8728b4 |
if err != nil { |
ebcb7d6b |
return nil, false, err |
9b782d3a |
} |
68e71aa3 |
if cLogCreated {
defer func() { |
2c252a48 |
if retErr != nil {
if err = cLog.Close(); err != nil {
logrus.Errorf("Error closing logger: %v", err)
} |
68e71aa3 |
}
}()
} |
1044093b |
|
c0391bf5 |
logReader, ok := cLog.(logger.LogReader) |
9b782d3a |
if !ok { |
ebcb7d6b |
return nil, false, logger.ErrReadLogsNotSupported{} |
066330fc |
} |
e3ba3dd5 |
|
4fdb17c7 |
follow := config.Follow && !cLogCreated |
c0391bf5 |
tailLines, err := strconv.Atoi(config.Tail)
if err != nil {
tailLines = -1
} |
589de356 |
|
06d8f504 |
var since time.Time
if config.Since != "" {
s, n, err := timetypes.ParseTimestamps(config.Since, 0)
if err != nil { |
ebcb7d6b |
return nil, false, err |
06d8f504 |
}
since = time.Unix(s, n)
} |
1044093b |
|
e8d9a61f |
var until time.Time
if config.Until != "" && config.Until != "0" {
s, n, err := timetypes.ParseTimestamps(config.Until, 0)
if err != nil {
return nil, false, err
}
until = time.Unix(s, n)
}
|
c0391bf5 |
readConfig := logger.ReadConfig{ |
06d8f504 |
Since: since, |
e8d9a61f |
Until: until, |
c0391bf5 |
Tail: tailLines,
Follow: follow,
} |
c2cf97a0 |
|
1044093b |
logs := logReader.ReadLogs(readConfig) |
ae4ee974 |
|
1044093b |
// past this point, we can't possibly return any errors, so we can just
// start a goroutine and return to tell the caller not to expect errors
// (if the caller wants to give up on logs, they have to cancel the context)
// this goroutine functions as a shim between the logger and the caller.
messageChan := make(chan *backend.LogMessage, 1)
go func() { |
2c252a48 |
if cLogCreated {
defer func() {
if err = cLog.Close(); err != nil {
logrus.Errorf("Error closing logger: %v", err)
}
}()
} |
916eabd4 |
// signal that the log reader is gone
defer logs.ConsumerGone() |
68e71aa3 |
|
1044093b |
// close the messages channel. closing is the only way to signal above
// that we're doing with logs (other than context cancel i guess).
defer close(messageChan)
lg.Debug("begin logs") |
2e4c2a6b |
defer lg.Debugf("end logs (%v)", ctx.Err())
|
1044093b |
for {
select {
// i do not believe as the system is currently designed any error
// is possible, but we should be prepared to handle it anyway. if
// we do get an error, copy only the error field to a new object so
// we don't end up with partial data in the other fields
case err := <-logs.Err:
lg.Errorf("Error streaming logs: %v", err)
select {
case <-ctx.Done():
case messageChan <- &backend.LogMessage{Err: err}:
}
return
case <-ctx.Done():
return
case msg, ok := <-logs.Msg:
// there is some kind of pool or ring buffer in the logger that
// produces these messages, and a possible future optimization
// might be to use that pool and reuse message objects
if !ok {
return
}
m := msg.AsLogMessage() // just a pointer conversion, does not copy data
// there could be a case where the reader stops accepting
// messages and the context is canceled. we need to check that
// here, or otherwise we risk blocking forever on the message
// send.
select {
case <-ctx.Done():
return
case messageChan <- m:
} |
c2cf97a0 |
} |
066330fc |
} |
1044093b |
}() |
ebcb7d6b |
return messageChan, container.Config.Tty, nil |
9b782d3a |
} |
c412300d |
|
68e71aa3 |
func (daemon *Daemon) getLogger(container *container.Container) (l logger.Logger, created bool, err error) {
container.Lock()
if container.State.Running {
l = container.LogDriver
}
container.Unlock()
if l == nil {
created = true
l, err = container.StartLogger() |
c412300d |
} |
68e71aa3 |
return |
c412300d |
}
|
1790980e |
// mergeLogConfig merges the daemon log config to the container's log config if the container's log driver is not specified.
func (daemon *Daemon) mergeAndVerifyLogConfig(cfg *containertypes.LogConfig) error {
if cfg.Type == "" {
cfg.Type = daemon.defaultLogConfig.Type
}
|
7dff3106 |
if cfg.Config == nil {
cfg.Config = make(map[string]string)
}
|
1790980e |
if cfg.Type == daemon.defaultLogConfig.Type {
for k, v := range daemon.defaultLogConfig.Config {
if _, ok := cfg.Config[k]; !ok {
cfg.Config[k] = v
} |
06808500 |
}
}
|
1790980e |
return logger.ValidateLogOpts(cfg.Type, cfg.Config) |
06808500 |
} |
b0b9a25e |
func (daemon *Daemon) setupDefaultLogConfig() error {
config := daemon.configStore
if len(config.LogConfig.Config) > 0 {
if err := logger.ValidateLogOpts(config.LogConfig.Type, config.LogConfig.Config); err != nil {
return errors.Wrap(err, "failed to set log opts")
}
}
daemon.defaultLogConfig = containertypes.LogConfig{
Type: config.LogConfig.Type,
Config: config.LogConfig.Config,
}
logrus.Debugf("Using default logging driver %s", daemon.defaultLogConfig.Type)
return nil
} |