Skip to content

Commit d680742

Browse files
committed
enable log colorization in development
I'm waiting on upstream to merge two merge requests. Merge-Request: https://gitlab.com/greyxor/slogor/-/merge_requests/7 Merge-Request: https://gitlab.com/greyxor/slogor/-/merge_requests/8
1 parent c64d20d commit d680742

File tree

8 files changed

+72
-36
lines changed

8 files changed

+72
-36
lines changed

cmd/texd/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ func setupLogger() (xlog.Logger, error) {
286286
xlog.WithSource(),
287287
}
288288
if texd.Development() {
289-
o = append(o, xlog.WriteTo(os.Stderr), xlog.AsText())
289+
o = append(o, xlog.WriteTo(os.Stderr), xlog.AsText(), xlog.Color())
290290
} else {
291291
o = append(o, xlog.WriteTo(os.Stdout), xlog.AsJSON())
292292
}

go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/docker/go-units v0.5.0
1010
github.com/gorilla/handlers v1.5.2
1111
github.com/gorilla/mux v1.8.1
12+
github.com/mattn/go-isatty v0.0.20
1213
github.com/moby/term v0.5.0
1314
github.com/oklog/ulid/v2 v2.1.0
1415
github.com/opencontainers/image-spec v1.1.0
@@ -17,6 +18,7 @@ require (
1718
github.com/spf13/pflag v1.0.5
1819
github.com/stretchr/testify v1.9.0
1920
github.com/thediveo/enumflag v0.10.1
21+
gitlab.com/greyxor/slogor v1.4.1
2022
)
2123

