github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/utils/syslog/config.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package syslog 5 6 import ( 7 "bytes" 8 "fmt" 9 "io/ioutil" 10 "path/filepath" 11 "strings" 12 "text/template" 13 ) 14 15 // tagOffset represents the substring start value for the tag to return 16 // the logfileName value from the syslogtag. Substrings in syslog are 17 // indexed from 1, hence the + 1. 18 const tagOffset = len("juju-") + 1 19 20 // The rsyslog conf for state server nodes. 21 // Messages are gathered from other nodes and accumulated in an all-machines.log file. 22 // 23 // The apparmor profile is quite strict about where rsyslog can write files. 24 // Instead of poking with the profile, the local provider now logs to 25 // {{logDir}}-{{user}}-{{env name}}/all-machines.log, and a symlink is made 26 // in the local provider log dir to point to that file. By 27 // default rsyslog creates files with 0644, but in the ubuntu package, the 28 // setting is changed to 0640. Using a new action directive (new as in 29 // not-legacy), we can specify the file create mode so it doesn't use 30 // the default. 31 // 32 // I would dearly love to write the filtering action as follows to avoid setting 33 // and resetting the global $FileCreateMode, but alas, precise doesn't support it 34 // 35 // if $syslogtag startswith "juju{{namespace}}-" then 36 // action(type="omfile" 37 // File="{{logDir}}{{namespace}}/all-machines.log" 38 // Template="JujuLogFormat{{namespace}}" 39 // FileCreateMode="0644") 40 // & stop 41 // 42 // Instead we need to mess with the global FileCreateMode. We set it back 43 // to the ubuntu default after defining our rule. 44 const stateServerRsyslogTemplate = ` 45 $ModLoad imuxsock 46 $ModLoad imfile 47 48 # Messages received from remote rsyslog machines have messages prefixed with a space, 49 # so add one in for local messages too if needed. 50 $template JujuLogFormat{{namespace}},"%syslogtag:{{tagStart}}:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" 51 52 $template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%" 53 54 {{range $i, $stateServerIP := stateServerHosts}} 55 # start: Forwarding rule for {{$stateServerIP}} 56 $ActionQueueType LinkedList 57 $ActionQueueFileName {{logfileName}}{{namespace}}_{{$i}} 58 $ActionResumeRetryCount -1 59 $ActionQueueSaveOnShutdown on 60 $DefaultNetstreamDriver gtls 61 $DefaultNetstreamDriverCAFile {{tlsCACertPath}} 62 $ActionSendStreamDriverAuthMode anon 63 $ActionSendStreamDriverMode 1 # run driver in TLS-only mode 64 65 :syslogtag, startswith, "juju{{namespace}}-" @@{{$stateServerIP}}:{{portNumber}};LongTagForwardFormat 66 # end: Forwarding rule for {{$stateServerIP}} 67 {{end}} 68 :syslogtag, startswith, "juju{{namespace}}-" stop 69 70 $FileCreateMode 0600 71 72 # Maximum size for the log on this outchannel is 512MB 73 # The command to execute when an outchannel as reached its size limit cannot accept any arguments 74 # that is why we have created the helper script for executing logrotate. 75 $outchannel logRotation,{{logDir}}/all-machines.log,536870912,{{logrotateHelperPath}} 76 77 $RuleSet remote 78 $FileCreateMode 0600 79 :syslogtag, startswith, "juju{{namespace}}-" :omfile:$logRotation;JujuLogFormat{{namespace}} 80 :syslogtag, startswith, "juju{{namespace}}-" stop 81 $FileCreateMode 0600 82 83 $InputFilePersistStateInterval 50 84 $InputFilePollInterval 5 85 $InputFileName {{logfilePath}} 86 $InputFileTag juju{{namespace}}-{{logfileName}}: 87 $InputFileStateFile {{logfileName}}{{namespace}} 88 $InputRunFileMonitor 89 90 $ModLoad imtcp 91 $DefaultNetstreamDriver gtls 92 $DefaultNetstreamDriverCAFile {{tlsCACertPath}} 93 $DefaultNetstreamDriverCertFile {{tlsCertPath}} 94 $DefaultNetstreamDriverKeyFile {{tlsKeyPath}} 95 $InputTCPServerStreamDriverAuthMode anon 96 $InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode 97 $InputTCPMaxSessions 10000 # default is 200, all agents connect to all rsyslog daemons 98 99 $InputTCPServerBindRuleset remote 100 $InputTCPServerRun {{portNumber}} 101 102 # switch back to default ruleset for further rules 103 $RuleSet RSYSLOG_DefaultRuleset 104 ` 105 106 // The rsyslog conf for non-state server nodes. 107 // Messages are forwarded to the state server node. 108 // 109 // Each forwarding rule must be repeated in full for every state server and 110 // each rule must use a unique ActionQueueFileName 111 // See: http://www.rsyslog.com/doc/rsyslog_reliable_forwarding.html 112 const nodeRsyslogTemplate = ` 113 $ModLoad imuxsock 114 $ModLoad imfile 115 116 $InputFilePersistStateInterval 50 117 $InputFilePollInterval 5 118 $InputFileName {{logfilePath}} 119 $InputFileTag juju{{namespace}}-{{logfileName}}: 120 $InputFileStateFile {{logfileName}}{{namespace}} 121 $InputRunFileMonitor 122 {{range $i, $stateServerIP := stateServerHosts}} 123 # start: Forwarding rule for {{$stateServerIP}} 124 $ActionQueueType LinkedList 125 $ActionQueueFileName {{logfileName}}{{namespace}}_{{$i}} 126 $ActionResumeRetryCount -1 127 $ActionQueueSaveOnShutdown on 128 $DefaultNetstreamDriver gtls 129 $DefaultNetstreamDriverCAFile {{tlsCACertPath}} 130 $ActionSendStreamDriverAuthMode anon 131 $ActionSendStreamDriverMode 1 # run driver in TLS-only mode 132 133 $template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%" 134 :syslogtag, startswith, "juju{{namespace}}-" @@{{$stateServerIP}}:{{portNumber}};LongTagForwardFormat 135 # end: Forwarding rule for {{$stateServerIP}} 136 {{end}} 137 & ~ 138 ` 139 140 // The logrotate conf for state serve nodes. 141 // default size is 512MB, ensuring that the log + one rotation 142 // will never take up more than 1GB of space. 143 // 144 // The size of the logrotate configuration is low for 145 // a very specific reason, see config comment. 146 const logrotateConf = ` 147 {{.LogDir}}/all-machines.log { 148 # rsyslogd informs logrotate when to rotate. 149 # The size specified here must be less than or equal to the log size 150 # when rsyslogd informs logrotate, or logrotate will take no action. 151 # The size value is otherwise unimportant. 152 size 1K 153 # maximum of one old file 154 rotate 1 155 # counting old files starts at 1 rather than 0 156 start 1 157 # ensure new file is created with the correct permissions 158 create 600 159 # reload rsyslog after rotation so it will use the new file 160 postrotate 161 service rsyslog reload 162 endscript 163 } 164 ` 165 166 var logrotateConfTemplate = template.Must(template.New("logrotate.conf").Parse(logrotateConf)) 167 168 // The logrotate helper script for state server nodes. 169 // We specify a state file to ensure we have the proper permissions. 170 const logrotateHelper = ` 171 /usr/sbin/logrotate -s {{.LogDir}}/logrotate.state {{.LogrotateConfPath}} 172 ` 173 174 var logrotateHelperTemplate = template.Must(template.New("logrotate.run").Parse(logrotateHelper)) 175 176 // nodeRsyslogTemplateTLSHeader is prepended to 177 // nodeRsyslogTemplate if TLS is to be used. 178 const nodeRsyslogTemplateTLSHeader = ` 179 ` 180 181 const ( 182 defaultConfigDir = "/etc/rsyslog.d" 183 defaultCACertFileName = "ca-cert.pem" 184 defaultServerCertFileName = "rsyslog-cert.pem" 185 defaultServerKeyFileName = "rsyslog-key.pem" 186 defaultLogrotateConfFileName = "logrotate.conf" 187 defaultLogrotateHelperFileName = "logrotate.run" 188 ) 189 190 // SyslogConfigRenderer instances are used to generate a rsyslog conf file. 191 type SyslogConfigRenderer interface { 192 Render() ([]byte, error) 193 } 194 195 // SyslogConfig provides a means to configure and generate rsyslog conf files for 196 // the state server nodes and unit nodes. 197 // rsyslog is configured to tail the specified log file. 198 type SyslogConfig struct { 199 // the template representing the config file contents. 200 configTemplate string 201 // the directory where the config file is written. 202 ConfigDir string 203 // the config file name. 204 ConfigFileName string 205 // the name of the log file to tail. 206 LogFileName string 207 // the name of the logrotate configuration file. 208 LogrotateConfFileName string 209 // the name of the script that executes the logrotate command. 210 LogrotateHelperFileName string 211 // the addresses of the state server to which messages should be forwarded. 212 StateServerAddresses []string 213 // CA certificate file name. 214 CACertFileName string 215 // Server certificate file name. 216 ServerCertFileName string 217 // Server private key file name. 218 ServerKeyFileName string 219 // the port number for the listener 220 Port int 221 // the directory for the logfiles 222 LogDir string 223 // namespace is used when there are multiple environments on one machine 224 Namespace string 225 } 226 227 // NewForwardConfig creates a SyslogConfig instance used on unit nodes to forward log entries 228 // to the state server nodes. 229 func NewForwardConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig { 230 conf := &SyslogConfig{ 231 configTemplate: nodeRsyslogTemplate, 232 StateServerAddresses: stateServerAddresses, 233 LogFileName: logFile, 234 Port: port, 235 LogDir: logDir, 236 } 237 if namespace != "" { 238 conf.Namespace = "-" + namespace 239 } 240 return conf 241 } 242 243 // NewAccumulateConfig creates a SyslogConfig instance used to accumulate log entries from the 244 // various unit nodes. 245 func NewAccumulateConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig { 246 conf := &SyslogConfig{ 247 configTemplate: stateServerRsyslogTemplate, 248 LogFileName: logFile, 249 Port: port, 250 LogDir: logDir, 251 StateServerAddresses: stateServerAddresses, 252 } 253 if namespace != "" { 254 conf.Namespace = "-" + namespace 255 } 256 return conf 257 } 258 259 func either(a, b string) string { 260 if a != "" { 261 return a 262 } 263 return b 264 } 265 266 func (slConfig *SyslogConfig) ConfigFilePath() string { 267 dir := either(slConfig.ConfigDir, defaultConfigDir) 268 return filepath.Join(dir, slConfig.ConfigFileName) 269 } 270 271 func (slConfig *SyslogConfig) CACertPath() string { 272 filename := either(slConfig.CACertFileName, defaultCACertFileName) 273 return filepath.Join(slConfig.LogDir, filename) 274 } 275 276 func (slConfig *SyslogConfig) ServerCertPath() string { 277 filename := either(slConfig.ServerCertFileName, defaultServerCertFileName) 278 return filepath.Join(slConfig.LogDir, filename) 279 } 280 281 func (slConfig *SyslogConfig) ServerKeyPath() string { 282 filename := either(slConfig.ServerCertFileName, defaultServerKeyFileName) 283 return filepath.Join(slConfig.LogDir, filename) 284 } 285 286 // LogrotateConfPath returns the the the entire logrotate.conf path including filename. 287 func (slConfig *SyslogConfig) LogrotateConfPath() string { 288 filename := either(slConfig.LogrotateConfFileName, defaultLogrotateConfFileName) 289 return filepath.Join(slConfig.LogDir, filename) 290 } 291 292 // LogrotateHelperPath returns the entire logrotate.helper path including filename. 293 func (slConfig *SyslogConfig) LogrotateHelperPath() string { 294 filename := either(slConfig.LogrotateHelperFileName, defaultLogrotateHelperFileName) 295 return filepath.Join(slConfig.LogDir, filename) 296 } 297 298 // LogrotateConfFile returns a ready to write to disk byte array of the logrotate.conf file. 299 func (slConfig *SyslogConfig) LogrotateConfFile() ([]byte, error) { 300 return slConfig.logrotateRender(logrotateConfTemplate) 301 } 302 303 // LogrotateHelperFile returns a ready to write to disk byte array of the logrotate.helper file. 304 func (slConfig *SyslogConfig) LogrotateHelperFile() ([]byte, error) { 305 return slConfig.logrotateRender(logrotateHelperTemplate) 306 } 307 308 func (slConfig *SyslogConfig) logrotateRender(t *template.Template) ([]byte, error) { 309 var buffer bytes.Buffer 310 if err := t.Execute(&buffer, slConfig); err != nil { 311 return nil, err 312 } 313 return buffer.Bytes(), nil 314 } 315 316 // Render generates the rsyslog config. 317 func (slConfig *SyslogConfig) Render() ([]byte, error) { 318 var stateServerHosts = func() []string { 319 var hosts []string 320 for _, addr := range slConfig.StateServerAddresses { 321 parts := strings.Split(addr, ":") 322 hosts = append(hosts, parts[0]) 323 } 324 return hosts 325 } 326 327 var logFilePath = func() string { 328 return fmt.Sprintf("%s/%s.log", slConfig.LogDir, slConfig.LogFileName) 329 } 330 331 t := template.New("syslogConfig") 332 t.Funcs(template.FuncMap{ 333 "logfileName": func() string { return slConfig.LogFileName }, 334 "stateServerHosts": stateServerHosts, 335 "logfilePath": logFilePath, 336 "portNumber": func() int { return slConfig.Port }, 337 "logDir": func() string { return slConfig.LogDir }, 338 "namespace": func() string { return slConfig.Namespace }, 339 "tagStart": func() int { return tagOffset + len(slConfig.Namespace) }, 340 "tlsCACertPath": slConfig.CACertPath, 341 "tlsCertPath": slConfig.ServerCertPath, 342 "tlsKeyPath": slConfig.ServerKeyPath, 343 "logrotateHelperPath": slConfig.LogrotateHelperPath, 344 }) 345 346 // Process the rsyslog config template and echo to the conf file. 347 p, err := t.Parse(slConfig.configTemplate) 348 if err != nil { 349 return nil, err 350 } 351 var confBuf bytes.Buffer 352 if err := p.Execute(&confBuf, nil); err != nil { 353 return nil, err 354 } 355 return confBuf.Bytes(), nil 356 } 357 358 // Write generates and writes the rsyslog config. 359 func (slConfig *SyslogConfig) Write() error { 360 data, err := slConfig.Render() 361 if err != nil { 362 return err 363 } 364 err = ioutil.WriteFile(slConfig.ConfigFilePath(), data, 0644) 365 return err 366 }