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  }