Repository: go-kratos/kratos
Pull request #1927 (2022-04-11 08:35:45)

feat(log): Helper implemented io.Writer
Implemented io.Writer, easy to use in standard library, like log.New.

#### Description (what this PR does / why we need it):

`Helper` can set to `http.Server{}.ErrorLog`.

Example 1(transport/http):

```go
// import stdlog "log"
// import log "github.com/go-kratos/kratos/v2/log"
// file: https://github.com/go-kratos/kratos/blob/main/transport/http/server.go#L153
srv.Server = &http.Server{
	Handler:   FilterChain(srv.filters...)(srv.router),
	// record http error log to helper.
	ErrorLog:  stdlog.New(srv.log.WithWriterLevel(log.LevelError), "", 0),
}
```

Example 2 (go-redis):

```go
// import stdlog "log"
// import log "github.com/go-kratos/kratos/v2/log"
helper := log.NewHelper(logger, log.WithWriterLevel(log.LevelInfo))
redis.SetLogger(stdlog.New(helper, "", 0))
```

#### Other special notes for reviewer:
<!--
* Somethings that need extra attention for reviewer
* Some additional notes, TODO list, etc.
-->



codecov-commenter: # [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/1927?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) Report
> Merging [#1927](https://codecov.io/gh/go-kratos/kratos/pull/1927?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (9b122eb) into [main](https://codecov.io/gh/go-kratos/kratos/commit/d5a2fb59adbbae7cca0e161748ae42fe04e90633?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (d5a2fb5) will **increase** coverage by `0.03%`.
> The diff coverage is `84.31%`.

> :exclamation: Current head 9b122eb differs from pull request most recent head 170d4c2. Consider uploading reports for the commit 170d4c2 to get more accurate results

```diff
@@            Coverage Diff             @@
##             main    #1927      +/-   ##
==========================================
+ Coverage   80.65%   80.68%   +0.03%     
==========================================
  Files          81       81              
  Lines        3587     3619      +32     
==========================================
+ Hits         2893     2920      +27     
- Misses        487      492       +5     
  Partials      207      207              
```


| [Impacted Files](https://codecov.io/gh/go-kratos/kratos/pull/1927?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) | Coverage Δ | |
|---|---|---|
| [metadata/metadata.go](https://codecov.io/gh/go-kratos/kratos/pull/1927/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-bWV0YWRhdGEvbWV0YWRhdGEuZ28=) | `87.50% <0.00%> (ø)` | |
| [transport/http/status/status.go](https://codecov.io/gh/go-kratos/kratos/pull/1927/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-dHJhbnNwb3J0L2h0dHAvc3RhdHVzL3N0YXR1cy5nbw==) | `100.00% <ø> (ø)` | |
| [log/helper.go](https://codecov.io/gh/go-kratos/kratos/pull/1927/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-bG9nL2hlbHBlci5nbw==) | `77.41% <70.58%> (-4.22%)` | :arrow_down: |
| [errors/errors.go](https://codecov.io/gh/go-kratos/kratos/pull/1927/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-ZXJyb3JzL2Vycm9ycy5nbw==) | `94.44% <93.54%> (-1.79%)` | :arrow_down: |
| [middleware/validate/validate.go](https://codecov.io/gh/go-kratos/kratos/pull/1927/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-bWlkZGxld2FyZS92YWxpZGF0ZS92YWxpZGF0ZS5nbw==) | `100.00% <100.00%> (ø)` | |
| [transport/http/client.go](https://codecov.io/gh/go-kratos/kratos/pull/1927/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-dHJhbnNwb3J0L2h0dHAvY2xpZW50Lmdv) | `78.88% <100.00%> (ø)` | |
| [transport/http/router.go](https://codecov.io/gh/go-kratos/kratos/pull/1927/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-dHJhbnNwb3J0L2h0dHAvcm91dGVyLmdv) | `100.00% <0.00%> (+4.76%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/go-kratos/kratos/pull/1927?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/1927?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Last update [d5a2fb5...170d4c2](https://codecov.io/gh/go-kratos/kratos/pull/1927?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).

tonybase: ```
w := log.NewWriter(logger, log.LevelInfo, "log")
redis.SetLogger(log.New(w "redis: ", log.LstdFlags|log.Lshortfile))
```
You can provide a log writer to output third-party logs
elricli: > ```go
> infoWriter := log.NewWriter(logger, log.LevelInfo, "log")
> ```

Create and use a new function: `NewWriter(logger Logger, level Level, msgKey string)` instead of use `WithWriterLevel`, is it?
elricli: Updated.

Patch:
diff --git a/cmd/protoc-gen-go-errors/errors.go b/cmd/protoc-gen-go-errors/errors.go
index 7c2008d6..9e8e02ad 100644
--- a/cmd/protoc-gen-go-errors/errors.go
+++ b/cmd/protoc-gen-go-errors/errors.go
@@ -82,11 +82,19 @@ func genErrorsReason(gen *protogen.Plugin, file *protogen.File, g *protogen.Gene
 		if enumCode == 0 {
 			continue
 		}
+
+		comment := v.Comments.Leading.String()
+		if comment == "" {
+			comment = v.Comments.Trailing.String()
+		}
+
 		err := &errorInfo{
 			Name:       string(enum.Desc.Name()),
 			Value:      string(v.Desc.Name()),
 			CamelValue: case2Camel(string(v.Desc.Name())),
 			HTTPCode:   enumCode,
+			Comment:    comment,
+			HasComment: len(comment) > 0,
 		}
 		ew.Errors = append(ew.Errors, err)
 	}
@@ -94,6 +102,7 @@ func genErrorsReason(gen *protogen.Plugin, file *protogen.File, g *protogen.Gene
 		return true
 	}
 	g.P(ew.execute())
+
 	return false
 }
 
diff --git a/cmd/protoc-gen-go-errors/template.go b/cmd/protoc-gen-go-errors/template.go
index 3b6e0fc3..e879fcc4 100644
--- a/cmd/protoc-gen-go-errors/template.go
+++ b/cmd/protoc-gen-go-errors/template.go
@@ -8,6 +8,7 @@ import (
 var errorsTemplate = `
 {{ range .Errors }}
 
+{{if .HasComment}}{{.Comment}}{{end}}
 func Is{{.CamelValue}}(err error) bool {
 	if err == nil {
 		return false
@@ -16,6 +17,7 @@ func Is{{.CamelValue}}(err error) bool {
 	return e.Reason == {{.Name}}_{{.Value}}.String() && e.Code == {{.HTTPCode}} 
 }
 
+{{if .HasComment}}{{.Comment}}{{end}}
 func Error{{.CamelValue}}(format string, args ...interface{}) *errors.Error {
 	 return errors.New({{.HTTPCode}}, {{.Name}}_{{.Value}}.String(), fmt.Sprintf(format, args...))
 }
@@ -28,6 +30,8 @@ type errorInfo struct {
 	Value      string
 	HTTPCode   int
 	CamelValue string
+	Comment    string
+	HasComment bool
 }
 
 type errorWrapper struct {
diff --git a/log/helper_writer.go b/log/helper_writer.go
new file mode 100644
index 00000000..0c76a7ba
--- /dev/null
+++ b/log/helper_writer.go
@@ -0,0 +1,43 @@
+package log
+
+import "io"
+
+type writerWrapper struct {
+	helper *Helper
+	level  Level
+}
+
+type WriterOptionFn func(w *writerWrapper)
+
+// WithWriteLevel set writerWrapper level.
+func WithWriterLevel(level Level) WriterOptionFn {
+	return func(w *writerWrapper) {
+		w.level = level
+	}
+}
+
+// WithWriteMessageKey set writerWrapper helper message key.
+func WithWriteMessageKey(key string) WriterOptionFn {
+	return func(w *writerWrapper) {
+		if key != "" {
+			w.helper.msgKey = key
+		}
+	}
+}
+
+// NewWriter return a writer wrapper.
+func NewWriter(logger Logger, opts ...WriterOptionFn) io.Writer {
+	ww := &writerWrapper{
+		helper: NewHelper(logger, WithMessageKey(DefaultMessageKey)),
+		level:  LevelInfo, // default level
+	}
+	for _, opt := range opts {
+		opt(ww)
+	}
+	return ww
+}
+
+func (ww *writerWrapper) Write(p []byte) (int, error) {
+	ww.helper.Log(ww.level, ww.helper.msgKey, string(p))
+	return 0, nil
+}


Test patch:
diff --git a/log/helper_writer_test.go b/log/helper_writer_test.go
new file mode 100644
index 00000000..46bf8d19
--- /dev/null
+++ b/log/helper_writer_test.go
@@ -0,0 +1,52 @@
+package log
+
+import (
+	"bytes"
+	"io"
+	"strings"
+	"testing"
+)
+
+func TestWriterWrapper(t *testing.T) {
+	var buf bytes.Buffer
+	logger := NewStdLogger(&buf)
+	content := "ThisIsSomeTestLogMessage"
+	testCases := []struct {
+		w                io.Writer
+		acceptLevel      Level
+		acceptMessageKey string
+	}{
+		{
+			w:                NewWriter(logger),
+			acceptLevel:      LevelInfo, // default level
+			acceptMessageKey: DefaultMessageKey,
+		},
+		{
+			w:                NewWriter(logger, WithWriterLevel(LevelDebug)),
+			acceptLevel:      LevelDebug,
+			acceptMessageKey: DefaultMessageKey,
+		},
+		{
+			w:                NewWriter(logger, WithWriteMessageKey("XxXxX")),
+			acceptLevel:      LevelInfo, // default level
+			acceptMessageKey: "XxXxX",
+		},
+		{
+			w:                NewWriter(logger, WithWriterLevel(LevelError), WithWriteMessageKey("XxXxX")),
+			acceptLevel:      LevelError,
+			acceptMessageKey: "XxXxX",
+		},
+	}
+	for _, tc := range testCases {
+		_, err := tc.w.Write([]byte(content))
+		if err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+		if !strings.Contains(buf.String(), tc.acceptLevel.String()) {
+			t.Errorf("expected level: %s, got: %s", tc.acceptLevel, buf.String())
+		}
+		if !strings.Contains(buf.String(), tc.acceptMessageKey) {
+			t.Errorf("expected message key: %s, got: %s", tc.acceptMessageKey, buf.String())
+		}
+	}
+}
diff --git a/options_test.go b/options_test.go
index 8d2dbee1..953c52f5 100644
--- a/options_test.go
+++ b/options_test.go
@@ -80,7 +80,7 @@ func TestLogger(t *testing.T) {
 	v := xlog.NewStdLogger(log.Writer())
 	Logger(v)(o)
 	if !reflect.DeepEqual(xlog.NewHelper(v), o.logger) {
-		t.Fatalf("o.logger:%s is not equal to xlog.NewHelper(v):%s", o.logger, xlog.NewHelper(v))
+		t.Fatalf("o.logger:%v is not equal to xlog.NewHelper(v):%v", o.logger, xlog.NewHelper(v))
 	}
 }