github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/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  	syslogChan chan *SyslogMessage
    67  	taskDir    string
    68  
    69  	logger *log.Logger
    70  }
    71  
    72  // NewSyslogCollector returns an implementation of the SyslogCollector
    73  func NewSyslogCollector(logger *log.Logger) *SyslogCollector {
    74  	return &SyslogCollector{logger: logger, syslogChan: make(chan *SyslogMessage, 2048)}
    75  }
    76  
    77  // LaunchCollector launches a new syslog server and starts writing log lines to
    78  // files and rotates them
    79  func (s *SyslogCollector) LaunchCollector(ctx *LogCollectorContext) (*SyslogCollectorState, error) {
    80  	l, err := s.getListener(ctx.PortLowerBound, ctx.PortUpperBound)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	s.logger.Printf("[DEBUG] syslog-server: launching syslog server on addr: %v", l.Addr().String())
    85  	s.ctx = ctx
    86  	// configuring the task dir
    87  	if err := s.configureTaskDir(); err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	s.server = NewSyslogServer(l, s.syslogChan, s.logger)
    92  	go s.server.Start()
    93  	logFileSize := int64(ctx.LogConfig.MaxFileSizeMB * 1024 * 1024)
    94  
    95  	//FIXME There's an easier way to get this
    96  	logdir := ctx.AllocDir.TaskDirs[ctx.TaskName].LogDir
    97  	lro, err := NewFileRotator(logdir, fmt.Sprintf("%v.stdout", ctx.TaskName),
    98  		ctx.LogConfig.MaxFiles, logFileSize, s.logger)
    99  
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	s.lro = lro
   104  
   105  	lre, err := NewFileRotator(logdir, fmt.Sprintf("%v.stderr", ctx.TaskName),
   106  		ctx.LogConfig.MaxFiles, logFileSize, s.logger)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	s.lre = lre
   111  
   112  	go s.collectLogs(lre, lro)
   113  	syslogAddr := fmt.Sprintf("%s://%s", l.Addr().Network(), l.Addr().String())
   114  	return &SyslogCollectorState{Addr: syslogAddr}, nil
   115  }
   116  
   117  func (s *SyslogCollector) collectLogs(we io.Writer, wo io.Writer) {
   118  	for logParts := range s.syslogChan {
   119  		// If the severity of the log line is err then we write to stderr
   120  		// otherwise all messages go to stdout
   121  		if logParts.Severity == syslog.LOG_ERR {
   122  			s.lre.Write(logParts.Message)
   123  			s.lre.Write([]byte{'\n'})
   124  		} else {
   125  			s.lro.Write(logParts.Message)
   126  			s.lro.Write([]byte{'\n'})
   127  		}
   128  	}
   129  }
   130  
   131  // Exit kills the syslog server
   132  func (s *SyslogCollector) Exit() error {
   133  	s.server.Shutdown()
   134  	s.lre.Close()
   135  	s.lro.Close()
   136  	return nil
   137  }
   138  
   139  // UpdateLogConfig updates the log configuration
   140  func (s *SyslogCollector) UpdateLogConfig(logConfig *structs.LogConfig) error {
   141  	s.ctx.LogConfig = logConfig
   142  	if s.lro == nil {
   143  		return fmt.Errorf("log rotator for stdout doesn't exist")
   144  	}
   145  	s.lro.MaxFiles = logConfig.MaxFiles
   146  	s.lro.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024)
   147  
   148  	if s.lre == nil {
   149  		return fmt.Errorf("log rotator for stderr doesn't exist")
   150  	}
   151  	s.lre.MaxFiles = logConfig.MaxFiles
   152  	s.lre.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024)
   153  	return nil
   154  }
   155  
   156  // configureTaskDir sets the task dir in the SyslogCollector
   157  func (s *SyslogCollector) configureTaskDir() error {
   158  	taskDir, ok := s.ctx.AllocDir.TaskDirs[s.ctx.TaskName]
   159  	if !ok {
   160  		return fmt.Errorf("couldn't find task directory for task %v", s.ctx.TaskName)
   161  	}
   162  	s.taskDir = taskDir.Dir
   163  	return nil
   164  }
   165  
   166  // getFreePort returns a free port ready to be listened on between upper and
   167  // lower bounds
   168  func (s *SyslogCollector) getListener(lowerBound uint, upperBound uint) (net.Listener, error) {
   169  	if runtime.GOOS == "windows" {
   170  		return s.listenerTCP(lowerBound, upperBound)
   171  	}
   172  
   173  	return s.listenerUnix()
   174  }
   175  
   176  // listenerTCP creates a TCP listener using an unused port between an upper and
   177  // lower bound
   178  func (s *SyslogCollector) listenerTCP(lowerBound uint, upperBound uint) (net.Listener, error) {
   179  	for i := lowerBound; i <= upperBound; i++ {
   180  		addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("localhost:%v", i))
   181  		if err != nil {
   182  			return nil, err
   183  		}
   184  		l, err := net.ListenTCP("tcp", addr)
   185  		if err != nil {
   186  			continue
   187  		}
   188  		return l, nil
   189  	}
   190  	return nil, fmt.Errorf("No free port found")
   191  }
   192  
   193  // listenerUnix creates a Unix domain socket
   194  func (s *SyslogCollector) listenerUnix() (net.Listener, error) {
   195  	f, err := ioutil.TempFile("", "plugin")
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	path := f.Name()
   200  
   201  	if err := f.Close(); err != nil {
   202  		return nil, err
   203  	}
   204  	if err := os.Remove(path); err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	return net.Listen("unix", path)
   209  }