mirror of
https://github.com/rs/zerolog
synced 2026-06-08 17:13:30 +00:00
f6fbd330be
* Test coverage improvements Implements #397 and #591, might help with #473 Test coverage for core is 97.6% with only real fringe cases remaining. Improve `Fields` `isNilValue()` portability. Added a new global handler `FatalExitFunc` to allow intercepting `Fatal()` messages. Fixed CBOR float constants (removed the CBOR prefix) Added tests for: - global `FatalExitFunc` to allow intercepting Fatal messages (both for testing and public use). - `Logger` - `DisableSampling` - `.With()` copying existing context if present. - `.With().Fields()` all forms of `ErrorStackMarshaler` returns. - `.WithLevel()` for `FatalLevel`, `PanicLevel`, and `DisabledLevel`. - `.Err()` with `nil` and non-`nil` `error` and test the resulting log level. - `.should()` covering `nil` writer. - `.Output()` gets context values. - `.UpdateContext()` on a disabled logger doesn't panic and is a nop. - `.With()` all forms of `ErrorStackMarshaler` returns. - with a `nil`writer. - `.Hook()` passing no hooks. - `Array` - `.MarshalZerologArray` is a nop that won't panic. - `Context` - ` .Err()` and `.AnErr()` for `nil` errors and all forms of `ErrorStackMarshaler` returns. - `Event` - `.Caller()` to ensure we don't panic or add invalid information if `runtime.Caller()` fails -` .Err()` and `.AnErr()` for `nil` errors and all forms of `ErrorStackMarshaler` returns. - `Fields` - ` .appendFields()` all forms of `ErrorStackMarshaler` returns. - `HookLevel` - `.Run()` methods. - `LevelSampler` - `.Sample() methods. - `Syslog` - `.Write()`, `.WriteLevel()`, and `.Close()` methods. - `.WriteLevel()` with an `InvalidLevel`. - `Writer` - `.Write()` short write and error cases. - `MultiLevelWriter` `.WriteLevel()` and for `.Write()` error and `.Close()` cases. - test of unmarshalling a level byte returns correct error. - CBOR decodeStream - `.decodeFloat()`, `.binaryFmt()`, `.DecodeIfBinaryToString()`, `.DecodeObjectToStr()`, `.DecodeIfBinaryToBytes()`, `.decodeTagData()`, and `.decodeSimpleFloat()` - handling of invalid UTF-8 sequences - handling of UTC times. - handling of timestamps - handling of various map lengths Restructure `Event` `.caller()` so we test for ok and eliminate untestable coverage hole. Restructure `Context` `.Err()` when the `ErrorStackMarshaler` returns a `nil` so there's code to cover. Inverted logic for`Event` `.Caller()`'s call to `runtime.Caller()` for simpler testing. Inverted logic for `Array` `.putArray()` and `Event` `.putEvent()` so there isn't uncoverable code. Restructure `Field` `.appendFieldList()` to early return when `ErrorStackMarshaler` returns a `nil` Added comments for things we can't get coverage on. Did a go fmt ./... Coverage of core is now 100% on `Array`, `Context`, `Ctx`, `Event`, `Field`, `Hook`, and `Syslog`. Coverage of `Globals`, `Log`, `Sampler`, and `Writer` is almost all except some real edge-cases. JSON encoder coverage is 100% CBOR encoder coverage is 96.3% with base, cbor, string, time and types at 100% and decode_stream (which is lacks coverage on some panic states, and two incorrect coverage-tool lapses) * Fix CBOR tests for StackMarshaler Forgot to use the `decodeIfBinaryToString()`
237 lines
6.4 KiB
Go
237 lines
6.4 KiB
Go
// +build !binary_log
|
|
// +build !windows
|
|
|
|
package zerolog
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type syslogEvent struct {
|
|
level string
|
|
msg string
|
|
}
|
|
type syslogTestWriter struct {
|
|
events []syslogEvent
|
|
}
|
|
|
|
func (w *syslogTestWriter) Write(p []byte) (int, error) {
|
|
return len(p), nil
|
|
}
|
|
func (w *syslogTestWriter) Trace(m string) error {
|
|
w.events = append(w.events, syslogEvent{"Trace", m})
|
|
return nil
|
|
}
|
|
func (w *syslogTestWriter) Debug(m string) error {
|
|
w.events = append(w.events, syslogEvent{"Debug", m})
|
|
return nil
|
|
}
|
|
func (w *syslogTestWriter) Info(m string) error {
|
|
w.events = append(w.events, syslogEvent{"Info", m})
|
|
return nil
|
|
}
|
|
func (w *syslogTestWriter) Warning(m string) error {
|
|
w.events = append(w.events, syslogEvent{"Warning", m})
|
|
return nil
|
|
}
|
|
func (w *syslogTestWriter) Err(m string) error {
|
|
w.events = append(w.events, syslogEvent{"Err", m})
|
|
return nil
|
|
}
|
|
func (w *syslogTestWriter) Emerg(m string) error {
|
|
w.events = append(w.events, syslogEvent{"Emerg", m})
|
|
return nil
|
|
}
|
|
func (w *syslogTestWriter) Crit(m string) error {
|
|
w.events = append(w.events, syslogEvent{"Crit", m})
|
|
return nil
|
|
}
|
|
|
|
func TestSyslogWriter(t *testing.T) {
|
|
sw := &syslogTestWriter{}
|
|
log := New(SyslogLevelWriter(sw))
|
|
log.Trace().Msg("trace")
|
|
log.Debug().Msg("debug")
|
|
log.Info().Msg("info")
|
|
log.Warn().Msg("warn")
|
|
log.Error().Msg("error")
|
|
log.Log().Msg("nolevel")
|
|
want := []syslogEvent{
|
|
{"Debug", `{"level":"debug","message":"debug"}` + "\n"},
|
|
{"Info", `{"level":"info","message":"info"}` + "\n"},
|
|
{"Warning", `{"level":"warn","message":"warn"}` + "\n"},
|
|
{"Err", `{"level":"error","message":"error"}` + "\n"},
|
|
{"Info", `{"message":"nolevel"}` + "\n"},
|
|
}
|
|
if got := sw.events; !reflect.DeepEqual(got, want) {
|
|
t.Errorf("Invalid syslog message routing: want %v, got %v", want, got)
|
|
}
|
|
}
|
|
|
|
type testCEEwriter struct {
|
|
buf *bytes.Buffer
|
|
}
|
|
|
|
// Only implement one method as we're just testing the prefixing
|
|
func (c testCEEwriter) Debug(m string) error { return nil }
|
|
|
|
func (c testCEEwriter) Info(m string) error {
|
|
_, err := c.buf.Write([]byte(m))
|
|
return err
|
|
}
|
|
|
|
func (c testCEEwriter) Warning(m string) error { return nil }
|
|
|
|
func (c testCEEwriter) Err(m string) error { return nil }
|
|
|
|
func (c testCEEwriter) Emerg(m string) error { return nil }
|
|
|
|
func (c testCEEwriter) Crit(m string) error { return nil }
|
|
|
|
func (c testCEEwriter) Write(b []byte) (int, error) {
|
|
return c.buf.Write(b)
|
|
}
|
|
|
|
func TestSyslogWriter_WithCEE(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
sw := testCEEwriter{&buf}
|
|
log := New(SyslogCEEWriter(sw))
|
|
log.Info().Str("key", "value").Msg("message string")
|
|
got := buf.String()
|
|
want := "@cee:{"
|
|
if !strings.HasPrefix(got, want) {
|
|
t.Errorf("Bad CEE message start: want %v, got %v", want, got)
|
|
}
|
|
}
|
|
|
|
type errorSyslogWriter struct {
|
|
*syslogTestWriter
|
|
writeError error
|
|
}
|
|
|
|
func (w *errorSyslogWriter) Write(p []byte) (int, error) {
|
|
if w.writeError != nil {
|
|
return 0, w.writeError
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func TestSyslogWriter_Write(t *testing.T) {
|
|
// Test Write method without prefix
|
|
sw := &syslogTestWriter{}
|
|
writer := SyslogLevelWriter(sw)
|
|
|
|
data := []byte("test message")
|
|
n, err := writer.Write(data)
|
|
if err != nil {
|
|
t.Errorf("Write failed: %v", err)
|
|
}
|
|
if n != len(data) {
|
|
t.Errorf("Write returned wrong length: got %d, want %d", n, len(data))
|
|
}
|
|
|
|
// Test Write method with CEE prefix
|
|
sw2 := &syslogTestWriter{}
|
|
writer2 := SyslogCEEWriter(sw2)
|
|
|
|
data2 := []byte("test message")
|
|
n2, err2 := writer2.Write(data2)
|
|
if err2 != nil {
|
|
t.Errorf("Write with CEE failed: %v", err2)
|
|
}
|
|
expectedLen := len(ceePrefix) + len(data2)
|
|
if n2 != expectedLen {
|
|
t.Errorf("Write with CEE returned wrong length: got %d, want %d", n2, expectedLen)
|
|
}
|
|
|
|
// Test Write method with CEE prefix and error on prefix write
|
|
sw3 := &errorSyslogWriter{syslogTestWriter: &syslogTestWriter{}, writeError: io.EOF}
|
|
writer3 := SyslogCEEWriter(sw3)
|
|
|
|
_, err3 := writer3.Write(data2)
|
|
if err3 != io.EOF {
|
|
t.Errorf("Write with CEE error failed: got %v, want %v", err3, io.EOF)
|
|
}
|
|
}
|
|
|
|
func TestSyslogWriter_WriteLevel_AllLevels(t *testing.T) {
|
|
sw := &syslogTestWriter{}
|
|
writer := SyslogLevelWriter(sw)
|
|
|
|
// Test all levels to ensure full coverage
|
|
writer.WriteLevel(TraceLevel, []byte(`{"level":"trace","message":"trace"}`+"\n"))
|
|
writer.WriteLevel(DebugLevel, []byte(`{"level":"debug","message":"debug"}`+"\n"))
|
|
writer.WriteLevel(InfoLevel, []byte(`{"level":"info","message":"info"}`+"\n"))
|
|
writer.WriteLevel(WarnLevel, []byte(`{"level":"warn","message":"warn"}`+"\n"))
|
|
writer.WriteLevel(ErrorLevel, []byte(`{"level":"error","message":"error"}`+"\n"))
|
|
writer.WriteLevel(FatalLevel, []byte(`{"level":"fatal","message":"fatal"}`+"\n"))
|
|
writer.WriteLevel(PanicLevel, []byte(`{"level":"panic","message":"panic"}`+"\n"))
|
|
writer.WriteLevel(NoLevel, []byte(`{"message":"nolevel"}`+"\n"))
|
|
|
|
want := []syslogEvent{
|
|
{"Debug", `{"level":"debug","message":"debug"}` + "\n"},
|
|
{"Info", `{"level":"info","message":"info"}` + "\n"},
|
|
{"Warning", `{"level":"warn","message":"warn"}` + "\n"},
|
|
{"Err", `{"level":"error","message":"error"}` + "\n"},
|
|
{"Emerg", `{"level":"fatal","message":"fatal"}` + "\n"},
|
|
{"Crit", `{"level":"panic","message":"panic"}` + "\n"},
|
|
{"Info", `{"message":"nolevel"}` + "\n"},
|
|
}
|
|
if got := sw.events; !reflect.DeepEqual(got, want) {
|
|
t.Errorf("Invalid syslog message routing: want %v, got %v", want, got)
|
|
}
|
|
}
|
|
|
|
type closableSyslogWriter struct {
|
|
*syslogTestWriter
|
|
closed bool
|
|
}
|
|
|
|
func (w *closableSyslogWriter) Close() error {
|
|
w.closed = true
|
|
return nil
|
|
}
|
|
|
|
func TestSyslogWriter_Close(t *testing.T) {
|
|
// Test with closable writer
|
|
sw := &closableSyslogWriter{syslogTestWriter: &syslogTestWriter{}}
|
|
writer := SyslogLevelWriter(sw).(syslogWriter) // Cast to concrete type to access Close
|
|
|
|
err := writer.Close()
|
|
if err != nil {
|
|
t.Errorf("Close failed: %v", err)
|
|
}
|
|
if !sw.closed {
|
|
t.Error("Close was not called on underlying writer")
|
|
}
|
|
|
|
// Test with non-closable writer
|
|
sw2 := &syslogTestWriter{}
|
|
writer2 := SyslogLevelWriter(sw2).(syslogWriter) // Cast to concrete type to access Close
|
|
|
|
err = writer2.Close()
|
|
if err != nil {
|
|
t.Errorf("Close failed for non-closable writer: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSyslogWriter_WriteLevel_InvalidLevel(t *testing.T) {
|
|
sw := &syslogTestWriter{}
|
|
writer := SyslogLevelWriter(sw)
|
|
|
|
// Test invalid level - should panic
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Error("Expected panic for invalid level")
|
|
} else if r != "invalid level" {
|
|
t.Errorf("Expected panic 'invalid level', got %v", r)
|
|
}
|
|
}()
|
|
|
|
writer.WriteLevel(Level(100), []byte("test"))
|
|
}
|