1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
package console
import (
"context"
"io"
"log/slog"
"os"
"strings"
"sync"
"time"
)
var bufferPool = &sync.Pool{
New: func() any { return new(buffer) },
}
var cwd, _ = os.Getwd()
// HandlerOptions are options for a ConsoleHandler.
// A zero HandlerOptions consists entirely of default values.
type HandlerOptions struct {
// AddSource causes the handler to compute the source code position
// of the log statement and add a SourceKey attribute to the output.
AddSource bool
// Level reports the minimum record level that will be logged.
// The handler discards records with lower levels.
// If Level is nil, the handler assumes LevelInfo.
// The handler calls Level.Level for each record processed;
// to adjust the minimum level dynamically, use a LevelVar.
Level slog.Leveler
// Disable colorized output
NoColor bool
// TimeFormat is the format used for time.DateTime
TimeFormat string
// Theme defines the colorized output using ANSI escape sequences
Theme Theme
}
type Handler struct {
opts HandlerOptions
out io.Writer
group string
context buffer
enc *encoder
}
var _ slog.Handler = (*Handler)(nil)
// NewHandler creates a Handler that writes to w,
// using the given options.
// If opts is nil, the default options are used.
func NewHandler(out io.Writer, opts *HandlerOptions) *Handler {
if opts == nil {
opts = new(HandlerOptions)
}
if opts.Level == nil {
opts.Level = slog.LevelInfo
}
if opts.TimeFormat == "" {
opts.TimeFormat = time.DateTime
}
if opts.Theme == nil {
opts.Theme = NewDefaultTheme()
}
return &Handler{
opts: *opts, // Copy struct
out: out,
group: "",
context: nil,
enc: &encoder{opts: *opts},
}
}
// Enabled implements slog.Handler.
func (h *Handler) Enabled(_ context.Context, l slog.Level) bool {
return l >= h.opts.Level.Level()
}
// Handle implements slog.Handler.
func (h *Handler) Handle(_ context.Context, rec slog.Record) error {
buf := bufferPool.Get().(*buffer)
h.enc.writeTimestamp(buf, rec.Time)
h.enc.writeLevel(buf, rec.Level)
if h.opts.AddSource && rec.PC > 0 {
h.enc.writeSource(buf, rec.PC, cwd)
}
h.enc.writeMessage(buf, rec.Level, rec.Message)
buf.copy(&h.context)
rec.Attrs(func(a slog.Attr) bool {
h.enc.writeAttr(buf, a, h.group)
return true
})
h.enc.NewLine(buf)
if _, err := buf.WriteTo(h.out); err != nil {
buf.Reset()
bufferPool.Put(buf)
return err
}
bufferPool.Put(buf)
return nil
}
// WithAttrs implements slog.Handler.
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
newCtx := h.context
for _, a := range attrs {
h.enc.writeAttr(&newCtx, a, h.group)
}
newCtx.Clip()
return &Handler{
opts: h.opts,
out: h.out,
group: h.group,
context: newCtx,
enc: h.enc,
}
}
// WithGroup implements slog.Handler.
func (h *Handler) WithGroup(name string) slog.Handler {
name = strings.TrimSpace(name)
if h.group != "" {
name = h.group + "." + name
}
return &Handler{
opts: h.opts,
out: h.out,
group: name,
context: h.context,
enc: h.enc,
}
}
|