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