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 }