github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/logfwd/syslog/client.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package syslog
     5  
     6  import (
     7  	"crypto/tls"
     8  	"fmt"
     9  	"io"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/rfc/rfc5424"
    15  	"github.com/juju/rfc/rfc5424/sdelements"
    16  
    17  	"github.com/juju/juju/logfwd"
    18  )
    19  
    20  // Sender exposes the underlying functionality needed by Client.
    21  type Sender interface {
    22  	io.Closer
    23  
    24  	// Send sends the RFC 5424 message over its connection.
    25  	Send(rfc5424.Message) error
    26  }
    27  
    28  // SenderOpener supports opening a syslog connection.
    29  type SenderOpener interface {
    30  	DialFunc(cfg *tls.Config, timeout time.Duration) (rfc5424.DialFunc, error)
    31  
    32  	Open(host string, cfg rfc5424.ClientConfig, dial rfc5424.DialFunc) (Sender, error)
    33  }
    34  
    35  type senderOpener struct{}
    36  
    37  func (senderOpener) DialFunc(cfg *tls.Config, timeout time.Duration) (rfc5424.DialFunc, error) {
    38  	dial, err := rfc5424.TLSDialFunc(cfg, timeout)
    39  	return dial, errors.Trace(err)
    40  }
    41  
    42  func (senderOpener) Open(host string, cfg rfc5424.ClientConfig, dial rfc5424.DialFunc) (Sender, error) {
    43  	sender, err := rfc5424.Open(host, cfg, dial)
    44  	return sender, errors.Trace(err)
    45  }
    46  
    47  // Client is the wrapper around a syslog (RFC 5424) connection.
    48  type Client struct {
    49  	// Sender is the message sender this client wraps.
    50  	Sender Sender
    51  }
    52  
    53  // Open connects to a remote syslog host and wraps that connection
    54  // in a new client.
    55  func Open(cfg RawConfig) (*Client, error) {
    56  	client, err := OpenForSender(cfg, &senderOpener{})
    57  	return client, errors.Trace(err)
    58  }
    59  
    60  // OpenForSender connects to a remote syslog host and wraps that
    61  // connection in a new client.
    62  func OpenForSender(cfg RawConfig, opener SenderOpener) (*Client, error) {
    63  	if err := cfg.Validate(); err != nil {
    64  		return nil, errors.Trace(err)
    65  	}
    66  
    67  	sender, err := open(cfg, opener)
    68  	if err != nil {
    69  		return nil, errors.Trace(err)
    70  	}
    71  
    72  	client := &Client{
    73  		Sender: sender,
    74  	}
    75  	return client, nil
    76  }
    77  
    78  func open(cfg RawConfig, opener SenderOpener) (Sender, error) {
    79  	tlsCfg, err := cfg.tlsConfig()
    80  	if err != nil {
    81  		return nil, errors.Annotate(err, "constructing TLS config")
    82  	}
    83  
    84  	var timeout time.Duration
    85  	dial, err := opener.DialFunc(tlsCfg, timeout)
    86  	if err != nil {
    87  		return nil, errors.Annotate(err, "obtaining dialer")
    88  	}
    89  
    90  	var clientCfg rfc5424.ClientConfig
    91  	client, err := opener.Open(cfg.Host, clientCfg, dial)
    92  	return client, errors.Annotate(err, "opening client connection")
    93  }
    94  
    95  // Close closes the client's connection.
    96  func (client Client) Close() error {
    97  	err := client.Sender.Close()
    98  	return errors.Trace(err)
    99  }
   100  
   101  // Send sends the record to the remote syslog host.
   102  func (client Client) Send(records []logfwd.Record) error {
   103  	for _, rec := range records {
   104  		msg, err := messageFromRecord(rec)
   105  		if err != nil {
   106  			return errors.Trace(err)
   107  		}
   108  		if err := client.Sender.Send(msg); err != nil {
   109  			return errors.Trace(err)
   110  		}
   111  	}
   112  	return nil
   113  }
   114  
   115  func messageFromRecord(rec logfwd.Record) (rfc5424.Message, error) {
   116  	msg := rfc5424.Message{
   117  		Header: rfc5424.Header{
   118  			Priority: rfc5424.Priority{
   119  				Severity: rfc5424.SeverityWarning,
   120  				Facility: rfc5424.FacilityUser,
   121  			},
   122  			Timestamp: rfc5424.Timestamp{rec.Timestamp},
   123  			Hostname: rfc5424.Hostname{
   124  				FQDN: rec.Origin.Hostname,
   125  			},
   126  			AppName: rfc5424.AppName((rec.Origin.Software.Name + "-" + rec.Origin.ModelUUID)[:48]),
   127  		},
   128  		StructuredData: rfc5424.StructuredData{
   129  			&sdelements.Origin{
   130  				EnterpriseID: sdelements.OriginEnterpriseID{
   131  					Number: sdelements.PrivateEnterpriseNumber(rec.Origin.Software.PrivateEnterpriseNumber),
   132  				},
   133  				SoftwareName:    rec.Origin.Software.Name,
   134  				SoftwareVersion: rec.Origin.Software.Version,
   135  			},
   136  			&sdelements.Private{
   137  				Name: "model",
   138  				PEN:  sdelements.PrivateEnterpriseNumber(rec.Origin.Software.PrivateEnterpriseNumber),
   139  				Data: []rfc5424.StructuredDataParam{{
   140  					Name:  "controller-uuid",
   141  					Value: rfc5424.StructuredDataParamValue(rec.Origin.ControllerUUID),
   142  				}, {
   143  					Name:  "model-uuid",
   144  					Value: rfc5424.StructuredDataParamValue(rec.Origin.ModelUUID),
   145  				}},
   146  			},
   147  			&sdelements.Private{
   148  				Name: "log",
   149  				PEN:  sdelements.PrivateEnterpriseNumber(rec.Origin.Software.PrivateEnterpriseNumber),
   150  				Data: []rfc5424.StructuredDataParam{{
   151  					Name:  "module",
   152  					Value: rfc5424.StructuredDataParamValue(rec.Location.Module),
   153  				}, {
   154  					Name:  "source",
   155  					Value: rfc5424.StructuredDataParamValue(fmt.Sprintf("%s:%d", rec.Location.Filename, rec.Location.Line)),
   156  				}},
   157  			},
   158  		},
   159  		Msg: rec.Message,
   160  	}
   161  
   162  	switch rec.Level {
   163  	case loggo.ERROR:
   164  		msg.Priority.Severity = rfc5424.SeverityError
   165  	case loggo.WARNING:
   166  		msg.Priority.Severity = rfc5424.SeverityWarning
   167  	case loggo.INFO:
   168  		msg.Priority.Severity = rfc5424.SeverityInformational
   169  	case loggo.DEBUG, loggo.TRACE:
   170  		msg.Priority.Severity = rfc5424.SeverityDebug
   171  	default:
   172  		return msg, errors.Errorf("unsupported log level %q", rec.Level)
   173  	}
   174  
   175  	if err := msg.Validate(); err != nil {
   176  		return msg, errors.Trace(err)
   177  	}
   178  	return msg, nil
   179  }