From 7a9bd8308cd9e8d673b77dd8f943f20145b78108 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 17 Sep 2024 18:58:59 +0200 Subject: [PATCH] handler: add styled output option --- handler.go | 46 +++++++++++++++++++++++++++++++++++++++++++--- level.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/handler.go b/handler.go index 2739f69..9c3d4da 100644 --- a/handler.go +++ b/handler.go @@ -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) @@ -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 { @@ -213,6 +232,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() @@ -229,7 +265,9 @@ 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) { @@ -237,7 +275,9 @@ func (d *DefaultHandler) writeCallSite(buf *buffer, file string, line int) { 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) { @@ -255,7 +295,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) { diff --git a/level.go b/level.go index db84295..b840ebb 100644 --- a/level.go +++ b/level.go @@ -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 + } +}