Repository: go-kratos/kratos
Pull request #1761 (2022-01-11 09:29:56)

feat(log): add global logger appliance, as process level default logger
<!--
🎉 Thanks for sending a pull request to Kratos! Here are some tips for you:

1. If this is your first time contributing to Kratos, please read our contribution guide: https://go-kratos.dev/en/docs/community/contribution/
2. Ensure you have added or ran the appropriate tests and lint for your PR, please use `make lint` and `make test` before filing your PR, use `make clean` to tidy your go mod.
3. If the PR is unfinished, you may need mark it as a WIP(Work In Progress) PR or draft PR
4. Please use a conventional commits format title: `<type>[optional scope]: <description>`
    
    Some suggestion on <type>:

    fix: A bug fix
    feat: A new feature
    test: Adding missing tests or correcting existing tests
    refactor: A code change that neither fixes a bug nor adds a feature
    break: Changes has break change

    docs: Documentation only changes
    deps: Changes external dependencies
    style: Changes that do not affect the meaning of the code (white-space, formatting, etc)
    chore Daily work, examples, etc.
    ci: Changes to our CI configuration files and scripts
-->

#### Description (what this PR does / why we need it):

Add global logger appliance, make logging in independence function easily.


#### Which issue(s) this PR fixes (resolves / be part of):
<!--
* Automatically closes linked issue when PR is merged.
* If you PR is not fully resolved issue, please use `part of #<issue number>` instead.

Usage: `fixes/resolves #<issue number>`, or `fixes/resolves (paste link of issue)`.
-->
fixes #

#### Other special notes for reviewer:
<!--
* Somethings that need extra attention for reviewer
* Some additional notes, TODO list, etc.
-->



