github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/client/driver/logging/universal_collector.go (about)

     1  // +build !windows
     2  
     3  package logging
     4  
     5  import (
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"log/syslog"
    10  	"net"
    11  
    12  	"github.com/hashicorp/nomad/client/allocdir"
    13  	cstructs "github.com/hashicorp/nomad/client/driver/structs"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  )
    16  
    17  // LogCollectorContext holds context to configure the syslog server
    18  type LogCollectorContext struct {
    19  	// TaskName is the name of the Task
    20  	TaskName string
    21  
    22  	// AllocDir is the handle to do operations on the alloc dir of
    23  	// the task
    24  	AllocDir *allocdir.AllocDir
    25  
    26  	// LogConfig provides configuration related to log rotation
    27  	LogConfig *structs.LogConfig
    28  
    29  	// PortUpperBound is the upper bound of the ports that we can use to start
    30  	// the syslog server
    31  	PortUpperBound uint
    32  
    33  	// PortLowerBound is the lower bound of the ports that we can use to start
    34  	// the syslog server
    35  	PortLowerBound uint
    36  }
    37  
    38  // SyslogCollectorState holds the address and islation information of a launched
    39  // syslog server
    40  type SyslogCollectorState struct {
    41  	IsolationConfig *cstructs.IsolationConfig
    42  	Addr            string
    43  }
    44  
    45  // LogCollector is an interface which allows a driver to launch a log server
    46  // and update log configuration
    47  type LogCollector interface {
    48  	LaunchCollector(ctx *LogCollectorContext) (*SyslogCollectorState, error)
    49  	Exit() error
    50  	UpdateLogConfig(logConfig *structs.LogConfig) error
    51  }
    52  
    53  // SyslogCollector is a LogCollector which starts a syslog server and does
    54  // rotation to incoming stream
    55  type SyslogCollector struct {
    56  	addr      net.Addr
    57  	logConfig *structs.LogConfig
    58  	ctx       *LogCollectorContext
    59  
    60  	lro        *FileRotator
    61  	lre        *FileRotator
    62  	server     *SyslogServer
    63  	syslogChan chan *SyslogMessage
    64  	taskDir    string
    65  
    66  	logger *log.Logger
    67  }
    68  
    69  // NewSyslogCollector returns an implementation of the SyslogCollector
    70  func NewSyslogCollector(logger *log.Logger) *SyslogCollector {
    71  	return &SyslogCollector{logger: logger, syslogChan: make(chan *SyslogMessage, 2048)}
    72  }
    73  
    74  // LaunchCollector launches a new syslog server and starts writing log lines to
    75  // files and rotates them
    76  func (s *SyslogCollector) LaunchCollector(ctx *LogCollectorContext) (*SyslogCollectorState, error) {
    77  	l, err := s.getListener(ctx.PortLowerBound, ctx.PortUpperBound)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	s.logger.Printf("[DEBUG] sylog-server: launching syslog server on addr: %v", l.Addr().String())
    82  	s.ctx = ctx
    83  	// configuring the task dir
    84  	if err := s.configureTaskDir(); err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	s.server = NewSyslogServer(l, s.syslogChan, s.logger)
    89  	go s.server.Start()
    90  	logFileSize := int64(ctx.LogConfig.MaxFileSizeMB * 1024 * 1024)
    91  
    92  	lro, err := NewFileRotator(ctx.AllocDir.LogDir(), fmt.Sprintf("%v.stdout", ctx.TaskName),
    93  		ctx.LogConfig.MaxFiles, logFileSize, s.logger)
    94  
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	s.lro = lro
    99  
   100  	lre, err := NewFileRotator(ctx.AllocDir.LogDir(), fmt.Sprintf("%v.stderr", ctx.TaskName),
   101  		ctx.LogConfig.MaxFiles, logFileSize, s.logger)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	s.lre = lre
   106  
   107  	go s.collectLogs(lre, lro)
   108  	return &SyslogCollectorState{Addr: l.Addr().String()}, nil
   109  }
   110  
   111  func (s *SyslogCollector) collectLogs(we io.Writer, wo io.Writer) {
   112  	for logParts := range s.syslogChan {
   113  		// If the severity of the log line is err then we write to stderr
   114  		// otherwise all messages go to stdout
   115  		if logParts.Severity == syslog.LOG_ERR {
   116  			s.lre.Write(logParts.Message)
   117  			s.lre.Write([]byte{'\n'})
   118  		} else {
   119  			s.lro.Write(logParts.Message)
   120  			s.lro.Write([]byte{'\n'})
   121  		}
   122  	}
   123  }
   124  
   125  // Exit kills the syslog server
   126  func (s *SyslogCollector) Exit() error {
   127  	s.server.Shutdown()
   128  	s.lre.Close()
   129  	s.lro.Close()
   130  	return nil
   131  }
   132  
   133  // UpdateLogConfig updates the log configuration
   134  func (s *SyslogCollector) UpdateLogConfig(logConfig *structs.LogConfig) error {
   135  	s.ctx.LogConfig = logConfig
   136  	if s.lro == nil {
   137  		return fmt.Errorf("log rotator for stdout doesn't exist")
   138  	}
   139  	s.lro.MaxFiles = logConfig.MaxFiles
   140  	s.lro.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024)
   141  
   142  	if s.lre == nil {
   143  		return fmt.Errorf("log rotator for stderr doesn't exist")
   144  	}
   145  	s.lre.MaxFiles = logConfig.MaxFiles
   146  	s.lre.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024)
   147  	return nil
   148  }
   149  
   150  // configureTaskDir sets the task dir in the SyslogCollector
   151  func (s *SyslogCollector) configureTaskDir() error {
   152  	taskDir, ok := s.ctx.AllocDir.TaskDirs[s.ctx.TaskName]
   153  	if !ok {
   154  		return fmt.Errorf("couldn't find task directory for task %v", s.ctx.TaskName)
   155  	}
   156  	s.taskDir = taskDir
   157  	return nil
   158  }
   159  
   160  // getFreePort returns a free port ready to be listened on between upper and
   161  // lower bounds
   162  func (s *SyslogCollector) getListener(lowerBound uint, upperBound uint) (net.Listener, error) {
   163  	for i := lowerBound; i <= upperBound; i++ {
   164  		addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("localhost:%v", i))
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  		l, err := net.ListenTCP("tcp", addr)
   169  		if err != nil {
   170  			continue
   171  		}
   172  		return l, nil
   173  	}
   174  	return nil, fmt.Errorf("No free port found")
   175  }