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 }