-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 3880728
Showing
5 changed files
with
443 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package log | ||
|
||
import ( | ||
"log/slog" | ||
"strconv" | ||
) | ||
|
||
type Color int32 | ||
|
||
const ( | ||
reset = "\033[0m" | ||
|
||
black Color = 30 | ||
red Color = 31 | ||
green Color = 32 | ||
yellow Color = 33 | ||
blue Color = 34 | ||
magenta Color = 35 | ||
cyan Color = 36 | ||
lightGray Color = 37 | ||
darkGray Color = 90 | ||
lightRed Color = 91 | ||
lightGreen Color = 92 | ||
lightYellow Color = 93 | ||
lightBlue Color = 94 | ||
lightMagenta Color = 95 | ||
lightCyan Color = 96 | ||
white Color = 97 | ||
) | ||
|
||
func (c Color) String() string { | ||
switch c { | ||
case black: | ||
return "30" | ||
case red: | ||
return "31" | ||
case green: | ||
return "32" | ||
case yellow: | ||
return "33" | ||
case blue: | ||
return "34" | ||
case magenta: | ||
return "35" | ||
case cyan: | ||
return "36" | ||
case lightGray: | ||
return "37" | ||
case darkGray: | ||
return "90" | ||
case lightRed: | ||
return "91" | ||
case lightGreen: | ||
return "92" | ||
case lightYellow: | ||
return "93" | ||
case lightBlue: | ||
return "94" | ||
case lightMagenta: | ||
return "95" | ||
case lightCyan: | ||
return "96" | ||
case white: | ||
return "97" | ||
default: | ||
return strconv.Itoa(int(c)) | ||
} | ||
} | ||
|
||
var ( | ||
colorHeader = []byte("\033[") | ||
colorEnd = []byte(reset) | ||
colorAgent byte = 'm' | ||
) | ||
|
||
func colorize(c Color, v string) []byte { | ||
b := make([]byte, 0, 7+len(c.String())+len(v)) | ||
return append(append(append(append(append(b, colorHeader...), []byte(c.String())...), colorAgent), []byte(v)...), colorEnd...) | ||
} | ||
|
||
func getColor(level slog.Level) Color { | ||
switch level { | ||
case slog.LevelDebug: | ||
return darkGray | ||
case slog.LevelInfo: | ||
return cyan | ||
case slog.LevelWarn: | ||
return lightYellow | ||
case slog.LevelError: | ||
return lightRed | ||
default: | ||
return black | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package log | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"log/slog" | ||
"sync" | ||
) | ||
|
||
var bufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }} | ||
|
||
func buildOutput(bs ...[]byte) []byte { | ||
buf := bufferPool.Get().(*bytes.Buffer) | ||
defer func() { | ||
buf.Reset() | ||
bufferPool.Put(buf) | ||
}() | ||
|
||
for _, b := range bs { | ||
buf.Write(b) | ||
buf.WriteByte(' ') | ||
} | ||
buf.WriteByte('\n') | ||
|
||
b := buf.Bytes() | ||
return b | ||
} | ||
|
||
//func suppressDefaults( | ||
// next func([]string, slog.Attr) slog.Attr, | ||
//) func([]string, slog.Attr) slog.Attr { | ||
// return func(groups []string, a slog.Attr) slog.Attr { | ||
// if a.Method == slog.TimeKey || | ||
// a.Method == slog.LevelKey || | ||
// a.Method == slog.MessageKey { | ||
// return slog.Attr{} | ||
// } | ||
// if next == nil { | ||
// return a | ||
// } | ||
// return next(groups, a) | ||
// } | ||
//} | ||
|
||
func putAttr(attrs Attrs, attr slog.Attr) { | ||
var v any | ||
switch attr.Value.Kind() { | ||
case slog.KindString: | ||
v = attr.Value.String() | ||
case slog.KindInt64: | ||
v = attr.Value.Int64() | ||
case slog.KindUint64: | ||
v = attr.Value.Uint64() | ||
case slog.KindFloat64: | ||
v = attr.Value.Float64() | ||
case slog.KindBool: | ||
v = attr.Value.Bool() | ||
case slog.KindDuration: | ||
v = attr.Value.Duration() | ||
case slog.KindTime: | ||
v = attr.Value.Time() | ||
case slog.KindAny: | ||
switch x := attr.Value.Any().(type) { | ||
case json.Marshaler: | ||
b, _ := json.Marshal(x) | ||
v = json.RawMessage(b) | ||
case fmt.Stringer: | ||
v = x.String() | ||
case error: | ||
v = x.Error() | ||
default: | ||
b, err := json.Marshal(x) | ||
if err != nil { | ||
panic(fmt.Sprintf("bad kind any: %s", attr.Value.Kind())) | ||
} | ||
v = json.RawMessage(b) | ||
} | ||
default: | ||
panic(fmt.Sprintf("bad kind: %s", attr.Value.Kind())) | ||
} | ||
|
||
attrs[attr.Key] = v | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/RealFax/slog | ||
|
||
go 1.23 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
package log | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"io" | ||
"log/slog" | ||
"runtime" | ||
"strconv" | ||
"strings" | ||
"sync" | ||
"time" | ||
) | ||
|
||
var TimeFormat = "[15:04:05.000]" | ||
|
||
type ( | ||
Attrs map[string]any | ||
|
||
groupOrAttrs struct { | ||
group string | ||
attrs []slog.Attr | ||
} | ||
|
||
Handler struct { | ||
addSource bool | ||
level slog.Leveler | ||
mu *sync.Mutex | ||
w io.Writer | ||
|
||
goas []groupOrAttrs | ||
} | ||
|
||
Record struct { | ||
Group string `json:"group,omitempty"` | ||
Level slog.Level `json:"level"` | ||
Time time.Time `json:"time"` | ||
Message string `json:"message"` | ||
Attrs Attrs `json:"attrs"` | ||
Source *string `json:"source,omitempty"` | ||
} | ||
) | ||
|
||
func (a Attrs) String() string { | ||
if len(a) == 0 { | ||
return "" | ||
} | ||
|
||
if ReleaseMode { | ||
b, _ := json.Marshal(a) | ||
return string(b) | ||
} | ||
b, _ := json.MarshalIndent(a, "", " ") | ||
return string(b) | ||
} | ||
|
||
func (r Record) Bytes() []byte { | ||
if ReleaseMode { | ||
b, _ := json.Marshal(r) | ||
return append(b, '\n') | ||
} | ||
bs := [][]byte{ | ||
colorize(lightGray, r.Time.Format(TimeFormat)), | ||
} | ||
|
||
if r.Group != "" { | ||
bs = append(bs, colorize(yellow, "@"+r.Group+"@")) | ||
} | ||
|
||
bs = append( | ||
bs, | ||
colorize(getColor(r.Level), r.Level.String()+":"), | ||
colorize(white, r.Message), | ||
colorize(lightMagenta, r.Attrs.String()), | ||
) | ||
return buildOutput(bs...) | ||
} | ||
|
||
func (h *Handler) withGroupOrAttrs(goa groupOrAttrs) *Handler { | ||
h2 := *h | ||
h2.goas = make([]groupOrAttrs, len(h.goas)+1) | ||
copy(h2.goas, h.goas) | ||
h2.goas[len(h2.goas)-1] = goa | ||
return &h2 | ||
} | ||
|
||
func (h *Handler) Enabled(_ context.Context, level slog.Level) bool { | ||
return level >= h.level.Level() | ||
} | ||
|
||
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler { | ||
if len(attrs) == 0 { | ||
return h | ||
} | ||
return h.withGroupOrAttrs(groupOrAttrs{attrs: attrs}) | ||
} | ||
|
||
func (h *Handler) WithGroup(name string) slog.Handler { | ||
if name == "" { | ||
return h | ||
} | ||
return h.withGroupOrAttrs(groupOrAttrs{group: name}) | ||
} | ||
|
||
func (h *Handler) Handle(_ context.Context, record slog.Record) (err error) { | ||
output := Record{ | ||
Level: record.Level, | ||
Time: record.Time, | ||
Message: record.Message, | ||
Attrs: make(Attrs), | ||
} | ||
|
||
switch { | ||
case record.NumAttrs() > 0: | ||
record.Attrs(func(attr slog.Attr) bool { | ||
putAttr(output.Attrs, attr) | ||
return true | ||
}) | ||
} | ||
|
||
group := make([]string, 0, len(h.goas)) | ||
for _, goa := range h.goas { | ||
if goa.group != "" { | ||
group = append(group, goa.group) | ||
} else { | ||
for _, a := range goa.attrs { | ||
output.Attrs[a.Key] = a.Value | ||
} | ||
} | ||
} | ||
|
||
if len(group) != 0 { | ||
output.Group = strings.Join(group, ".") | ||
} | ||
|
||
if h.addSource { | ||
_, filename, line, ok := runtime.Caller(4) | ||
if ok { | ||
src := filename + ":" + strconv.Itoa(line) | ||
output.Source = &src | ||
} | ||
} | ||
|
||
_, err = h.w.Write(output.Bytes()) | ||
return err | ||
} | ||
|
||
func NewHandler(opts *slog.HandlerOptions, w io.Writer) *Handler { | ||
if opts == nil { | ||
opts = &slog.HandlerOptions{} | ||
} | ||
|
||
h := &Handler{ | ||
addSource: opts.AddSource, | ||
mu: &sync.Mutex{}, | ||
w: w, | ||
level: opts.Level, | ||
} | ||
|
||
return h | ||
} |
Oops, something went wrong.