-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
127d094
commit 4381899
Showing
2 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
// Copyright 2016 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package syslog | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"time" | ||
|
||
"github.com/juju/errors" | ||
"github.com/juju/loggo" | ||
|
||
"github.com/juju/juju/logfwd" | ||
"github.com/juju/juju/standards/rfc5424" | ||
"github.com/juju/juju/standards/rfc5424/sdelements" | ||
"github.com/juju/juju/standards/tls" | ||
) | ||
|
||
// Sender exposes the underlying functionality needed by Client. | ||
type Sender interface { | ||
io.Closer | ||
|
||
// Send sends the RFC 5424 message over its connection. | ||
Send(rfc5424.Message) error | ||
} | ||
|
||
// SenderOpener supports opening a syslog connection. | ||
type SenderOpener interface { | ||
DialFunc(cfg tls.Config, timeout time.Duration) (rfc5424.DialFunc, error) | ||
|
||
Open(host string, cfg rfc5424.ClientConfig, dial rfc5424.DialFunc) (Sender, error) | ||
} | ||
|
||
type senderOpener struct{} | ||
|
||
func (senderOpener) DialFunc(cfg tls.Config, timeout time.Duration) (rfc5424.DialFunc, error) { | ||
dial, err := rfc5424.TLSDialFunc(cfg, timeout) | ||
return dial, errors.Trace(err) | ||
} | ||
|
||
func (senderOpener) Open(host string, cfg rfc5424.ClientConfig, dial rfc5424.DialFunc) (Sender, error) { | ||
sender, err := rfc5424.Open(host, cfg, dial) | ||
return sender, errors.Trace(err) | ||
} | ||
|
||
// Client is the wrapper around a syslog (RFC 5424) connection. | ||
type Client struct { | ||
// Sender is the message sender this client wraps. | ||
Sender Sender | ||
} | ||
|
||
// Open connects to a remote syslog host and wraps that connection | ||
// in a new client. | ||
func Open(cfg RawConfig) (*Client, error) { | ||
client, err := OpenForSender(cfg, &senderOpener{}) | ||
return client, errors.Trace(err) | ||
} | ||
|
||
// OpenForSender connects to a remote syslog host and wraps that | ||
// connection in a new client. | ||
func OpenForSender(cfg RawConfig, opener SenderOpener) (*Client, error) { | ||
if err := cfg.Validate(); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
|
||
sender, err := open(cfg, opener) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
|
||
client := &Client{ | ||
Sender: sender, | ||
} | ||
return client, nil | ||
} | ||
|
||
func open(cfg RawConfig, opener SenderOpener) (Sender, error) { | ||
tlsCfg := tls.Config{ | ||
RawCert: tls.RawCert{ | ||
CertPEM: cfg.ClientCert, | ||
KeyPEM: cfg.ClientKey, | ||
CACertPEM: cfg.ClientCACert, | ||
}, | ||
//ServerName: "", | ||
ExpectedServerCertPEM: cfg.ExpectedServerCert, | ||
} | ||
var timeout time.Duration | ||
dial, err := opener.DialFunc(tlsCfg, timeout) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
|
||
var clientCfg rfc5424.ClientConfig | ||
client, err := opener.Open(cfg.Host, clientCfg, dial) | ||
return client, errors.Trace(err) | ||
} | ||
|
||
// Close closes the client's connection. | ||
func (client Client) Close() error { | ||
err := client.Sender.Close() | ||
return errors.Trace(err) | ||
} | ||
|
||
// Send sends the record to the remote syslog host. | ||
func (client Client) Send(rec logfwd.Record) error { | ||
msg, err := messageFromRecord(rec) | ||
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
if err := client.Sender.Send(msg); err != nil { | ||
return errors.Trace(err) | ||
} | ||
return nil | ||
} | ||
|
||
func messageFromRecord(rec logfwd.Record) (rfc5424.Message, error) { | ||
msg := rfc5424.Message{ | ||
Header: rfc5424.Header{ | ||
Priority: rfc5424.Priority{ | ||
Severity: rfc5424.SeverityWarning, | ||
Facility: rfc5424.FacilityUser, | ||
}, | ||
Timestamp: rfc5424.Timestamp{rec.Timestamp}, | ||
Hostname: rfc5424.Hostname{ | ||
FQDN: rec.Origin.Hostname, | ||
}, | ||
AppName: rfc5424.AppName((rec.Origin.Software.Name + "-" + rec.Origin.ModelUUID)[:48]), | ||
}, | ||
StructuredData: rfc5424.StructuredData{ | ||
&sdelements.Origin{ | ||
EnterpriseID: sdelements.OriginEnterpriseID{ | ||
Number: sdelements.PrivateEnterpriseNumber(rec.Origin.Software.PrivateEnterpriseNumber), | ||
}, | ||
SoftwareName: rec.Origin.Software.Name, | ||
SoftwareVersion: rec.Origin.Software.Version, | ||
}, | ||
&sdelements.Private{ | ||
Name: "model", | ||
PEN: sdelements.PrivateEnterpriseNumber(rec.Origin.Software.PrivateEnterpriseNumber), | ||
Data: []rfc5424.StructuredDataParam{{ | ||
Name: "controller-uuid", | ||
Value: rfc5424.StructuredDataParamValue(rec.Origin.ControllerUUID), | ||
}, { | ||
Name: "model-uuid", | ||
Value: rfc5424.StructuredDataParamValue(rec.Origin.ModelUUID), | ||
}}, | ||
}, | ||
&sdelements.Private{ | ||
Name: "log", | ||
PEN: sdelements.PrivateEnterpriseNumber(rec.Origin.Software.PrivateEnterpriseNumber), | ||
Data: []rfc5424.StructuredDataParam{{ | ||
Name: "module", | ||
Value: rfc5424.StructuredDataParamValue(rec.Location.Module), | ||
}, { | ||
Name: "source", | ||
Value: rfc5424.StructuredDataParamValue(fmt.Sprintf("%s:%d", rec.Location.Filename, rec.Location.Line)), | ||
}}, | ||
}, | ||
}, | ||
Msg: rec.Message, | ||
} | ||
|
||
switch rec.Level { | ||
case loggo.ERROR: | ||
msg.Priority.Severity = rfc5424.SeverityError | ||
case loggo.WARNING: | ||
msg.Priority.Severity = rfc5424.SeverityError | ||
case loggo.INFO: | ||
msg.Priority.Severity = rfc5424.SeverityError | ||
case loggo.DEBUG, loggo.TRACE: | ||
msg.Priority.Severity = rfc5424.SeverityError | ||
default: | ||
return msg, errors.Errorf("unsupported log level %q", rec.Level) | ||
} | ||
|
||
if err := msg.Validate(); err != nil { | ||
return msg, errors.Trace(err) | ||
} | ||
return msg, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
// Copyright 2016 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package syslog_test | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/juju/loggo" | ||
"github.com/juju/testing" | ||
jc "github.com/juju/testing/checkers" | ||
"github.com/juju/version" | ||
gc "gopkg.in/check.v1" | ||
"gopkg.in/juju/names.v2" | ||
|
||
"github.com/juju/juju/logfwd" | ||
"github.com/juju/juju/logfwd/syslog" | ||
"github.com/juju/juju/standards/rfc5424" | ||
"github.com/juju/juju/standards/rfc5424/sdelements" | ||
"github.com/juju/juju/standards/tls" | ||
coretesting "github.com/juju/juju/testing" | ||
) | ||
|
||
type ClientSuite struct { | ||
testing.IsolationSuite | ||
|
||
stub *testing.Stub | ||
sender *stubSender | ||
} | ||
|
||
var _ = gc.Suite(&ClientSuite{}) | ||
|
||
func (s *ClientSuite) SetUpTest(c *gc.C) { | ||
s.IsolationSuite.SetUpTest(c) | ||
|
||
s.stub = &testing.Stub{} | ||
s.sender = &stubSender{stub: s.stub} | ||
} | ||
|
||
func (s *ClientSuite) DialFunc(cfg tls.Config, timeout time.Duration) (rfc5424.DialFunc, error) { | ||
s.stub.AddCall("DialFunc", cfg, timeout) | ||
if err := s.stub.NextErr(); err != nil { | ||
return nil, err | ||
} | ||
|
||
dial := func(network, address string) (rfc5424.Conn, error) { | ||
s.stub.AddCall("dial", network, address) | ||
if err := s.stub.NextErr(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return nil, nil | ||
} | ||
return dial, nil | ||
} | ||
|
||
func (s *ClientSuite) Open(host string, cfg rfc5424.ClientConfig, dial rfc5424.DialFunc) (syslog.Sender, error) { | ||
s.stub.AddCall("Open", host, cfg, dial) | ||
if err := s.stub.NextErr(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return s.sender, nil | ||
} | ||
|
||
func (s *ClientSuite) TestOpen(c *gc.C) { | ||
cfg := syslog.RawConfig{ | ||
Host: "a.b.c:9876", | ||
ExpectedServerCert: validCert2, | ||
ClientCACert: coretesting.CACert, | ||
ClientCert: coretesting.ServerCert, | ||
ClientKey: coretesting.ServerKey, | ||
} | ||
|
||
client, err := syslog.OpenForSender(cfg, s) | ||
c.Assert(err, jc.ErrorIsNil) | ||
|
||
s.stub.CheckCallNames(c, "DialFunc", "Open") | ||
tlsConfig := tls.Config{ | ||
RawCert: tls.RawCert{ | ||
CertPEM: coretesting.ServerCert, | ||
KeyPEM: coretesting.ServerKey, | ||
CACertPEM: coretesting.CACert, | ||
}, | ||
ExpectedServerCertPEM: validCert2, | ||
} | ||
s.stub.CheckCall(c, 0, "DialFunc", tlsConfig, time.Duration(0)) | ||
c.Check(client.Sender, gc.Equals, s.sender) | ||
} | ||
|
||
func (s *ClientSuite) TestClose(c *gc.C) { | ||
client := syslog.Client{Sender: s.sender} | ||
|
||
err := client.Close() | ||
c.Assert(err, jc.ErrorIsNil) | ||
|
||
s.stub.CheckCallNames(c, "Close") | ||
} | ||
|
||
func (s *ClientSuite) TestSend(c *gc.C) { | ||
tag := names.NewMachineTag("99") | ||
cID := "9f484882-2f18-4fd2-967d-db9663db7bea" | ||
mID := "deadbeef-2f18-4fd2-967d-db9663db7bea" | ||
ver := version.MustParse("1.2.3") | ||
ts := time.Unix(12345, 0) | ||
rec := logfwd.Record{ | ||
Origin: logfwd.OriginForMachineAgent(tag, cID, mID, ver), | ||
Timestamp: time.Unix(12345, 0), | ||
Level: loggo.ERROR, | ||
Location: logfwd.SourceLocation{ | ||
Module: "juju.x.y", | ||
Filename: "x/y/spam.go", | ||
Line: 42, | ||
}, | ||
Message: "(╯°□°)╯︵ ┻━┻", | ||
} | ||
client := syslog.Client{Sender: s.sender} | ||
|
||
err := client.Send(rec) | ||
c.Assert(err, jc.ErrorIsNil) | ||
|
||
s.stub.CheckCallNames(c, "Send") | ||
s.stub.CheckCall(c, 0, "Send", rfc5424.Message{ | ||
Header: rfc5424.Header{ | ||
Priority: rfc5424.Priority{ | ||
Severity: rfc5424.SeverityError, | ||
Facility: rfc5424.FacilityUser, | ||
}, | ||
Timestamp: rfc5424.Timestamp{ts}, | ||
Hostname: rfc5424.Hostname{ | ||
FQDN: "machine-99.deadbeef-2f18-4fd2-967d-db9663db7bea", | ||
}, | ||
AppName: "jujud-machine-agent-deadbeef-2f18-4fd2-967d-db96", | ||
}, | ||
StructuredData: rfc5424.StructuredData{ | ||
&sdelements.Origin{ | ||
EnterpriseID: sdelements.OriginEnterpriseID{ | ||
Number: 28978, | ||
}, | ||
SoftwareName: "jujud-machine-agent", | ||
SoftwareVersion: ver, | ||
}, | ||
&sdelements.Private{ | ||
Name: "model", | ||
PEN: 28978, | ||
Data: []rfc5424.StructuredDataParam{{ | ||
Name: "controller-uuid", | ||
Value: "9f484882-2f18-4fd2-967d-db9663db7bea", | ||
}, { | ||
Name: "model-uuid", | ||
Value: "deadbeef-2f18-4fd2-967d-db9663db7bea", | ||
}}, | ||
}, | ||
&sdelements.Private{ | ||
Name: "log", | ||
PEN: 28978, | ||
Data: []rfc5424.StructuredDataParam{{ | ||
Name: "module", | ||
Value: "juju.x.y", | ||
}, { | ||
Name: "source", | ||
Value: "x/y/spam.go:42", | ||
}}, | ||
}, | ||
}, | ||
Msg: "(╯°□°)╯︵ ┻━┻", | ||
}) | ||
} | ||
|
||
type stubSender struct { | ||
stub *testing.Stub | ||
} | ||
|
||
func (s *stubSender) Send(msg rfc5424.Message) error { | ||
s.stub.AddCall("Send", msg) | ||
if err := s.stub.NextErr(); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (s *stubSender) Close() error { | ||
s.stub.AddCall("Close") | ||
if err := s.stub.NextErr(); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |