mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-19 09:51:55 +08:00
feat: logging
This commit is contained in:
parent
65b199d392
commit
c13a7a7873
188
internal/pkg/logging/handler.go
Normal file
188
internal/pkg/logging/handler.go
Normal file
@ -0,0 +1,188 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
types "github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
type HookHandlerOptions struct {
|
||||
Level slog.Leveler
|
||||
WriteFunc func(ctx context.Context, record *Record) error
|
||||
}
|
||||
|
||||
var _ slog.Handler = (*HookHandler)(nil)
|
||||
|
||||
type HookHandler struct {
|
||||
mutex *sync.Mutex
|
||||
parent *HookHandler
|
||||
options *HookHandlerOptions
|
||||
group string
|
||||
attrs []slog.Attr
|
||||
}
|
||||
|
||||
func NewHookHandler(options HookHandlerOptions) *HookHandler {
|
||||
h := &HookHandler{
|
||||
mutex: &sync.Mutex{},
|
||||
options: &options,
|
||||
}
|
||||
|
||||
if h.options.WriteFunc == nil {
|
||||
panic("`options.WriteFunc` is nil")
|
||||
}
|
||||
|
||||
if h.options.Level == nil {
|
||||
h.options.Level = slog.LevelInfo
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *HookHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||
return level >= h.options.Level.Level()
|
||||
}
|
||||
|
||||
func (h *HookHandler) WithGroup(name string) slog.Handler {
|
||||
if name == "" {
|
||||
return h
|
||||
}
|
||||
|
||||
return &HookHandler{
|
||||
parent: h,
|
||||
mutex: h.mutex,
|
||||
options: h.options,
|
||||
group: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HookHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
if len(attrs) == 0 {
|
||||
return h
|
||||
}
|
||||
|
||||
return &HookHandler{
|
||||
parent: h,
|
||||
mutex: h.mutex,
|
||||
options: h.options,
|
||||
attrs: attrs,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HookHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
if h.group != "" {
|
||||
h.mutex.Lock()
|
||||
attrs := make([]any, 0, len(h.attrs)+r.NumAttrs())
|
||||
for _, a := range h.attrs {
|
||||
attrs = append(attrs, a)
|
||||
}
|
||||
h.mutex.Unlock()
|
||||
|
||||
r.Attrs(func(a slog.Attr) bool {
|
||||
attrs = append(attrs, a)
|
||||
return true
|
||||
})
|
||||
|
||||
r = slog.NewRecord(r.Time, r.Level, r.Message, r.PC)
|
||||
r.AddAttrs(slog.Group(h.group, attrs...))
|
||||
} else if len(h.attrs) > 0 {
|
||||
r = r.Clone()
|
||||
|
||||
h.mutex.Lock()
|
||||
r.AddAttrs(h.attrs...)
|
||||
h.mutex.Unlock()
|
||||
}
|
||||
|
||||
if h.parent != nil {
|
||||
return h.parent.Handle(ctx, r)
|
||||
}
|
||||
|
||||
data := make(map[string]any, r.NumAttrs())
|
||||
|
||||
r.Attrs(func(a slog.Attr) bool {
|
||||
if err := h.resolveAttr(data, a); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
log := &Record{
|
||||
Time: r.Time,
|
||||
Message: r.Message,
|
||||
Data: types.JSONMap[any](data),
|
||||
}
|
||||
switch r.Level {
|
||||
case slog.LevelDebug:
|
||||
log.Level = LevelDebug
|
||||
case slog.LevelInfo:
|
||||
log.Level = LevelInfo
|
||||
case slog.LevelWarn:
|
||||
log.Level = LevelWarn
|
||||
case slog.LevelError:
|
||||
log.Level = LevelError
|
||||
default:
|
||||
log.Level = Level(fmt.Sprintf("LV(%d)", r.Level))
|
||||
}
|
||||
|
||||
if err := h.writeRecord(ctx, log); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HookHandler) SetLevel(level slog.Level) {
|
||||
h.mutex.Lock()
|
||||
h.options.Level = level
|
||||
h.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (h *HookHandler) writeRecord(ctx context.Context, r *Record) error {
|
||||
if h.parent != nil {
|
||||
return h.parent.writeRecord(ctx, r)
|
||||
}
|
||||
|
||||
return h.options.WriteFunc(ctx, r)
|
||||
}
|
||||
|
||||
func (h *HookHandler) resolveAttr(data map[string]any, attr slog.Attr) error {
|
||||
attr.Value = attr.Value.Resolve()
|
||||
|
||||
if attr.Equal(slog.Attr{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch attr.Value.Kind() {
|
||||
case slog.KindGroup:
|
||||
{
|
||||
attrs := attr.Value.Group()
|
||||
if len(attrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupData := make(map[string]any, len(attrs))
|
||||
|
||||
for _, subAttr := range attrs {
|
||||
h.resolveAttr(groupData, subAttr)
|
||||
}
|
||||
|
||||
if len(groupData) > 0 {
|
||||
data[attr.Key] = groupData
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
switch v := attr.Value.Any().(type) {
|
||||
case error:
|
||||
data[attr.Key] = v.Error()
|
||||
default:
|
||||
data[attr.Key] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
31
internal/pkg/logging/level.go
Normal file
31
internal/pkg/logging/level.go
Normal file
@ -0,0 +1,31 @@
|
||||
package logging
|
||||
|
||||
import "log/slog"
|
||||
|
||||
type Level string
|
||||
|
||||
const (
|
||||
LevelDebug Level = "DEBUG"
|
||||
LevelInfo Level = "INFO"
|
||||
LevelWarn Level = "WARN"
|
||||
LevelError Level = "ERROR"
|
||||
)
|
||||
|
||||
func (l Level) String() string {
|
||||
return string(l)
|
||||
}
|
||||
|
||||
func (l Level) Level() slog.Level {
|
||||
switch l {
|
||||
case LevelDebug:
|
||||
return slog.LevelDebug
|
||||
case LevelInfo:
|
||||
return slog.LevelInfo
|
||||
case LevelWarn:
|
||||
return slog.LevelWarn
|
||||
case LevelError:
|
||||
return slog.LevelError
|
||||
default:
|
||||
return slog.Level(-1)
|
||||
}
|
||||
}
|
14
internal/pkg/logging/record.go
Normal file
14
internal/pkg/logging/record.go
Normal file
@ -0,0 +1,14 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
types "github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
Time time.Time
|
||||
Level Level
|
||||
Message string
|
||||
Data types.JSONMap[any]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user