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  }