345 lines
9.1 KiB
Go
345 lines
9.1 KiB
Go
package log
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log/syslog"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jmhodges/clock"
|
|
"github.com/letsencrypt/boulder/test"
|
|
)
|
|
|
|
const stdoutLevel = 7
|
|
const syslogLevel = 7
|
|
|
|
func setup(t *testing.T) *impl {
|
|
// Write all logs to UDP on a high port so as to not bother the system
|
|
// which is running the test
|
|
writer, err := syslog.Dial("udp", "127.0.0.1:65530", syslog.LOG_INFO|syslog.LOG_LOCAL0, "")
|
|
test.AssertNotError(t, err, "Could not construct syslog object")
|
|
|
|
logger, err := New(writer, stdoutLevel, syslogLevel)
|
|
test.AssertNotError(t, err, "Could not construct syslog object")
|
|
impl, ok := logger.(*impl)
|
|
if !ok {
|
|
t.Fatalf("Wrong type returned from New: %T", logger)
|
|
}
|
|
return impl
|
|
}
|
|
|
|
func TestConstruction(t *testing.T) {
|
|
t.Parallel()
|
|
_ = setup(t)
|
|
}
|
|
|
|
func TestSingleton(t *testing.T) {
|
|
t.Parallel()
|
|
log1 := Get()
|
|
test.AssertNotNil(t, log1, "Logger shouldn't be nil")
|
|
|
|
log2 := Get()
|
|
test.AssertEquals(t, log1, log2)
|
|
|
|
audit := setup(t)
|
|
|
|
// Should not work
|
|
err := Set(audit)
|
|
test.AssertError(t, err, "Can't re-set")
|
|
|
|
// Verify no change
|
|
log4 := Get()
|
|
|
|
// Verify that log4 != log3
|
|
test.AssertNotEquals(t, log4, audit)
|
|
|
|
// Verify that log4 == log2 == log1
|
|
test.AssertEquals(t, log4, log2)
|
|
test.AssertEquals(t, log4, log1)
|
|
}
|
|
|
|
func TestConstructionNil(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := New(nil, stdoutLevel, syslogLevel)
|
|
test.AssertError(t, err, "Nil shouldn't be permitted.")
|
|
}
|
|
|
|
func TestEmit(t *testing.T) {
|
|
t.Parallel()
|
|
log := setup(t)
|
|
|
|
log.AuditInfo("test message")
|
|
}
|
|
|
|
func TestEmitEmpty(t *testing.T) {
|
|
t.Parallel()
|
|
log := setup(t)
|
|
|
|
log.AuditInfo("")
|
|
}
|
|
|
|
func TestStdoutLogger(t *testing.T) {
|
|
stdout := bytes.NewBuffer(nil)
|
|
stderr := bytes.NewBuffer(nil)
|
|
logger := &impl{
|
|
&stdoutWriter{
|
|
prefix: "prefix ",
|
|
level: 7,
|
|
clkFormat: "2006-01-02",
|
|
clk: clock.NewFake(),
|
|
stdout: stdout,
|
|
stderr: stderr,
|
|
},
|
|
}
|
|
|
|
logger.AuditErr("Error Audit")
|
|
logger.Warning("Warning log")
|
|
logger.Info("Info log")
|
|
|
|
test.AssertEquals(t, stdout.String(), "1970-01-01 prefix 6 log.test pcbo7wk Info log\n")
|
|
test.AssertEquals(t, stderr.String(), "1970-01-01 prefix 3 log.test 46_ghQg [AUDIT] Error Audit\n1970-01-01 prefix 4 log.test 97r2xAw Warning log\n")
|
|
}
|
|
|
|
func TestSyslogMethods(t *testing.T) {
|
|
t.Parallel()
|
|
impl := setup(t)
|
|
|
|
impl.AuditInfo("audit-logger_test.go: audit-info")
|
|
impl.AuditErr("audit-logger_test.go: audit-err")
|
|
impl.Debug("audit-logger_test.go: debug")
|
|
impl.Err("audit-logger_test.go: err")
|
|
impl.Info("audit-logger_test.go: info")
|
|
impl.Warning("audit-logger_test.go: warning")
|
|
impl.AuditInfof("audit-logger_test.go: %s", "audit-info")
|
|
impl.AuditErrf("audit-logger_test.go: %s", "audit-err")
|
|
impl.Debugf("audit-logger_test.go: %s", "debug")
|
|
impl.Errf("audit-logger_test.go: %s", "err")
|
|
impl.Infof("audit-logger_test.go: %s", "info")
|
|
impl.Warningf("audit-logger_test.go: %s", "warning")
|
|
}
|
|
|
|
func TestAuditObject(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
log := NewMock()
|
|
|
|
// Test a simple object
|
|
log.AuditObject("Prefix", "String")
|
|
if len(log.GetAllMatching("[AUDIT]")) != 1 {
|
|
t.Errorf("Failed to audit log simple object")
|
|
}
|
|
|
|
// Test a system object
|
|
log.Clear()
|
|
log.AuditObject("Prefix", t)
|
|
if len(log.GetAllMatching("[AUDIT]")) != 1 {
|
|
t.Errorf("Failed to audit log system object")
|
|
}
|
|
|
|
// Test a complex object
|
|
log.Clear()
|
|
type validObj struct {
|
|
A string
|
|
B string
|
|
}
|
|
var valid = validObj{A: "B", B: "C"}
|
|
log.AuditObject("Prefix", valid)
|
|
if len(log.GetAllMatching("[AUDIT]")) != 1 {
|
|
t.Errorf("Failed to audit log complex object")
|
|
}
|
|
|
|
// Test logging an unserializable object
|
|
log.Clear()
|
|
type invalidObj struct {
|
|
A chan string
|
|
}
|
|
|
|
var invalid = invalidObj{A: make(chan string)}
|
|
log.AuditObject("Prefix", invalid)
|
|
if len(log.GetAllMatching("[AUDIT]")) != 1 {
|
|
t.Errorf("Failed to audit log unserializable object %v", log.GetAllMatching("[AUDIT]"))
|
|
}
|
|
}
|
|
|
|
func TestTransmission(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
l, err := newUDPListener("127.0.0.1:0")
|
|
test.AssertNotError(t, err, "Failed to open log server")
|
|
defer func() {
|
|
err = l.Close()
|
|
test.AssertNotError(t, err, "listener.Close returned error")
|
|
}()
|
|
|
|
fmt.Printf("Going to %s\n", l.LocalAddr().String())
|
|
writer, err := syslog.Dial("udp", l.LocalAddr().String(), syslog.LOG_INFO|syslog.LOG_LOCAL0, "")
|
|
test.AssertNotError(t, err, "Failed to find connect to log server")
|
|
|
|
impl, err := New(writer, stdoutLevel, syslogLevel)
|
|
test.AssertNotError(t, err, "Failed to construct audit logger")
|
|
|
|
data := make([]byte, 128)
|
|
|
|
impl.AuditInfo("audit-logger_test.go: audit-info")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.AuditErr("audit-logger_test.go: audit-err")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.Debug("audit-logger_test.go: debug")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.Err("audit-logger_test.go: err")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.Info("audit-logger_test.go: info")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.Warning("audit-logger_test.go: warning")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.AuditInfof("audit-logger_test.go: %s", "audit-info")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.AuditErrf("audit-logger_test.go: %s", "audit-err")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.Debugf("audit-logger_test.go: %s", "debug")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.Errf("audit-logger_test.go: %s", "err")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.Infof("audit-logger_test.go: %s", "info")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
|
|
impl.Warningf("audit-logger_test.go: %s", "warning")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
}
|
|
|
|
func TestSyslogLevels(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
l, err := newUDPListener("127.0.0.1:0")
|
|
test.AssertNotError(t, err, "Failed to open log server")
|
|
defer func() {
|
|
err = l.Close()
|
|
test.AssertNotError(t, err, "listener.Close returned error")
|
|
}()
|
|
|
|
fmt.Printf("Going to %s\n", l.LocalAddr().String())
|
|
writer, err := syslog.Dial("udp", l.LocalAddr().String(), syslog.LOG_INFO|syslog.LOG_LOCAL0, "")
|
|
test.AssertNotError(t, err, "Failed to find connect to log server")
|
|
|
|
// create a logger with syslog level debug
|
|
impl, err := New(writer, stdoutLevel, int(syslog.LOG_DEBUG))
|
|
test.AssertNotError(t, err, "Failed to construct audit logger")
|
|
|
|
data := make([]byte, 512)
|
|
|
|
// debug messages should be sent to the logger
|
|
impl.Debug("log_test.go: debug")
|
|
_, _, err = l.ReadFrom(data)
|
|
test.AssertNotError(t, err, "Failed to find packet")
|
|
test.Assert(t, strings.Contains(string(data), "log_test.go: debug"), "Failed to find log message")
|
|
|
|
// create a logger with syslog level info
|
|
impl, err = New(writer, stdoutLevel, int(syslog.LOG_INFO))
|
|
test.AssertNotError(t, err, "Failed to construct audit logger")
|
|
|
|
// debug messages should not be sent to the logger
|
|
impl.Debug("log_test.go: debug")
|
|
n, _, err := l.ReadFrom(data)
|
|
if n != 0 && err == nil {
|
|
t.Error("Failed to withhold debug log message")
|
|
}
|
|
}
|
|
|
|
func newUDPListener(addr string) (*net.UDPConn, error) {
|
|
l, err := net.ListenPacket("udp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = l.SetDeadline(time.Now().Add(100 * time.Millisecond))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = l.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = l.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return l.(*net.UDPConn), nil
|
|
}
|
|
|
|
// TestStdoutFailure tests that audit logging with a bothWriter panics if stdout
|
|
// becomes unavailable.
|
|
func TestStdoutFailure(t *testing.T) {
|
|
// Save the stdout fd so we can restore it later
|
|
saved := os.Stdout
|
|
|
|
// Create a throw-away pipe FD to replace stdout with
|
|
_, w, err := os.Pipe()
|
|
test.AssertNotError(t, err, "failed to create pipe")
|
|
os.Stdout = w
|
|
|
|
// Setup the logger
|
|
log := setup(t)
|
|
|
|
// Close Stdout so that the fmt.Printf in bothWriter's logAtLevel
|
|
// function will return an err on next log.
|
|
err = os.Stdout.Close()
|
|
test.AssertNotError(t, err, "failed to close stdout")
|
|
|
|
// Defer a function that will check if there was a panic to recover from. If
|
|
// there wasn't then the test should fail, we were able to AuditInfo when
|
|
// Stdout was inoperable.
|
|
defer func() {
|
|
if recovered := recover(); recovered == nil {
|
|
t.Errorf("log.AuditInfo with Stdout closed did not panic")
|
|
}
|
|
|
|
// Restore stdout so that subsequent tests don't fail
|
|
os.Stdout = saved
|
|
}()
|
|
|
|
// Try to audit log something
|
|
log.AuditInfo("This should cause a panic, stdout is closed!")
|
|
}
|
|
|
|
func TestLogAtLevelEscapesNewlines(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
w := &bothWriter{sync.Mutex{},
|
|
nil,
|
|
&stdoutWriter{
|
|
stdout: &buf,
|
|
clk: clock.NewFake(),
|
|
level: 6,
|
|
},
|
|
-1,
|
|
}
|
|
w.logAtLevel(6, "foo\nbar")
|
|
|
|
test.Assert(t, strings.Contains(buf.String(), "foo\\nbar"), "failed to escape newline")
|
|
}
|