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 }