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 }