116 lines
3.1 KiB
Go
116 lines
3.1 KiB
Go
|
package loggers
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"io"
|
||
|
"log/slog"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"runtime"
|
||
|
"strconv"
|
||
|
"time"
|
||
|
|
||
|
"go.elara.ws/loggers/internal/colors"
|
||
|
"golang.org/x/term"
|
||
|
)
|
||
|
|
||
|
// Options represents common options for slog handlers
|
||
|
// in this package.
|
||
|
type Options struct {
|
||
|
// TimeFormat represents the format of the timestamp
|
||
|
// provided by the [Pretty] logger.
|
||
|
TimeFormat string
|
||
|
|
||
|
// Level is the log level above which the handler will
|
||
|
// handle records.
|
||
|
Level slog.Level
|
||
|
|
||
|
// ShowCaller indicates whether the caller that created the
|
||
|
// record should be provided in the output
|
||
|
ShowCaller bool
|
||
|
|
||
|
// ForceColors prevents checking whether a handler outputs
|
||
|
// to a valid tty and always enables colors if set to true.
|
||
|
ForceColors bool
|
||
|
}
|
||
|
|
||
|
// groupOrAttr represents a group name or [log/slog.Attr].
|
||
|
type groupOrAttr struct {
|
||
|
attr slog.Attr
|
||
|
group string
|
||
|
}
|
||
|
|
||
|
// writeGroup writes all the attributes in a group to buf.
|
||
|
func writeGroup(colorize bool, buf *bytes.Buffer, group slog.Attr) {
|
||
|
attrs := group.Value.Group()
|
||
|
for i, attr := range attrs {
|
||
|
attr.Key = group.Key + "." + attr.Key
|
||
|
writeAttr(colorize, buf, attr)
|
||
|
if i < len(attrs)-1 {
|
||
|
buf.WriteByte(' ')
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// writeAttr writes a single attribute to buf.
|
||
|
//
|
||
|
// If the attribute is a [log/slog.Group], it calls [writeGroup].
|
||
|
// If the value of the attribute is an error, it will color it red.
|
||
|
func writeAttr(colorize bool, buf *bytes.Buffer, attr slog.Attr) {
|
||
|
attr.Value = attr.Value.Resolve()
|
||
|
|
||
|
if attr.Equal(slog.Attr{}) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if attr.Value.Kind() == slog.KindGroup {
|
||
|
writeGroup(colorize, buf, attr)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if _, ok := attr.Value.Any().(error); ok {
|
||
|
colors.WriteString(colorize, buf, colors.Red, attr.Key+"=")
|
||
|
colors.WriteCode(colorize, buf, colors.LightRed)
|
||
|
} else {
|
||
|
colors.WriteString(colorize, buf, colors.Cyan, attr.Key+"=")
|
||
|
colors.WriteCode(colorize, buf, colors.White)
|
||
|
}
|
||
|
|
||
|
abuf := buf.AvailableBuffer()
|
||
|
switch attr.Value.Kind() {
|
||
|
case slog.KindInt64:
|
||
|
abuf = strconv.AppendInt(abuf, attr.Value.Int64(), 10)
|
||
|
case slog.KindUint64:
|
||
|
abuf = strconv.AppendUint(abuf, attr.Value.Uint64(), 10)
|
||
|
case slog.KindFloat64:
|
||
|
abuf = strconv.AppendFloat(abuf, attr.Value.Float64(), 'g', -1, 64)
|
||
|
case slog.KindBool:
|
||
|
abuf = strconv.AppendBool(abuf, attr.Value.Bool())
|
||
|
case slog.KindDuration:
|
||
|
abuf = append(abuf, attr.Value.Duration().String()...)
|
||
|
case slog.KindTime:
|
||
|
abuf = attr.Value.Time().AppendFormat(abuf, time.RFC3339)
|
||
|
default:
|
||
|
abuf = strconv.AppendQuote(abuf, attr.Value.String())
|
||
|
}
|
||
|
buf.Write(abuf)
|
||
|
colors.WriteCode(colorize, buf, colors.Reset)
|
||
|
}
|
||
|
|
||
|
// writeCaller extracts the caller from the given program counter
|
||
|
// and writes it to buf, enclosed in square brackets.
|
||
|
func writeCaller(colorize bool, buf *bytes.Buffer, pc uintptr) {
|
||
|
frame, _ := runtime.CallersFrames([]uintptr{pc}).Next()
|
||
|
text := "[" + filepath.Base(frame.File) + ":" + strconv.Itoa(frame.Line) + "]"
|
||
|
buf.WriteByte(' ')
|
||
|
colors.WriteString(colorize, buf, colors.Bold+colors.LightBlue, text)
|
||
|
}
|
||
|
|
||
|
// isTerm checks if wr corresponds to a valid tty.
|
||
|
func isTerm(wr io.Writer) bool {
|
||
|
if fl, ok := wr.(*os.File); ok {
|
||
|
return term.IsTerminal(int(fl.Fd()))
|
||
|
}
|
||
|
return false
|
||
|
}
|