github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/client/driver/logging/universal_collector.go (about)

     1  // +build darwin dragonfly freebsd linux netbsd openbsd solaris windows
     2  
     3  package logging
     4  
     5  import (
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"log"
    10  	"net"
    11  	"os"
    12  	"runtime"
    13  
    14  	syslog "github.com/RackSec/srslog"
    15  	"github.com/hashicorp/nomad/client/allocdir"
    16  	cstructs "github.com/hashicorp/nomad/client/driver/structs"
    17  	"github.com/hashicorp/nomad/nomad/structs"
    18  )
    19  
    20  // LogCollectorContext holds context to configure the syslog server
    21  type LogCollectorContext struct {
    22  	// TaskName is the name of the Task
    23  	TaskName string
    24  
    25  	// AllocDir is the handle to do operations on the alloc dir of
    26  	// the task
    27  	AllocDir *allocdir.AllocDir
    28  
    29  	// LogConfig provides configuration related to log rotation
    30  	LogConfig *structs.LogConfig
    31  
    32  	// PortUpperBound is the upper bound of the ports that we can use to start
    33  	// the syslog server
    34  	PortUpperBound uint
    35  
    36  	// PortLowerBound is the lower bound of the ports that we can use to start
    37  	// the syslog server
    38  	PortLowerBound uint
    39  }
    40  
    41  // SyslogCollectorState holds the address and islation information of a launched
    42  // syslog server
    43  type SyslogCollectorState struct {
    44  	IsolationConfig *cstructs.IsolationConfig
    45  	Addr            string
    46  }
    47  
    48  // LogCollector is an interface which allows a driver to launch a log server
    49  // and update log configuration
    50  type LogCollector interface {
    51  	LaunchCollector(ctx *LogCollectorContext) (*SyslogCollectorState, error)
    52  	Exit() error
    53  	UpdateLogConfig(logConfig *structs.LogConfig) error
    54  }
    55  
    56  // SyslogCollector is a LogCollector which starts a syslog server and does
    57  // rotation to incoming stream
    58  type SyslogCollector struct {
    59  	addr      net.Addr
    60  	logConfig *structs.LogConfig
    61  	ctx       *LogCollectorContext
    62  
    63  	lro        *FileRotator
    64  	lre        *FileRotator
    65  	server     *SyslogServer
    66  	shuttler   *Shuttler
    67  	syslogChan chan *SyslogMessage
    68  	taskDir    string
    69  
    70  	logger *log.Logger
    71  }
    72  
    73  // NewSyslogCollector returns an implementation of the SyslogCollector
    74  func NewSyslogCollector(logger *log.Logger) *SyslogCollector {
    75  	return &SyslogCollector{
    76  		logger:     logger,
    77  		syslogChan: make(chan *SyslogMessage, 2048),
    78  	}
    79  }
    80  
    81  // LaunchCollector launches a new syslog server and starts writing log lines to
    82  // files and rotates them
    83  func (s *SyslogCollector) LaunchCollector(ctx *LogCollectorContext) (*SyslogCollectorState, error) {
    84  	l, err := s.getListener(ctx.PortLowerBound, ctx.PortUpperBound)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	s.logger.Printf("[DEBUG] sylog-server: launching syslog server on addr: %v", l.Addr().String())
    89  	s.ctx = ctx
    90  	// configuring the task dir
    91  	if err := s.configureTaskDir(); err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	if scfg := ctx.LogConfig.LogShuttleConfig; scfg != nil {
    96  		s.logger.Printf("[DEBUG] sylog-server: launching log shuttle")
    97  
    98  		// TODO: replace configurable vals from the task environment
    99  		// scfg.Procid = ctx.TaskEnv.ReplaceEnv(scfg.Procid)
   100  		//scfg.Appname = ctx.TaskEnv.ReplaceEnv(scfg.Appname)
   101  		//scfg.Hostname = ctx.TaskEnv.ReplaceEnv(scfg.Hostname)
   102  
   103  		shuttler, err := NewShuttler(scfg, s.logger)
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  		s.shuttler = shuttler
   108  	}
   109  	s.server = NewSyslogServer(l, s.syslogChan, s.logger)
   110  
   111  	go s.server.Start()
   112  	logFileSize := int64(ctx.LogConfig.MaxFileSizeMB * 1024 * 1024)
   113  
   114  	lro, err := NewFileRotator(ctx.AllocDir.LogDir(), fmt.Sprintf("%v.stdout", ctx.TaskName),
   115  		ctx.LogConfig.MaxFiles, logFileSize, s.logger)
   116  
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	s.lro = lro
   121  
   122  	lre, err := NewFileRotator(ctx.AllocDir.LogDir(), fmt.Sprintf("%v.stderr", ctx.TaskName),
   123  		ctx.LogConfig.MaxFiles, logFileSize, s.logger)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	s.lre = lre
   128  
   129  	go s.collectLogs(lre, lro)
   130  	syslogAddr := fmt.Sprintf("%s://%s", l.Addr().Network(), l.Addr().String())
   131  	return &SyslogCollectorState{Addr: syslogAddr}, nil
   132  }
   133  
   134  func (s *SyslogCollector) collectLogs(we io.Writer, wo io.Writer) {
   135  	for logParts := range s.syslogChan {
   136  		// If the severity of the log line is err then we write to stderr
   137  		// otherwise all messages go to stdout
   138  		if logParts.Severity == syslog.LOG_ERR {
   139  			s.lre.Write(logParts.Message)
   140  			s.lre.Write([]byte{'\n'})
   141  		} else {
   142  			s.lro.Write(logParts.Message)
   143  			s.lro.Write([]byte{'\n'})
   144  		}
   145  		if s.shuttler != nil {
   146  			s.shuttler.Write(logParts.Message)
   147  			s.shuttler.Write([]byte{'\n'})
   148  		}
   149  	}
   150  }
   151  
   152  // Exit kills the syslog server
   153  func (s *SyslogCollector) Exit() error {
   154  	if s.shuttler != nil {
   155  		s.shuttler.Shutdown()
   156  	}
   157  	s.server.Shutdown()
   158  	s.lre.Close()
   159  	s.lro.Close()
   160  	return nil
   161  }
   162  
   163  // UpdateLogConfig updates the log configuration
   164  func (s *SyslogCollector) UpdateLogConfig(logConfig *structs.LogConfig) error {
   165  	s.ctx.LogConfig = logConfig
   166  	if s.lro == nil {
   167  		return fmt.Errorf("log rotator for stdout doesn't exist")
   168  	}
   169  	s.lro.MaxFiles = logConfig.MaxFiles
   170  	s.lro.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024)
   171  
   172  	if s.lre == nil {
   173  		return fmt.Errorf("log rotator for stderr doesn't exist")
   174  	}
   175  	s.lre.MaxFiles = logConfig.MaxFiles
   176  	s.lre.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024)
   177  	return nil
   178  }
   179  
   180  // configureTaskDir sets the task dir in the SyslogCollector
   181  func (s *SyslogCollector) configureTaskDir() error {
   182  	taskDir, ok := s.ctx.AllocDir.TaskDirs[s.ctx.TaskName]
   183  	if !ok {
   184  		return fmt.Errorf("couldn't find task directory for task %v", s.ctx.TaskName)
   185  	}
   186  	s.taskDir = taskDir
   187  	return nil
   188  }
   189  
   190  // getFreePort returns a free port ready to be listened on between upper and
   191  // lower bounds
   192  func (s *SyslogCollector) getListener(lowerBound uint, upperBound uint) (net.Listener, error) {
   193  	if runtime.GOOS == "windows" {
   194  		return s.listenerTCP(lowerBound, upperBound)
   195  	}
   196  
   197  	return s.listenerUnix()
   198  }
   199  
   200  // listenerTCP creates a TCP listener using an unused port between an upper and
   201  // lower bound
   202  func (s *SyslogCollector) listenerTCP(lowerBound uint, upperBound uint) (net.Listener, error) {
   203  	for i := lowerBound; i <= upperBound; i++ {
   204  		addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("localhost:%v", i))
   205  		if err != nil {
   206  			return nil, err
   207  		}
   208  		l, err := net.ListenTCP("tcp", addr)
   209  		if err != nil {
   210  			continue
   211  		}
   212  		return l, nil
   213  	}
   214  	return nil, fmt.Errorf("No free port found")
   215  }
   216  
   217  // listenerUnix creates a Unix domain socket
   218  func (s *SyslogCollector) listenerUnix() (net.Listener, error) {
   219  	f, err := ioutil.TempFile("", "plugin")
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	path := f.Name()
   224  
   225  	if err := f.Close(); err != nil {
   226  		return nil, err
   227  	}
   228  	if err := os.Remove(path); err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	return net.Listen("unix", path)
   233  }