2014-07-31 21:03:21 +00:00
|
|
|
package daemon
|
|
|
|
|
|
|
|
import (
|
2016-12-25 06:37:31 +00:00
|
|
|
"errors"
|
2014-07-31 21:03:21 +00:00
|
|
|
"strconv"
|
2016-01-27 22:09:42 +00:00
|
|
|
"time"
|
2014-07-31 21:03:21 +00:00
|
|
|
|
2016-03-25 18:33:54 +00:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
|
2015-03-26 22:22:04 +00:00
|
|
|
"github.com/Sirupsen/logrus"
|
2017-03-20 17:07:04 +00:00
|
|
|
"github.com/docker/docker/api/types"
|
2016-01-27 22:09:42 +00:00
|
|
|
"github.com/docker/docker/api/types/backend"
|
2016-09-06 18:18:12 +00:00
|
|
|
containertypes "github.com/docker/docker/api/types/container"
|
|
|
|
timetypes "github.com/docker/docker/api/types/time"
|
2015-11-12 19:55:17 +00:00
|
|
|
"github.com/docker/docker/container"
|
2015-07-01 00:40:13 +00:00
|
|
|
"github.com/docker/docker/daemon/logger"
|
2014-07-31 21:03:21 +00:00
|
|
|
)
|
|
|
|
|
2017-03-20 17:07:04 +00:00
|
|
|
// 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.
|
|
|
|
func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *types.ContainerLogsOptions) (<-chan *backend.LogMessage, error) {
|
|
|
|
lg := logrus.WithFields(logrus.Fields{
|
|
|
|
"module": "daemon",
|
|
|
|
"method": "(*Daemon).ContainerLogs",
|
|
|
|
"container": containerName,
|
|
|
|
})
|
|
|
|
|
2016-11-22 13:51:22 +00:00
|
|
|
if !(config.ShowStdout || config.ShowStderr) {
|
2017-03-20 17:07:04 +00:00
|
|
|
return nil, errors.New("You must choose at least one stream")
|
2016-11-22 13:51:22 +00:00
|
|
|
}
|
2015-12-11 17:39:28 +00:00
|
|
|
container, err := daemon.GetContainer(containerName)
|
2015-09-28 20:36:29 +00:00
|
|
|
if err != nil {
|
2017-03-20 17:07:04 +00:00
|
|
|
return nil, err
|
2015-09-28 20:36:29 +00:00
|
|
|
}
|
|
|
|
|
2017-03-24 07:55:55 +00:00
|
|
|
if container.RemovalInProgress || container.Dead {
|
2017-03-20 17:07:04 +00:00
|
|
|
return nil, errors.New("can not get logs from container which is dead or marked for removal")
|
2017-03-24 07:55:55 +00:00
|
|
|
}
|
|
|
|
|
2016-11-22 13:51:22 +00:00
|
|
|
if container.HostConfig.LogConfig.Type == "none" {
|
2017-03-20 17:07:04 +00:00
|
|
|
return nil, logger.ErrReadLogsNotSupported
|
2014-07-31 21:03:21 +00:00
|
|
|
}
|
2015-04-11 21:49:14 +00:00
|
|
|
|
2015-11-03 18:45:12 +00:00
|
|
|
cLog, err := daemon.getLogger(container)
|
2015-04-09 04:23:30 +00:00
|
|
|
if err != nil {
|
2017-03-20 17:07:04 +00:00
|
|
|
return nil, err
|
2015-07-01 00:40:13 +00:00
|
|
|
}
|
2017-03-20 17:07:04 +00:00
|
|
|
|
2015-07-03 13:50:06 +00:00
|
|
|
logReader, ok := cLog.(logger.LogReader)
|
2015-07-01 00:40:13 +00:00
|
|
|
if !ok {
|
2017-03-20 17:07:04 +00:00
|
|
|
return nil, logger.ErrReadLogsNotSupported
|
2014-07-31 21:03:21 +00:00
|
|
|
}
|
2015-05-07 01:09:27 +00:00
|
|
|
|
2015-07-03 13:50:06 +00:00
|
|
|
follow := config.Follow && container.IsRunning()
|
|
|
|
tailLines, err := strconv.Atoi(config.Tail)
|
|
|
|
if err != nil {
|
|
|
|
tailLines = -1
|
|
|
|
}
|
2015-04-23 22:08:41 +00:00
|
|
|
|
2016-01-27 22:09:42 +00:00
|
|
|
var since time.Time
|
|
|
|
if config.Since != "" {
|
|
|
|
s, n, err := timetypes.ParseTimestamps(config.Since, 0)
|
|
|
|
if err != nil {
|
2017-03-20 17:07:04 +00:00
|
|
|
return nil, err
|
2016-01-27 22:09:42 +00:00
|
|
|
}
|
|
|
|
since = time.Unix(s, n)
|
|
|
|
}
|
2017-03-20 17:07:04 +00:00
|
|
|
|
2015-07-03 13:50:06 +00:00
|
|
|
readConfig := logger.ReadConfig{
|
2016-01-27 22:09:42 +00:00
|
|
|
Since: since,
|
2015-07-03 13:50:06 +00:00
|
|
|
Tail: tailLines,
|
|
|
|
Follow: follow,
|
|
|
|
}
|
2014-10-30 19:33:26 +00:00
|
|
|
|
2017-03-20 17:07:04 +00:00
|
|
|
logs := logReader.ReadLogs(readConfig)
|
2015-12-19 14:43:10 +00:00
|
|
|
|
2017-03-20 17:07:04 +00:00
|
|
|
// 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() {
|
|
|
|
// set up some defers
|
|
|
|
defer func() {
|
|
|
|
// ok so this function, originally, was placed right after that
|
|
|
|
// logger.ReadLogs call above. I THINK that means it sets off the
|
|
|
|
// chain of events that results in the logger needing to be closed.
|
|
|
|
// i do not know if an error in time parsing above causing an early
|
|
|
|
// return will result in leaking the logger. if that is the case,
|
|
|
|
// it would also have been a bug in the original code
|
|
|
|
logs.Close()
|
|
|
|
if cLog != container.LogDriver {
|
|
|
|
// Since the logger isn't cached in the container, which
|
|
|
|
// occurs if it is running, it must get explicitly closed
|
|
|
|
// here to avoid leaking it and any file handles it has.
|
|
|
|
if err := cLog.Close(); err != nil {
|
|
|
|
logrus.Errorf("Error closing logger: %v", err)
|
|
|
|
}
|
2015-06-04 19:15:33 +00:00
|
|
|
}
|
2017-03-20 17:07:04 +00:00
|
|
|
}()
|
|
|
|
// 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")
|
|
|
|
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():
|
|
|
|
lg.Debug("logs: end stream, ctx is done: %v", ctx.Err())
|
|
|
|
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 {
|
|
|
|
lg.Debug("end logs")
|
|
|
|
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:
|
|
|
|
}
|
2014-10-30 19:33:26 +00:00
|
|
|
}
|
2014-07-31 21:03:21 +00:00
|
|
|
}
|
2017-03-20 17:07:04 +00:00
|
|
|
}()
|
|
|
|
return messageChan, nil
|
2015-07-01 00:40:13 +00:00
|
|
|
}
|
2015-11-03 18:45:12 +00:00
|
|
|
|
2015-11-12 19:55:17 +00:00
|
|
|
func (daemon *Daemon) getLogger(container *container.Container) (logger.Logger, error) {
|
|
|
|
if container.LogDriver != nil && container.IsRunning() {
|
|
|
|
return container.LogDriver, nil
|
2015-11-03 18:45:12 +00:00
|
|
|
}
|
2016-11-22 11:40:54 +00:00
|
|
|
return container.StartLogger()
|
2015-11-03 18:45:12 +00:00
|
|
|
}
|
|
|
|
|
2016-03-12 12:50:37 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2016-07-12 19:05:44 +00:00
|
|
|
if cfg.Config == nil {
|
|
|
|
cfg.Config = make(map[string]string)
|
|
|
|
}
|
|
|
|
|
2016-03-12 12:50:37 +00:00
|
|
|
if cfg.Type == daemon.defaultLogConfig.Type {
|
|
|
|
for k, v := range daemon.defaultLogConfig.Config {
|
|
|
|
if _, ok := cfg.Config[k]; !ok {
|
|
|
|
cfg.Config[k] = v
|
|
|
|
}
|
2016-03-02 12:22:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-12 12:50:37 +00:00
|
|
|
return logger.ValidateLogOpts(cfg.Type, cfg.Config)
|
2016-03-02 12:22:18 +00:00
|
|
|
}
|