github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/log/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 imfile 47 48 $InputFilePersistStateInterval 50 49 $InputFilePollInterval 5 50 $InputFileName {{logfilePath}} 51 $InputFileTag juju{{namespace}}-{{logfileName}}: 52 $InputFileStateFile {{logfileName}}{{namespace}} 53 $InputRunFileMonitor 54 55 $ModLoad imtcp 56 $DefaultNetstreamDriver gtls 57 $DefaultNetstreamDriverCAFile {{tlsCACertPath}} 58 $DefaultNetstreamDriverCertFile {{tlsCertPath}} 59 $DefaultNetstreamDriverKeyFile {{tlsKeyPath}} 60 $InputTCPServerStreamDriverAuthMode anon 61 $InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode 62 $InputTCPServerRun {{portNumber}} 63 64 # Messages received from remote rsyslog machines have messages prefixed with a space, 65 # so add one in for local messages too if needed. 66 $template JujuLogFormat{{namespace}},"%syslogtag:{{tagStart}}:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" 67 68 $FileCreateMode 0644 69 :syslogtag, startswith, "juju{{namespace}}-" {{logDir}}/all-machines.log;JujuLogFormat{{namespace}} 70 & ~ 71 $FileCreateMode 0640 72 ` 73 74 // The rsyslog conf for non-state server nodes. 75 // Messages are forwarded to the state server node. 76 const nodeRsyslogTemplate = ` 77 $ModLoad imfile 78 79 # Enable reliable forwarding. 80 $ActionQueueType LinkedList 81 $ActionQueueFileName {{logfileName}}{{namespace}} 82 $ActionResumeRetryCount -1 83 $ActionQueueSaveOnShutdown on 84 85 $InputFilePersistStateInterval 50 86 $InputFilePollInterval 5 87 $InputFileName {{logfilePath}} 88 $InputFileTag juju{{namespace}}-{{logfileName}}: 89 $InputFileStateFile {{logfileName}}{{namespace}} 90 $InputRunFileMonitor 91 92 $DefaultNetstreamDriver gtls 93 $DefaultNetstreamDriverCAFile {{tlsCACertPath}} 94 $ActionSendStreamDriverAuthMode anon 95 $ActionSendStreamDriverMode 1 # run driver in TLS-only mode 96 97 $template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%" 98 99 :syslogtag, startswith, "juju{{namespace}}-" @@{{bootstrapIP}}:{{portNumber}};LongTagForwardFormat 100 & ~ 101 ` 102 103 // nodeRsyslogTemplateTLSHeader is prepended to 104 // nodeRsyslogTemplate if TLS is to be used. 105 const nodeRsyslogTemplateTLSHeader = ` 106 ` 107 108 const ( 109 defaultConfigDir = "/etc/rsyslog.d" 110 defaultCACertFileName = "ca-cert.pem" 111 defaultServerCertFileName = "rsyslog-cert.pem" 112 defaultServerKeyFileName = "rsyslog-key.pem" 113 ) 114 115 // SyslogConfigRenderer instances are used to generate a rsyslog conf file. 116 type SyslogConfigRenderer interface { 117 Render() ([]byte, error) 118 } 119 120 // SyslogConfig provides a means to configure and generate rsyslog conf files for 121 // the state server nodes and unit nodes. 122 // rsyslog is configured to tail the specified log file. 123 type SyslogConfig struct { 124 // the template representing the config file contents. 125 configTemplate string 126 // the directory where the config file is written. 127 ConfigDir string 128 // the config file name. 129 ConfigFileName string 130 // the name of the log file to tail. 131 LogFileName string 132 // the addresses of the state server to which messages should be forwarded. 133 StateServerAddresses []string 134 // CA certificate file name. 135 CACertFileName string 136 // Server certificate file name. 137 ServerCertFileName string 138 // Server private key file name. 139 ServerKeyFileName string 140 // the port number for the listener 141 Port int 142 // the directory for the logfiles 143 LogDir string 144 // namespace is used when there are multiple environments on one machine 145 Namespace string 146 } 147 148 // NewForwardConfig creates a SyslogConfig instance used on unit nodes to forward log entries 149 // to the state server nodes. 150 func NewForwardConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig { 151 conf := &SyslogConfig{ 152 configTemplate: nodeRsyslogTemplate, 153 StateServerAddresses: stateServerAddresses, 154 LogFileName: logFile, 155 Port: port, 156 LogDir: logDir, 157 } 158 if namespace != "" { 159 conf.Namespace = "-" + namespace 160 } 161 return conf 162 } 163 164 // NewAccumulateConfig creates a SyslogConfig instance used to accumulate log entries from the 165 // various unit nodes. 166 func NewAccumulateConfig(logFile, logDir string, port int, namespace string) *SyslogConfig { 167 conf := &SyslogConfig{ 168 configTemplate: stateServerRsyslogTemplate, 169 LogFileName: logFile, 170 Port: port, 171 LogDir: logDir, 172 } 173 if namespace != "" { 174 conf.Namespace = "-" + namespace 175 } 176 return conf 177 } 178 179 func either(a, b string) string { 180 if a != "" { 181 return a 182 } 183 return b 184 } 185 186 func (slConfig *SyslogConfig) ConfigFilePath() string { 187 dir := either(slConfig.ConfigDir, defaultConfigDir) 188 return filepath.Join(dir, slConfig.ConfigFileName) 189 } 190 191 func (slConfig *SyslogConfig) CACertPath() string { 192 filename := either(slConfig.CACertFileName, defaultCACertFileName) 193 return filepath.Join(slConfig.LogDir, filename) 194 } 195 196 func (slConfig *SyslogConfig) ServerCertPath() string { 197 filename := either(slConfig.ServerCertFileName, defaultServerCertFileName) 198 return filepath.Join(slConfig.LogDir, filename) 199 } 200 201 func (slConfig *SyslogConfig) ServerKeyPath() string { 202 filename := either(slConfig.ServerCertFileName, defaultServerKeyFileName) 203 return filepath.Join(slConfig.LogDir, filename) 204 } 205 206 // Render generates the rsyslog config. 207 func (slConfig *SyslogConfig) Render() ([]byte, error) { 208 // TODO: for HA, we will want to send to all state server addresses (maybe). 209 var bootstrapIP = func() string { 210 addr := slConfig.StateServerAddresses[0] 211 parts := strings.Split(addr, ":") 212 return parts[0] 213 } 214 215 var logFilePath = func() string { 216 return fmt.Sprintf("%s/%s.log", slConfig.LogDir, slConfig.LogFileName) 217 } 218 219 t := template.New("") 220 t.Funcs(template.FuncMap{ 221 "logfileName": func() string { return slConfig.LogFileName }, 222 "bootstrapIP": bootstrapIP, 223 "logfilePath": logFilePath, 224 "portNumber": func() int { return slConfig.Port }, 225 "logDir": func() string { return slConfig.LogDir }, 226 "namespace": func() string { return slConfig.Namespace }, 227 "tagStart": func() int { return tagOffset + len(slConfig.Namespace) }, 228 "tlsCACertPath": slConfig.CACertPath, 229 "tlsCertPath": slConfig.ServerCertPath, 230 "tlsKeyPath": slConfig.ServerKeyPath, 231 }) 232 233 // Process the rsyslog config template and echo to the conf file. 234 p, err := t.Parse(slConfig.configTemplate) 235 if err != nil { 236 return nil, err 237 } 238 var confBuf bytes.Buffer 239 if err := p.Execute(&confBuf, nil); err != nil { 240 return nil, err 241 } 242 return confBuf.Bytes(), nil 243 } 244 245 // Write generates and writes the rsyslog config. 246 func (slConfig *SyslogConfig) Write() error { 247 data, err := slConfig.Render() 248 if err != nil { 249 return err 250 } 251 err = ioutil.WriteFile(slConfig.ConfigFilePath(), data, 0644) 252 return err 253 }