github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/client/driver/logging/universal_collector.go (about) 1 // +build !windows 2 3 package logging 4 5 import ( 6 "fmt" 7 "io" 8 "log" 9 "log/syslog" 10 "net" 11 12 "github.com/hashicorp/nomad/client/allocdir" 13 cstructs "github.com/hashicorp/nomad/client/driver/structs" 14 "github.com/hashicorp/nomad/nomad/structs" 15 ) 16 17 // LogCollectorContext holds context to configure the syslog server 18 type LogCollectorContext struct { 19 // TaskName is the name of the Task 20 TaskName string 21 22 // AllocDir is the handle to do operations on the alloc dir of 23 // the task 24 AllocDir *allocdir.AllocDir 25 26 // LogConfig provides configuration related to log rotation 27 LogConfig *structs.LogConfig 28 29 // PortUpperBound is the upper bound of the ports that we can use to start 30 // the syslog server 31 PortUpperBound uint 32 33 // PortLowerBound is the lower bound of the ports that we can use to start 34 // the syslog server 35 PortLowerBound uint 36 } 37 38 // SyslogCollectorState holds the address and islation information of a launched 39 // syslog server 40 type SyslogCollectorState struct { 41 IsolationConfig *cstructs.IsolationConfig 42 Addr string 43 } 44 45 // LogCollector is an interface which allows a driver to launch a log server 46 // and update log configuration 47 type LogCollector interface { 48 LaunchCollector(ctx *LogCollectorContext) (*SyslogCollectorState, error) 49 Exit() error 50 UpdateLogConfig(logConfig *structs.LogConfig) error 51 } 52 53 // SyslogCollector is a LogCollector which starts a syslog server and does 54 // rotation to incoming stream 55 type SyslogCollector struct { 56 addr net.Addr 57 logConfig *structs.LogConfig 58 ctx *LogCollectorContext 59 60 lro *FileRotator 61 lre *FileRotator 62 server *SyslogServer 63 syslogChan chan *SyslogMessage 64 taskDir string 65 66 logger *log.Logger 67 } 68 69 // NewSyslogCollector returns an implementation of the SyslogCollector 70 func NewSyslogCollector(logger *log.Logger) *SyslogCollector { 71 return &SyslogCollector{logger: logger, 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.Printf("[DEBUG] sylog-server: launching syslog server on addr: %v", 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 lro, err := NewFileRotator(ctx.AllocDir.LogDir(), fmt.Sprintf("%v.stdout", ctx.TaskName), 93 ctx.LogConfig.MaxFiles, logFileSize, s.logger) 94 95 if err != nil { 96 return nil, err 97 } 98 s.lro = lro 99 100 lre, err := NewFileRotator(ctx.AllocDir.LogDir(), fmt.Sprintf("%v.stderr", ctx.TaskName), 101 ctx.LogConfig.MaxFiles, logFileSize, s.logger) 102 if err != nil { 103 return nil, err 104 } 105 s.lre = lre 106 107 go s.collectLogs(lre, lro) 108 return &SyslogCollectorState{Addr: l.Addr().String()}, nil 109 } 110 111 func (s *SyslogCollector) collectLogs(we io.Writer, wo io.Writer) { 112 for logParts := range s.syslogChan { 113 // If the severity of the log line is err then we write to stderr 114 // otherwise all messages go to stdout 115 if logParts.Severity == syslog.LOG_ERR { 116 s.lre.Write(logParts.Message) 117 s.lre.Write([]byte{'\n'}) 118 } else { 119 s.lro.Write(logParts.Message) 120 s.lro.Write([]byte{'\n'}) 121 } 122 } 123 } 124 125 // Exit kills the syslog server 126 func (s *SyslogCollector) Exit() error { 127 s.server.Shutdown() 128 s.lre.Close() 129 s.lro.Close() 130 return nil 131 } 132 133 // UpdateLogConfig updates the log configuration 134 func (s *SyslogCollector) UpdateLogConfig(logConfig *structs.LogConfig) error { 135 s.ctx.LogConfig = logConfig 136 if s.lro == nil { 137 return fmt.Errorf("log rotator for stdout doesn't exist") 138 } 139 s.lro.MaxFiles = logConfig.MaxFiles 140 s.lro.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024) 141 142 if s.lre == nil { 143 return fmt.Errorf("log rotator for stderr doesn't exist") 144 } 145 s.lre.MaxFiles = logConfig.MaxFiles 146 s.lre.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024) 147 return nil 148 } 149 150 // configureTaskDir sets the task dir in the SyslogCollector 151 func (s *SyslogCollector) configureTaskDir() error { 152 taskDir, ok := s.ctx.AllocDir.TaskDirs[s.ctx.TaskName] 153 if !ok { 154 return fmt.Errorf("couldn't find task directory for task %v", s.ctx.TaskName) 155 } 156 s.taskDir = taskDir 157 return nil 158 } 159 160 // getFreePort returns a free port ready to be listened on between upper and 161 // lower bounds 162 func (s *SyslogCollector) getListener(lowerBound uint, upperBound uint) (net.Listener, error) { 163 for i := lowerBound; i <= upperBound; i++ { 164 addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("localhost:%v", i)) 165 if err != nil { 166 return nil, err 167 } 168 l, err := net.ListenTCP("tcp", addr) 169 if err != nil { 170 continue 171 } 172 return l, nil 173 } 174 return nil, fmt.Errorf("No free port found") 175 }