Skip to content

Commit

Permalink
handler: add styled output option
Browse files Browse the repository at this point in the history
  • Loading branch information
ellemouton committed Sep 17, 2024
1 parent 8028172 commit 1ef3989
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 3 deletions.
46 changes: 43 additions & 3 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ import (
"io"
"log/slog"
"strconv"
"strings"
"sync"
"sync/atomic"
"unicode"
"unicode/utf8"
)

const (
resetSeq = "0"
boldSeq = "1"
faintSeq = "2"

esc = '\x1b'
csi = string(esc) + "["
)

// HandlerOption is the signature of a functional option that can be used to
// modify the behaviour of the DefaultHandler.
type HandlerOption func(*handlerOpts)
Expand Down Expand Up @@ -39,6 +49,15 @@ func WithCallerFlags(flags uint32) HandlerOption {
}
}

// WithStyledOutput can be used to add additional styling to the logs. This
// currently includes colored & bold tags and faint fonts for attribute keys and
// callsites.
func WithStyledOutput() HandlerOption {
return func(opts *handlerOpts) {
opts.styled = true
}
}

// WithNoTimestamp is an option that can be used to omit timestamps from the log
// lines.
func WithNoTimestamp() HandlerOption {
Expand Down Expand Up @@ -211,6 +230,23 @@ func (d *DefaultHandler) with(tag string, withCallstackOffset bool,
return &sl
}

func (d *DefaultHandler) styleString(s string, styles ...string) string {
if !d.opts.styled {
return s
}

if len(styles) == 0 {
return s
}

seq := strings.Join(styles, ";")
if seq == "" {
return s
}

return fmt.Sprintf("%s%sm%s%sm", csi, seq, s, csi+resetSeq)
}

func (d *DefaultHandler) appendAttr(buf *buffer, a slog.Attr) {
// Resolve the Attr's value before doing anything else.
a.Value = a.Value.Resolve()
Expand All @@ -227,15 +263,19 @@ func (d *DefaultHandler) appendAttr(buf *buffer, a slog.Attr) {
func (d *DefaultHandler) writeLevel(buf *buffer, level Level) {
str := fmt.Sprintf("[%s] ", level)

buf.writeString(str)
buf.writeString(d.styleString(
str, boldSeq, string(level.ansiColoSeq())),
)
}

func (d *DefaultHandler) writeCallSite(buf *buffer, file string, line int) {
if file == "" {
return
}

buf.writeString(fmt.Sprintf(" %s:%d", file, line))
buf.writeString(
d.styleString(fmt.Sprintf(" %s:%d", file, line), faintSeq),
)
}

func appendString(buf *buffer, str string) {
Expand All @@ -253,7 +293,7 @@ func (d *DefaultHandler) appendKey(buf *buffer, key string) {
}
key += "="

buf.writeString(key)
buf.writeString(d.styleString(key, faintSeq))
}

func appendValue(buf *buffer, v slog.Value) {
Expand Down
28 changes: 28 additions & 0 deletions level.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,31 @@ func (l Level) String() string {
return str("CRT", l-LevelCritical)
}
}

type ansiColorSeq string

const (
ansiColorSeqDarkTeal ansiColorSeq = "38;5;30"
ansiColorSeqDarkBlue ansiColorSeq = "38;5;63"
ansiColorSeqLightBlue ansiColorSeq = "38;5;86"
ansiColorSeqYellow ansiColorSeq = "38;5;192"
ansiColorSeqRed ansiColorSeq = "38;5;204"
ansiColorSeqPink ansiColorSeq = "38;5;134"
)

func (l Level) ansiColoSeq() ansiColorSeq {
switch l {
case LevelTrace:
return ansiColorSeqDarkTeal
case LevelDebug:
return ansiColorSeqDarkBlue
case LevelWarn:
return ansiColorSeqYellow
case LevelError:
return ansiColorSeqRed
case LevelCritical:
return ansiColorSeqPink
default:
return ansiColorSeqLightBlue
}
}

0 comments on commit 1ef3989

Please sign in to comment.