Patch:
diff --git a/app.go b/app.go
index 736c27a1..8418af42 100644
--- a/app.go
+++ b/app.go
@@ -39,7 +39,7 @@ type App struct {
 func New(opts ...Option) *App {
 	o := options{
 		ctx:              context.Background(),
-		logger:           log.NewHelper(log.DefaultLogger),
+		logger:           log.NewHelper(log.GetLogger()),
 		sigs:             []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT},
 		registrarTimeout: 10 * time.Second,
 		stopTimeout:      10 * time.Second,
diff --git a/config/config.go b/config/config.go
index 5fed286b..82c198a1 100644
--- a/config/config.go
+++ b/config/config.go
@@ -49,7 +49,7 @@ type config struct {
 // New new a config with options.
 func New(opts ...Option) Config {
 	o := options{
-		logger:   log.DefaultLogger,
+		logger:   log.GetLogger(),
 		decoder:  defaultDecoder,
 		resolver: defaultResolver,
 	}
diff --git a/contrib/config/apollo/apollo.go b/contrib/config/apollo/apollo.go
index 866c0ea3..2b69ecfd 100644
--- a/contrib/config/apollo/apollo.go
+++ b/contrib/config/apollo/apollo.go
@@ -99,7 +99,7 @@ func WithLogger(logger log.Logger) Option {
 
 func NewSource(opts ...Option) config.Source {
 	op := options{
-		logger: log.DefaultLogger,
+		logger: log.GetLogger(),
 	}
 	for _, o := range opts {
 		o(&op)
@@ -167,7 +167,7 @@ func resolve(key string, value interface{}, target map[string]interface{}) {
 		// current exists, then check existing value type, if it's not map
 		// that means duplicate keys, and at least one is not map instance.
 		if cursor, ok = v.(map[string]interface{}); !ok {
-			_ = log.DefaultLogger.Log(log.LevelWarn,
+			_ = log.GetLogger().Log(log.LevelWarn,
 				"msg",
 				fmt.Sprintf("duplicate key: %v\n", strings.Join(keys[:i+1], ".")),
 			)
diff --git a/contrib/config/apollo/watcher.go b/contrib/config/apollo/watcher.go
index c36d3004..6ada4d95 100644
--- a/contrib/config/apollo/watcher.go
+++ b/contrib/config/apollo/watcher.go
@@ -63,7 +63,7 @@ func (c *customChangeListener) OnNewestChange(changeEvent *storage.FullChangeEve
 
 func newWatcher(a *apollo, logger log.Logger) (config.Watcher, error) {
 	if logger == nil {
-		logger = log.DefaultLogger
+		logger = log.GetLogger()
 	}
 
 	changeCh := make(chan []*config.KeyValue)
diff --git a/log/global.go b/log/global.go
new file mode 100644
index 00000000..ab993ba0
--- /dev/null
+++ b/log/global.go
@@ -0,0 +1,126 @@
+package log
+
+import (
+	"fmt"
+	"os"
+	"sync"
+)
+
+// globalLogger is designed as a global logger in current process.
+var (
+	global = &loggerAppliance{}
+)
+
+type loggerAppliance struct {
+	lock   sync.Mutex
+	logger Logger
+	helper *Helper
+}
+
+func init() {
+	global.SetLogger(DefaultLogger)
+}
+
+func (a *loggerAppliance) SetLogger(in Logger) {
+	a.lock.Lock()
+	defer a.lock.Unlock()
+	a.logger = in
+	a.helper = NewHelper(a.logger)
+}
+
+func (a *loggerAppliance) GetLogger() Logger {
+	return a.logger
+}
+
+// SetLogger should be called before any other log call.
+// And it is NOT THREAD SAFE.
+func SetLogger(logger Logger) {
+	global.SetLogger(logger)
+}
+
+func GetLogger() Logger {
+	return global.GetLogger()
+}
+
+// Log Print log by level and keyvals.
+func Log(level Level, keyvals ...interface{}) {
+	_ = global.logger.Log(level, keyvals...)
+}
+
+// Debug logs a message at debug level.
+func Debug(a ...interface{}) {
+	global.helper.Log(LevelDebug, global.helper.msgKey, fmt.Sprint(a...))
+}
+
+// Debugf logs a message at debug level.
+func Debugf(format string, a ...interface{}) {
+	global.helper.Log(LevelDebug, global.helper.msgKey, fmt.Sprintf(format, a...))
+}
+
+// Debugw logs a message at debug level.
+func Debugw(keyvals ...interface{}) {
+	global.helper.Log(LevelDebug, keyvals...)
+}
+
+// Info logs a message at info level.
+func Info(a ...interface{}) {
+	global.helper.Log(LevelInfo, global.helper.msgKey, fmt.Sprint(a...))
+}
+
+// Infof logs a message at info level.
+func Infof(format string, a ...interface{}) {
+	global.helper.Log(LevelInfo, global.helper.msgKey, fmt.Sprintf(format, a...))
+}
+
+// Infow logs a message at info level.
+func Infow(keyvals ...interface{}) {
+	global.helper.Log(LevelInfo, keyvals...)
+}
+
+// Warn logs a message at warn level.
+func Warn(a ...interface{}) {
+	global.helper.Log(LevelWarn, global.helper.msgKey, fmt.Sprint(a...))
+}
+
+// Warnf logs a message at warnf level.
+func Warnf(format string, a ...interface{}) {
+	global.helper.Log(LevelWarn, global.helper.msgKey, fmt.Sprintf(format, a...))
+}
+
+// Warnw logs a message at warnf level.
+func Warnw(keyvals ...interface{}) {
+	global.helper.Log(LevelWarn, keyvals...)
+}
+
+// Error logs a message at error level.
+func Error(a ...interface{}) {
+	global.helper.Log(LevelError, global.helper.msgKey, fmt.Sprint(a...))
+}
+
+// Errorf logs a message at error level.
+func Errorf(format string, a ...interface{}) {
+	global.helper.Log(LevelError, global.helper.msgKey, fmt.Sprintf(format, a...))
+}
+
+// Errorw logs a message at error level.
+func Errorw(keyvals ...interface{}) {
+	global.helper.Log(LevelError, keyvals...)
+}
+
+// Fatal logs a message at fatal level.
+func Fatal(a ...interface{}) {
+	global.helper.Log(LevelFatal, global.helper.msgKey, fmt.Sprint(a...))
+	os.Exit(1)
+}
+
+// Fatalf logs a message at fatal level.
+func Fatalf(format string, a ...interface{}) {
+	global.helper.Log(LevelFatal, global.helper.msgKey, fmt.Sprintf(format, a...))
+	os.Exit(1)
+}
+
+// Fatalw logs a message at fatal level.
+func Fatalw(keyvals ...interface{}) {
+	global.helper.Log(LevelFatal, keyvals...)
+	os.Exit(1)
+}
diff --git a/middleware/recovery/recovery.go b/middleware/recovery/recovery.go
index 4637bf02..298d7531 100644
--- a/middleware/recovery/recovery.go
+++ b/middleware/recovery/recovery.go
@@ -40,7 +40,7 @@ func WithLogger(logger log.Logger) Option {
 // Recovery is a server middleware that recovers from any panics.
 func Recovery(opts ...Option) middleware.Middleware {
 	op := options{
-		logger: log.DefaultLogger,
+		logger: log.GetLogger(),
 		handler: func(ctx context.Context, req, err interface{}) error {
 			return ErrUnknownRequest
 		},
diff --git a/transport/grpc/resolver/discovery/builder.go b/transport/grpc/resolver/discovery/builder.go
index de8dccf2..d0acf646 100644
--- a/transport/grpc/resolver/discovery/builder.go
+++ b/transport/grpc/resolver/discovery/builder.go
@@ -48,7 +48,7 @@ type builder struct {
 func NewBuilder(d registry.Discovery, opts ...Option) resolver.Builder {
 	b := &builder{
 		discoverer: d,
-		logger:     log.DefaultLogger,
+		logger:     log.GetLogger(),
 		timeout:    time.Second * 10,
 		insecure:   false,
 	}
diff --git a/transport/grpc/server.go b/transport/grpc/server.go
index 0ae458d6..c7ebbaa1 100644
--- a/transport/grpc/server.go
+++ b/transport/grpc/server.go
@@ -122,7 +122,7 @@ func NewServer(opts ...ServerOption) *Server {
 		address: ":0",
 		timeout: 1 * time.Second,
 		health:  health.NewServer(),
-		log:     log.NewHelper(log.DefaultLogger),
+		log:     log.NewHelper(log.GetLogger()),
 	}
 	for _, o := range opts {
 		o(srv)
diff --git a/transport/http/resolver.go b/transport/http/resolver.go
index b4110476..37c2dec2 100644
--- a/transport/http/resolver.go
+++ b/transport/http/resolver.go
@@ -57,7 +57,7 @@ func newResolver(ctx context.Context, discovery registry.Discovery, target *Targ
 	r := &resolver{
 		target:     target,
 		watcher:    watcher,
-		logger:     log.NewHelper(log.DefaultLogger),
+		logger:     log.NewHelper(log.GetLogger()),
 		rebalancer: rebalancer,
 		insecure:   insecure,
 	}
diff --git a/transport/http/server.go b/transport/http/server.go
index f30268cf..8002624b 100644
--- a/transport/http/server.go
+++ b/transport/http/server.go
@@ -143,7 +143,7 @@ func NewServer(opts ...ServerOption) *Server {
 		enc:         DefaultResponseEncoder,
 		ene:         DefaultErrorEncoder,
 		strictSlash: true,
-		log:         log.NewHelper(log.DefaultLogger),
+		log:         log.NewHelper(log.GetLogger()),
 	}
 	for _, o := range opts {
 		o(srv)


Test patch:
diff --git a/config/config_test.go b/config/config_test.go
index 27f453c1..ecf0b350 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -123,7 +123,7 @@ func TestConfig(t *testing.T) {
 		WithSource(newTestJSONSource(_testJSON)),
 		WithDecoder(defaultDecoder),
 		WithResolver(defaultResolver),
-		WithLogger(log.DefaultLogger),
+		WithLogger(log.GetLogger()),
 	)
 	err = c.Close()
 	assert.Nil(t, err)
@@ -133,7 +133,7 @@ func TestConfig(t *testing.T) {
 		sources:  []Source{jSource},
 		decoder:  defaultDecoder,
 		resolver: defaultResolver,
-		logger:   log.DefaultLogger,
+		logger:   log.GetLogger(),
 	}
 	cf := &config{}
 	cf.opts = opts
diff --git a/log/global_test.go b/log/global_test.go
new file mode 100644
index 00000000..9e2d95f1
--- /dev/null
+++ b/log/global_test.go
@@ -0,0 +1,68 @@
+package log
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+	"testing"
+)
+
+func TestGlobalLog(t *testing.T) {
+	buffer := &bytes.Buffer{}
+	SetLogger(NewStdLogger(buffer))
+
+	testCases := []struct {
+		level   Level
+		content []interface{}
+	}{
+		{
+			LevelDebug,
+			[]interface{}{"test debug"},
+		},
+		{
+			LevelInfo,
+			[]interface{}{"test info"},
+		},
+		{
+			LevelInfo,
+			[]interface{}{"test %s", "info"},
+		},
+		{
+			LevelWarn,
+			[]interface{}{"test warn"},
+		},
+		{
+			LevelError,
+			[]interface{}{"test error"},
+		},
+		{
+			LevelError,
+			[]interface{}{"test %s", "error"},
+		},
+	}
+
+	expected := []string{}
+	for _, tc := range testCases {
+		msg := fmt.Sprintf(tc.content[0].(string), tc.content[1:]...)
+		switch tc.level {
+		case LevelDebug:
+			Debugf(tc.content[0].(string), tc.content[1:]...)
+			expected = append(expected, fmt.Sprintf("%s msg=%s", "DEBUG", msg))
+		case LevelInfo:
+			Infof(tc.content[0].(string), tc.content[1:]...)
+			expected = append(expected, fmt.Sprintf("%s msg=%s", "INFO", msg))
+		case LevelWarn:
+			Warnf(tc.content[0].(string), tc.content[1:]...)
+			expected = append(expected, fmt.Sprintf("%s msg=%s", "WARN", msg))
+		case LevelError:
+			Errorf(tc.content[0].(string), tc.content[1:]...)
+			expected = append(expected, fmt.Sprintf("%s msg=%s", "ERROR", msg))
+		}
+	}
+	expected = append(expected, "")
+
+	t.Logf("Content: %s", buffer.String())
+	if buffer.String() != strings.Join(expected, "\n") {
+		t.Errorf("Expected: %s, got: %s", strings.Join(expected, "\n"), buffer.String())
+	}
+}
diff --git a/middleware/recovery/recovery_test.go b/middleware/recovery/recovery_test.go
index 11a9cacf..aa77ab9f 100644
--- a/middleware/recovery/recovery_test.go
+++ b/middleware/recovery/recovery_test.go
@@ -19,7 +19,7 @@ func TestOnce(t *testing.T) {
 	next := func(ctx context.Context, req interface{}) (interface{}, error) {
 		panic("panic reason")
 	}
-	_, e := Recovery(WithLogger(log.DefaultLogger))(next)(context.Background(), "panic")
+	_, e := Recovery(WithLogger(log.GetLogger()))(next)(context.Background(), "panic")
 	t.Logf("succ and reason is %v", e)
 }
 
diff --git a/transport/grpc/resolver/discovery/resolver_test.go b/transport/grpc/resolver/discovery/resolver_test.go
index e48c542c..ae8f1b89 100644
--- a/transport/grpc/resolver/discovery/resolver_test.go
+++ b/transport/grpc/resolver/discovery/resolver_test.go
@@ -56,7 +56,7 @@ func TestWatch(t *testing.T) {
 	r := &discoveryResolver{
 		w:        &testWatch{},
 		cc:       &testClientConn{te: t},
-		log:      log.NewHelper(log.DefaultLogger),
+		log:      log.NewHelper(log.GetLogger()),
 		ctx:      ctx,
 		cancel:   cancel,
 		insecure: false,
@@ -75,7 +75,7 @@ func TestWatchError(t *testing.T) {
 	r := &discoveryResolver{
 		w:      &testWatch{err: errors.New("bad")},
 		cc:     &testClientConn{te: t},
-		log:    log.NewHelper(log.DefaultLogger),
+		log:    log.NewHelper(log.GetLogger()),
 		ctx:    ctx,
 		cancel: cancel,
 	}
@@ -93,7 +93,7 @@ func TestWatchContextCancel(t *testing.T) {
 	r := &discoveryResolver{
 		w:      &testWatch{err: context.Canceled},
 		cc:     &testClientConn{te: t},
-		log:    log.NewHelper(log.DefaultLogger),
+		log:    log.NewHelper(log.GetLogger()),
 		ctx:    ctx,
 		cancel: cancel,
 	}