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 }