github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 }