github.com/letsencrypt/boulder@v0.20251208.0/log/log_test.go (about)

     1  package log
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log/syslog"
     7  	"net"
     8  	"os"
     9  	"strings"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/jmhodges/clock"
    15  
    16  	"github.com/letsencrypt/boulder/test"
    17  )
    18  
    19  const stdoutLevel = 7
    20  const syslogLevel = 7
    21  
    22  func setup(t *testing.T) *impl {
    23  	// Write all logs to UDP on a high port so as to not bother the system
    24  	// which is running the test
    25  	writer, err := syslog.Dial("udp", "127.0.0.1:65530", syslog.LOG_INFO|syslog.LOG_LOCAL0, "")
    26  	test.AssertNotError(t, err, "Could not construct syslog object")
    27  
    28  	logger, err := New(writer, stdoutLevel, syslogLevel)
    29  	test.AssertNotError(t, err, "Could not construct syslog object")
    30  	impl, ok := logger.(*impl)
    31  	if !ok {
    32  		t.Fatalf("Wrong type returned from New: %T", logger)
    33  	}
    34  	return impl
    35  }
    36  
    37  func TestConstruction(t *testing.T) {
    38  	t.Parallel()
    39  	_ = setup(t)
    40  }
    41  
    42  func TestSingleton(t *testing.T) {
    43  	t.Parallel()
    44  	log1 := Get()
    45  	test.AssertNotNil(t, log1, "Logger shouldn't be nil")
    46  
    47  	log2 := Get()
    48  	test.AssertEquals(t, log1, log2)
    49  
    50  	audit := setup(t)
    51  
    52  	// Should not work
    53  	err := Set(audit)
    54  	test.AssertError(t, err, "Can't re-set")
    55  
    56  	// Verify no change
    57  	log4 := Get()
    58  
    59  	// Verify that log4 != log3
    60  	test.AssertNotEquals(t, log4, audit)
    61  
    62  	// Verify that log4 == log2 == log1
    63  	test.AssertEquals(t, log4, log2)
    64  	test.AssertEquals(t, log4, log1)
    65  }
    66  
    67  func TestConstructionNil(t *testing.T) {
    68  	t.Parallel()
    69  	_, err := New(nil, stdoutLevel, syslogLevel)
    70  	test.AssertError(t, err, "Nil shouldn't be permitted.")
    71  }
    72  
    73  func TestEmit(t *testing.T) {
    74  	t.Parallel()
    75  	log := setup(t)
    76  
    77  	log.AuditInfo("test message")
    78  }
    79  
    80  func TestEmitEmpty(t *testing.T) {
    81  	t.Parallel()
    82  	log := setup(t)
    83  
    84  	log.AuditInfo("")
    85  }
    86  
    87  func TestStdoutLogger(t *testing.T) {
    88  	stdout := bytes.NewBuffer(nil)
    89  	stderr := bytes.NewBuffer(nil)
    90  	logger := &impl{
    91  		&stdoutWriter{
    92  			prefix:    "prefix ",
    93  			level:     7,
    94  			clkFormat: "2006-01-02",
    95  			clk:       clock.NewFake(),
    96  			stdout:    stdout,
    97  			stderr:    stderr,
    98  		},
    99  	}
   100  
   101  	logger.AuditErr("Error Audit")
   102  	logger.Warning("Warning log")
   103  	logger.Info("Info log")
   104  
   105  	test.AssertEquals(t, stdout.String(), "1970-01-01 prefix 6 log.test JSP6nQ Info log\n")
   106  	test.AssertEquals(t, stderr.String(), "1970-01-01 prefix 3 log.test 4xe4gA [AUDIT] Error Audit\n1970-01-01 prefix 4 log.test d52dyA Warning log\n")
   107  }
   108  
   109  func TestSyslogMethods(t *testing.T) {
   110  	t.Parallel()
   111  	impl := setup(t)
   112  
   113  	impl.AuditInfo("audit-logger_test.go: audit-info")
   114  	impl.AuditErr("audit-logger_test.go: audit-err")
   115  	impl.Debug("audit-logger_test.go: debug")
   116  	impl.Err("audit-logger_test.go: err")
   117  	impl.Info("audit-logger_test.go: info")
   118  	impl.Warning("audit-logger_test.go: warning")
   119  	impl.AuditInfof("audit-logger_test.go: %s", "audit-info")
   120  	impl.AuditErrf("audit-logger_test.go: %s", "audit-err")
   121  	impl.Debugf("audit-logger_test.go: %s", "debug")
   122  	impl.Errf("audit-logger_test.go: %s", "err")
   123  	impl.Infof("audit-logger_test.go: %s", "info")
   124  	impl.Warningf("audit-logger_test.go: %s", "warning")
   125  }
   126  
   127  func TestAuditObject(t *testing.T) {
   128  	t.Parallel()
   129  
   130  	log := NewMock()
   131  
   132  	// Test a simple object
   133  	log.AuditObject("Prefix", "String")
   134  	if len(log.GetAllMatching("[AUDIT]")) != 1 {
   135  		t.Errorf("Failed to audit log simple object")
   136  	}
   137  
   138  	// Test a system object
   139  	log.Clear()
   140  	log.AuditObject("Prefix", t)
   141  	if len(log.GetAllMatching("[AUDIT]")) != 1 {
   142  		t.Errorf("Failed to audit log system object")
   143  	}
   144  
   145  	// Test a complex object
   146  	log.Clear()
   147  	type validObj struct {
   148  		A string
   149  		B string
   150  	}
   151  	var valid = validObj{A: "B", B: "C"}
   152  	log.AuditObject("Prefix", valid)
   153  	if len(log.GetAllMatching("[AUDIT]")) != 1 {
   154  		t.Errorf("Failed to audit log complex object")
   155  	}
   156  
   157  	// Test logging an unserializable object
   158  	log.Clear()
   159  	type invalidObj struct {
   160  		A chan string
   161  	}
   162  
   163  	var invalid = invalidObj{A: make(chan string)}
   164  	log.AuditObject("Prefix", invalid)
   165  	if len(log.GetAllMatching("[AUDIT]")) != 1 {
   166  		t.Errorf("Failed to audit log unserializable object %v", log.GetAllMatching("[AUDIT]"))
   167  	}
   168  }
   169  
   170  func TestTransmission(t *testing.T) {
   171  	t.Parallel()
   172  
   173  	l, err := newUDPListener("127.0.0.1:0")
   174  	test.AssertNotError(t, err, "Failed to open log server")
   175  	defer func() {
   176  		err = l.Close()
   177  		test.AssertNotError(t, err, "listener.Close returned error")
   178  	}()
   179  
   180  	fmt.Printf("Going to %s\n", l.LocalAddr().String())
   181  	writer, err := syslog.Dial("udp", l.LocalAddr().String(), syslog.LOG_INFO|syslog.LOG_LOCAL0, "")
   182  	test.AssertNotError(t, err, "Failed to find connect to log server")
   183  
   184  	impl, err := New(writer, stdoutLevel, syslogLevel)
   185  	test.AssertNotError(t, err, "Failed to construct audit logger")
   186  
   187  	data := make([]byte, 128)
   188  
   189  	impl.AuditInfo("audit-logger_test.go: audit-info")
   190  	_, _, err = l.ReadFrom(data)
   191  	test.AssertNotError(t, err, "Failed to find packet")
   192  
   193  	impl.AuditErr("audit-logger_test.go: audit-err")
   194  	_, _, err = l.ReadFrom(data)
   195  	test.AssertNotError(t, err, "Failed to find packet")
   196  
   197  	impl.Debug("audit-logger_test.go: debug")
   198  	_, _, err = l.ReadFrom(data)
   199  	test.AssertNotError(t, err, "Failed to find packet")
   200  
   201  	impl.Err("audit-logger_test.go: err")
   202  	_, _, err = l.ReadFrom(data)
   203  	test.AssertNotError(t, err, "Failed to find packet")
   204  
   205  	impl.Info("audit-logger_test.go: info")
   206  	_, _, err = l.ReadFrom(data)
   207  	test.AssertNotError(t, err, "Failed to find packet")
   208  
   209  	impl.Warning("audit-logger_test.go: warning")
   210  	_, _, err = l.ReadFrom(data)
   211  	test.AssertNotError(t, err, "Failed to find packet")
   212  
   213  	impl.AuditInfof("audit-logger_test.go: %s", "audit-info")
   214  	_, _, err = l.ReadFrom(data)
   215  	test.AssertNotError(t, err, "Failed to find packet")
   216  
   217  	impl.AuditErrf("audit-logger_test.go: %s", "audit-err")
   218  	_, _, err = l.ReadFrom(data)
   219  	test.AssertNotError(t, err, "Failed to find packet")
   220  
   221  	impl.Debugf("audit-logger_test.go: %s", "debug")
   222  	_, _, err = l.ReadFrom(data)
   223  	test.AssertNotError(t, err, "Failed to find packet")
   224  
   225  	impl.Errf("audit-logger_test.go: %s", "err")
   226  	_, _, err = l.ReadFrom(data)
   227  	test.AssertNotError(t, err, "Failed to find packet")
   228  
   229  	impl.Infof("audit-logger_test.go: %s", "info")
   230  	_, _, err = l.ReadFrom(data)
   231  	test.AssertNotError(t, err, "Failed to find packet")
   232  
   233  	impl.Warningf("audit-logger_test.go: %s", "warning")
   234  	_, _, err = l.ReadFrom(data)
   235  	test.AssertNotError(t, err, "Failed to find packet")
   236  }
   237  
   238  func TestSyslogLevels(t *testing.T) {
   239  	t.Parallel()
   240  
   241  	l, err := newUDPListener("127.0.0.1:0")
   242  	test.AssertNotError(t, err, "Failed to open log server")
   243  	defer func() {
   244  		err = l.Close()
   245  		test.AssertNotError(t, err, "listener.Close returned error")
   246  	}()
   247  
   248  	fmt.Printf("Going to %s\n", l.LocalAddr().String())
   249  	writer, err := syslog.Dial("udp", l.LocalAddr().String(), syslog.LOG_INFO|syslog.LOG_LOCAL0, "")
   250  	test.AssertNotError(t, err, "Failed to find connect to log server")
   251  
   252  	// create a logger with syslog level debug
   253  	impl, err := New(writer, stdoutLevel, int(syslog.LOG_DEBUG))
   254  	test.AssertNotError(t, err, "Failed to construct audit logger")
   255  
   256  	data := make([]byte, 512)
   257  
   258  	// debug messages should be sent to the logger
   259  	impl.Debug("log_test.go: debug")
   260  	_, _, err = l.ReadFrom(data)
   261  	test.AssertNotError(t, err, "Failed to find packet")
   262  	test.Assert(t, strings.Contains(string(data), "log_test.go: debug"), "Failed to find log message")
   263  
   264  	// create a logger with syslog level info
   265  	impl, err = New(writer, stdoutLevel, int(syslog.LOG_INFO))
   266  	test.AssertNotError(t, err, "Failed to construct audit logger")
   267  
   268  	// debug messages should not be sent to the logger
   269  	impl.Debug("log_test.go: debug")
   270  	n, _, err := l.ReadFrom(data)
   271  	if n != 0 && err == nil {
   272  		t.Error("Failed to withhold debug log message")
   273  	}
   274  }
   275  
   276  func newUDPListener(addr string) (*net.UDPConn, error) {
   277  	l, err := net.ListenPacket("udp", addr)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  	err = l.SetDeadline(time.Now().Add(100 * time.Millisecond))
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	err = l.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
   286  	if err != nil {
   287  		return nil, err
   288  	}
   289  	err = l.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  	return l.(*net.UDPConn), nil
   294  }
   295  
   296  // TestStdoutFailure tests that audit logging with a bothWriter panics if stdout
   297  // becomes unavailable.
   298  func TestStdoutFailure(t *testing.T) {
   299  	// Save the stdout fd so we can restore it later
   300  	saved := os.Stdout
   301  
   302  	// Create a throw-away pipe FD to replace stdout with
   303  	_, w, err := os.Pipe()
   304  	test.AssertNotError(t, err, "failed to create pipe")
   305  	os.Stdout = w
   306  
   307  	// Setup the logger
   308  	log := setup(t)
   309  
   310  	// Close Stdout so that the fmt.Printf in bothWriter's logAtLevel
   311  	// function will return an err on next log.
   312  	err = os.Stdout.Close()
   313  	test.AssertNotError(t, err, "failed to close stdout")
   314  
   315  	// Defer a function that will check if there was a panic to recover from. If
   316  	// there wasn't then the test should fail, we were able to AuditInfo when
   317  	// Stdout was inoperable.
   318  	defer func() {
   319  		if recovered := recover(); recovered == nil {
   320  			t.Errorf("log.AuditInfo with Stdout closed did not panic")
   321  		}
   322  
   323  		// Restore stdout so that subsequent tests don't fail
   324  		os.Stdout = saved
   325  	}()
   326  
   327  	// Try to audit log something
   328  	log.AuditInfo("This should cause a panic, stdout is closed!")
   329  }
   330  
   331  func TestLogAtLevelEscapesNewlines(t *testing.T) {
   332  	var buf bytes.Buffer
   333  	w := &bothWriter{sync.Mutex{},
   334  		nil,
   335  		&stdoutWriter{
   336  			stdout: &buf,
   337  			clk:    clock.NewFake(),
   338  			level:  6,
   339  		},
   340  		-1,
   341  	}
   342  	w.logAtLevel(6, "foo\nbar")
   343  
   344  	test.Assert(t, strings.Contains(buf.String(), "foo\\nbar"), "failed to escape newline")
   345  }
   346  
   347  func TestLogLineChecksum(t *testing.T) {
   348  	testCases := []struct {
   349  		name     string
   350  		function func(string) string
   351  		input    string
   352  		expected string
   353  	}{
   354  		{
   355  			name:     "LogLineChecksum with Hello, World!",
   356  			function: LogLineChecksum,
   357  			input:    "Hello, World!",
   358  			expected: "0MNK7A",
   359  		},
   360  	}
   361  
   362  	for _, tc := range testCases {
   363  		t.Run(tc.name, func(t *testing.T) {
   364  			checksum := tc.function(tc.input)
   365  			if checksum != tc.expected {
   366  				t.Fatalf("got %q, want %q", checksum, tc.expected)
   367  			}
   368  		})
   369  	}
   370  }