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,14 @@
|
||||
package logger
|
||||
|
||||
import "context"
|
||||
|
||||
type loggerKey struct{}
|
||||
|
||||
func FromContext(ctx context.Context) (Logger, bool) {
|
||||
l, ok := ctx.Value(loggerKey{}).(Logger)
|
||||
return l, ok
|
||||
}
|
||||
|
||||
func NewContext(ctx context.Context, l Logger) context.Context {
|
||||
return context.WithValue(ctx, loggerKey{}, l)
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
dlog "go-micro.dev/v6/debug/log"
|
||||
)
|
||||
|
||||
// debugLogHandler is a slog handler that writes to the debug/log buffer
|
||||
type debugLogHandler struct {
|
||||
level slog.Leveler
|
||||
attrs []slog.Attr
|
||||
group string
|
||||
}
|
||||
|
||||
// newDebugLogHandler creates a new handler that writes to debug/log
|
||||
func newDebugLogHandler(level slog.Leveler) *debugLogHandler {
|
||||
return &debugLogHandler{
|
||||
level: level,
|
||||
attrs: make([]slog.Attr, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *debugLogHandler) Enabled(_ context.Context, level slog.Level) bool {
|
||||
return level >= h.level.Level()
|
||||
}
|
||||
|
||||
func (h *debugLogHandler) Handle(_ context.Context, r slog.Record) error {
|
||||
// Build metadata from attributes
|
||||
metadata := make(map[string]string)
|
||||
|
||||
// Add handler's attributes
|
||||
for _, attr := range h.attrs {
|
||||
metadata[attr.Key] = attr.Value.String()
|
||||
}
|
||||
|
||||
// Add record's attributes
|
||||
r.Attrs(func(a slog.Attr) bool {
|
||||
metadata[a.Key] = a.Value.String()
|
||||
return true
|
||||
})
|
||||
|
||||
// Add level to metadata
|
||||
metadata["level"] = r.Level.String()
|
||||
|
||||
// Add source if available
|
||||
if sourcePath := extractSourceFilePath(r.PC); sourcePath != "" {
|
||||
metadata["file"] = sourcePath
|
||||
}
|
||||
|
||||
// Create debug log record
|
||||
rec := dlog.Record{
|
||||
Timestamp: r.Time,
|
||||
Message: r.Message,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
// Write to debug log
|
||||
_ = dlog.DefaultLog.Write(rec)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *debugLogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
newAttrs := make([]slog.Attr, len(h.attrs)+len(attrs))
|
||||
copy(newAttrs, h.attrs)
|
||||
copy(newAttrs[len(h.attrs):], attrs)
|
||||
|
||||
return &debugLogHandler{
|
||||
level: h.level,
|
||||
attrs: newAttrs,
|
||||
group: h.group,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *debugLogHandler) WithGroup(name string) slog.Handler {
|
||||
// For simplicity, we'll just track the group name
|
||||
// A full implementation would nest attributes properly
|
||||
return &debugLogHandler{
|
||||
level: h.level,
|
||||
attrs: h.attrs,
|
||||
group: name,
|
||||
}
|
||||
}
|
||||
|
||||
// multiHandler sends records to multiple handlers
|
||||
type multiHandler struct {
|
||||
handlers []slog.Handler
|
||||
}
|
||||
|
||||
func newMultiHandler(handlers ...slog.Handler) *multiHandler {
|
||||
return &multiHandler{
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *multiHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||
// Enabled if any handler is enabled
|
||||
for _, handler := range h.handlers {
|
||||
if handler.Enabled(ctx, level) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *multiHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
for _, handler := range h.handlers {
|
||||
// Clone the record for each handler to avoid issues
|
||||
if err := handler.Handle(ctx, r.Clone()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
newHandlers := make([]slog.Handler, len(h.handlers))
|
||||
for i, handler := range h.handlers {
|
||||
newHandlers[i] = handler.WithAttrs(attrs)
|
||||
}
|
||||
return &multiHandler{handlers: newHandlers}
|
||||
}
|
||||
|
||||
func (h *multiHandler) WithGroup(name string) slog.Handler {
|
||||
newHandlers := make([]slog.Handler, len(h.handlers))
|
||||
for i, handler := range h.handlers {
|
||||
newHandlers[i] = handler.WithGroup(name)
|
||||
}
|
||||
return &multiHandler{handlers: newHandlers}
|
||||
}
|
||||
|
||||
// extractSourceFilePath extracts the package/file:line from a PC
|
||||
func extractSourceFilePath(pc uintptr) string {
|
||||
if pc == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
fs := runtime.CallersFrames([]uintptr{pc})
|
||||
f, _ := fs.Next()
|
||||
if f.File == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Extract just filename, not full path
|
||||
idx := strings.LastIndexByte(f.File, '/')
|
||||
if idx == -1 {
|
||||
return fmt.Sprintf("%s:%d", f.File, f.Line)
|
||||
}
|
||||
|
||||
// Get package/file:line
|
||||
idx2 := strings.LastIndexByte(f.File[:idx], '/')
|
||||
if idx2 == -1 {
|
||||
return fmt.Sprintf("%s:%d", f.File[idx+1:], f.Line)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", f.File[idx2+1:], f.Line)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
dlog "go-micro.dev/v6/debug/log"
|
||||
)
|
||||
|
||||
func TestDebugLogBuffer(t *testing.T) {
|
||||
// Create a new logger
|
||||
l := NewLogger(WithLevel(InfoLevel))
|
||||
|
||||
// Log some messages
|
||||
l.Log(InfoLevel, "test message 1")
|
||||
l.Log(WarnLevel, "test message 2")
|
||||
l.Logf(ErrorLevel, "formatted message %d", 3)
|
||||
|
||||
// Read from debug log buffer
|
||||
records, err := dlog.DefaultLog.Read()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read from debug log: %v", err)
|
||||
}
|
||||
|
||||
// We should have at least our 3 messages
|
||||
if len(records) < 3 {
|
||||
t.Fatalf("Expected at least 3 log records in debug buffer, got %d", len(records))
|
||||
}
|
||||
|
||||
// Check that our messages are there
|
||||
foundCount := 0
|
||||
for _, rec := range records {
|
||||
msg, ok := rec.Message.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if msg == "test message 1" || msg == "test message 2" || msg == "formatted message 3" {
|
||||
foundCount++
|
||||
// Verify metadata is present
|
||||
if rec.Metadata == nil {
|
||||
t.Errorf("Record has nil metadata")
|
||||
}
|
||||
// Verify level is in metadata
|
||||
if _, ok := rec.Metadata["level"]; !ok {
|
||||
t.Errorf("Record missing level in metadata")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if foundCount < 3 {
|
||||
t.Errorf("Expected to find 3 specific messages in debug log, found %d", foundCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebugLogWithFields(t *testing.T) {
|
||||
// Create a logger with fields
|
||||
l := NewLogger(WithLevel(InfoLevel), WithFields(map[string]interface{}{
|
||||
"service": "test",
|
||||
"version": "1.0",
|
||||
}))
|
||||
|
||||
// Log a message
|
||||
l.Log(InfoLevel, "message with fields")
|
||||
|
||||
// Read from debug log buffer
|
||||
records, err := dlog.DefaultLog.Read()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read from debug log: %v", err)
|
||||
}
|
||||
|
||||
// Find our message
|
||||
found := false
|
||||
for _, rec := range records {
|
||||
msg, ok := rec.Message.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if msg == "message with fields" {
|
||||
found = true
|
||||
// Verify fields are in metadata
|
||||
if rec.Metadata["service"] != "test" {
|
||||
t.Errorf("Expected service=test in metadata, got %s", rec.Metadata["service"])
|
||||
}
|
||||
if rec.Metadata["version"] != "1.0" {
|
||||
t.Errorf("Expected version=1.0 in metadata, got %s", rec.Metadata["version"])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Error("Did not find message with fields in debug log")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Helper struct {
|
||||
logger Logger
|
||||
}
|
||||
|
||||
func NewHelper(logger Logger) *Helper {
|
||||
return &Helper{logger: logger}
|
||||
}
|
||||
|
||||
// Extract always returns valid Helper with logger from context or with DefaultLogger as fallback.
|
||||
// Can be used in pair with function Inject.
|
||||
// Example: propagate RequestID to logger in service handler methods.
|
||||
func Extract(ctx context.Context) *Helper {
|
||||
if l, ok := FromContext(ctx); ok {
|
||||
return NewHelper(l)
|
||||
}
|
||||
|
||||
return NewHelper(DefaultLogger)
|
||||
}
|
||||
|
||||
func (h *Helper) Inject(ctx context.Context) context.Context {
|
||||
return NewContext(ctx, h.logger)
|
||||
}
|
||||
|
||||
func (h *Helper) Log(level Level, args ...interface{}) {
|
||||
h.logger.Log(level, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Logf(level Level, template string, args ...interface{}) {
|
||||
h.logger.Logf(level, template, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Info(args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(InfoLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Log(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Infof(template string, args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(InfoLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Logf(InfoLevel, template, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Trace(args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(TraceLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Log(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Tracef(template string, args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(TraceLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Logf(TraceLevel, template, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Debug(args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(DebugLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Log(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Debugf(template string, args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(DebugLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Logf(DebugLevel, template, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Warn(args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(WarnLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Log(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Warnf(template string, args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(WarnLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Logf(WarnLevel, template, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Error(args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(ErrorLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Log(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Errorf(template string, args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(ErrorLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Logf(ErrorLevel, template, args...)
|
||||
}
|
||||
|
||||
func (h *Helper) Fatal(args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(FatalLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Log(FatalLevel, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (h *Helper) Fatalf(template string, args ...interface{}) {
|
||||
if !h.logger.Options().Level.Enabled(FatalLevel) {
|
||||
return
|
||||
}
|
||||
h.logger.Logf(FatalLevel, template, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (h *Helper) WithError(err error) *Helper {
|
||||
return &Helper{logger: h.logger.Fields(map[string]interface{}{"error": err})}
|
||||
}
|
||||
|
||||
func (h *Helper) WithFields(fields map[string]interface{}) *Helper {
|
||||
return &Helper{logger: h.logger.Fields(fields)}
|
||||
}
|
||||
|
||||
func HelperOrDefault(h *Helper) *Helper {
|
||||
if h == nil {
|
||||
return DefaultHelper
|
||||
}
|
||||
return h
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Level int8
|
||||
|
||||
const (
|
||||
// TraceLevel level. Designates finer-grained informational events than the Debug.
|
||||
TraceLevel Level = iota - 2
|
||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||
DebugLevel
|
||||
// InfoLevel is the default logging priority.
|
||||
// General operational entries about what's going on inside the application.
|
||||
InfoLevel
|
||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||
WarnLevel
|
||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||
ErrorLevel
|
||||
// FatalLevel level. Logs and then calls `logger.Exit(1)`. highest level of severity.
|
||||
FatalLevel
|
||||
)
|
||||
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case TraceLevel:
|
||||
return "trace"
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case WarnLevel:
|
||||
return "warn"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Enabled returns true if the given level is at or above this level.
|
||||
func (l Level) Enabled(lvl Level) bool {
|
||||
return lvl >= l
|
||||
}
|
||||
|
||||
// ToSlog converts our Level to slog.Level.
|
||||
func (l Level) ToSlog() slog.Level {
|
||||
const (
|
||||
traceLevelOffset = 4
|
||||
fatalLevelOffset = 4
|
||||
)
|
||||
|
||||
switch l {
|
||||
case TraceLevel:
|
||||
return slog.LevelDebug - traceLevelOffset // Lower than Debug
|
||||
case DebugLevel:
|
||||
return slog.LevelDebug
|
||||
case InfoLevel:
|
||||
return slog.LevelInfo
|
||||
case WarnLevel:
|
||||
return slog.LevelWarn
|
||||
case ErrorLevel:
|
||||
return slog.LevelError
|
||||
case FatalLevel:
|
||||
return slog.LevelError + fatalLevelOffset // Higher than Error
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
// GetLevel converts a level string into a logger Level value.
|
||||
// returns an error if the input string does not match known values.
|
||||
func GetLevel(levelStr string) (Level, error) {
|
||||
switch levelStr {
|
||||
case TraceLevel.String():
|
||||
return TraceLevel, nil
|
||||
case DebugLevel.String():
|
||||
return DebugLevel, nil
|
||||
case InfoLevel.String():
|
||||
return InfoLevel, nil
|
||||
case WarnLevel.String():
|
||||
return WarnLevel, nil
|
||||
case ErrorLevel.String():
|
||||
return ErrorLevel, nil
|
||||
case FatalLevel.String():
|
||||
return FatalLevel, nil
|
||||
}
|
||||
return InfoLevel, fmt.Errorf("unknown Level String: '%s', defaulting to InfoLevel", levelStr)
|
||||
}
|
||||
|
||||
func Info(args ...interface{}) {
|
||||
DefaultLogger.Log(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func Infof(template string, args ...interface{}) {
|
||||
DefaultLogger.Logf(InfoLevel, template, args...)
|
||||
}
|
||||
|
||||
func Trace(args ...interface{}) {
|
||||
DefaultLogger.Log(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func Tracef(template string, args ...interface{}) {
|
||||
DefaultLogger.Logf(TraceLevel, template, args...)
|
||||
}
|
||||
|
||||
func Debug(args ...interface{}) {
|
||||
DefaultLogger.Log(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func Debugf(template string, args ...interface{}) {
|
||||
DefaultLogger.Logf(DebugLevel, template, args...)
|
||||
}
|
||||
|
||||
func Warn(args ...interface{}) {
|
||||
DefaultLogger.Log(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func Warnf(template string, args ...interface{}) {
|
||||
DefaultLogger.Logf(WarnLevel, template, args...)
|
||||
}
|
||||
|
||||
func Error(args ...interface{}) {
|
||||
DefaultLogger.Log(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func Errorf(template string, args ...interface{}) {
|
||||
DefaultLogger.Logf(ErrorLevel, template, args...)
|
||||
}
|
||||
|
||||
func Fatal(args ...interface{}) {
|
||||
DefaultLogger.Log(FatalLevel, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func Fatalf(template string, args ...interface{}) {
|
||||
DefaultLogger.Logf(FatalLevel, template, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Returns true if the given level is at or lower the current logger level.
|
||||
func V(lvl Level, log Logger) bool {
|
||||
l := DefaultLogger
|
||||
if log != nil {
|
||||
l = log
|
||||
}
|
||||
return l.Options().Level <= lvl
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Package log provides a log interface
|
||||
package logger
|
||||
|
||||
var (
|
||||
// Default logger.
|
||||
DefaultLogger Logger = NewLogger()
|
||||
|
||||
// Default logger helper.
|
||||
DefaultHelper *Helper = NewHelper(DefaultLogger)
|
||||
)
|
||||
|
||||
// Logger is a generic logging interface.
|
||||
type Logger interface {
|
||||
// Init initializes options
|
||||
Init(options ...Option) error
|
||||
// The Logger options
|
||||
Options() Options
|
||||
// Fields set fields to always be logged
|
||||
Fields(fields map[string]interface{}) Logger
|
||||
// Log writes a log entry
|
||||
Log(level Level, v ...interface{})
|
||||
// Logf writes a formatted log entry
|
||||
Logf(level Level, format string, v ...interface{})
|
||||
// String returns the name of logger
|
||||
String() string
|
||||
}
|
||||
|
||||
func Init(opts ...Option) error {
|
||||
return DefaultLogger.Init(opts...)
|
||||
}
|
||||
|
||||
func Fields(fields map[string]interface{}) Logger {
|
||||
return DefaultLogger.Fields(fields)
|
||||
}
|
||||
|
||||
func Log(level Level, v ...interface{}) {
|
||||
DefaultLogger.Log(level, v...)
|
||||
}
|
||||
|
||||
func Logf(level Level, format string, v ...interface{}) {
|
||||
DefaultLogger.Logf(level, format, v...)
|
||||
}
|
||||
|
||||
func String() string {
|
||||
return DefaultLogger.String()
|
||||
}
|
||||
|
||||
func LoggerOrDefault(l Logger) Logger {
|
||||
if l == nil {
|
||||
return DefaultLogger
|
||||
}
|
||||
return l
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
l := NewLogger(WithLevel(TraceLevel), WithCallerSkipCount(2))
|
||||
|
||||
h1 := NewHelper(l).WithFields(map[string]interface{}{"key1": "val1"})
|
||||
h1.Log(TraceLevel, "simple log before trace_msg1")
|
||||
h1.Trace("trace_msg1")
|
||||
h1.Log(TraceLevel, "simple log after trace_msg1")
|
||||
h1.Warn("warn_msg1")
|
||||
|
||||
h2 := NewHelper(l).WithFields(map[string]interface{}{"key2": "val2"})
|
||||
h2.Logf(TraceLevel, "formatted log before trace_msg%s", "2")
|
||||
h2.Trace("trace_msg2")
|
||||
h2.Logf(TraceLevel, "formatted log after trace_msg%s", "2")
|
||||
h2.Warn("warn_msg2")
|
||||
|
||||
l = NewLogger(WithLevel(TraceLevel), WithCallerSkipCount(1))
|
||||
l.Fields(map[string]interface{}{"key3": "val4"}).Log(InfoLevel, "test_msg")
|
||||
}
|
||||
|
||||
func TestExtract(t *testing.T) {
|
||||
l := NewLogger(WithLevel(TraceLevel), WithCallerSkipCount(2)).Fields(map[string]interface{}{"requestID": "req-1"})
|
||||
|
||||
ctx := NewContext(context.Background(), l)
|
||||
|
||||
Info("info message without request ID")
|
||||
Extract(ctx).Info("info message with request ID")
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
type Options struct {
|
||||
// It's common to set this to a file, or leave it default which is `os.Stderr`
|
||||
Out io.Writer
|
||||
// Alternative options
|
||||
Context context.Context
|
||||
// fields to always be logged
|
||||
Fields map[string]interface{}
|
||||
// Caller skip frame count for file:line info
|
||||
CallerSkipCount int
|
||||
// The logging level the logger should log at. default is `InfoLevel`
|
||||
Level Level
|
||||
}
|
||||
|
||||
// WithFields set default fields for the logger.
|
||||
func WithFields(fields map[string]interface{}) Option {
|
||||
return func(args *Options) {
|
||||
args.Fields = fields
|
||||
}
|
||||
}
|
||||
|
||||
// WithLevel set default level for the logger.
|
||||
func WithLevel(level Level) Option {
|
||||
return func(args *Options) {
|
||||
args.Level = level
|
||||
}
|
||||
}
|
||||
|
||||
// WithOutput set default output writer for the logger.
|
||||
func WithOutput(out io.Writer) Option {
|
||||
return func(args *Options) {
|
||||
args.Out = out
|
||||
}
|
||||
}
|
||||
|
||||
// WithCallerSkipCount set frame count to skip.
|
||||
func WithCallerSkipCount(c int) Option {
|
||||
return func(args *Options) {
|
||||
args.CallerSkipCount = c
|
||||
}
|
||||
}
|
||||
|
||||
func SetOption(k, v interface{}) Option {
|
||||
return func(o *Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user