github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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. The file is also 27 // created with 0644 so the user can read it without poking permissions. By 28 // default rsyslog creates files with 0644, but in the ubuntu package, the 29 // setting is changed to 0640, which means normal users can't read the log 30 // file. Using a new action directive (new as in not-legacy), we can specify 31 // the file create mode so it doesn't use the default. 32 // 33 // I would dearly love to write the filtering action as follows to avoid setting 34 // and resetting the global $FileCreateMode, but alas, precise doesn't support it 35 // 36 // if $syslogtag startswith "juju{{namespace}}-" then 37 // action(type="omfile" 38 // File="{{logDir}}{{namespace}}/all-machines.log" 39 // Template="JujuLogFormat{{namespace}}" 40 // FileCreateMode="0644") 41 // & stop 42 // 43 // Instead we need to mess with the global FileCreateMode. We set it back 44 // to the ubuntu default after defining our rule. 45 const stateServerRsyslogTemplate = ` 46 $ModLoad imuxsock 47 $ModLoad imfile 48 49 # Messages received from remote rsyslog machines have messages prefixed with a space, 50 # so add one in for local messages too if needed. 51 $template JujuLogFormat{{namespace}},"%syslogtag:{{tagStart}}:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" 52 53 $template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%" 54 55 $RuleSet local 56 {{range $i, $stateServerIP := stateServerHosts}} 57 # start: Forwarding rule for {{$stateServerIP}} 58 $ActionQueueType LinkedList 59 $ActionQueueFileName {{logfileName}}{{namespace}}_{{$i}} 60 $ActionResumeRetryCount -1 61 $ActionQueueSaveOnShutdown on 62 $DefaultNetstreamDriver gtls 63 $DefaultNetstreamDriverCAFile {{tlsCACertPath}} 64 $ActionSendStreamDriverAuthMode anon 65 $ActionSendStreamDriverMode 1 # run driver in TLS-only mode 66 67 :syslogtag, startswith, "juju{{namespace}}-" @@{{$stateServerIP}}:{{portNumber}};LongTagForwardFormat 68 # end: Forwarding rule for {{$stateServerIP}} 69 {{end}} 70 & ~ 71 $FileCreateMode 0640 72 73 $RuleSet remote 74 $FileCreateMode 0644 75 :syslogtag, startswith, "juju{{namespace}}-" {{logDir}}/all-machines.log;JujuLogFormat{{namespace}} 76 & ~ 77 $FileCreateMode 0640 78 79 $InputFilePersistStateInterval 50 80 $InputFilePollInterval 5 81 $InputFileName {{logfilePath}} 82 $InputFileTag juju{{namespace}}-{{logfileName}}: 83 $InputFileStateFile {{logfileName}}{{namespace}} 84 $InputRunFileMonitor 85 $DefaultRuleset local 86 87 $ModLoad imtcp 88 $DefaultNetstreamDriver gtls 89 $DefaultNetstreamDriverCAFile {{tlsCACertPath}} 90 $DefaultNetstreamDriverCertFile {{tlsCertPath}} 91 $DefaultNetstreamDriverKeyFile {{tlsKeyPath}} 92 $InputTCPServerStreamDriverAuthMode anon 93 $InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode 94 95 $InputTCPServerBindRuleset remote 96 $InputTCPServerRun {{portNumber}} 97 ` 98 99 // The rsyslog conf for non-state server nodes. 100 // Messages are forwarded to the state server node. 101 // 102 // Each forwarding rule must be repeated in full for every state server and 103 // each rule must use a unique ActionQueueFileName 104 // See: http://www.rsyslog.com/doc/rsyslog_reliable_forwarding.html 105 const nodeRsyslogTemplate = ` 106 $ModLoad imuxsock 107 $ModLoad imfile 108 109 $InputFilePersistStateInterval 50 110 $InputFilePollInterval 5 111 $InputFileName {{logfilePath}} 112 $InputFileTag juju{{namespace}}-{{logfileName}}: 113 $InputFileStateFile {{logfileName}}{{namespace}} 114 $InputRunFileMonitor 115 {{range $i, $stateServerIP := stateServerHosts}} 116 # start: Forwarding rule for {{$stateServerIP}} 117 $ActionQueueType LinkedList 118 $ActionQueueFileName {{logfileName}}{{namespace}}_{{$i}} 119 $ActionResumeRetryCount -1 120 $ActionQueueSaveOnShutdown on 121 $DefaultNetstreamDriver gtls 122 $DefaultNetstreamDriverCAFile {{tlsCACertPath}} 123 $ActionSendStreamDriverAuthMode anon 124 $ActionSendStreamDriverMode 1 # run driver in TLS-only mode 125 126 $template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%" 127 :syslogtag, startswith, "juju{{namespace}}-" @@{{$stateServerIP}}:{{portNumber}};LongTagForwardFormat 128 # end: Forwarding rule for {{$stateServerIP}} 129 {{end}} 130 & ~ 131 ` 132 133 // nodeRsyslogTemplateTLSHeader is prepended to 134 // nodeRsyslogTemplate if TLS is to be used. 135 const nodeRsyslogTemplateTLSHeader = ` 136 ` 137 138 const ( 139 defaultConfigDir = "/etc/rsyslog.d" 140 defaultCACertFileName = "ca-cert.pem" 141 defaultServerCertFileName = "rsyslog-cert.pem" 142 defaultServerKeyFileName = "rsyslog-key.pem" 143 ) 144 145 // SyslogConfigRenderer instances are used to generate a rsyslog conf file. 146 type SyslogConfigRenderer interface { 147 Render() ([]byte, error) 148 } 149 150 // SyslogConfig provides a means to configure and generate rsyslog conf files for 151 // the state server nodes and unit nodes. 152 // rsyslog is configured to tail the specified log file. 153 type SyslogConfig struct { 154 // the template representing the config file contents. 155 configTemplate string 156 // the directory where the config file is written. 157 ConfigDir string 158 // the config file name. 159 ConfigFileName string 160 // the name of the log file to tail. 161 LogFileName string 162 // the addresses of the state server to which messages should be forwarded. 163 StateServerAddresses []string 164 // CA certificate file name. 165 CACertFileName string 166 // Server certificate file name. 167 ServerCertFileName string 168 // Server private key file name. 169 ServerKeyFileName string 170 // the port number for the listener 171 Port int 172 // the directory for the logfiles 173 LogDir string 174 // namespace is used when there are multiple environments on one machine 175 Namespace string 176 } 177 178 // NewForwardConfig creates a SyslogConfig instance used on unit nodes to forward log entries 179 // to the state server nodes. 180 func NewForwardConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig { 181 conf := &SyslogConfig{ 182 configTemplate: nodeRsyslogTemplate, 183 StateServerAddresses: stateServerAddresses, 184 LogFileName: logFile, 185 Port: port, 186 LogDir: logDir, 187 } 188 if namespace != "" { 189 conf.Namespace = "-" + namespace 190 } 191 return conf 192 } 193 194 // NewAccumulateConfig creates a SyslogConfig instance used to accumulate log entries from the 195 // various unit nodes. 196 func NewAccumulateConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig { 197 conf := &SyslogConfig{ 198 configTemplate: stateServerRsyslogTemplate, 199 LogFileName: logFile, 200 Port: port, 201 LogDir: logDir, 202 StateServerAddresses: stateServerAddresses, 203 } 204 if namespace != "" { 205 conf.Namespace = "-" + namespace 206 } 207 return conf 208 } 209 210 func either(a, b string) string { 211 if a != "" { 212 return a 213 } 214 return b 215 } 216 217 func (slConfig *SyslogConfig) ConfigFilePath() string { 218 dir := either(slConfig.ConfigDir, defaultConfigDir) 219 return filepath.Join(dir, slConfig.ConfigFileName) 220 } 221 222 func (slConfig *SyslogConfig) CACertPath() string { 223 filename := either(slConfig.CACertFileName, defaultCACertFileName) 224 return filepath.Join(slConfig.LogDir, filename) 225 } 226 227 func (slConfig *SyslogConfig) ServerCertPath() string { 228 filename := either(slConfig.ServerCertFileName, defaultServerCertFileName) 229 return filepath.Join(slConfig.LogDir, filename) 230 } 231 232 func (slConfig *SyslogConfig) ServerKeyPath() string { 233 filename := either(slConfig.ServerCertFileName, defaultServerKeyFileName) 234 return filepath.Join(slConfig.LogDir, filename) 235 } 236 237 // Render generates the rsyslog config. 238 func (slConfig *SyslogConfig) Render() ([]byte, error) { 239 var stateServerHosts = func() []string { 240 var hosts []string 241 for _, addr := range slConfig.StateServerAddresses { 242 parts := strings.Split(addr, ":") 243 hosts = append(hosts, parts[0]) 244 } 245 return hosts 246 } 247 248 var logFilePath = func() string { 249 return fmt.Sprintf("%s/%s.log", slConfig.LogDir, slConfig.LogFileName) 250 } 251 252 t := template.New("") 253 t.Funcs(template.FuncMap{ 254 "logfileName": func() string { return slConfig.LogFileName }, 255 "stateServerHosts": stateServerHosts, 256 "logfilePath": logFilePath, 257 "portNumber": func() int { return slConfig.Port }, 258 "logDir": func() string { return slConfig.LogDir }, 259 "namespace": func() string { return slConfig.Namespace }, 260 "tagStart": func() int { return tagOffset + len(slConfig.Namespace) }, 261 "tlsCACertPath": slConfig.CACertPath, 262 "tlsCertPath": slConfig.ServerCertPath, 263 "tlsKeyPath": slConfig.ServerKeyPath, 264 }) 265 266 // Process the rsyslog config template and echo to the conf file. 267 p, err := t.Parse(slConfig.configTemplate) 268 if err != nil { 269 return nil, err 270 } 271 var confBuf bytes.Buffer 272 if err := p.Execute(&confBuf, nil); err != nil { 273 return nil, err 274 } 275 return confBuf.Bytes(), nil 276 } 277 278 // Write generates and writes the rsyslog config. 279 func (slConfig *SyslogConfig) Write() error { 280 data, err := slConfig.Render() 281 if err != nil { 282 return err 283 } 284 err = ioutil.WriteFile(slConfig.ConfigFilePath(), data, 0644) 285 return err 286 }