2224
require (
@@ -61,3 +63,5 @@ require (
6163
)
6264

6365
replace github.com/spf13/afero => github.com/digineo/afero v1.11.1-0.20240128222722-ade8094005cb
66+
67+
replace gitlab.com/greyxor/slogor => gitlab.com/dmke/slogor v1.4.2-0.20241030183918-8885575df1e5

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,8 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
14561456
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
14571457
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
14581458
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
1459+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
1460+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
14591461
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
14601462
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
14611463
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -1588,6 +1590,8 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
15881590
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
15891591
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
15901592
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
1593+
gitlab.com/dmke/slogor v1.4.2-0.20241030183918-8885575df1e5 h1:oIZBFZcMCiRwi1CdWSf62YF4XKq0GyhKIBsxZo24AWw=
1594+
gitlab.com/dmke/slogor v1.4.2-0.20241030183918-8885575df1e5/go.mod h1:RvD/RqmEGpqVhL8tqqfVduuxtcik6uXCnzfikaFCwto=
15911595
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
15921596
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
15931597
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=

service/middleware/logging_test.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ func TestLogging(t *testing.T) {
3636
require.Equal(t, http.StatusOK, w.Code)
3737

3838
assert.Equal(t, strings.Join([]string{
39-
"time=2022-04-15T05:20:00.000Z",
40-
"level=INFO",
41-
`msg=""`,
39+
"[05:20:00.000] INFO logging.go:68",
40+
"",
4241
"method=GET",
4342
"status=200",
4443
"bytes=0",

service/status_test.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,8 @@ func TestHandleStatus_withFailIO(t *testing.T) {
8585
assert.Equal(t, http.StatusOK, rec.code)
8686
assert.Equal(t, mimeTypeJSON, rec.h.Get("Content-Type"))
8787
assert.Equal(t, strings.Join([]string{
88-
"time=2022-04-15T05:20:00.000Z",
89-
"level=ERROR",
90-
`msg="failed to write response"`,
88+
"[05:20:00.000] ERROR status.go:46",
89+
"failed to write response",
9190
`error="io: read/write on closed pipe"`,
9291
}, " ")+"\n", buf.String())
9392
}

xlog/attrs.go

+7
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,18 @@ const ErrorKey = "error"
2323
// ErrorValue holds an error value.
2424
type ErrorValue struct{ error }
2525

26+
var _ slog.LogValuer = (*ErrorValue)(nil)
27+
2628
// Value extracts the error message.
2729
func (err ErrorValue) Value() slog.Value {
2830
return slog.StringValue(err.Error())
2931
}
3032

33+
// LogValue implements [slog.LogValuer].
34+
func (err ErrorValue) LogValue() slog.Value {
35+
return slog.StringValue(err.Error())
36+
}
37+
3138
// Error constructs a first-class error log attribute.
3239
//
3340
// Not to be confused with (xlog.Logger).Error() or (log/slog).Error(),

xlog/options.go

+31-13
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,33 @@ package xlog
33
import (
44
"io"
55
"log/slog"
6+
"os"
67
"time"
78

89
"github.com/digineo/texd/internal"
10+
"github.com/mattn/go-isatty"
11+
"gitlab.com/greyxor/slogor"
912
)
1013

1114
// An Option represents a functional configuration option. These are
1215
// used to configure new logger instances.
1316
type Option func(*options) error
1417

1518
type options struct {
16-
discard bool
17-
output io.Writer
18-
clock internal.Clock
19+
discard bool
20+
output io.Writer
21+
color bool
22+
clock internal.Clock
23+
level slog.Leveler
24+
source bool
25+
1926
buildHandler func(o *options) slog.Handler
20-
handlerOpts *slog.HandlerOptions
2127
}
2228

2329
// Leveled sets the log level.
2430
func Leveled(l slog.Level) Option {
2531
return func(o *options) error {
26-
o.handlerOpts.Level = l
32+
o.level = l
2733
return nil
2834
}
2935
}
@@ -36,7 +42,7 @@ func LeveledString(s string) Option {
3642
if err != nil {
3743
return err
3844
}
39-
o.handlerOpts.Level = l
45+
o.level = l
4046
return nil
4147
}
4248
}
@@ -60,16 +66,17 @@ func MockClock(t time.Time) Option {
6066
// WithSource enables source code positions in log messages.
6167
func WithSource() Option {
6268
return func(o *options) error {
63-
o.handlerOpts.AddSource = true
69+
o.source = true
6470
return nil
6571
}
6672
}
6773

68-
// WithAttrReplacer configures an attribute replacer.
69-
// See (slog.HandlerOptions).ReplaceAtrr for details.
70-
func WithAttrReplacer(f func(groups []string, a slog.Attr) slog.Attr) Option {
74+
// Color enables colorful log output. If the output writer set by
75+
// [WriteTo] isn't a TTY, or messages are output [AsJSON], enabling
76+
// colors won't have an effect.
77+
func Color() Option {
7178
return func(o *options) error {
72-
o.handlerOpts.ReplaceAttr = f
79+
o.color = true
7380
return nil
7481
}
7582
}
@@ -79,7 +86,10 @@ func WithAttrReplacer(f func(groups []string, a slog.Attr) slog.Attr) Option {
7986
func AsJSON() Option {
8087
return func(o *options) error {
8188
o.buildHandler = func(o *options) slog.Handler {
82-
return slog.NewJSONHandler(o.output, o.handlerOpts)
89+
return slog.NewJSONHandler(o.output, &slog.HandlerOptions{
90+
AddSource: o.source,
91+
Level: o.level,
92+
})
8393
}
8494
return nil
8595
}
@@ -90,7 +100,15 @@ func AsJSON() Option {
90100
func AsText() Option {
91101
return func(o *options) error {
92102
o.buildHandler = func(o *options) slog.Handler {
93-
return slog.NewTextHandler(o.output, o.handlerOpts)
103+
opts := []slogor.OptionFn{
104+
slogor.SetLevel(o.level.Level()),
105+
slogor.SetTimeFormat("[15:04:05.000]"),
106+
slogor.ShowSource(),
107+
}
108+
if f, isFile := o.output.(*os.File); !isFile || !isatty.IsTerminal(f.Fd()) {
109+
opts = append(opts, slogor.DisableColor())
110+
}
111+
return slogor.NewHandler(o.output, opts...)
94112
}
95113
return nil
96114
}

xlog/xlog.go

+21-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Package xlog provides a very thin wrapper aroud log/slog.
1+
// Package xlog provides a thin wrapper around [log/slog].
22
package xlog
33

44
import (
@@ -9,6 +9,8 @@ import (
99
"runtime"
1010
"strings"
1111
"time"
12+
13+
"github.com/digineo/texd/internal"
1214
)
1315

1416
// A Logger allows writing messages with various severities.
@@ -37,8 +39,8 @@ type logger struct {
3739
// written to stdout, and the log level is INFO.
3840
func New(opt ...Option) (Logger, error) {
3941
opts := options{
40-
output: os.Stdout,
41-
handlerOpts: &slog.HandlerOptions{},
42+
level: slog.LevelInfo,
43+
output: os.Stdout,
4244
}
4345

4446
for _, o := range opt {
@@ -53,22 +55,15 @@ func New(opt ...Option) (Logger, error) {
5355
}
5456

5557
// setup mock time
58+
h := opts.buildHandler(&opts)
5659
if opts.clock != nil {
57-
repl := opts.handlerOpts.ReplaceAttr
58-
opts.handlerOpts.ReplaceAttr = func(groups []string, a slog.Attr) slog.Attr {
59-
if len(groups) == 0 && a.Key == slog.TimeKey {
60-
a.Value = slog.TimeValue(opts.clock.Now())
61-
}
62-
if repl == nil {
63-
return a
64-
}
65-
return repl(groups, a)
60+
h = &mockTimeHandler{
61+
clock: opts.clock,
62+
Handler: h,
6663
}
6764
}
6865

69-
return &logger{
70-
l: slog.New(opts.buildHandler(&opts)),
71-
}, nil
66+
return &logger{l: slog.New(h)}, nil
7267
}
7368

7469
// log creates a log record. It is called by Debug, Info, etc.
@@ -77,7 +72,7 @@ func (log *logger) log(level slog.Level, msg string, a ...slog.Attr) {
7772
return
7873
}
7974
var pcs [1]uintptr
80-
runtime.Callers(3, pcs[:]) // skip runtime.Callers, log, and our caller
75+
runtime.Callers(3, pcs[:]) //nolint:mnd // skip runtime.Callers, log, and our caller
8176
r := slog.NewRecord(time.Now(), level, msg, pcs[0])
8277
r.AddAttrs(a...)
8378
_ = log.l.Handler().Handle(context.Background(), r)
@@ -129,3 +124,13 @@ func ParseLevel(s string) (l slog.Level, err error) {
129124
}
130125
return
131126
}
127+
128+
type mockTimeHandler struct {
129+
clock internal.Clock
130+
slog.Handler
131+
}
132+
133+
func (h *mockTimeHandler) Handle(ctx context.Context, a slog.Record) error {
134+
a.Time = h.clock.Now()
135+
return h.Handler.Handle(ctx, a)
136+
}

0 commit comments

Comments
 (0)