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  }