Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion load/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func loadMessagerWithPatch(msg proto.Message, path string, fmt format.Format, pa
default:
return xerrors.Newf("unknown patch type: %v", patch)
}
log.Debugf("patched(%s) %s by %v: %s", patch, name, patchPaths, msg)
log.Debugf("patched(%s) %s by %v", patch, name, patchPaths)
return nil
}

Expand Down
100 changes: 100 additions & 0 deletions log/driver/customdriver/customdriver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Package customdriver adapts a user-provided [Logger] to the
// driver.Driver interface, so that tableau's log output can be routed into
// an external logging system.
package customdriver

import (
"fmt"
"strings"

"github.com/tableauio/tableau/log/core"
"github.com/tableauio/tableau/log/driver"
)

var _ driver.Driver = (*CustomDriver)(nil)

// Logger is a minimal Printf-style logging interface, satisfied directly by
// most loggers (e.g. *zap.SugaredLogger) or via a thin adapter (e.g. slog).
type Logger interface {
Debugf(format string, args ...any)
Infof(format string, args ...any)
Warnf(format string, args ...any)
Errorf(format string, args ...any)
DPanicf(format string, args ...any)
Panicf(format string, args ...any)
Fatalf(format string, args ...any)
}

// CustomDriver forwards core.Record entries to a user-provided Logger.
type CustomDriver struct {
logger Logger
}

// New creates a CustomDriver that forwards log records to logger.
func New(logger Logger) *CustomDriver {
return &CustomDriver{logger: logger}
}

func (d *CustomDriver) Name() string {
return "custom"
}

// GetLevel always returns core.DebugLevel; filtering is delegated to logger.
func (d *CustomDriver) GetLevel(logger string) core.Level {
return core.DebugLevel
}

// Print normalizes r (from a Xxx/Xxxf/Xxxw call) into one message and
// forwards it via the corresponding Xxxf method.
func (d *CustomDriver) Print(r *core.Record) {
msg := formatRecord(r)
switch r.Level {
case core.DebugLevel:
d.logger.Debugf("%s", msg)
case core.InfoLevel:
d.logger.Infof("%s", msg)
case core.WarnLevel:
d.logger.Warnf("%s", msg)
case core.ErrorLevel:
d.logger.Errorf("%s", msg)
case core.DPanicLevel:
d.logger.DPanicf("%s", msg)
case core.PanicLevel:
d.logger.Panicf("%s", msg)
case core.FatalLevel:
d.logger.Fatalf("%s", msg)
}
}

// formatRecord renders r's format/args and any key-value pairs into one message.
func formatRecord(r *core.Record) string {
var msg string
if r.Format != nil && *r.Format != "" {
msg = fmt.Sprintf(*r.Format, r.Args...)
} else if len(r.Args) > 0 {
msg = fmt.Sprint(r.Args...)
}
if len(r.KVs) > 0 {
if msg != "" {
msg += " "
}
msg += formatKVs(r.KVs)
}
return msg
}

// formatKVs renders kvs as "key1=value1 key2=value2".
func formatKVs(kvs []any) string {
var b strings.Builder
for i := 0; i < len(kvs); i += 2 {
if i > 0 {
b.WriteByte(' ')
}
if i+1 < len(kvs) {
fmt.Fprintf(&b, "%v=%v", kvs[i], kvs[i+1])
} else {
fmt.Fprintf(&b, "%v=(MISSING)", kvs[i])
}
}
return b.String()
}
47 changes: 42 additions & 5 deletions log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,74 @@ package log

