diff --git a/encoding.go b/encoding.go index cdc56e1..4573885 100644 --- a/encoding.go +++ b/encoding.go @@ -9,15 +9,16 @@ import ( ) type encoder struct { - nocolor bool + noColor bool + timeFormat string } func (e *encoder) NewLine(buf *buffer) { - buf.AppendString("\r\n") + buf.AppendByte('\n') } func (e *encoder) withColor(b *buffer, c color, f func()) { - if c == "" || e.nocolor { + if c == "" || e.noColor { f() return } @@ -69,7 +70,7 @@ func (e *encoder) writeColoredDuration(w *buffer, d time.Duration, seq color) { } func (e *encoder) writeTimestamp(buf *buffer, tt time.Time) { - e.writeColoredTime(buf, tt, time.DateTime, colorTimestamp) + e.writeColoredTime(buf, tt, e.timeFormat, colorTimestamp) buf.AppendByte(' ') } @@ -125,7 +126,7 @@ func (e *encoder) writeValue(buf *buffer, value slog.Value) { case slog.KindFloat64: e.writeColoredFloat(buf, value.Float64(), colorAttrValue) case slog.KindTime: - e.writeColoredTime(buf, value.Time(), time.RFC3339, colorAttrValue) + e.writeColoredTime(buf, value.Time(), e.timeFormat, colorAttrValue) case slog.KindUint64: e.writeColoredUint(buf, value.Uint64(), colorAttrValue) case slog.KindDuration: @@ -150,22 +151,31 @@ func (e *encoder) writeValue(buf *buffer, value slog.Value) { func (e *encoder) writeLevel(buf *buffer, l slog.Level) { var style color var str string + var delta int switch { case l >= slog.LevelError: style = colorLevelError str = "ERR" + delta = int(l - slog.LevelError) case l >= slog.LevelWarn: style = colorLevelWarn str = "WRN" + delta = int(l - slog.LevelWarn) case l >= slog.LevelInfo: style = colorLevelInfo str = "INF" + delta = int(l - slog.LevelInfo) case l >= slog.LevelDebug: style = colorLevelDebug str = "DBG" + delta = int(l - slog.LevelDebug) default: style = bold - str = "???" + str = "DBG" + delta = int(l - slog.LevelDebug) + } + if delta != 0 { + str = fmt.Sprintf("%s%+d", str, delta) } e.writeColoredString(buf, str, style) buf.AppendByte(' ') diff --git a/handler.go b/handler.go index b90db56..ead4462 100644 --- a/handler.go +++ b/handler.go @@ -7,6 +7,7 @@ import ( "os" "strings" "sync" + "time" ) var bufferPool = &sync.Pool{ @@ -31,6 +32,9 @@ type HandlerOptions struct { // Disable colorized output NoColor bool + + // TimeFormat is the format used for time.DateTime + TimeFormat string } type Handler struct { @@ -53,13 +57,16 @@ func NewHandler(out io.Writer, opts *HandlerOptions) *Handler { if opts.Level == nil { opts.Level = slog.LevelInfo } + if opts.TimeFormat == "" { + opts.TimeFormat = time.DateTime + } opt := *opts // Copy struct return &Handler{ opts: &opt, out: out, group: "", context: nil, - enc: &encoder{nocolor: opt.NoColor}, + enc: &encoder{noColor: opt.NoColor, timeFormat: opt.TimeFormat}, } } diff --git a/handler_test.go b/handler_test.go index f8a297d..2bb4649 100644 --- a/handler_test.go +++ b/handler_test.go @@ -20,7 +20,20 @@ func TestHandler_colors(t *testing.T) { rec := slog.NewRecord(now, slog.LevelInfo, "foobar", 0) AssertNoError(t, h.Handle(context.Background(), rec)) - expected := fmt.Sprintf("\x1b[90m%s\x1b[0m \x1b[92mINF\x1b[0m \x1b[97mfoobar\x1b[0m\r\n", now.Format(time.DateTime)) + expected := fmt.Sprintf("\x1b[90m%s\x1b[0m \x1b[92mINF\x1b[0m \x1b[97mfoobar\x1b[0m\n", now.Format(time.DateTime)) + AssertEqual(t, expected, buf.String()) +} + +func TestHandler_TimeFormat(t *testing.T) { + buf := bytes.Buffer{} + h := NewHandler(&buf, &HandlerOptions{TimeFormat: time.RFC3339Nano, NoColor: true}) + now := time.Now() + rec := slog.NewRecord(now, slog.LevelInfo, "foobar", 0) + endTime := now.Add(time.Second) + rec.AddAttrs(slog.Time("endtime", endTime)) + AssertNoError(t, h.Handle(context.Background(), rec)) + + expected := fmt.Sprintf("%s INF foobar endtime=%s\n", now.Format(time.RFC3339Nano), endTime.Format(time.RFC3339Nano)) AssertEqual(t, expected, buf.String()) } @@ -31,7 +44,7 @@ func TestHandler_NoColor(t *testing.T) { rec := slog.NewRecord(now, slog.LevelInfo, "foobar", 0) AssertNoError(t, h.Handle(context.Background(), rec)) - expected := fmt.Sprintf("%s INF foobar\r\n", now.Format(time.DateTime)) + expected := fmt.Sprintf("%s INF foobar\n", now.Format(time.DateTime)) AssertEqual(t, expected, buf.String()) } @@ -63,7 +76,7 @@ func TestHandler_Attr(t *testing.T) { ) AssertNoError(t, h.Handle(context.Background(), rec)) - expected := fmt.Sprintf("%s INF foobar bool=true int=-12 uint=12 float=3.14 foo=bar time=%s dur=1s group.foo=bar group.subgroup.foo=bar err=the error stringer=stringer nostringer={bar}\r\n", now.Format(time.DateTime), now.Format(time.RFC3339)) + expected := fmt.Sprintf("%s INF foobar bool=true int=-12 uint=12 float=3.14 foo=bar time=%s dur=1s group.foo=bar group.subgroup.foo=bar err=the error stringer=stringer nostringer={bar}\n", now.Format(time.DateTime), now.Format(time.DateTime)) AssertEqual(t, expected, buf.String()) } @@ -84,12 +97,12 @@ func TestHandler_WithAttr(t *testing.T) { }) AssertNoError(t, h2.Handle(context.Background(), rec)) - expected := fmt.Sprintf("%s INF foobar bool=true int=-12 uint=12 float=3.14 foo=bar time=%s dur=1s group.foo=bar group.subgroup.foo=bar\r\n", now.Format(time.DateTime), now.Format(time.RFC3339)) + expected := fmt.Sprintf("%s INF foobar bool=true int=-12 uint=12 float=3.14 foo=bar time=%s dur=1s group.foo=bar group.subgroup.foo=bar\n", now.Format(time.DateTime), now.Format(time.DateTime)) AssertEqual(t, expected, buf.String()) buf.Reset() AssertNoError(t, h.Handle(context.Background(), rec)) - AssertEqual(t, fmt.Sprintf("%s INF foobar\r\n", now.Format(time.DateTime)), buf.String()) + AssertEqual(t, fmt.Sprintf("%s INF foobar\n", now.Format(time.DateTime)), buf.String()) } func TestHandler_WithGroup(t *testing.T) { @@ -100,31 +113,31 @@ func TestHandler_WithGroup(t *testing.T) { rec.Add("int", 12) h2 := h.WithGroup("group1").WithAttrs([]slog.Attr{slog.String("foo", "bar")}) AssertNoError(t, h2.Handle(context.Background(), rec)) - expected := fmt.Sprintf("%s INF foobar group1.foo=bar group1.int=12\r\n", now.Format(time.DateTime)) + expected := fmt.Sprintf("%s INF foobar group1.foo=bar group1.int=12\n", now.Format(time.DateTime)) AssertEqual(t, expected, buf.String()) buf.Reset() h3 := h2.WithGroup("group2") AssertNoError(t, h3.Handle(context.Background(), rec)) - expected = fmt.Sprintf("%s INF foobar group1.foo=bar group1.group2.int=12\r\n", now.Format(time.DateTime)) + expected = fmt.Sprintf("%s INF foobar group1.foo=bar group1.group2.int=12\n", now.Format(time.DateTime)) AssertEqual(t, expected, buf.String()) buf.Reset() AssertNoError(t, h.Handle(context.Background(), rec)) - AssertEqual(t, fmt.Sprintf("%s INF foobar int=12\r\n", now.Format(time.DateTime)), buf.String()) + AssertEqual(t, fmt.Sprintf("%s INF foobar int=12\n", now.Format(time.DateTime)), buf.String()) } func TestHandler_Levels(t *testing.T) { levels := map[slog.Level]string{ - slog.LevelDebug - 1: "???", + slog.LevelDebug - 1: "DBG-1", slog.LevelDebug: "DBG", - slog.LevelDebug + 1: "DBG", + slog.LevelDebug + 1: "DBG+1", slog.LevelInfo: "INF", - slog.LevelInfo + 1: "INF", + slog.LevelInfo + 1: "INF+1", slog.LevelWarn: "WRN", - slog.LevelWarn + 1: "WRN", + slog.LevelWarn + 1: "WRN+1", slog.LevelError: "ERR", - slog.LevelError + 1: "ERR", + slog.LevelError + 1: "ERR+1", } for l := range levels { @@ -137,7 +150,7 @@ func TestHandler_Levels(t *testing.T) { rec := slog.NewRecord(now, ll, "foobar", 0) if ll >= l { AssertNoError(t, h.Handle(context.Background(), rec)) - AssertEqual(t, fmt.Sprintf("%s %s foobar\r\n", now.Format(time.DateTime), s), buf.String()) + AssertEqual(t, fmt.Sprintf("%s %s foobar\n", now.Format(time.DateTime), s), buf.String()) buf.Reset() } } @@ -155,10 +168,10 @@ func TestHandler_Source(t *testing.T) { AssertNoError(t, h.Handle(context.Background(), rec)) cwd, _ := os.Getwd() file, _ = filepath.Rel(cwd, file) - AssertEqual(t, fmt.Sprintf("%s INF %s:%d > foobar\r\n", now.Format(time.DateTime), file, line), buf.String()) + AssertEqual(t, fmt.Sprintf("%s INF %s:%d > foobar\n", now.Format(time.DateTime), file, line), buf.String()) buf.Reset() AssertNoError(t, h2.Handle(context.Background(), rec)) - AssertEqual(t, fmt.Sprintf("%s INF foobar\r\n", now.Format(time.DateTime)), buf.String()) + AssertEqual(t, fmt.Sprintf("%s INF foobar\n", now.Format(time.DateTime)), buf.String()) } func TestHandler_Err(t *testing.T) {