github.com/smithx10/nomad@v0.9.1-rc1/client/logmon/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  	"net"
    10  	"os"
    11  	"runtime"
    12  
    13  	syslog "github.com/RackSec/srslog"
    14  	hclog "github.com/hashicorp/go-hclog"
    15  	"github.com/hashicorp/nomad/client/allocdir"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  )
    18  
    19  // LogCollectorContext holds context to configure the syslog server
    20  type LogCollectorContext struct {
    21  	// TaskName is the name of the Task
    22  	TaskName string
    23  
    24  	// AllocDir is the handle to do operations on the alloc dir of
    25  	// the task
    26  	AllocDir *allocdir.AllocDir
    27  
    28  	// LogConfig provides configuration related to log rotation
    29  	LogConfig *structs.LogConfig
    30  
    31  	// PortUpperBound is the upper bound of the ports that we can use to start
    32  	// the syslog server
    33  	PortUpperBound uint
    34  
    35  	// PortLowerBound is the lower bound of the ports that we can use to start
    36  	// the syslog server
    37  	PortLowerBound uint
    38  }
    39  
    40  // SyslogCollectorState holds the address and isolation information of a launched
    41  // syslog server
    42  type SyslogCollectorState struct {
    43  	Addr string
    44  }
    45  
    46  // LogCollector is an interface which allows a driver to launch a log server
    47  // and update log configuration
    48  type LogCollector interface {
    49  	LaunchCollector(ctx *LogCollectorContext) (*SyslogCollectorState, error)
    50  	Exit() error
    51  	UpdateLogConfig(logConfig *structs.LogConfig) error
    52  }
    53  
    54  // SyslogCollector is a LogCollector which starts a syslog server and does
    55  // rotation to incoming stream
    56  type SyslogCollector struct {
    57  	ctx *LogCollectorContext
    58  
    59  	lro        *FileRotator
    60  	lre        *FileRotator
    61  	server     *SyslogServer
    62  	syslogChan chan *SyslogMessage
    63  	taskDir    string
    64  
    65  	logger hclog.Logger
    66  }
    67  
    68  // NewSyslogCollector returns an implementation of the SyslogCollector
    69  func NewSyslogCollector(logger hclog.Logger) *SyslogCollector {
    70  	return &SyslogCollector{logger: logger.Named("syslog-server"),
    71  		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.Debug("launching syslog server on addr", "addr", 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  	//FIXME There's an easier way to get this
    93  	logdir := ctx.AllocDir.TaskDirs[ctx.TaskName].LogDir
    94  	lro, err := NewFileRotator(logdir, fmt.Sprintf("%v.stdout", ctx.TaskName),
    95  		ctx.LogConfig.MaxFiles, logFileSize, s.logger)
    96  
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	s.lro = lro
   101  
   102  	lre, err := NewFileRotator(logdir, fmt.Sprintf("%v.stderr", ctx.TaskName),
   103  		ctx.LogConfig.MaxFiles, logFileSize, s.logger)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	s.lre = lre
   108  
   109  	go s.collectLogs(lre, lro)
   110  	syslogAddr := fmt.Sprintf("%s://%s", l.Addr().Network(), l.Addr().String())
   111  	return &SyslogCollectorState{Addr: syslogAddr}, nil
   112  }
   113  
   114  func (s *SyslogCollector) collectLogs(we io.Writer, wo io.Writer) {
   115  	for logParts := range s.syslogChan {
   116  		// If the severity of the log line is err then we write to stderr
   117  		// otherwise all messages go to stdout
   118  		if logParts.Severity == syslog.LOG_ERR {
   119  			s.lre.Write(logParts.Message)
   120  			s.lre.Write([]byte{'\n'})
   121  		} else {
   122  			s.lro.Write(logParts.Message)
   123  			s.lro.Write([]byte{'\n'})
   124  		}
   125  	}
   126  }
   127  
   128  // Exit kills the syslog server
   129  func (s *SyslogCollector) Exit() error {
   130  	s.server.Shutdown()
   131  	s.lre.Close()
   132  	s.lro.Close()
   133  	return nil
   134  }
   135  
   136  // UpdateLogConfig updates the log configuration
   137  func (s *SyslogCollector) UpdateLogConfig(logConfig *structs.LogConfig) error {
   138  	s.ctx.LogConfig = logConfig
   139  	if s.lro == nil {
   140  		return fmt.Errorf("log rotator for stdout doesn't exist")
   141  	}
   142  	s.lro.MaxFiles = logConfig.MaxFiles
   143  	s.lro.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024)
   144  
   145  	if s.lre == nil {
   146  		return fmt.Errorf("log rotator for stderr doesn't exist")
   147  	}
   148  	s.lre.MaxFiles = logConfig.MaxFiles
   149  	s.lre.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024)
   150  	return nil
   151  }
   152  
   153  // configureTaskDir sets the task dir in the SyslogCollector
   154  func (s *SyslogCollector) configureTaskDir() error {
   155  	taskDir, ok := s.ctx.AllocDir.TaskDirs[s.ctx.TaskName]
   156  	if !ok {
   157  		return fmt.Errorf("couldn't find task directory for task %v", s.ctx.TaskName)
   158  	}
   159  	s.taskDir = taskDir.Dir
   160  	return nil
   161  }
   162  
   163  // getFreePort returns a free port ready to be listened on between upper and
   164  // lower bounds
   165  func (s *SyslogCollector) getListener(lowerBound uint, upperBound uint) (net.Listener, error) {
   166  	if runtime.GOOS == "windows" {
   167  		return s.listenerTCP(lowerBound, upperBound)
   168  	}
   169  
   170  	return s.listenerUnix()
   171  }
   172  
   173  // listenerTCP creates a TCP listener using an unused port between an upper and
   174  // lower bound
   175  func (s *SyslogCollector) listenerTCP(lowerBound uint, upperBound uint) (net.Listener, error) {
   176  	for i := lowerBound; i <= upperBound; i++ {
   177  		addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("localhost:%v", i))
   178  		if err != nil {
   179  			return nil, err
   180  		}
   181  		l, err := net.ListenTCP("tcp", addr)
   182  		if err != nil {
   183  			continue
   184  		}
   185  		return l, nil
   186  	}
   187  	return nil, fmt.Errorf("No free port found")
   188  }
   189  
   190  // listenerUnix creates a Unix domain socket
   191  func (s *SyslogCollector) listenerUnix() (net.Listener, error) {
   192  	f, err := ioutil.TempFile("", "plugin")
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	path := f.Name()
   197  
   198  	if err := f.Close(); err != nil {
   199  		return nil, err
   200  	}
   201  	if err := os.Remove(path); err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	return net.Listen("unix", path)
   206  }