import (
"fmt"
"sync"

"github.com/tableauio/tableau/log/core"
"github.com/tableauio/tableau/log/driver"
"github.com/tableauio/tableau/log/driver/customdriver"
"github.com/tableauio/tableau/log/driver/zapdriver"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

var defaultLogger *Logger
// Logger is the minimal Printf-style logging interface expected by
// SetLogger. *zap.SugaredLogger satisfies it directly; other logging
// systems (e.g. slog, logrus) can be plugged in via a thin adapter.
type Logger = customdriver.Logger

var defaultLogger *sugaredLogger

var gOpts *Options
var atomicLevel zap.AtomicLevel

// mu guards hasCustomLogger, which is read/written by SetLogger and Init.
var mu sync.Mutex
var hasCustomLogger bool

func init() {
defaultLogger = &Logger{
defaultLogger = &sugaredLogger{
level: core.DebugLevel,
}
gOpts = &Options{}
atomicLevel = zap.NewAtomicLevelAt(zap.DebugLevel)
}

// SetLogger installs a user-provided logger (e.g. *zap.SugaredLogger, or a
// custom adapter around slog/logrus/etc.) as tableau's log destination.
//
// Once set, subsequent calls to Init (triggered internally by
// tableau.GenProto/GenConf via log.Options) will no longer install the
// built-in zap-based driver, so the custom logger keeps taking effect.
func SetLogger(logger Logger) {
mu.Lock()
hasCustomLogger = true
mu.Unlock()
SetDriver(customdriver.New(logger))
}

// Init initializes the built-in zap-based logger from opts.
//
// NOTE: if a custom logger has already been installed via SetLogger, Init
// only updates the log level used for LevelEnabled, and leaves the custom
// logger driver untouched.
func Init(opts *Options) error {
gOpts = opts // remember as global options.

if err := atomicLevel.UnmarshalText([]byte(opts.Level)); err != nil {
return fmt.Errorf("illegal log level: %s", opts.Level)
}

mu.Lock()
skip := hasCustomLogger
mu.Unlock()
if skip {
return nil
}

logger, err := zapdriver.NewLogger(opts.Mode, opts.Level, opts.Filename, opts.Sink)
if err != nil {
return err
}
if err := atomicLevel.UnmarshalText([]byte(opts.Level)); err != nil {
return fmt.Errorf("illegal log level: %s", opts.Level)
}
SetDriver(zapdriver.NewWithLogger(atomicLevel, logger))
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion log/log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func Test_logs(t *testing.T) {
}

func TestMain(m *testing.M) {
defaultLogger = &Logger{
defaultLogger = &sugaredLogger{
level: core.DebugLevel,
// driver: &defaultdriver.DefaultDriver{
// CallerSkip: 1,
Expand Down
95 changes: 26 additions & 69 deletions log/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,158 +7,115 @@ import (
"github.com/tableauio/tableau/log/driver"
)

type LoggerIface interface {
// Debug uses fmt.Sprint to construct and log a message.
Debug(args ...any)

// Info uses fmt.Sprint to construct and log a message.
Info(args ...any)

// Warn uses fmt.Sprint to construct and log a message.
Warn(args ...any)

// Error uses fmt.Sprint to construct and log a message.
Error(args ...any)

// DPanic uses fmt.Sprint to construct and log a message. In development, the
// logger then panics. (See DPanicLevel for details.)
DPanic(args ...any)

// Panic uses fmt.Sprint to construct and log a message, then panics.
Panic(args ...any)

// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.
Fatal(args ...any)

// Debugf uses fmt.Sprintf to log a templated message.
Debugf(format string, args ...any)

// Infof uses fmt.Sprintf to log a templated message.
Infof(format string, args ...any)

// Warnf uses fmt.Sprintf to log a templated message.
Warnf(format string, args ...any)

// Errorf uses fmt.Sprintf to log a templated message.
Errorf(format string, args ...any)

// DPanicf uses fmt.Sprintf to log a templated message. In development, the
// logger then panics. (See DPanicLevel for details.)
DPanicf(format string, args ...any)

// Panicf uses fmt.Sprintf to log a templated message, then panics.
Panicf(format string, args ...any)

// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit.
Fatalf(format string, args ...any)
}

type Logger struct {
// sugaredLogger is tableau's built-in logger implementation, which dispatches
// records to a pluggable driver.Driver (default: zap-based; can be replaced
// by a user-provided logger via SetLogger).
type sugaredLogger struct {
level core.Level
driver driver.Driver
}

func (l *Logger) Debug(args ...any) {
func (l *sugaredLogger) Debug(args ...any) {
l.log(core.DebugLevel, "", args, nil)
}

func (l *Logger) Info(args ...any) {
func (l *sugaredLogger) Info(args ...any) {
l.log(core.InfoLevel, "", args, nil)
}

func (l *Logger) Warn(args ...any) {
func (l *sugaredLogger) Warn(args ...any) {
l.log(core.WarnLevel, "", args, nil)
}

func (l *Logger) Error(args ...any) {
func (l *sugaredLogger) Error(args ...any) {
l.log(core.ErrorLevel, "", args, nil)
}

func (l *Logger) DPanic(args ...any) {
func (l *sugaredLogger) DPanic(args ...any) {
l.log(core.DPanicLevel, "%+v", args, nil)
// TODO: panic only in development
os.Exit(-1)
}

func (l *Logger) Panic(args ...any) {
func (l *sugaredLogger) Panic(args ...any) {
l.log(core.PanicLevel, "%+v", args, nil)
os.Exit(-1)
}

func (l *Logger) Fatal(args ...any) {
func (l *sugaredLogger) Fatal(args ...any) {
l.log(core.FatalLevel, "%+v", args, nil)
os.Exit(-1)
}

func (l *Logger) Debugf(format string, args ...any) {
func (l *sugaredLogger) Debugf(format string, args ...any) {
l.log(core.DebugLevel, format, args, nil)
}

func (l *Logger) Infof(format string, args ...any) {
func (l *sugaredLogger) Infof(format string, args ...any) {
l.log(core.InfoLevel, format, args, nil)
}

func (l *Logger) Warnf(format string, args ...any) {
func (l *sugaredLogger) Warnf(format string, args ...any) {
l.log(core.WarnLevel, format, args, nil)
}

func (l *Logger) Errorf(format string, args ...any) {
func (l *sugaredLogger) Errorf(format string, args ...any) {
l.log(core.ErrorLevel, format, args, nil)
}

func (l *Logger) DPanicf(format string, args ...any) {
func (l *sugaredLogger) DPanicf(format string, args ...any) {
l.log(core.DPanicLevel, format, args, nil)
// TODO: panic only in development
panic("log panic")
}

func (l *Logger) Panicf(format string, args ...any) {
func (l *sugaredLogger) Panicf(format string, args ...any) {
l.log(core.PanicLevel, format, args, nil)
panic("log panic")
}

func (l *Logger) Fatalf(format string, args ...any) {
func (l *sugaredLogger) Fatalf(format string, args ...any) {
l.log(core.FatalLevel, format, args, nil)
os.Exit(-1)
}

// Debugw logs a message with some additional context. The variadic key-value
// pairs are treated as they are in With.
func (l *Logger) Debugw(msg string, keysAndValues ...any) {
func (l *sugaredLogger) Debugw(msg string, keysAndValues ...any) {
l.log(core.DebugLevel, msg, nil, keysAndValues)
}

// Infow logs a message with some additional context.
func (l *Logger) Infow(msg string, keysAndValues ...any) {
func (l *sugaredLogger) Infow(msg string, keysAndValues ...any) {
l.log(core.InfoLevel, msg, nil, keysAndValues)
}

// Warnw logs a message with some additional context.
func (l *Logger) Warnw(msg string, keysAndValues ...any) {
func (l *sugaredLogger) Warnw(msg string, keysAndValues ...any) {
l.log(core.WarnLevel, msg, nil, keysAndValues)
}

// Errorw logs a message with some additional context.
func (l *Logger) Errorw(msg string, keysAndValues ...any) {
func (l *sugaredLogger) Errorw(msg string, keysAndValues ...any) {
l.log(core.ErrorLevel, msg, nil, keysAndValues)
}

// DPanicw logs a message with some additional context.
func (l *Logger) DPanicw(msg string, keysAndValues ...any) {
func (l *sugaredLogger) DPanicw(msg string, keysAndValues ...any) {
l.log(core.DPanicLevel, msg, nil, keysAndValues)
}

// Panicw logs a message with some additional context.
func (l *Logger) Panicw(msg string, keysAndValues ...any) {
func (l *sugaredLogger) Panicw(msg string, keysAndValues ...any) {
l.log(core.PanicLevel, msg, nil, keysAndValues)
}

// Fatalw logs a message with some additional context.
func (l *Logger) Fatalw(msg string, keysAndValues ...any) {
func (l *sugaredLogger) Fatalw(msg string, keysAndValues ...any) {
l.log(core.FatalLevel, msg, nil, keysAndValues)
}

func (l *Logger) log(lvl core.Level, format string, fmtArgs []any, kvs []any) {
func (l *sugaredLogger) log(lvl core.Level, format string, fmtArgs []any, kvs []any) {
// If logging at this level is completely disabled, skip the overhead of
// string formatting.
// if lvl < DPanicLevel {
Expand Down
4 changes: 2 additions & 2 deletions log/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ func TestDefaultLogger_Debugf(t *testing.T) {
}
tests := []struct {
name string
l *Logger
l *sugaredLogger
args args
}{
// TODO: Add test cases.
{
name: "test",
l: &Logger{
l: &sugaredLogger{
level: core.DebugLevel,
driver: &defaultdriver.DefaultDriver{},
},
Expand Down
Loading
Loading