chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lvl, err := GetLevel(os.Getenv("MICRO_LOG_LEVEL"))
|
||||
if err != nil {
|
||||
lvl = InfoLevel
|
||||
}
|
||||
|
||||
DefaultLogger = NewLogger(WithLevel(lvl))
|
||||
}
|
||||
|
||||
type defaultLogger struct {
|
||||
opts Options
|
||||
slog *slog.Logger
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// Init (opts...) should only overwrite provided options.
|
||||
func (l *defaultLogger) Init(opts ...Option) error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
for _, o := range opts {
|
||||
o(&l.opts)
|
||||
}
|
||||
|
||||
// Recreate slog logger with new options
|
||||
handlerOpts := &slog.HandlerOptions{
|
||||
Level: l.opts.Level.ToSlog(),
|
||||
AddSource: true,
|
||||
}
|
||||
|
||||
// Create text handler for stdout
|
||||
textHandler := slog.NewTextHandler(l.opts.Out, handlerOpts)
|
||||
|
||||
// Create debug log handler for debug/log buffer
|
||||
debugHandler := newDebugLogHandler(handlerOpts.Level)
|
||||
|
||||
// Combine both handlers
|
||||
handler := newMultiHandler(textHandler, debugHandler)
|
||||
|
||||
l.slog = slog.New(handler)
|
||||
|
||||
// Add fields if any
|
||||
if len(l.opts.Fields) > 0 {
|
||||
const fieldsPerKV = 2
|
||||
args := make([]any, 0, len(l.opts.Fields)*fieldsPerKV)
|
||||
for k, v := range l.opts.Fields {
|
||||
args = append(args, k, v)
|
||||
}
|
||||
|
||||
l.slog = l.slog.With(args...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *defaultLogger) String() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Fields(fields map[string]interface{}) Logger {
|
||||
l.RLock()
|
||||
nfields := copyFields(l.opts.Fields)
|
||||
opts := l.opts
|
||||
l.RUnlock()
|
||||
|
||||
for k, v := range fields {
|
||||
nfields[k] = v
|
||||
}
|
||||
|
||||
// Create new logger without locks
|
||||
newLogger := NewLogger(
|
||||
WithLevel(opts.Level),
|
||||
WithFields(nfields),
|
||||
WithOutput(opts.Out),
|
||||
WithCallerSkipCount(opts.CallerSkipCount),
|
||||
)
|
||||
|
||||
return newLogger
|
||||
}
|
||||
|
||||
func copyFields(src map[string]interface{}) map[string]interface{} {
|
||||
dst := make(map[string]interface{}, len(src))
|
||||
for k, v := range src {
|
||||
dst[k] = v
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Log(level Level, v ...interface{}) {
|
||||
// TODO decide does we need to write message if log level not used?
|
||||
if !l.opts.Level.Enabled(level) {
|
||||
return
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
slogger := l.slog
|
||||
|
||||
if slogger == nil {
|
||||
// Fallback if not initialized
|
||||
slogger = slog.Default()
|
||||
}
|
||||
l.RUnlock()
|
||||
|
||||
// Get caller information
|
||||
var pcs [1]uintptr
|
||||
|
||||
runtime.Callers(l.opts.CallerSkipCount, pcs[:])
|
||||
r := slog.NewRecord(time.Now(), level.ToSlog(), fmt.Sprint(v...), pcs[0])
|
||||
|
||||
_ = slogger.Handler().Handle(context.Background(), r)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) {
|
||||
// TODO decide does we need to write message if log level not used?
|
||||
if !l.opts.Level.Enabled(level) {
|
||||
return
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
slogger := l.slog
|
||||
|
||||
if slogger == nil {
|
||||
// Fallback if not initialized
|
||||
slogger = slog.Default()
|
||||
}
|
||||
l.RUnlock()
|
||||
|
||||
// Get caller information
|
||||
var pcs [1]uintptr
|
||||
|
||||
runtime.Callers(l.opts.CallerSkipCount, pcs[:])
|
||||
r := slog.NewRecord(time.Now(), level.ToSlog(), fmt.Sprintf(format, v...), pcs[0])
|
||||
|
||||
_ = slogger.Handler().Handle(context.Background(), r)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Options() Options {
|
||||
// not guard against options Context values
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
opts := l.opts
|
||||
opts.Fields = copyFields(l.opts.Fields)
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
// NewLogger builds a new logger based on options.
|
||||
func NewLogger(opts ...Option) Logger {
|
||||
// Default options
|
||||
const defaultCallerSkipCount = 2
|
||||
|
||||
options := Options{
|
||||
Level: InfoLevel,
|
||||
Fields: make(map[string]interface{}),
|
||||
Out: os.Stderr,
|
||||
CallerSkipCount: defaultCallerSkipCount,
|
||||
Context: context.Background(),
|
||||
}
|
||||
|
||||
l := &defaultLogger{opts: options}
|
||||
if err := l.Init(opts...); err != nil {
|
||||
l.Log(FatalLevel, err)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
Reference in New Issue
Block a user