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