Compare commits

..

60 Commits

Author SHA1 Message Date
Kevin Wan
bb33a20bc8 Update readme-cn.md 2022-06-03 19:18:58 +08:00
Kevin Wan
5536473a08 Update readme.md 2022-06-03 19:18:07 +08:00
Kevin Wan
323b35ed2d Update readme.md
update docs.
2022-06-03 19:15:34 +08:00
Kevin Wan
30958a91f7 docs: add docs for logx (#1960) 2022-06-03 19:11:06 +08:00
Kevin Wan
b94b68a427 chore: refactoring mapping string to slice (#1959) 2022-06-03 10:49:22 +08:00
家福
07145b210e fix: panic on convert to string on fillSliceFromString() (#1951)
* Update unmarshaler.go

 fix: 修复fillSliceFromString()方法中mapValue 强转string后的panic 错误

* test: 增加单元测试

增加单元测试

* Update unmarshaler_test.go
2022-06-03 00:27:48 +08:00
Kevin Wan
321a20add6 chore: update roadmap (#1948) 2022-06-02 09:28:29 +08:00
kunyu
65098d4737 Delete duplicated crash recover logic. (#1950)
* Update statinterceptor.go

* Update statinterceptor_test.go
2022-06-01 22:53:05 +08:00
Kevin Wan
35425f6164 Update readme-cn.md 2022-06-01 12:34:13 +08:00
Kevin Wan
a0060ff81b Update readme-cn.md 2022-05-31 10:05:59 +08:00
Kevin Wan
289a325757 chore: refine docker for better compatible with package main (#1944)
* chore: refine docker for better compatible with package main

* chore: default to current dir on goctl docker command
2022-05-30 13:26:58 +08:00
Kevin Wan
3fbe0f87b7 Update readme-cn.md 2022-05-28 18:54:45 +08:00
Kevin Wan
ea98d210fd Update readme-cn.md 2022-05-28 14:40:44 +08:00
Kevin Wan
b9bc1fdcf8 Update readme.md 2022-05-28 14:39:25 +08:00
Kevin Wan
6dc570bcd7 Update readme-cn.md 2022-05-28 14:36:13 +08:00
Kevin Wan
e21997f0d7 Update readme.md 2022-05-28 14:31:07 +08:00
Kevin Wan
92c0b7c3c5 Update readme-cn.md 2022-05-27 18:45:32 +08:00
vic
6d3ed98744 优化代码 (#1939) 2022-05-27 18:36:18 +08:00
NoTryNoSuccess
fb519fa547 core/mr:a little optimization for collector initialization in ForEach function (#1937)
Co-authored-by: notrynosuccess <daihongshan@gmail.com>
2022-05-27 17:19:40 +08:00
chen quan
e9501c3fb3 chore(action): simplified release configuration (#1935) 2022-05-27 16:31:05 +08:00
chen quan
fd12659729 chore: add release action to auto build binaries (#1884)
* chore: add release action to auto build binaries

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* fix: test bugs

Signed-off-by: chenquan <chenquan.dev@gmail.com>
2022-05-25 23:42:24 +08:00
Kevin Wan
72ebbb9774 feat: update docker alpine package mirror (#1924)
* feat: update docker alpine package mirror

* chore: format code
2022-05-23 09:13:21 +08:00
anqiansong
f1fdd55b38 Support built-in shorthand flags (#1925) 2022-05-23 09:13:12 +08:00
anqiansong
58787746db fix: Useless delete cache logic in update (#1923)
* Fix bug: useless delete cache logic in update

* Format code
2022-05-23 09:12:06 +08:00
Kevin Wan
ca88b69d24 feat: set default connection idle time for grpc servers (#1922)
* feat: set default connection idle time for grpc servers

* feat: add grpc health check
2022-05-21 19:38:27 +08:00
Kevin Wan
6b1e15cab1 chore: update k8s.io/client-go for security reason, go is upgrade to 1.16 (#1912)
* chore: fix jwt dependency security issue

* chore: update clickhouse driver

* chore: fix a security issue

* chore: update dependencies
2022-05-21 14:34:01 +08:00
Kevin Wan
6f86e5bff8 Update readme-cn.md 2022-05-20 19:13:49 +08:00
Kevin Wan
3f492df74e Update readme-cn.md 2022-05-17 23:23:48 +08:00
anqiansong
5e7b1f6bfe Fix process blocking problem during check (#1911) 2022-05-17 09:42:18 +08:00
Kevin Wan
e80a64fa67 feat: support WithStreamClientInterceptor for zrpc clients (#1907)
* feat: support WithStreamClientInterceptor for zrpc clients

* fix: data race
2022-05-14 19:58:17 +08:00
Kevin Wan
95282edb78 Update FUNDING.yml
update sponsor
2022-05-14 17:29:26 +08:00
Kevin Wan
7b82eda993 chore: use get for quickstart, plain logs for easy understanding (#1905) 2022-05-14 17:01:37 +08:00
Kevin Wan
5d09cd0e7c use goproxy properly, remove files (#1903) 2022-05-14 16:00:20 +08:00
Kevin Wan
1e717f9f5c feat: add toml config (#1899) 2022-05-13 23:17:43 +08:00
Kevin Wan
c6e2b4a43a chore: coding style for quickstart (#1902) 2022-05-13 23:10:55 +08:00
chen quan
e567a0c718 refactor: refactor trace in redis & sql & mongo (#1865)
* refactor: refactor tracing in redis & sql & mongo

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* fix: fix some tests

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: add missing content

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: adjust `log` and `return`

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: reformat code

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: reformat code

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: reformat code

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: simpler span name

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: fix a bug

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: fix a bug

Signed-off-by: chenquan <chenquan.dev@gmail.com>
2022-05-13 12:32:34 +08:00
anqiansong
52f060caae feat: Add goctl quickstart (#1889)
* Add goctl quickstart

* Format code

* Format code
2022-05-13 12:23:24 +08:00
anqiansong
f486685e99 Fix code generation (#1897) 2022-05-13 00:16:17 +08:00
过客龙门
3ae874d75d fix ts tpl (#1879) 2022-05-11 23:45:32 +08:00
Kevin Wan
c58eb13328 Update readme-cn.md
update logo
2022-05-11 23:33:54 +08:00
givemeafish
14ca39bc86 fix:tools/goctl/rpc/generator/template_test.go file has wrong parameters (#1882) 2022-05-11 23:24:34 +08:00
Kevin Wan
3ea8a2d4b6 Update readme-cn.md 2022-05-11 18:19:00 +08:00
Kevin Wan
6d2b9fd904 chore: improve codecov (#1878) 2022-05-08 13:17:48 +08:00
Kevin Wan
5451d96a81 chore: update some logs (#1875) 2022-05-07 23:34:55 +08:00
Kevin Wan
69c2bad410 feat: logx with color (#1872)
* feat: logx with color

* chore: update logs

* fix test error

* chore: change colors of http codes

* chore: add comments

* chore: use faith/color instead of ascii code color

* chore: update colors

* chore: update colors

* chore: fix duplicated slowcall text

* chore: remove slowcall colors
2022-05-07 23:22:39 +08:00
anqiansong
5383e29ce6 feat: Replace cli to cobra (#1855)
* Replace cli

* Replace cli

* Replace cli

* Format code

* Add compare case

* Add compare case

* Add compare case

* Support go style flag

* Support go style flag

* Add test case
2022-05-07 15:40:11 +08:00
Kevin Wan
51472004a3 Update readme.md 2022-05-07 10:11:21 +08:00
Kevin Wan
caf5b7b1f1 Update readme-cn.md 2022-05-07 10:10:44 +08:00
Kevin Wan
bef9aa55e6 Update readme.md 2022-05-07 10:08:25 +08:00
Kevin Wan
d0a59b13a6 chore: fix deprecated usages (#1871)
* add conf documents

* chore: use {} instead of () for environment variables

* chore: fix deprecated usages

* chore: fix unstable tests

* chore: show stack on github actions
2022-05-06 15:13:46 +08:00
Kevin Wan
469e62067c add conf documents (#1869)
* add conf documents

* chore: use {} instead of () for environment variables
2022-05-06 11:05:06 +08:00
Kevin Wan
a36d58aac9 fix time, duration, slice types on logx.Field (#1868)
* chore: refine tests

* fix #1866
2022-05-05 23:37:18 +08:00
Kevin Wan
aa5118c2aa chore: refine tests (#1864) 2022-05-04 17:52:58 +08:00
Kevin Wan
974ba5c9aa test: add codecov (#1863) 2022-05-04 16:19:51 +08:00
Kevin Wan
ec1de4f48d test: add codecov (#1861)
* test: add codecov

* test: add codecov
2022-05-03 21:22:15 +08:00
Kevin Wan
bab72b7630 chore: use time.Now() instead of timex.Time() because go optimized it (#1860) 2022-05-03 19:51:47 +08:00
Kevin Wan
ac321fc146 feat: add fields with logx methods, support using third party logging libs. (#1847)
* backup

* simplify

* chore: remove unused pool

* chore: fix lint errors

* chore: use strings.Builder instead of bytes.Buffer

* test: add more tests

* chore: fix reviewdog

* test: fix data race

* feat: make logger customizable

* chore: fix reviewdog

* test: fix fails

* chore: fix set writer twice

* chore: use context instead of golang.org context

* chore: specify uint32 for level types
2022-05-03 17:34:26 +08:00
全自动盒子
ae2c76765c fix typo (#1857) 2022-05-03 16:25:13 +08:00
Kevin Wan
f21970c117 test: add more tests (#1856) 2022-05-02 21:24:20 +08:00
Kevin Wan
d0a58d1f2d docs: update readme (#1849) 2022-05-01 12:48:47 +08:00
170 changed files with 6158 additions and 2875 deletions

3
.github/FUNDING.yml vendored
View File

@@ -9,4 +9,5 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username otechie: # Replace with a single Otechie username
custom: https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg custom: # https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg
ethereum: 0x5052b7f6B937B02563996D23feb69b38D06Ca150 | kevwan

28
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,28 @@
on:
push:
tags:
- "tools/goctl/*"
jobs:
releases-matrix:
name: Release goctl binary
runs-on: ubuntu-latest
strategy:
matrix:
# build and publish in parallel: linux/386, linux/amd64, linux/arm64,
# windows/386, windows/amd64, windows/arm64, darwin/amd64, darwin/arm64
goos: [ linux, windows, darwin ]
goarch: [ "386", amd64, arm64 ]
exclude:
- goarch: "386"
goos: darwin
steps:
- uses: actions/checkout@v2
- uses: zeromicro/go-zero-release-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
goversion: "https://dl.google.com/go/go1.17.5.linux-amd64.tar.gz"
project_path: "tools/goctl"
binary_name: "goctl"
extra_files: tools/goctl/goctl.md

1
.gitignore vendored
View File

@@ -18,6 +18,7 @@
# for test purpose # for test purpose
**/adhoc **/adhoc
go.work go.work
go.work.sum
# gitlab ci # gitlab ci
.cache .cache

View File

@@ -22,7 +22,7 @@ We hope that the items listed below will inspire further engagement from the com
## 2022 ## 2022
- [x] Support `context` in redis related methods for timeout and tracing - [x] Support `context` in redis related methods for timeout and tracing
- [x] Support `context` in sql related methods for timeout and tracing - [x] Support `context` in sql related methods for timeout and tracing
- [ ] Support `context` in mongodb related methods for timeout and tracing - [x] Support `context` in mongodb related methods for timeout and tracing
- [x] Add `httpc.Do` with HTTP call governance, like circuit breaker etc. - [x] Add `httpc.Do` with HTTP call governance, like circuit breaker etc.
- [ ] Support `goctl doctor` command to report potential issues for given service - [ ] Support `goctl doctor` command to report potential issues for given service
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file - [ ] Support `goctl mock` command to start a mocking server with given `.api` file

View File

@@ -69,11 +69,8 @@ func (f *Filter) Exists(data []byte) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
if !isSet {
return false, nil
}
return true, nil return isSet, nil
} }
func (f *Filter) getLocations(data []byte) []uint { func (f *Filter) getLocations(data []byte) []uint {

View File

@@ -5,12 +5,12 @@ import (
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"time"
"github.com/zeromicro/go-zero/core/mathx" "github.com/zeromicro/go-zero/core/mathx"
"github.com/zeromicro/go-zero/core/proc" "github.com/zeromicro/go-zero/core/proc"
"github.com/zeromicro/go-zero/core/stat" "github.com/zeromicro/go-zero/core/stat"
"github.com/zeromicro/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
"github.com/zeromicro/go-zero/core/timex"
) )
const ( const (
@@ -198,7 +198,7 @@ type errorWindow struct {
func (ew *errorWindow) add(reason string) { func (ew *errorWindow) add(reason string) {
ew.lock.Lock() ew.lock.Lock()
ew.reasons[ew.index] = fmt.Sprintf("%s %s", timex.Time().Format(timeFormat), reason) ew.reasons[ew.index] = fmt.Sprintf("%s %s", time.Now().Format(timeFormat), reason)
ew.index = (ew.index + 1) % numHistoryReasons ew.index = (ew.index + 1) % numHistoryReasons
ew.count = mathx.MinInt(ew.count+1, numHistoryReasons) ew.count = mathx.MinInt(ew.count+1, numHistoryReasons)
ew.lock.Unlock() ew.lock.Unlock()

73
core/color/color.go Normal file
View File

@@ -0,0 +1,73 @@
package color
import "github.com/fatih/color"
const (
// NoColor is no color for both foreground and background.
NoColor Color = iota
// FgBlack is the foreground color black.
FgBlack
// FgRed is the foreground color red.
FgRed
// FgGreen is the foreground color green.
FgGreen
// FgYellow is the foreground color yellow.
FgYellow
// FgBlue is the foreground color blue.
FgBlue
// FgMagenta is the foreground color magenta.
FgMagenta
// FgCyan is the foreground color cyan.
FgCyan
// FgWhite is the foreground color white.
FgWhite
// BgBlack is the background color black.
BgBlack
// BgRed is the background color red.
BgRed
// BgGreen is the background color green.
BgGreen
// BgYellow is the background color yellow.
BgYellow
// BgBlue is the background color blue.
BgBlue
// BgMagenta is the background color magenta.
BgMagenta
// BgCyan is the background color cyan.
BgCyan
// BgWhite is the background color white.
BgWhite
)
var colors = map[Color][]color.Attribute{
FgBlack: {color.FgBlack, color.Bold},
FgRed: {color.FgRed, color.Bold},
FgGreen: {color.FgGreen, color.Bold},
FgYellow: {color.FgYellow, color.Bold},
FgBlue: {color.FgBlue, color.Bold},
FgMagenta: {color.FgMagenta, color.Bold},
FgCyan: {color.FgCyan, color.Bold},
FgWhite: {color.FgWhite, color.Bold},
BgBlack: {color.BgBlack, color.FgHiWhite, color.Bold},
BgRed: {color.BgRed, color.FgHiWhite, color.Bold},
BgGreen: {color.BgGreen, color.FgHiWhite, color.Bold},
BgYellow: {color.BgHiYellow, color.FgHiBlack, color.Bold},
BgBlue: {color.BgBlue, color.FgHiWhite, color.Bold},
BgMagenta: {color.BgMagenta, color.FgHiWhite, color.Bold},
BgCyan: {color.BgCyan, color.FgHiWhite, color.Bold},
BgWhite: {color.BgHiWhite, color.FgHiBlack, color.Bold},
}
type Color uint32
// WithColor returns a string with the given color applied.
func WithColor(text string, colour Color) string {
c := color.New(colors[colour]...)
return c.Sprint(text)
}
// WithColorPadding returns a string with the given color applied with leading and trailing spaces.
func WithColorPadding(text string, colour Color) string {
return WithColor(" "+text+" ", colour)
}

17
core/color/color_test.go Normal file
View File

@@ -0,0 +1,17 @@
package color
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithColor(t *testing.T) {
output := WithColor("Hello", BgRed)
assert.Equal(t, "Hello", output)
}
func TestWithColorPadding(t *testing.T) {
output := WithColorPadding("Hello", BgRed)
assert.Equal(t, " Hello ", output)
}

View File

@@ -6,24 +6,26 @@ import (
"log" "log"
"os" "os"
"path" "path"
"strings"
"github.com/zeromicro/go-zero/core/mapping" "github.com/zeromicro/go-zero/core/mapping"
) )
var loaders = map[string]func([]byte, interface{}) error{ var loaders = map[string]func([]byte, interface{}) error{
".json": LoadConfigFromJsonBytes, ".json": LoadFromJsonBytes,
".yaml": LoadConfigFromYamlBytes, ".toml": LoadFromTomlBytes,
".yml": LoadConfigFromYamlBytes, ".yaml": LoadFromYamlBytes,
".yml": LoadFromYamlBytes,
} }
// LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable. // Load loads config into v from file, .json, .yaml and .yml are acceptable.
func LoadConfig(file string, v interface{}, opts ...Option) error { func Load(file string, v interface{}, opts ...Option) error {
content, err := ioutil.ReadFile(file) content, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
return err return err
} }
loader, ok := loaders[path.Ext(file)] loader, ok := loaders[strings.ToLower(path.Ext(file))]
if !ok { if !ok {
return fmt.Errorf("unrecognized file type: %s", file) return fmt.Errorf("unrecognized file type: %s", file)
} }
@@ -40,19 +42,42 @@ func LoadConfig(file string, v interface{}, opts ...Option) error {
return loader(content, v) return loader(content, v)
} }
// LoadConfigFromJsonBytes loads config into v from content json bytes. // LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
func LoadConfigFromJsonBytes(content []byte, v interface{}) error { // Deprecated: use Load instead.
func LoadConfig(file string, v interface{}, opts ...Option) error {
return Load(file, v, opts...)
}
// LoadFromJsonBytes loads config into v from content json bytes.
func LoadFromJsonBytes(content []byte, v interface{}) error {
return mapping.UnmarshalJsonBytes(content, v) return mapping.UnmarshalJsonBytes(content, v)
} }
// LoadConfigFromYamlBytes loads config into v from content yaml bytes. // LoadConfigFromJsonBytes loads config into v from content json bytes.
func LoadConfigFromYamlBytes(content []byte, v interface{}) error { // Deprecated: use LoadFromJsonBytes instead.
func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
return LoadFromJsonBytes(content, v)
}
// LoadFromTomlBytes loads config into v from content toml bytes.
func LoadFromTomlBytes(content []byte, v interface{}) error {
return mapping.UnmarshalTomlBytes(content, v)
}
// LoadFromYamlBytes loads config into v from content yaml bytes.
func LoadFromYamlBytes(content []byte, v interface{}) error {
return mapping.UnmarshalYamlBytes(content, v) return mapping.UnmarshalYamlBytes(content, v)
} }
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
// Deprecated: use LoadFromYamlBytes instead.
func LoadConfigFromYamlBytes(content []byte, v interface{}) error {
return LoadFromYamlBytes(content, v)
}
// MustLoad loads config into v from path, exits on error. // MustLoad loads config into v from path, exits on error.
func MustLoad(path string, v interface{}, opts ...Option) { func MustLoad(path string, v interface{}, opts ...Option) {
if err := LoadConfig(path, v, opts...); err != nil { if err := Load(path, v, opts...); err != nil {
log.Fatalf("error: config file %s, %s", path, err.Error()) log.Fatalf("error: config file %s, %s", path, err.Error())
} }
} }

View File

@@ -11,14 +11,14 @@ import (
) )
func TestLoadConfig_notExists(t *testing.T) { func TestLoadConfig_notExists(t *testing.T) {
assert.NotNil(t, LoadConfig("not_a_file", nil)) assert.NotNil(t, Load("not_a_file", nil))
} }
func TestLoadConfig_notRecogFile(t *testing.T) { func TestLoadConfig_notRecogFile(t *testing.T) {
filename, err := fs.TempFilenameWithText("hello") filename, err := fs.TempFilenameWithText("hello")
assert.Nil(t, err) assert.Nil(t, err)
defer os.Remove(filename) defer os.Remove(filename)
assert.NotNil(t, LoadConfig(filename, nil)) assert.NotNil(t, Load(filename, nil))
} }
func TestConfigJson(t *testing.T) { func TestConfigJson(t *testing.T) {
@@ -57,6 +57,58 @@ func TestConfigJson(t *testing.T) {
} }
} }
func TestConfigToml(t *testing.T) {
text := `a = "foo"
b = 1
c = "${FOO}"
d = "abcd!@#$112"
`
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(".toml", text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val)
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C)
assert.Equal(t, "abcd!@#$112", val.D)
}
func TestConfigTomlEnv(t *testing.T) {
text := `a = "foo"
b = 1
c = "${FOO}"
d = "abcd!@#112"
`
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(".toml", text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val, UseEnv())
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "2", val.C)
assert.Equal(t, "abcd!@#112", val.D)
}
func TestConfigJsonEnv(t *testing.T) { func TestConfigJsonEnv(t *testing.T) {
tests := []string{ tests := []string{
".json", ".json",

45
core/conf/readme.md Normal file
View File

@@ -0,0 +1,45 @@
## How to use
1. Define a config structure, like below:
```go
RestfulConf struct {
Host string `json:",default=0.0.0.0"`
Port int
LogMode string `json:",options=[file,console]"
Verbose bool `json:",optional"`
MaxConns int `json:",default=10000"`
MaxBytes int64 `json:",default=1048576"`
Timeout time.Duration `json:",default=3s"`
CpuThreshold int64 `json:",default=900,range=[0:1000]"`
}
```
2. Write the yaml or json config file:
```yaml
# most fields are optional or have default values
Port: 8080
LogMode: console
# you can use env settings
MaxBytes: ${MAX_BYTES}
```
3. Load the config from a file:
```go
// exit on error
var config RestfulConf
conf.MustLoad(configFile, &config)
// or handle the error on your own
var config RestfulConf
if err := conf.Load(configFile, &config); err != nil {
log.Fatal(err)
}
// enable reading from environments
var config RestfulConf
conf.MustLoad(configFile, &config, conf.UseEnv())
```

49
core/fs/temps_test.go Normal file
View File

@@ -0,0 +1,49 @@
package fs
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTempFileWithText(t *testing.T) {
f, err := TempFileWithText("test")
if err != nil {
t.Error(err)
}
if f == nil {
t.Error("TempFileWithText returned nil")
}
if f.Name() == "" {
t.Error("TempFileWithText returned empty file name")
}
defer os.Remove(f.Name())
bs, err := ioutil.ReadAll(f)
assert.Nil(t, err)
if len(bs) != 4 {
t.Error("TempFileWithText returned wrong file size")
}
if f.Close() != nil {
t.Error("TempFileWithText returned error on close")
}
}
func TestTempFilenameWithText(t *testing.T) {
f, err := TempFilenameWithText("test")
if err != nil {
t.Error(err)
}
if f == "" {
t.Error("TempFilenameWithText returned empty file name")
}
defer os.Remove(f)
bs, err := ioutil.ReadFile(f)
assert.Nil(t, err)
if len(bs) != 4 {
t.Error("TempFilenameWithText returned wrong file size")
}
}

87
core/jsonx/json_test.go Normal file
View File

@@ -0,0 +1,87 @@
package jsonx
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMarshal(t *testing.T) {
var v = struct {
Name string `json:"name"`
Age int `json:"age"`
}{
Name: "John",
Age: 30,
}
bs, err := Marshal(v)
assert.Nil(t, err)
assert.Equal(t, `{"name":"John","age":30}`, string(bs))
}
func TestUnmarshal(t *testing.T) {
const s = `{"name":"John","age":30}`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := Unmarshal([]byte(s), &v)
assert.Nil(t, err)
assert.Equal(t, "John", v.Name)
assert.Equal(t, 30, v.Age)
}
func TestUnmarshalError(t *testing.T) {
const s = `{"name":"John","age":30`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := Unmarshal([]byte(s), &v)
assert.NotNil(t, err)
}
func TestUnmarshalFromString(t *testing.T) {
const s = `{"name":"John","age":30}`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := UnmarshalFromString(s, &v)
assert.Nil(t, err)
assert.Equal(t, "John", v.Name)
assert.Equal(t, 30, v.Age)
}
func TestUnmarshalFromStringError(t *testing.T) {
const s = `{"name":"John","age":30`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := UnmarshalFromString(s, &v)
assert.NotNil(t, err)
}
func TestUnmarshalFromRead(t *testing.T) {
const s = `{"name":"John","age":30}`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := UnmarshalFromReader(strings.NewReader(s), &v)
assert.Nil(t, err)
assert.Equal(t, "John", v.Name)
assert.Equal(t, 30, v.Age)
}
func TestUnmarshalFromReaderError(t *testing.T) {
const s = `{"name":"John","age":30`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := UnmarshalFromReader(strings.NewReader(s), &v)
assert.NotNil(t, err)
}

26
core/logx/color.go Normal file
View File

@@ -0,0 +1,26 @@
package logx
import (
"sync/atomic"
"github.com/zeromicro/go-zero/core/color"
)
// WithColor is a helper function to add color to a string, only in plain encoding.
func WithColor(text string, colour color.Color) string {
if atomic.LoadUint32(&encoding) == plainEncodingType {
return color.WithColor(text, colour)
}
return text
}
// WithColorPadding is a helper function to add color to a string with leading and trailing spaces,
// only in plain encoding.
func WithColorPadding(text string, colour color.Color) string {
if atomic.LoadUint32(&encoding) == plainEncodingType {
return color.WithColorPadding(text, colour)
}
return text
}

33
core/logx/color_test.go Normal file
View File

@@ -0,0 +1,33 @@
package logx
import (
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/color"
)
func TestWithColor(t *testing.T) {
old := atomic.SwapUint32(&encoding, plainEncodingType)
defer atomic.StoreUint32(&encoding, old)
output := WithColor("hello", color.BgBlue)
assert.Equal(t, "hello", output)
atomic.StoreUint32(&encoding, jsonEncodingType)
output = WithColor("hello", color.BgBlue)
assert.Equal(t, "hello", output)
}
func TestWithColorPadding(t *testing.T) {
old := atomic.SwapUint32(&encoding, plainEncodingType)
defer atomic.StoreUint32(&encoding, old)
output := WithColorPadding("hello", color.BgBlue)
assert.Equal(t, " hello ", output)
atomic.StoreUint32(&encoding, jsonEncodingType)
output = WithColorPadding("hello", color.BgBlue)
assert.Equal(t, "hello", output)
}

View File

@@ -1,18 +1,13 @@
package logx package logx
import ( import (
"context"
"fmt" "fmt"
"io"
"sync/atomic"
"time" "time"
"github.com/zeromicro/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
const durationCallerDepth = 3
type durationLogger logEntry
// WithDuration returns a Logger which logs the given duration. // WithDuration returns a Logger which logs the given duration.
func WithDuration(d time.Duration) Logger { func WithDuration(d time.Duration) Logger {
return &durationLogger{ return &durationLogger{
@@ -20,57 +15,62 @@ func WithDuration(d time.Duration) Logger {
} }
} }
type durationLogger logEntry
func (l *durationLogger) Error(v ...interface{}) { func (l *durationLogger) Error(v ...interface{}) {
if shallLog(ErrorLevel) { l.err(fmt.Sprint(v...))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
}
} }
func (l *durationLogger) Errorf(format string, v ...interface{}) { func (l *durationLogger) Errorf(format string, v ...interface{}) {
if shallLog(ErrorLevel) { l.err(fmt.Sprintf(format, v...))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
}
} }
func (l *durationLogger) Errorv(v interface{}) { func (l *durationLogger) Errorv(v interface{}) {
if shallLog(ErrorLevel) { l.err(v)
l.write(errorLog, levelError, v) }
}
func (l *durationLogger) Errorw(msg string, fields ...LogField) {
l.err(msg, fields...)
} }
func (l *durationLogger) Info(v ...interface{}) { func (l *durationLogger) Info(v ...interface{}) {
if shallLog(InfoLevel) { l.info(fmt.Sprint(v...))
l.write(infoLog, levelInfo, fmt.Sprint(v...))
}
} }
func (l *durationLogger) Infof(format string, v ...interface{}) { func (l *durationLogger) Infof(format string, v ...interface{}) {
if shallLog(InfoLevel) { l.info(fmt.Sprintf(format, v...))
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
}
} }
func (l *durationLogger) Infov(v interface{}) { func (l *durationLogger) Infov(v interface{}) {
if shallLog(InfoLevel) { l.info(v)
l.write(infoLog, levelInfo, v) }
}
func (l *durationLogger) Infow(msg string, fields ...LogField) {
l.info(msg, fields...)
} }
func (l *durationLogger) Slow(v ...interface{}) { func (l *durationLogger) Slow(v ...interface{}) {
if shallLog(ErrorLevel) { l.slow(fmt.Sprint(v...))
l.write(slowLog, levelSlow, fmt.Sprint(v...))
}
} }
func (l *durationLogger) Slowf(format string, v ...interface{}) { func (l *durationLogger) Slowf(format string, v ...interface{}) {
if shallLog(ErrorLevel) { l.slow(fmt.Sprintf(format, v...))
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
}
} }
func (l *durationLogger) Slowv(v interface{}) { func (l *durationLogger) Slowv(v interface{}) {
if shallLog(ErrorLevel) { l.slow(v)
l.write(slowLog, levelSlow, v) }
func (l *durationLogger) Sloww(msg string, fields ...LogField) {
l.slow(msg, fields...)
}
func (l *durationLogger) WithContext(ctx context.Context) Logger {
return &traceLogger{
ctx: ctx,
logEntry: logEntry{
Duration: l.Duration,
},
} }
} }
@@ -79,16 +79,23 @@ func (l *durationLogger) WithDuration(duration time.Duration) Logger {
return l return l
} }
func (l *durationLogger) write(writer io.Writer, level string, val interface{}) { func (l *durationLogger) err(v interface{}, fields ...LogField) {
switch atomic.LoadUint32(&encoding) { if shallLog(ErrorLevel) {
case plainEncodingType: fields = append(fields, Field(durationKey, l.Duration))
writePlainAny(writer, level, val, l.Duration) getWriter().Error(v, fields...)
default: }
outputJson(writer, &durationLogger{ }
Timestamp: getTimestamp(),
Level: level, func (l *durationLogger) info(v interface{}, fields ...LogField) {
Content: val, if shallLog(InfoLevel) {
Duration: l.Duration, fields = append(fields, Field(durationKey, l.Duration))
}) getWriter().Info(v, fields...)
}
}
func (l *durationLogger) slow(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
fields = append(fields, Field(durationKey, l.Duration))
getWriter().Slow(v, fields...)
} }
} }

View File

@@ -1,41 +1,62 @@
package logx package logx
import ( import (
"log" "context"
"strings" "strings"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
) )
func TestWithDurationError(t *testing.T) { func TestWithDurationError(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Error("foo") WithDuration(time.Second).Error("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
} }
func TestWithDurationErrorf(t *testing.T) { func TestWithDurationErrorf(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Errorf("foo") WithDuration(time.Second).Errorf("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
} }
func TestWithDurationErrorv(t *testing.T) { func TestWithDurationErrorv(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Errorv("foo") WithDuration(time.Second).Errorv("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationErrorw(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Errorw("foo", Field("foo", "bar"))
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
} }
func TestWithDurationInfo(t *testing.T) { func TestWithDurationInfo(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Info("foo") WithDuration(time.Second).Info("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
} }
func TestWithDurationInfoConsole(t *testing.T) { func TestWithDurationInfoConsole(t *testing.T) {
@@ -45,43 +66,96 @@ func TestWithDurationInfoConsole(t *testing.T) {
atomic.StoreUint32(&encoding, old) atomic.StoreUint32(&encoding, old)
}() }()
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) o := writer.Swap(w)
defer writer.Store(o)
WithDuration(time.Second).Info("foo") WithDuration(time.Second).Info("foo")
assert.True(t, strings.Contains(builder.String(), "ms"), builder.String()) assert.True(t, strings.Contains(w.String(), "ms"), w.String())
} }
func TestWithDurationInfof(t *testing.T) { func TestWithDurationInfof(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Infof("foo") WithDuration(time.Second).Infof("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
} }
func TestWithDurationInfov(t *testing.T) { func TestWithDurationInfov(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Infov("foo") WithDuration(time.Second).Infov("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationInfow(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Infow("foo", Field("foo", "bar"))
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
}
func TestWithDurationWithContextInfow(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar")
WithDuration(time.Second).WithContext(ctx).Infow("foo", Field("foo", "bar"))
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
assert.True(t, strings.Contains(w.String(), "trace"), w.String())
assert.True(t, strings.Contains(w.String(), "span"), w.String())
} }
func TestWithDurationSlow(t *testing.T) { func TestWithDurationSlow(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Slow("foo") WithDuration(time.Second).Slow("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
} }
func TestWithDurationSlowf(t *testing.T) { func TestWithDurationSlowf(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).WithDuration(time.Hour).Slowf("foo") WithDuration(time.Second).WithDuration(time.Hour).Slowf("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
} }
func TestWithDurationSlowv(t *testing.T) { func TestWithDurationSlowv(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).WithDuration(time.Hour).Slowv("foo") WithDuration(time.Second).WithDuration(time.Hour).Slowv("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationSloww(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).WithDuration(time.Hour).Sloww("foo", Field("foo", "bar"))
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
} }

View File

@@ -1,7 +1,6 @@
package logx package logx
import ( import (
"log"
"strings" "strings"
"testing" "testing"
@@ -9,23 +8,27 @@ import (
) )
func TestLessLogger_Error(t *testing.T) { func TestLessLogger_Error(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
l := NewLessLogger(500) l := NewLessLogger(500)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
l.Error("hello") l.Error("hello")
} }
assert.Equal(t, 1, strings.Count(builder.String(), "\n")) assert.Equal(t, 1, strings.Count(w.String(), "\n"))
} }
func TestLessLogger_Errorf(t *testing.T) { func TestLessLogger_Errorf(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
l := NewLessLogger(500) l := NewLessLogger(500)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
l.Errorf("hello") l.Errorf("hello")
} }
assert.Equal(t, 1, strings.Count(builder.String(), "\n")) assert.Equal(t, 1, strings.Count(w.String(), "\n"))
} }

38
core/logx/logger.go Normal file
View File

@@ -0,0 +1,38 @@
package logx
import (
"context"
"time"
)
// A Logger represents a logger.
type Logger interface {
// Error logs a message at error level.
Error(...interface{})
// Errorf logs a message at error level.
Errorf(string, ...interface{})
// Errorv logs a message at error level.
Errorv(interface{})
// Errorw logs a message at error level.
Errorw(string, ...LogField)
// Info logs a message at info level.
Info(...interface{})
// Infof logs a message at info level.
Infof(string, ...interface{})
// Infov logs a message at info level.
Infov(interface{})
// Infow logs a message at info level.
Infow(string, ...LogField)
// Slow logs a message at slow level.
Slow(...interface{})
// Slowf logs a message at slow level.
Slowf(string, ...interface{})
// Slowv logs a message at slow level.
Slowv(interface{})
// Sloww logs a message at slow level.
Sloww(string, ...LogField)
// WithContext returns a new logger with the given context.
WithContext(context.Context) Logger
// WithDuration returns a new logger with the given duration.
WithDuration(time.Duration) Logger
}

View File

@@ -1,93 +1,29 @@
package logx package logx
import ( import (
"bytes"
"encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"path" "path"
"runtime"
"runtime/debug" "runtime/debug"
"strconv"
"strings"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/zeromicro/go-zero/core/iox"
"github.com/zeromicro/go-zero/core/sysx" "github.com/zeromicro/go-zero/core/sysx"
"github.com/zeromicro/go-zero/core/timex"
) )
const ( const callerDepth = 5
// InfoLevel logs everything
InfoLevel = iota
// ErrorLevel includes errors, slows, stacks
ErrorLevel
// SevereLevel only log severe messages
SevereLevel
)
const (
jsonEncodingType = iota
plainEncodingType
jsonEncoding = "json"
plainEncoding = "plain"
plainEncodingSep = '\t'
)
const (
accessFilename = "access.log"
errorFilename = "error.log"
severeFilename = "severe.log"
slowFilename = "slow.log"
statFilename = "stat.log"
consoleMode = "console"
volumeMode = "volume"
levelAlert = "alert"
levelInfo = "info"
levelError = "error"
levelSevere = "severe"
levelFatal = "fatal"
levelSlow = "slow"
levelStat = "stat"
backupFileDelimiter = "-"
callerInnerDepth = 5
flags = 0x0
)
var ( var (
// ErrLogPathNotSet is an error that indicates the log path is not set. timeFormat = "2006-01-02T15:04:05.000Z07:00"
ErrLogPathNotSet = errors.New("log path must be set") logLevel uint32
// ErrLogNotInitialized is an error that log is not initialized. encoding uint32 = jsonEncodingType
ErrLogNotInitialized = errors.New("log not initialized")
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
ErrLogServiceNameNotSet = errors.New("log service name must be set")
timeFormat = "2006-01-02T15:04:05.000Z07:00"
writeConsole bool
logLevel uint32
encoding uint32 = jsonEncodingType
// use uint32 for atomic operations // use uint32 for atomic operations
disableStat uint32 disableStat uint32
infoLog io.WriteCloser
errorLog io.WriteCloser
severeLog io.WriteCloser
slowLog io.WriteCloser
statLog io.WriteCloser
stackLog io.Writer
once sync.Once options logOptions
initialized uint32 writer = new(atomicWriter)
options logOptions
) )
type ( type (
@@ -95,109 +31,37 @@ type (
Timestamp string `json:"@timestamp"` Timestamp string `json:"@timestamp"`
Level string `json:"level"` Level string `json:"level"`
Duration string `json:"duration,omitempty"` Duration string `json:"duration,omitempty"`
Caller string `json:"caller,omitempty"`
Content interface{} `json:"content"` Content interface{} `json:"content"`
} }
logEntryWithFields map[string]interface{}
logOptions struct { logOptions struct {
gzipEnabled bool gzipEnabled bool
logStackCooldownMills int logStackCooldownMills int
keepDays int keepDays int
} }
// LogField is a key-value pair that will be added to the log entry.
LogField struct {
Key string
Value interface{}
}
// LogOption defines the method to customize the logging. // LogOption defines the method to customize the logging.
LogOption func(options *logOptions) LogOption func(options *logOptions)
// A Logger represents a logger.
Logger interface {
Error(...interface{})
Errorf(string, ...interface{})
Errorv(interface{})
Info(...interface{})
Infof(string, ...interface{})
Infov(interface{})
Slow(...interface{})
Slowf(string, ...interface{})
Slowv(interface{})
WithDuration(time.Duration) Logger
}
) )
// MustSetup sets up logging with given config c. It exits on error.
func MustSetup(c LogConf) {
Must(SetUp(c))
}
// SetUp sets up the logx. If already set up, just return nil.
// we allow SetUp to be called multiple times, because for example
// we need to allow different service frameworks to initialize logx respectively.
// the same logic for SetUp
func SetUp(c LogConf) error {
if len(c.TimeFormat) > 0 {
timeFormat = c.TimeFormat
}
switch c.Encoding {
case plainEncoding:
atomic.StoreUint32(&encoding, plainEncodingType)
default:
atomic.StoreUint32(&encoding, jsonEncodingType)
}
switch c.Mode {
case consoleMode:
setupWithConsole(c)
return nil
case volumeMode:
return setupWithVolume(c)
default:
return setupWithFiles(c)
}
}
// Alert alerts v in alert level, and the message is written to error log. // Alert alerts v in alert level, and the message is written to error log.
func Alert(v string) { func Alert(v string) {
outputText(errorLog, levelAlert, v) getWriter().Alert(v)
} }
// Close closes the logging. // Close closes the logging.
func Close() error { func Close() error {
if writeConsole { if w := writer.Swap(nil); w != nil {
return nil return w.(io.Closer).Close()
}
if atomic.LoadUint32(&initialized) == 0 {
return ErrLogNotInitialized
}
atomic.StoreUint32(&initialized, 0)
if infoLog != nil {
if err := infoLog.Close(); err != nil {
return err
}
}
if errorLog != nil {
if err := errorLog.Close(); err != nil {
return err
}
}
if severeLog != nil {
if err := severeLog.Close(); err != nil {
return err
}
}
if slowLog != nil {
if err := slowLog.Close(); err != nil {
return err
}
}
if statLog != nil {
if err := statLog.Close(); err != nil {
return err
}
} }
return nil return nil
@@ -205,16 +69,7 @@ func Close() error {
// Disable disables the logging. // Disable disables the logging.
func Disable() { func Disable() {
once.Do(func() { writer.Store(nopWriter{})
atomic.StoreUint32(&initialized, 1)
infoLog = iox.NopCloser(ioutil.Discard)
errorLog = iox.NopCloser(ioutil.Discard)
severeLog = iox.NopCloser(ioutil.Discard)
slowLog = iox.NopCloser(ioutil.Discard)
statLog = iox.NopCloser(ioutil.Discard)
stackLog = ioutil.Discard
})
} }
// DisableStat disables the stat logs. // DisableStat disables the stat logs.
@@ -224,22 +79,12 @@ func DisableStat() {
// Error writes v into error log. // Error writes v into error log.
func Error(v ...interface{}) { func Error(v ...interface{}) {
ErrorCaller(1, v...) errorTextSync(fmt.Sprint(v...))
}
// ErrorCaller writes v with context into error log.
func ErrorCaller(callDepth int, v ...interface{}) {
errorTextSync(fmt.Sprint(v...), callDepth+callerInnerDepth)
}
// ErrorCallerf writes v with context in format into error log.
func ErrorCallerf(callDepth int, format string, v ...interface{}) {
errorTextSync(fmt.Errorf(format, v...).Error(), callDepth+callerInnerDepth)
} }
// Errorf writes v with format into error log. // Errorf writes v with format into error log.
func Errorf(format string, v ...interface{}) { func Errorf(format string, v ...interface{}) {
ErrorCallerf(1, format, v...) errorTextSync(fmt.Errorf(format, v...).Error())
} }
// ErrorStack writes v along with call stack into error log. // ErrorStack writes v along with call stack into error log.
@@ -260,6 +105,49 @@ func Errorv(v interface{}) {
errorAnySync(v) errorAnySync(v)
} }
// Errorw writes msg along with fields into error log.
func Errorw(msg string, fields ...LogField) {
errorFieldsSync(msg, fields...)
}
// Field returns a LogField for the given key and value.
func Field(key string, value interface{}) LogField {
switch val := value.(type) {
case error:
return LogField{Key: key, Value: val.Error()}
case []error:
var errs []string
for _, err := range val {
errs = append(errs, err.Error())
}
return LogField{Key: key, Value: errs}
case time.Duration:
return LogField{Key: key, Value: fmt.Sprint(val)}
case []time.Duration:
var durs []string
for _, dur := range val {
durs = append(durs, fmt.Sprint(dur))
}
return LogField{Key: key, Value: durs}
case []time.Time:
var times []string
for _, t := range val {
times = append(times, fmt.Sprint(t))
}
return LogField{Key: key, Value: times}
case fmt.Stringer:
return LogField{Key: key, Value: val.String()}
case []fmt.Stringer:
var strs []string
for _, str := range val {
strs = append(strs, str.String())
}
return LogField{Key: key, Value: strs}
default:
return LogField{Key: key, Value: val}
}
}
// Info writes v into access log. // Info writes v into access log.
func Info(v ...interface{}) { func Info(v ...interface{}) {
infoTextSync(fmt.Sprint(v...)) infoTextSync(fmt.Sprint(v...))
@@ -275,14 +163,32 @@ func Infov(v interface{}) {
infoAnySync(v) infoAnySync(v)
} }
// Infow writes msg along with fields into access log.
func Infow(msg string, fields ...LogField) {
infoFieldsSync(msg, fields...)
}
// Must checks if err is nil, otherwise logs the error and exits. // Must checks if err is nil, otherwise logs the error and exits.
func Must(err error) { func Must(err error) {
if err != nil { if err == nil {
msg := formatWithCaller(err.Error(), 3) return
log.Print(msg)
outputText(severeLog, levelFatal, msg)
os.Exit(1)
} }
msg := err.Error()
log.Print(msg)
getWriter().Severe(msg)
os.Exit(1)
}
// MustSetup sets up logging with given config c. It exits on error.
func MustSetup(c LogConf) {
Must(SetUp(c))
}
// Reset clears the writer and resets the log level.
func Reset() Writer {
SetLevel(InfoLevel)
return writer.Swap(nil)
} }
// SetLevel sets the logging level. It can be used to suppress some logs. // SetLevel sets the logging level. It can be used to suppress some logs.
@@ -290,6 +196,43 @@ func SetLevel(level uint32) {
atomic.StoreUint32(&logLevel, level) atomic.StoreUint32(&logLevel, level)
} }
// SetWriter sets the logging writer. It can be used to customize the logging.
// Call Reset before calling SetWriter again.
func SetWriter(w Writer) {
if writer.Load() == nil {
writer.Store(w)
}
}
// SetUp sets up the logx. If already set up, just return nil.
// we allow SetUp to be called multiple times, because for example
// we need to allow different service frameworks to initialize logx respectively.
// the same logic for SetUp
func SetUp(c LogConf) error {
setupLogLevel(c)
if len(c.TimeFormat) > 0 {
timeFormat = c.TimeFormat
}
switch c.Encoding {
case plainEncoding:
atomic.StoreUint32(&encoding, plainEncodingType)
default:
atomic.StoreUint32(&encoding, jsonEncodingType)
}
switch c.Mode {
case fileMode:
return setupWithFiles(c)
case volumeMode:
return setupWithVolume(c)
default:
setupWithConsole()
return nil
}
}
// Severe writes v into severe log. // Severe writes v into severe log.
func Severe(v ...interface{}) { func Severe(v ...interface{}) {
severeSync(fmt.Sprint(v...)) severeSync(fmt.Sprint(v...))
@@ -315,6 +258,11 @@ func Slowv(v interface{}) {
slowAnySync(v) slowAnySync(v)
} }
// Sloww writes msg along with fields into slow log.
func Sloww(msg string, fields ...LogField) {
slowFieldsSync(msg, fields...)
}
// Stat writes v into stat log. // Stat writes v into stat log.
func Stat(v ...interface{}) { func Stat(v ...interface{}) {
statSync(fmt.Sprint(v...)) statSync(fmt.Sprint(v...))
@@ -357,52 +305,30 @@ func createOutput(path string) (io.WriteCloser, error) {
func errorAnySync(v interface{}) { func errorAnySync(v interface{}) {
if shallLog(ErrorLevel) { if shallLog(ErrorLevel) {
outputAny(errorLog, levelError, v) getWriter().Error(v)
} }
} }
func errorTextSync(msg string, callDepth int) { func errorFieldsSync(content string, fields ...LogField) {
if shallLog(ErrorLevel) { if shallLog(ErrorLevel) {
outputError(errorLog, msg, callDepth) getWriter().Error(content, fields...)
} }
} }
func formatWithCaller(msg string, callDepth int) string { func errorTextSync(msg string) {
var buf strings.Builder if shallLog(ErrorLevel) {
getWriter().Error(msg)
caller := getCaller(callDepth)
if len(caller) > 0 {
buf.WriteString(caller)
buf.WriteByte(' ')
} }
buf.WriteString(msg)
return buf.String()
} }
func getCaller(callDepth int) string { func getWriter() Writer {
var buf strings.Builder w := writer.Load()
if w == nil {
_, file, line, ok := runtime.Caller(callDepth) w = newConsoleWriter()
if ok { writer.Store(w)
short := file
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
short = file[i+1:]
break
}
}
buf.WriteString(short)
buf.WriteByte(':')
buf.WriteString(strconv.Itoa(line))
} }
return buf.String() return w
}
func getTimestamp() string {
return timex.Time().Format(timeFormat)
} }
func handleOptions(opts []LogOption) { func handleOptions(opts []LogOption) {
@@ -413,56 +339,19 @@ func handleOptions(opts []LogOption) {
func infoAnySync(val interface{}) { func infoAnySync(val interface{}) {
if shallLog(InfoLevel) { if shallLog(InfoLevel) {
outputAny(infoLog, levelInfo, val) getWriter().Info(val)
}
}
func infoFieldsSync(content string, fields ...LogField) {
if shallLog(InfoLevel) {
getWriter().Info(content, fields...)
} }
} }
func infoTextSync(msg string) { func infoTextSync(msg string) {
if shallLog(InfoLevel) { if shallLog(InfoLevel) {
outputText(infoLog, levelInfo, msg) getWriter().Info(msg)
}
}
func outputAny(writer io.Writer, level string, val interface{}) {
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainAny(writer, level, val)
default:
info := logEntry{
Timestamp: getTimestamp(),
Level: level,
Content: val,
}
outputJson(writer, info)
}
}
func outputText(writer io.Writer, level, msg string) {
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainText(writer, level, msg)
default:
info := logEntry{
Timestamp: getTimestamp(),
Level: level,
Content: msg,
}
outputJson(writer, info)
}
}
func outputError(writer io.Writer, msg string, callDepth int) {
content := formatWithCaller(msg, callDepth)
outputText(writer, levelError, content)
}
func outputJson(writer io.Writer, info interface{}) {
if content, err := json.Marshal(info); err != nil {
log.Println(err.Error())
} else if atomic.LoadUint32(&initialized) == 0 || writer == nil {
log.Println(string(content))
} else {
writer.Write(append(content, '\n'))
} }
} }
@@ -477,72 +366,18 @@ func setupLogLevel(c LogConf) {
} }
} }
func setupWithConsole(c LogConf) { func setupWithConsole() {
once.Do(func() { SetWriter(newConsoleWriter())
atomic.StoreUint32(&initialized, 1)
writeConsole = true
setupLogLevel(c)
infoLog = newLogWriter(log.New(os.Stdout, "", flags))
errorLog = newLogWriter(log.New(os.Stderr, "", flags))
severeLog = newLogWriter(log.New(os.Stderr, "", flags))
slowLog = newLogWriter(log.New(os.Stderr, "", flags))
stackLog = newLessWriter(errorLog, options.logStackCooldownMills)
statLog = infoLog
})
} }
func setupWithFiles(c LogConf) error { func setupWithFiles(c LogConf) error {
var opts []LogOption w, err := newFileWriter(c)
var err error if err != nil {
return err
if len(c.Path) == 0 {
return ErrLogPathNotSet
} }
opts = append(opts, WithCooldownMillis(c.StackCooldownMillis)) SetWriter(w)
if c.Compress { return nil
opts = append(opts, WithGzip())
}
if c.KeepDays > 0 {
opts = append(opts, WithKeepDays(c.KeepDays))
}
accessFile := path.Join(c.Path, accessFilename)
errorFile := path.Join(c.Path, errorFilename)
severeFile := path.Join(c.Path, severeFilename)
slowFile := path.Join(c.Path, slowFilename)
statFile := path.Join(c.Path, statFilename)
once.Do(func() {
atomic.StoreUint32(&initialized, 1)
handleOptions(opts)
setupLogLevel(c)
if infoLog, err = createOutput(accessFile); err != nil {
return
}
if errorLog, err = createOutput(errorFile); err != nil {
return
}
if severeLog, err = createOutput(severeFile); err != nil {
return
}
if slowLog, err = createOutput(slowFile); err != nil {
return
}
if statLog, err = createOutput(statFile); err != nil {
return
}
stackLog = newLessWriter(errorLog, options.logStackCooldownMills)
})
return err
} }
func setupWithVolume(c LogConf) error { func setupWithVolume(c LogConf) error {
@@ -556,7 +391,7 @@ func setupWithVolume(c LogConf) error {
func severeSync(msg string) { func severeSync(msg string) {
if shallLog(SevereLevel) { if shallLog(SevereLevel) {
outputText(severeLog, levelSevere, fmt.Sprintf("%s\n%s", msg, string(debug.Stack()))) getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
} }
} }
@@ -570,99 +405,30 @@ func shallLogStat() bool {
func slowAnySync(v interface{}) { func slowAnySync(v interface{}) {
if shallLog(ErrorLevel) { if shallLog(ErrorLevel) {
outputAny(slowLog, levelSlow, v) getWriter().Slow(v)
}
}
func slowFieldsSync(content string, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Slow(content, fields...)
} }
} }
func slowTextSync(msg string) { func slowTextSync(msg string) {
if shallLog(ErrorLevel) { if shallLog(ErrorLevel) {
outputText(slowLog, levelSlow, msg) getWriter().Slow(msg)
} }
} }
func stackSync(msg string) { func stackSync(msg string) {
if shallLog(ErrorLevel) { if shallLog(ErrorLevel) {
outputText(stackLog, levelError, fmt.Sprintf("%s\n%s", msg, string(debug.Stack()))) getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
} }
} }
func statSync(msg string) { func statSync(msg string) {
if shallLogStat() && shallLog(InfoLevel) { if shallLogStat() && shallLog(InfoLevel) {
outputText(statLog, levelStat, msg) getWriter().Stat(msg)
} }
} }
func writePlainAny(writer io.Writer, level string, val interface{}, fields ...string) {
switch v := val.(type) {
case string:
writePlainText(writer, level, v, fields...)
case error:
writePlainText(writer, level, v.Error(), fields...)
case fmt.Stringer:
writePlainText(writer, level, v.String(), fields...)
default:
var buf bytes.Buffer
buf.WriteString(getTimestamp())
buf.WriteByte(plainEncodingSep)
buf.WriteString(level)
for _, item := range fields {
buf.WriteByte(plainEncodingSep)
buf.WriteString(item)
}
buf.WriteByte(plainEncodingSep)
if err := json.NewEncoder(&buf).Encode(val); err != nil {
log.Println(err.Error())
return
}
buf.WriteByte('\n')
if atomic.LoadUint32(&initialized) == 0 || writer == nil {
log.Println(buf.String())
return
}
if _, err := writer.Write(buf.Bytes()); err != nil {
log.Println(err.Error())
}
}
}
func writePlainText(writer io.Writer, level, msg string, fields ...string) {
var buf bytes.Buffer
buf.WriteString(getTimestamp())
buf.WriteByte(plainEncodingSep)
buf.WriteString(level)
for _, item := range fields {
buf.WriteByte(plainEncodingSep)
buf.WriteString(item)
}
buf.WriteByte(plainEncodingSep)
buf.WriteString(msg)
buf.WriteByte('\n')
if atomic.LoadUint32(&initialized) == 0 || writer == nil {
log.Println(buf.String())
return
}
if _, err := writer.Write(buf.Bytes()); err != nil {
log.Println(err.Error())
}
}
type logWriter struct {
logger *log.Logger
}
func newLogWriter(logger *log.Logger) logWriter {
return logWriter{
logger: logger,
}
}
func (lw logWriter) Close() error {
return nil
}
func (lw logWriter) Write(data []byte) (int, error) {
lw.logger.Print(string(data))
return len(data), nil
}

View File

@@ -4,10 +4,10 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"reflect"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
@@ -19,8 +19,9 @@ import (
) )
var ( var (
s = []byte("Sending #11 notification (id: 1451875113812010473) in #1 connection") s = []byte("Sending #11 notification (id: 1451875113812010473) in #1 connection")
pool = make(chan []byte, 1) pool = make(chan []byte, 1)
_ Writer = (*mockWriter)(nil)
) )
type mockWriter struct { type mockWriter struct {
@@ -28,10 +29,46 @@ type mockWriter struct {
builder strings.Builder builder strings.Builder
} }
func (mw *mockWriter) Write(data []byte) (int, error) { func (mw *mockWriter) Alert(v interface{}) {
mw.lock.Lock() mw.lock.Lock()
defer mw.lock.Unlock() defer mw.lock.Unlock()
return mw.builder.Write(data) output(&mw.builder, levelAlert, v)
}
func (mw *mockWriter) Error(v interface{}, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelError, v, fields...)
}
func (mw *mockWriter) Info(v interface{}, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelInfo, v, fields...)
}
func (mw *mockWriter) Severe(v interface{}) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelSevere, v)
}
func (mw *mockWriter) Slow(v interface{}, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelSlow, v, fields...)
}
func (mw *mockWriter) Stack(v interface{}) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelError, v)
}
func (mw *mockWriter) Stat(v interface{}, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelStat, v, fields...)
} }
func (mw *mockWriter) Close() error { func (mw *mockWriter) Close() error {
@@ -56,95 +93,211 @@ func (mw *mockWriter) String() string {
return mw.builder.String() return mw.builder.String()
} }
func TestField(t *testing.T) {
tests := []struct {
name string
f LogField
want map[string]interface{}
}{
{
name: "error",
f: Field("foo", errors.New("bar")),
want: map[string]interface{}{
"foo": "bar",
},
},
{
name: "errors",
f: Field("foo", []error{errors.New("bar"), errors.New("baz")}),
want: map[string]interface{}{
"foo": []interface{}{"bar", "baz"},
},
},
{
name: "strings",
f: Field("foo", []string{"bar", "baz"}),
want: map[string]interface{}{
"foo": []interface{}{"bar", "baz"},
},
},
{
name: "duration",
f: Field("foo", time.Second),
want: map[string]interface{}{
"foo": "1s",
},
},
{
name: "durations",
f: Field("foo", []time.Duration{time.Second, 2 * time.Second}),
want: map[string]interface{}{
"foo": []interface{}{"1s", "2s"},
},
},
{
name: "times",
f: Field("foo", []time.Time{
time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC),
time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC),
}),
want: map[string]interface{}{
"foo": []interface{}{"2020-01-01 00:00:00 +0000 UTC", "2020-01-02 00:00:00 +0000 UTC"},
},
},
{
name: "stringer",
f: Field("foo", ValStringer{val: "bar"}),
want: map[string]interface{}{
"foo": "bar",
},
},
{
name: "stringers",
f: Field("foo", []fmt.Stringer{ValStringer{val: "bar"}, ValStringer{val: "baz"}}),
want: map[string]interface{}{
"foo": []interface{}{"bar", "baz"},
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
Infow("foo", test.f)
validateFields(t, w.String(), test.want)
})
}
}
func TestFileLineFileMode(t *testing.T) { func TestFileLineFileMode(t *testing.T) {
writer := new(mockWriter) w := new(mockWriter)
errorLog = writer old := writer.Swap(w)
atomic.StoreUint32(&initialized, 1) defer writer.Store(old)
file, line := getFileLine() file, line := getFileLine()
Error("anything") Error("anything")
assert.True(t, writer.Contains(fmt.Sprintf("%s:%d", file, line+1))) assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
writer.Reset()
file, line = getFileLine() file, line = getFileLine()
Errorf("anything %s", "format") Errorf("anything %s", "format")
assert.True(t, writer.Contains(fmt.Sprintf("%s:%d", file, line+1))) assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
} }
func TestFileLineConsoleMode(t *testing.T) { func TestFileLineConsoleMode(t *testing.T) {
writer := new(mockWriter) w := new(mockWriter)
writeConsole = true old := writer.Swap(w)
errorLog = newLogWriter(log.New(writer, "[ERROR] ", flags)) defer writer.Store(old)
atomic.StoreUint32(&initialized, 1)
file, line := getFileLine() file, line := getFileLine()
Error("anything") Error("anything")
assert.True(t, writer.Contains(fmt.Sprintf("%s:%d", file, line+1))) assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
writer.Reset() w.Reset()
file, line = getFileLine() file, line = getFileLine()
Errorf("anything %s", "format") Errorf("anything %s", "format")
assert.True(t, writer.Contains(fmt.Sprintf("%s:%d", file, line+1))) assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
} }
func TestStructedLogAlert(t *testing.T) { func TestStructedLogAlert(t *testing.T) {
doTestStructedLog(t, levelAlert, func(writer io.WriteCloser) { w := new(mockWriter)
errorLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelAlert, w, func(v ...interface{}) {
Alert(fmt.Sprint(v...)) Alert(fmt.Sprint(v...))
}) })
} }
func TestStructedLogError(t *testing.T) { func TestStructedLogError(t *testing.T) {
doTestStructedLog(t, levelError, func(writer io.WriteCloser) { w := new(mockWriter)
errorLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
Error(v...) Error(v...)
}) })
} }
func TestStructedLogErrorf(t *testing.T) { func TestStructedLogErrorf(t *testing.T) {
doTestStructedLog(t, levelError, func(writer io.WriteCloser) { w := new(mockWriter)
errorLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
Errorf("%s", fmt.Sprint(v...)) Errorf("%s", fmt.Sprint(v...))
}) })
} }
func TestStructedLogErrorv(t *testing.T) { func TestStructedLogErrorv(t *testing.T) {
doTestStructedLog(t, levelError, func(writer io.WriteCloser) { w := new(mockWriter)
errorLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
Errorv(fmt.Sprint(v...)) Errorv(fmt.Sprint(v...))
}) })
} }
func TestStructedLogErrorw(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
Errorw(fmt.Sprint(v...), Field("foo", "bar"))
})
}
func TestStructedLogInfo(t *testing.T) { func TestStructedLogInfo(t *testing.T) {
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) { w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
Info(v...) Info(v...)
}) })
} }
func TestStructedLogInfof(t *testing.T) { func TestStructedLogInfof(t *testing.T) {
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) { w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
Infof("%s", fmt.Sprint(v...)) Infof("%s", fmt.Sprint(v...))
}) })
} }
func TestStructedLogInfov(t *testing.T) { func TestStructedLogInfov(t *testing.T) {
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) { w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
Infov(fmt.Sprint(v...)) Infov(fmt.Sprint(v...))
}) })
} }
func TestStructedLogInfow(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
Infow(fmt.Sprint(v...), Field("foo", "bar"))
})
}
func TestStructedLogInfoConsoleAny(t *testing.T) { func TestStructedLogInfoConsoleAny(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) { w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLogConsole(t, w, func(v ...interface{}) {
old := atomic.LoadUint32(&encoding) old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType) atomic.StoreUint32(&encoding, plainEncodingType)
defer func() { defer func() {
@@ -156,9 +309,11 @@ func TestStructedLogInfoConsoleAny(t *testing.T) {
} }
func TestStructedLogInfoConsoleAnyString(t *testing.T) { func TestStructedLogInfoConsoleAnyString(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) { w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLogConsole(t, w, func(v ...interface{}) {
old := atomic.LoadUint32(&encoding) old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType) atomic.StoreUint32(&encoding, plainEncodingType)
defer func() { defer func() {
@@ -170,9 +325,11 @@ func TestStructedLogInfoConsoleAnyString(t *testing.T) {
} }
func TestStructedLogInfoConsoleAnyError(t *testing.T) { func TestStructedLogInfoConsoleAnyError(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) { w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLogConsole(t, w, func(v ...interface{}) {
old := atomic.LoadUint32(&encoding) old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType) atomic.StoreUint32(&encoding, plainEncodingType)
defer func() { defer func() {
@@ -184,9 +341,11 @@ func TestStructedLogInfoConsoleAnyError(t *testing.T) {
} }
func TestStructedLogInfoConsoleAnyStringer(t *testing.T) { func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) { w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLogConsole(t, w, func(v ...interface{}) {
old := atomic.LoadUint32(&encoding) old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType) atomic.StoreUint32(&encoding, plainEncodingType)
defer func() { defer func() {
@@ -200,9 +359,11 @@ func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
} }
func TestStructedLogInfoConsoleText(t *testing.T) { func TestStructedLogInfoConsoleText(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) { w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLogConsole(t, w, func(v ...interface{}) {
old := atomic.LoadUint32(&encoding) old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType) atomic.StoreUint32(&encoding, plainEncodingType)
defer func() { defer func() {
@@ -214,69 +375,94 @@ func TestStructedLogInfoConsoleText(t *testing.T) {
} }
func TestStructedLogSlow(t *testing.T) { func TestStructedLogSlow(t *testing.T) {
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) { w := new(mockWriter)
slowLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
Slow(v...) Slow(v...)
}) })
} }
func TestStructedLogSlowf(t *testing.T) { func TestStructedLogSlowf(t *testing.T) {
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) { w := new(mockWriter)
slowLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
Slowf(fmt.Sprint(v...)) Slowf(fmt.Sprint(v...))
}) })
} }
func TestStructedLogSlowv(t *testing.T) { func TestStructedLogSlowv(t *testing.T) {
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) { w := new(mockWriter)
slowLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
Slowv(fmt.Sprint(v...)) Slowv(fmt.Sprint(v...))
}) })
} }
func TestStructedLogSloww(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
Sloww(fmt.Sprint(v...), Field("foo", time.Second))
})
}
func TestStructedLogStat(t *testing.T) { func TestStructedLogStat(t *testing.T) {
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) { w := new(mockWriter)
statLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelStat, w, func(v ...interface{}) {
Stat(v...) Stat(v...)
}) })
} }
func TestStructedLogStatf(t *testing.T) { func TestStructedLogStatf(t *testing.T) {
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) { w := new(mockWriter)
statLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelStat, w, func(v ...interface{}) {
Statf(fmt.Sprint(v...)) Statf(fmt.Sprint(v...))
}) })
} }
func TestStructedLogSevere(t *testing.T) { func TestStructedLogSevere(t *testing.T) {
doTestStructedLog(t, levelSevere, func(writer io.WriteCloser) { w := new(mockWriter)
severeLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelSevere, w, func(v ...interface{}) {
Severe(v...) Severe(v...)
}) })
} }
func TestStructedLogSeveref(t *testing.T) { func TestStructedLogSeveref(t *testing.T) {
doTestStructedLog(t, levelSevere, func(writer io.WriteCloser) { w := new(mockWriter)
severeLog = writer old := writer.Swap(w)
}, func(v ...interface{}) { defer writer.Store(old)
doTestStructedLog(t, levelSevere, w, func(v ...interface{}) {
Severef(fmt.Sprint(v...)) Severef(fmt.Sprint(v...))
}) })
} }
func TestStructedLogWithDuration(t *testing.T) { func TestStructedLogWithDuration(t *testing.T) {
const message = "hello there" const message = "hello there"
writer := new(mockWriter) w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
atomic.StoreUint32(&initialized, 1) defer writer.Store(old)
WithDuration(time.Second).Info(message) WithDuration(time.Second).Info(message)
var entry logEntry var entry logEntry
if err := json.Unmarshal([]byte(writer.builder.String()), &entry); err != nil { if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
t.Error(err) t.Error(err)
} }
assert.Equal(t, levelInfo, entry.Level) assert.Equal(t, levelInfo, entry.Level)
@@ -287,11 +473,12 @@ func TestStructedLogWithDuration(t *testing.T) {
func TestSetLevel(t *testing.T) { func TestSetLevel(t *testing.T) {
SetLevel(ErrorLevel) SetLevel(ErrorLevel)
const message = "hello there" const message = "hello there"
writer := new(mockWriter) w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
atomic.StoreUint32(&initialized, 1) defer writer.Store(old)
Info(message) Info(message)
assert.Equal(t, 0, writer.builder.Len()) assert.Equal(t, 0, w.builder.Len())
} }
func TestSetLevelTwiceWithMode(t *testing.T) { func TestSetLevelTwiceWithMode(t *testing.T) {
@@ -300,29 +487,35 @@ func TestSetLevelTwiceWithMode(t *testing.T) {
"console", "console",
"volumn", "volumn",
} }
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
for _, mode := range testModes { for _, mode := range testModes {
testSetLevelTwiceWithMode(t, mode) testSetLevelTwiceWithMode(t, mode, w)
} }
} }
func TestSetLevelWithDuration(t *testing.T) { func TestSetLevelWithDuration(t *testing.T) {
SetLevel(ErrorLevel) SetLevel(ErrorLevel)
const message = "hello there" const message = "hello there"
writer := new(mockWriter) w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
atomic.StoreUint32(&initialized, 1) defer writer.Store(old)
WithDuration(time.Second).Info(message) WithDuration(time.Second).Info(message)
assert.Equal(t, 0, writer.builder.Len()) assert.Equal(t, 0, w.builder.Len())
} }
func TestErrorfWithWrappedError(t *testing.T) { func TestErrorfWithWrappedError(t *testing.T) {
SetLevel(ErrorLevel) SetLevel(ErrorLevel)
const message = "there" const message = "there"
writer := new(mockWriter) w := new(mockWriter)
errorLog = writer old := writer.Swap(w)
atomic.StoreUint32(&initialized, 1) defer writer.Store(old)
Errorf("hello %w", errors.New(message)) Errorf("hello %w", errors.New(message))
assert.True(t, strings.Contains(writer.builder.String(), "hello there")) assert.True(t, strings.Contains(w.String(), "hello there"))
} }
func TestMustNil(t *testing.T) { func TestMustNil(t *testing.T) {
@@ -330,6 +523,11 @@ func TestMustNil(t *testing.T) {
} }
func TestSetup(t *testing.T) { func TestSetup(t *testing.T) {
defer func() {
SetLevel(InfoLevel)
atomic.StoreUint32(&encoding, jsonEncodingType)
}()
MustSetup(LogConf{ MustSetup(LogConf{
ServiceName: "any", ServiceName: "any",
Mode: "console", Mode: "console",
@@ -344,6 +542,17 @@ func TestSetup(t *testing.T) {
Mode: "volume", Mode: "volume",
Path: os.TempDir(), Path: os.TempDir(),
}) })
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
TimeFormat: timeFormat,
})
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
Encoding: plainEncoding,
})
assert.NotNil(t, setupWithVolume(LogConf{})) assert.NotNil(t, setupWithVolume(LogConf{}))
assert.NotNil(t, setupWithFiles(LogConf{})) assert.NotNil(t, setupWithFiles(LogConf{}))
assert.Nil(t, setupWithFiles(LogConf{ assert.Nil(t, setupWithFiles(LogConf{
@@ -364,6 +573,8 @@ func TestSetup(t *testing.T) {
_, err := createOutput("") _, err := createOutput("")
assert.NotNil(t, err) assert.NotNil(t, err)
Disable() Disable()
SetLevel(InfoLevel)
atomic.StoreUint32(&encoding, jsonEncodingType)
} }
func TestDisable(t *testing.T) { func TestDisable(t *testing.T) {
@@ -373,7 +584,6 @@ func TestDisable(t *testing.T) {
WithKeepDays(1)(&opt) WithKeepDays(1)(&opt)
WithGzip()(&opt) WithGzip()(&opt)
assert.Nil(t, Close()) assert.Nil(t, Close())
writeConsole = false
assert.Nil(t, Close()) assert.Nil(t, Close())
} }
@@ -381,11 +591,20 @@ func TestDisableStat(t *testing.T) {
DisableStat() DisableStat()
const message = "hello there" const message = "hello there"
writer := new(mockWriter) w := new(mockWriter)
statLog = writer old := writer.Swap(w)
atomic.StoreUint32(&initialized, 1) defer writer.Store(old)
Stat(message) Stat(message)
assert.Equal(t, 0, writer.builder.Len()) assert.Equal(t, 0, w.builder.Len())
}
func TestSetWriter(t *testing.T) {
Reset()
SetWriter(nopWriter{})
assert.NotNil(t, writer.Load())
assert.True(t, writer.Load() == nopWriter{})
SetWriter(new(mockWriter))
assert.True(t, writer.Load() == nopWriter{})
} }
func TestWithGzip(t *testing.T) { func TestWithGzip(t *testing.T) {
@@ -487,15 +706,12 @@ func put(b []byte) {
} }
} }
func doTestStructedLog(t *testing.T, level string, setup func(writer io.WriteCloser), func doTestStructedLog(t *testing.T, level string, w *mockWriter, write func(...interface{})) {
write func(...interface{})) {
const message = "hello there" const message = "hello there"
writer := new(mockWriter)
setup(writer)
atomic.StoreUint32(&initialized, 1)
write(message) write(message)
fmt.Println(w.String())
var entry logEntry var entry logEntry
if err := json.Unmarshal([]byte(writer.builder.String()), &entry); err != nil { if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
t.Error(err) t.Error(err)
} }
assert.Equal(t, level, entry.Level) assert.Equal(t, level, entry.Level)
@@ -504,18 +720,14 @@ func doTestStructedLog(t *testing.T, level string, setup func(writer io.WriteClo
assert.True(t, strings.Contains(val, message)) assert.True(t, strings.Contains(val, message))
} }
func doTestStructedLogConsole(t *testing.T, setup func(writer io.WriteCloser), func doTestStructedLogConsole(t *testing.T, w *mockWriter, write func(...interface{})) {
write func(...interface{})) {
const message = "hello there" const message = "hello there"
writer := new(mockWriter)
setup(writer)
atomic.StoreUint32(&initialized, 1)
write(message) write(message)
println(writer.String()) assert.True(t, strings.Contains(w.String(), message))
assert.True(t, strings.Contains(writer.String(), message))
} }
func testSetLevelTwiceWithMode(t *testing.T, mode string) { func testSetLevelTwiceWithMode(t *testing.T, mode string, w *mockWriter) {
writer.Store(nil)
SetUp(LogConf{ SetUp(LogConf{
Mode: mode, Mode: mode,
Level: "error", Level: "error",
@@ -527,17 +739,14 @@ func testSetLevelTwiceWithMode(t *testing.T, mode string) {
Path: "/dev/null", Path: "/dev/null",
}) })
const message = "hello there" const message = "hello there"
writer := new(mockWriter)
infoLog = writer
atomic.StoreUint32(&initialized, 1)
Info(message) Info(message)
assert.Equal(t, 0, writer.builder.Len()) assert.Equal(t, 0, w.builder.Len())
Infof(message) Infof(message)
assert.Equal(t, 0, writer.builder.Len()) assert.Equal(t, 0, w.builder.Len())
ErrorStack(message) ErrorStack(message)
assert.Equal(t, 0, writer.builder.Len()) assert.Equal(t, 0, w.builder.Len())
ErrorStackf(message) ErrorStackf(message)
assert.Equal(t, 0, writer.builder.Len()) assert.Equal(t, 0, w.builder.Len())
} }
type ValStringer struct { type ValStringer struct {
@@ -547,3 +756,18 @@ type ValStringer struct {
func (v ValStringer) String() string { func (v ValStringer) String() string {
return v.val return v.val
} }
func validateFields(t *testing.T, content string, fields map[string]interface{}) {
var m map[string]interface{}
if err := json.Unmarshal([]byte(content), &m); err != nil {
t.Error(err)
}
for k, v := range fields {
if reflect.TypeOf(v).Kind() == reflect.Slice {
assert.EqualValues(t, v, m[k])
} else {
assert.Equal(t, v, m[k], content)
}
}
}

22
core/logx/logwriter.go Normal file
View File

@@ -0,0 +1,22 @@
package logx
import "log"
type logWriter struct {
logger *log.Logger
}
func newLogWriter(logger *log.Logger) logWriter {
return logWriter{
logger: logger,
}
}
func (lw logWriter) Close() error {
return nil
}
func (lw logWriter) Write(data []byte) (int, error) {
lw.logger.Print(string(data))
return len(data), nil
}

197
core/logx/readme-cn.md Normal file
View File

@@ -0,0 +1,197 @@
<IMG align="right" width="150px" src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/go-zero.png">
# logx
[English](readme.md) | 简体中文
## logx 配置
```go
type LogConf struct {
ServiceName string `json:",optional"`
Mode string `json:",default=console,options=[console,file,volume]"`
Encoding string `json:",default=json,options=[json,plain]"`
TimeFormat string `json:",optional"`
Path string `json:",default=logs"`
Level string `json:",default=info,options=[info,error,severe]"`
Compress bool `json:",optional"`
KeepDays int `json:",optional"`
StackCooldownMillis int `json:",default=100"`
}
```
- `ServiceName`:设置服务名称,可选。在 `volume` 模式下,该名称用于生成日志文件。在 `rest/zrpc` 服务中,名称将被自动设置为 `rest``zrpc` 的名称。
- `Mode`:输出日志的模式,默认是 `console`
- `console` 模式将日志写到 `stdout/stderr`
- `file` 模式将日志写到 `Path` 指定目录的文件中
- `volume` 模式在 docker 中使用,将日志写入挂载的卷中
- `Encoding`: 指示如何对日志进行编码,默认是 `json`
- `json`模式以 json 格式写日志
- `plain`模式用纯文本写日志,并带有终端颜色显示
- `TimeFormat`:自定义时间格式,可选。默认是 `2006-01-02T15:04:05.000Z07:00`
- `Path`:设置日志路径,默认为 `logs`
- `Level`: 用于过滤日志的日志级别。默认为 `info`
- `info`,所有日志都被写入
- `error`, `info` 的日志被丢弃
- `severe`, `info``error` 日志被丢弃,只有 `severe` 日志被写入
- `Compress`: 是否压缩日志文件,只在 `file` 模式下工作
- `KeepDays`:日志文件被保留多少天,在给定的天数之后,过期的文件将被自动删除。对 `console` 模式没有影响
- `StackCooldownMillis`:多少毫秒后再次写入堆栈跟踪。用来避免堆栈跟踪日志过多
## 打印日志方法
```go
type Logger interface {
// Error logs a message at error level.
Error(...interface{})
// Errorf logs a message at error level.
Errorf(string, ...interface{})
// Errorv logs a message at error level.
Errorv(interface{})
// Errorw logs a message at error level.
Errorw(string, ...LogField)
// Info logs a message at info level.
Info(...interface{})
// Infof logs a message at info level.
Infof(string, ...interface{})
// Infov logs a message at info level.
Infov(interface{})
// Infow logs a message at info level.
Infow(string, ...LogField)
// Slow logs a message at slow level.
Slow(...interface{})
// Slowf logs a message at slow level.
Slowf(string, ...interface{})
// Slowv logs a message at slow level.
Slowv(interface{})
// Sloww logs a message at slow level.
Sloww(string, ...LogField)
// WithContext returns a new logger with the given context.
WithContext(context.Context) Logger
// WithDuration returns a new logger with the given duration.
WithDuration(time.Duration) Logger
}
```
- `Error`, `Info`, `Slow`: 将任何类型的信息写进日志,使用 `fmt.Sprint(...)` 来转换为 `string`
- `Errorf`, `Infof`, `Slowf`: 将指定格式的信息写入日志
- `Errorv`, `Infov`, `Slowv`: 将任何类型的信息写入日志,用 `json marshal` 编码
- `Errorw`, `Infow`, `Sloww`: 写日志,并带上给定的 `key:value` 字段
- `WithContext`:将给定的 ctx 注入日志信息,例如用于记录 `trace-id``span-id`
- `WithDuration`: 将指定的时间写入日志信息中,字段名为 `duration`
## 与第三方日志库集成
- zap
- 实现:[https://github.com/zeromicro/zero-contrib/blob/main/logx/zapx/zap.go](https://github.com/zeromicro/zero-contrib/blob/main/logx/zapx/zap.go)
- 使用示例:[https://github.com/zeromicro/zero-examples/blob/main/logx/zaplog/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/zaplog/main.go)
- logrus
- 实现:[https://github.com/zeromicro/zero-contrib/blob/main/logx/logrusx/logrus.go](https://github.com/zeromicro/zero-contrib/blob/main/logx/logrusx/logrus.go)
- 使用示例:[https://github.com/zeromicro/zero-examples/blob/main/logx/logrus/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/logrus/main.go)
对于其它的日志库,请参考上面示例实现,并欢迎提交 `PR` 到 [https://github.com/zeromicro/zero-contrib](https://github.com/zeromicro/zero-contrib)
## 将日志写到指定的存储
`logx`定义了两个接口,方便自定义 `logx`,将日志写入任何存储。
- `logx.NewWriter(w io.Writer)`
- `logx.SetWriter(write logx.Writer)`
例如如果我们想把日志写进kafka而不是控制台或文件我们可以像下面这样做。
```go
type KafkaWriter struct {
Pusher *kq.Pusher
}
func NewKafkaWriter(pusher *kq.Pusher) *KafkaWriter {
return &KafkaWriter{
Pusher: pusher,
}
}
func (w *KafkaWriter) Write(p []byte) (n int, err error) {
// writing log with newlines, trim them.
if err := w.Pusher.Push(strings.TrimSpace(string(p))); err != nil {
return 0, err
}
return len(p), nil
}
func main() {
pusher := kq.NewPusher([]string{"localhost:9092"}, "go-zero")
defer pusher.Close()
writer := logx.NewWriter(NewKafkaWriter(pusher))
logx.SetWriter(writer)
// more code
}
```
完整代码:[https://github.com/zeromicro/zero-examples/blob/main/logx/tokafka/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/tokafka/main.go)
## 过滤敏感字段
如果我们需要防止 `password` 字段被记录下来,我们可以像下面这样实现。
```go
type (
Message struct {
Name string
Password string
Message string
}
SensitiveLogger struct {
logx.Writer
}
)
func NewSensitiveLogger(writer logx.Writer) *SensitiveLogger {
return &SensitiveLogger{
Writer: writer,
}
}
func (l *SensitiveLogger) Info(msg interface{}, fields ...logx.LogField) {
if m, ok := msg.(Message); ok {
l.Writer.Info(Message{
Name: m.Name,
Password: "******",
Message: m.Message,
}, fields...)
} else {
l.Writer.Info(msg, fields...)
}
}
func main() {
// setup logx to make sure originalWriter not nil,
// the injected writer is only for filtering, like a middleware.
originalWriter := logx.Reset()
writer := NewSensitiveLogger(originalWriter)
logx.SetWriter(writer)
logx.Infov(Message{
Name: "foo",
Password: "shouldNotAppear",
Message: "bar",
})
// more code
}
```
完整代码:[https://github.com/zeromicro/zero-examples/blob/main/logx/filterfields/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/filterfields/main.go)
## 更多示例
[https://github.com/zeromicro/zero-examples/tree/main/logx](https://github.com/zeromicro/zero-examples/tree/main/logx)
## Give a Star! ⭐
如果你正在使用或者觉得这个项目对你有帮助,请 **star** 支持,感谢!

197
core/logx/readme.md Normal file
View File

@@ -0,0 +1,197 @@
<img align="right" width="150px" src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/go-zero.png">
# logx
English | [简体中文](readme-cn.md)
## logx configurations
```go
type LogConf struct {
ServiceName string `json:",optional"`
Mode string `json:",default=console,options=[console,file,volume]"`
Encoding string `json:",default=json,options=[json,plain]"`
TimeFormat string `json:",optional"`
Path string `json:",default=logs"`
Level string `json:",default=info,options=[info,error,severe]"`
Compress bool `json:",optional"`
KeepDays int `json:",optional"`
StackCooldownMillis int `json:",default=100"`
}
```
- `ServiceName`: set the service name, optional. on `volume` mode, the name is used to generate the log files. Within `rest/zrpc` services, the name will be set to the name of `rest` or `zrpc` automatically.
- `Mode`: the mode to output the logs, default is `console`.
- `console` mode writes the logs to `stdout/stderr`.
- `file` mode writes the logs to the files specified by `Path`.
- `volume` mode is used in docker, to write logs into mounted volumes.
- `Encoding`: indicates how to encode the logs, default is `json`.
- `json` mode writes the logs in json format.
- `plain` mode writes the logs with plain text, with terminal color enabled.
- `TimeFormat`: customize the time format, optional. Default is `2006-01-02T15:04:05.000Z07:00`.
- `Path`: set the log path, default to `logs`.
- `Level`: the logging level to filter logs. Default is `info`.
- `info`, all logs are written.
- `error`, `info` logs are suppressed.
- `severe`, `info` and `error` logs are suppressed, only `severe` logs are written.
- `Compress`: whether or not to compress log files, only works with `file` mode.
- `KeepDays`: how many days that the log files are kept, after the given days, the outdated files will be deleted automatically. It has no effect on `console` mode.
- `StackCooldownMillis`: how many milliseconds to rewrite stacktrace again. Its used to avoid stacktrace flooding.
## Logging methods
```go
type Logger interface {
// Error logs a message at error level.
Error(...interface{})
// Errorf logs a message at error level.
Errorf(string, ...interface{})
// Errorv logs a message at error level.
Errorv(interface{})
// Errorw logs a message at error level.
Errorw(string, ...LogField)
// Info logs a message at info level.
Info(...interface{})
// Infof logs a message at info level.
Infof(string, ...interface{})
// Infov logs a message at info level.
Infov(interface{})
// Infow logs a message at info level.
Infow(string, ...LogField)
// Slow logs a message at slow level.
Slow(...interface{})
// Slowf logs a message at slow level.
Slowf(string, ...interface{})
// Slowv logs a message at slow level.
Slowv(interface{})
// Sloww logs a message at slow level.
Sloww(string, ...LogField)
// WithContext returns a new logger with the given context.
WithContext(context.Context) Logger
// WithDuration returns a new logger with the given duration.
WithDuration(time.Duration) Logger
}
```
- `Error`, `Info`, `Slow`: write any kind of messages into logs, with like `fmt.Sprint(…)`.
- `Errorf`, `Infof`, `Slowf`: write messages with given format into logs.
- `Errorv`, `Infov`, `Slowv`: write any kind of messages into logs, with json marshalling to encode them.
- `Errorw`, `Infow`, `Sloww`: write the string message with given `key:value` fields.
- `WithContext`: inject the given ctx into the log messages, typically used to log `trace-id` and `span-id`.
- `WithDuration`: write elapsed duration into the log messages, with key `duration`.
## Integrating with third-party logging libs
- zap
- implementation: [https://github.com/zeromicro/zero-contrib/blob/main/logx/zapx/zap.go](https://github.com/zeromicro/zero-contrib/blob/main/logx/zapx/zap.go)
- usage example: [https://github.com/zeromicro/zero-examples/blob/main/logx/zaplog/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/zaplog/main.go)
- logrus
- implementation: [https://github.com/zeromicro/zero-contrib/blob/main/logx/logrusx/logrus.go](https://github.com/zeromicro/zero-contrib/blob/main/logx/logrusx/logrus.go)
- usage example: [https://github.com/zeromicro/zero-examples/blob/main/logx/logrus/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/logrus/main.go)
For more libs, please implement and PR to [https://github.com/zeromicro/zero-contrib](https://github.com/zeromicro/zero-contrib)
## Write the logs to specific stores
`logx` defined two interfaces to let you customize `logx` to write logs into any stores.
- `logx.NewWriter(w io.Writer)`
- `logx.SetWriter(writer logx.Writer)`
For example, if we want to write the logs into kafka instead of console or files, we can do it like below:
```go
type KafkaWriter struct {
Pusher *kq.Pusher
}
func NewKafkaWriter(pusher *kq.Pusher) *KafkaWriter {
return &KafkaWriter{
Pusher: pusher,
}
}
func (w *KafkaWriter) Write(p []byte) (n int, err error) {
// writing log with newlines, trim them.
if err := w.Pusher.Push(strings.TrimSpace(string(p))); err != nil {
return 0, err
}
return len(p), nil
}
func main() {
pusher := kq.NewPusher([]string{"localhost:9092"}, "go-zero")
defer pusher.Close()
writer := logx.NewWriter(NewKafkaWriter(pusher))
logx.SetWriter(writer)
// more code
}
```
Complete code: [https://github.com/zeromicro/zero-examples/blob/main/logx/tokafka/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/tokafka/main.go)
## Filtering sensitive fields
If we need to prevent the `password` fields from logging, we can do it like below:
```go
type (
Message struct {
Name string
Password string
Message string
}
SensitiveLogger struct {
logx.Writer
}
)
func NewSensitiveLogger(writer logx.Writer) *SensitiveLogger {
return &SensitiveLogger{
Writer: writer,
}
}
func (l *SensitiveLogger) Info(msg interface{}, fields ...logx.LogField) {
if m, ok := msg.(Message); ok {
l.Writer.Info(Message{
Name: m.Name,
Password: "******",
Message: m.Message,
}, fields...)
} else {
l.Writer.Info(msg, fields...)
}
}
func main() {
// setup logx to make sure originalWriter not nil,
// the injected writer is only for filtering, like a middleware.
originalWriter := logx.Reset()
writer := NewSensitiveLogger(originalWriter)
logx.SetWriter(writer)
logx.Infov(Message{
Name: "foo",
Password: "shouldNotAppear",
Message: "bar",
})
// more code
}
```
Complete code: [https://github.com/zeromicro/zero-examples/blob/main/logx/filterfields/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/filterfields/main.go)
## More examples
[https://github.com/zeromicro/zero-examples/tree/main/logx](https://github.com/zeromicro/zero-examples/tree/main/logx)
## Give a Star! ⭐
If you like or are using this project to learn or start your solution, please give it a star. Thanks!

View File

@@ -15,7 +15,6 @@ import (
"github.com/zeromicro/go-zero/core/fs" "github.com/zeromicro/go-zero/core/fs"
"github.com/zeromicro/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/timex"
) )
const ( const (
@@ -211,6 +210,12 @@ func (l *RotateLogger) maybeCompressFile(file string) {
ErrorStack(r) ErrorStack(r)
} }
}() }()
if _, err := os.Stat(file); err != nil {
// file not exists or other error, ignore compression
return
}
compressLogFile(file) compressLogFile(file)
} }
@@ -290,12 +295,12 @@ func (l *RotateLogger) write(v []byte) {
} }
func compressLogFile(file string) { func compressLogFile(file string) {
start := timex.Now() start := time.Now()
Infof("compressing log file: %s", file) Infof("compressing log file: %s", file)
if err := gzipFile(file); err != nil { if err := gzipFile(file); err != nil {
Errorf("compress error: %s", err) Errorf("compress error: %s", err)
} else { } else {
Infof("compressed log file: %s, took %s", file, timex.Since(start)) Infof("compressed log file: %s, took %s", file, time.Since(start))
} }
} }

View File

@@ -75,10 +75,7 @@ func TestRotateLoggerMayCompressFileTrue(t *testing.T) {
logger, err := NewLogger(filename, new(DailyRotateRule), true) logger, err := NewLogger(filename, new(DailyRotateRule), true)
assert.Nil(t, err) assert.Nil(t, err)
if len(filename) > 0 { if len(filename) > 0 {
defer func() { defer os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
os.Remove(filename)
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}()
} }
logger.maybeCompressFile(filename) logger.maybeCompressFile(filename)
_, err = os.Stat(filename) _, err = os.Stat(filename)
@@ -92,7 +89,6 @@ func TestRotateLoggerRotate(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
if len(filename) > 0 { if len(filename) > 0 {
defer func() { defer func() {
os.Remove(filename)
os.Remove(logger.getBackupFilename()) os.Remove(logger.getBackupFilename())
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz") os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}() }()
@@ -102,6 +98,10 @@ func TestRotateLoggerRotate(t *testing.T) {
case *os.LinkError: case *os.LinkError:
// avoid rename error on docker container // avoid rename error on docker container
assert.Equal(t, syscall.EXDEV, v.Err) assert.Equal(t, syscall.EXDEV, v.Err)
case *os.PathError:
// ignore remove error for tests,
// files are cleaned in GitHub actions.
assert.Equal(t, "remove", v.Op)
default: default:
assert.Nil(t, err) assert.Nil(t, err)
} }
@@ -115,12 +115,18 @@ func TestRotateLoggerWrite(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
if len(filename) > 0 { if len(filename) > 0 {
defer func() { defer func() {
os.Remove(filename)
os.Remove(logger.getBackupFilename()) os.Remove(logger.getBackupFilename())
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz") os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}() }()
} }
// the following write calls cannot be changed to Write, because of DATA RACE.
logger.write([]byte(`foo`)) logger.write([]byte(`foo`))
rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat) rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
logger.write([]byte(`bar`)) logger.write([]byte(`bar`))
logger.Close()
logger.write([]byte(`baz`))
}
func TestLogWriterClose(t *testing.T) {
assert.Nil(t, newLogWriter(nil).Close())
} }

View File

@@ -29,16 +29,16 @@ func TestRedirector(t *testing.T) {
} }
func captureOutput(f func()) string { func captureOutput(f func()) string {
writer := new(mockWriter) w := new(mockWriter)
infoLog = writer old := writer.Swap(w)
atomic.StoreUint32(&initialized, 1) defer writer.Store(old)
prevLevel := atomic.LoadUint32(&logLevel) prevLevel := atomic.LoadUint32(&logLevel)
SetLevel(InfoLevel) SetLevel(InfoLevel)
f() f()
SetLevel(prevLevel) SetLevel(prevLevel)
return writer.builder.String() return w.String()
} }
func getContent(jsonStr string) string { func getContent(jsonStr string) string {

View File

@@ -3,73 +3,79 @@ package logx
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"sync/atomic"
"time" "time"
"github.com/zeromicro/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
// WithContext sets ctx to log, for keeping tracing information.
func WithContext(ctx context.Context) Logger {
return &traceLogger{
ctx: ctx,
}
}
type traceLogger struct { type traceLogger struct {
logEntry logEntry
Trace string `json:"trace,omitempty"` ctx context.Context
Span string `json:"span,omitempty"`
ctx context.Context
} }
func (l *traceLogger) Error(v ...interface{}) { func (l *traceLogger) Error(v ...interface{}) {
if shallLog(ErrorLevel) { l.err(fmt.Sprint(v...))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
}
} }
func (l *traceLogger) Errorf(format string, v ...interface{}) { func (l *traceLogger) Errorf(format string, v ...interface{}) {
if shallLog(ErrorLevel) { l.err(fmt.Sprintf(format, v...))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
}
} }
func (l *traceLogger) Errorv(v interface{}) { func (l *traceLogger) Errorv(v interface{}) {
if shallLog(ErrorLevel) { l.err(fmt.Sprint(v))
l.write(errorLog, levelError, v) }
}
func (l *traceLogger) Errorw(msg string, fields ...LogField) {
l.err(msg, fields...)
} }
func (l *traceLogger) Info(v ...interface{}) { func (l *traceLogger) Info(v ...interface{}) {
if shallLog(InfoLevel) { l.info(fmt.Sprint(v...))
l.write(infoLog, levelInfo, fmt.Sprint(v...))
}
} }
func (l *traceLogger) Infof(format string, v ...interface{}) { func (l *traceLogger) Infof(format string, v ...interface{}) {
if shallLog(InfoLevel) { l.info(fmt.Sprintf(format, v...))
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
}
} }
func (l *traceLogger) Infov(v interface{}) { func (l *traceLogger) Infov(v interface{}) {
if shallLog(InfoLevel) { l.info(v)
l.write(infoLog, levelInfo, v) }
}
func (l *traceLogger) Infow(msg string, fields ...LogField) {
l.info(msg, fields...)
} }
func (l *traceLogger) Slow(v ...interface{}) { func (l *traceLogger) Slow(v ...interface{}) {
if shallLog(ErrorLevel) { l.slow(fmt.Sprint(v...))
l.write(slowLog, levelSlow, fmt.Sprint(v...))
}
} }
func (l *traceLogger) Slowf(format string, v ...interface{}) { func (l *traceLogger) Slowf(format string, v ...interface{}) {
if shallLog(ErrorLevel) { l.slow(fmt.Sprintf(format, v...))
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
}
} }
func (l *traceLogger) Slowv(v interface{}) { func (l *traceLogger) Slowv(v interface{}) {
if shallLog(ErrorLevel) { l.slow(v)
l.write(slowLog, levelSlow, v) }
func (l *traceLogger) Sloww(msg string, fields ...LogField) {
l.slow(msg, fields...)
}
func (l *traceLogger) WithContext(ctx context.Context) Logger {
if ctx == nil {
return l
} }
l.ctx = ctx
return l
} }
func (l *traceLogger) WithDuration(duration time.Duration) Logger { func (l *traceLogger) WithDuration(duration time.Duration) Logger {
@@ -77,31 +83,37 @@ func (l *traceLogger) WithDuration(duration time.Duration) Logger {
return l return l
} }
func (l *traceLogger) write(writer io.Writer, level string, val interface{}) { func (l *traceLogger) buildFields(fields ...LogField) []LogField {
if len(l.Duration) > 0 {
fields = append(fields, Field(durationKey, l.Duration))
}
traceID := traceIdFromContext(l.ctx) traceID := traceIdFromContext(l.ctx)
if len(traceID) > 0 {
fields = append(fields, Field(traceKey, traceID))
}
spanID := spanIdFromContext(l.ctx) spanID := spanIdFromContext(l.ctx)
if len(spanID) > 0 {
fields = append(fields, Field(spanKey, spanID))
}
switch atomic.LoadUint32(&encoding) { return fields
case plainEncodingType: }
writePlainAny(writer, level, val, l.Duration, traceID, spanID)
default: func (l *traceLogger) err(v interface{}, fields ...LogField) {
outputJson(writer, &traceLogger{ if shallLog(ErrorLevel) {
logEntry: logEntry{ getWriter().Error(v, l.buildFields(fields...)...)
Timestamp: getTimestamp(),
Level: level,
Duration: l.Duration,
Content: val,
},
Trace: traceID,
Span: spanID,
})
} }
} }
// WithContext sets ctx to log, for keeping tracing information. func (l *traceLogger) info(v interface{}, fields ...LogField) {
func WithContext(ctx context.Context) Logger { if shallLog(InfoLevel) {
return &traceLogger{ getWriter().Info(v, l.buildFields(fields...)...)
ctx: ctx, }
}
func (l *traceLogger) slow(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Slow(v, l.buildFields(fields...)...)
} }
} }

View File

@@ -2,7 +2,8 @@ package logx
import ( import (
"context" "context"
"log" "encoding/json"
"fmt"
"strings" "strings"
"sync/atomic" "sync/atomic"
"testing" "testing"
@@ -13,142 +14,195 @@ import (
sdktrace "go.opentelemetry.io/otel/sdk/trace" sdktrace "go.opentelemetry.io/otel/sdk/trace"
) )
const (
traceKey = "trace"
spanKey = "span"
)
func TestTraceLog(t *testing.T) { func TestTraceLog(t *testing.T) {
var buf mockWriter SetLevel(InfoLevel)
atomic.StoreUint32(&initialized, 1) w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider() otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample())) tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp) otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp) defer otel.SetTracerProvider(otp)
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar") ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog) defer span.End()
assert.True(t, strings.Contains(buf.String(), traceKey))
assert.True(t, strings.Contains(buf.String(), spanKey)) WithContext(ctx).Info(testlog)
validate(t, w.String(), true, true)
} }
func TestTraceError(t *testing.T) { func TestTraceError(t *testing.T) {
var buf mockWriter w := new(mockWriter)
atomic.StoreUint32(&initialized, 1) old := writer.Swap(w)
errorLog = newLogWriter(log.New(&buf, "", flags)) writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider() otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample())) tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp) otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp) defer otel.SetTracerProvider(otp)
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar") ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
l := WithContext(ctx).(*traceLogger) defer span.End()
SetLevel(InfoLevel)
var nilCtx context.Context
l := WithContext(context.Background())
l = l.WithContext(nilCtx)
l = l.WithContext(ctx)
SetLevel(ErrorLevel)
l.WithDuration(time.Second).Error(testlog) l.WithDuration(time.Second).Error(testlog)
assert.True(t, strings.Contains(buf.String(), traceKey)) validate(t, w.String(), true, true)
assert.True(t, strings.Contains(buf.String(), spanKey)) w.Reset()
buf.Reset()
l.WithDuration(time.Second).Errorf(testlog) l.WithDuration(time.Second).Errorf(testlog)
assert.True(t, strings.Contains(buf.String(), traceKey)) validate(t, w.String(), true, true)
assert.True(t, strings.Contains(buf.String(), spanKey)) w.Reset()
buf.Reset()
l.WithDuration(time.Second).Errorv(testlog) l.WithDuration(time.Second).Errorv(testlog)
assert.True(t, strings.Contains(buf.String(), traceKey)) fmt.Println(w.String())
assert.True(t, strings.Contains(buf.String(), spanKey)) validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorw(testlog, Field("foo", "bar"))
fmt.Println(w.String())
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
} }
func TestTraceInfo(t *testing.T) { func TestTraceInfo(t *testing.T) {
var buf mockWriter w := new(mockWriter)
atomic.StoreUint32(&initialized, 1) old := writer.Swap(w)
infoLog = newLogWriter(log.New(&buf, "", flags)) writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider() otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample())) tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp) otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp) defer otel.SetTracerProvider(otp)
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar") ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
l := WithContext(ctx).(*traceLogger) defer span.End()
SetLevel(InfoLevel) SetLevel(InfoLevel)
l := WithContext(ctx)
l.WithDuration(time.Second).Info(testlog) l.WithDuration(time.Second).Info(testlog)
assert.True(t, strings.Contains(buf.String(), traceKey)) validate(t, w.String(), true, true)
assert.True(t, strings.Contains(buf.String(), spanKey)) w.Reset()
buf.Reset()
l.WithDuration(time.Second).Infof(testlog) l.WithDuration(time.Second).Infof(testlog)
assert.True(t, strings.Contains(buf.String(), traceKey)) validate(t, w.String(), true, true)
assert.True(t, strings.Contains(buf.String(), spanKey)) w.Reset()
buf.Reset()
l.WithDuration(time.Second).Infov(testlog) l.WithDuration(time.Second).Infov(testlog)
assert.True(t, strings.Contains(buf.String(), traceKey)) validate(t, w.String(), true, true)
assert.True(t, strings.Contains(buf.String(), spanKey)) w.Reset()
l.WithDuration(time.Second).Infow(testlog, Field("foo", "bar"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
} }
func TestTraceInfoConsole(t *testing.T) { func TestTraceInfoConsole(t *testing.T) {
old := atomic.LoadUint32(&encoding) old := atomic.SwapUint32(&encoding, jsonEncodingType)
atomic.StoreUint32(&encoding, jsonEncodingType) defer atomic.StoreUint32(&encoding, old)
w := new(mockWriter)
o := writer.Swap(w)
writer.lock.RLock()
defer func() { defer func() {
atomic.StoreUint32(&encoding, old) writer.lock.RUnlock()
writer.Store(o)
}() }()
var buf mockWriter
atomic.StoreUint32(&initialized, 1)
infoLog = newLogWriter(log.New(&buf, "", flags))
otp := otel.GetTracerProvider() otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample())) tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp) otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp) defer otel.SetTracerProvider(otp)
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar") ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
l := WithContext(ctx).(*traceLogger) defer span.End()
l := WithContext(ctx)
SetLevel(InfoLevel) SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog) l.WithDuration(time.Second).Info(testlog)
assert.True(t, strings.Contains(buf.String(), traceIdFromContext(ctx))) validate(t, w.String(), true, true)
assert.True(t, strings.Contains(buf.String(), spanIdFromContext(ctx))) w.Reset()
buf.Reset()
l.WithDuration(time.Second).Infof(testlog) l.WithDuration(time.Second).Infof(testlog)
assert.True(t, strings.Contains(buf.String(), traceIdFromContext(ctx))) validate(t, w.String(), true, true)
assert.True(t, strings.Contains(buf.String(), spanIdFromContext(ctx))) w.Reset()
buf.Reset()
l.WithDuration(time.Second).Infov(testlog) l.WithDuration(time.Second).Infov(testlog)
assert.True(t, strings.Contains(buf.String(), traceIdFromContext(ctx))) validate(t, w.String(), true, true)
assert.True(t, strings.Contains(buf.String(), spanIdFromContext(ctx)))
} }
func TestTraceSlow(t *testing.T) { func TestTraceSlow(t *testing.T) {
var buf mockWriter w := new(mockWriter)
atomic.StoreUint32(&initialized, 1) old := writer.Swap(w)
slowLog = newLogWriter(log.New(&buf, "", flags)) writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider() otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample())) tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp) otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp) defer otel.SetTracerProvider(otp)
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar") ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
l := WithContext(ctx).(*traceLogger) defer span.End()
l := WithContext(ctx)
SetLevel(InfoLevel) SetLevel(InfoLevel)
l.WithDuration(time.Second).Slow(testlog) l.WithDuration(time.Second).Slow(testlog)
assert.True(t, strings.Contains(buf.String(), traceKey)) assert.True(t, strings.Contains(w.String(), traceKey))
assert.True(t, strings.Contains(buf.String(), spanKey)) assert.True(t, strings.Contains(w.String(), spanKey))
buf.Reset() w.Reset()
l.WithDuration(time.Second).Slowf(testlog) l.WithDuration(time.Second).Slowf(testlog)
assert.True(t, strings.Contains(buf.String(), traceKey)) fmt.Println("buf:", w.String())
assert.True(t, strings.Contains(buf.String(), spanKey)) validate(t, w.String(), true, true)
buf.Reset() w.Reset()
l.WithDuration(time.Second).Slowv(testlog) l.WithDuration(time.Second).Slowv(testlog)
assert.True(t, strings.Contains(buf.String(), traceKey)) validate(t, w.String(), true, true)
assert.True(t, strings.Contains(buf.String(), spanKey)) w.Reset()
l.WithDuration(time.Second).Sloww(testlog, Field("foo", "bar"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
} }
func TestTraceWithoutContext(t *testing.T) { func TestTraceWithoutContext(t *testing.T) {
var buf mockWriter w := new(mockWriter)
atomic.StoreUint32(&initialized, 1) old := writer.Swap(w)
infoLog = newLogWriter(log.New(&buf, "", flags)) writer.lock.RLock()
l := WithContext(context.Background()).(*traceLogger) defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
l := WithContext(context.Background())
SetLevel(InfoLevel) SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog) l.WithDuration(time.Second).Info(testlog)
assert.False(t, strings.Contains(buf.String(), traceKey)) validate(t, w.String(), false, false)
assert.False(t, strings.Contains(buf.String(), spanKey)) w.Reset()
buf.Reset()
l.WithDuration(time.Second).Infof(testlog) l.WithDuration(time.Second).Infof(testlog)
assert.False(t, strings.Contains(buf.String(), traceKey)) validate(t, w.String(), false, false)
assert.False(t, strings.Contains(buf.String(), spanKey)) }
func validate(t *testing.T, body string, expectedTrace, expectedSpan bool) {
var val mockValue
assert.Nil(t, json.Unmarshal([]byte(body), &val), body)
assert.Equal(t, expectedTrace, len(val.Trace) > 0, body)
assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
}
type mockValue struct {
Trace string `json:"trace"`
Span string `json:"span"`
} }

35
core/logx/util.go Normal file
View File

@@ -0,0 +1,35 @@
package logx
import (
"fmt"
"runtime"
"strings"
"time"
)
func getCaller(callDepth int) string {
_, file, line, ok := runtime.Caller(callDepth)
if !ok {
return ""
}
return prettyCaller(file, line)
}
func getTimestamp() string {
return time.Now().Format(timeFormat)
}
func prettyCaller(file string, line int) string {
idx := strings.LastIndexByte(file, '/')
if idx < 0 {
return fmt.Sprintf("%s:%d", file, line)
}
idx = strings.LastIndexByte(file[:idx], '/')
if idx < 0 {
return fmt.Sprintf("%s:%d", file, line)
}
return fmt.Sprintf("%s:%d", file[idx+1:], line)
}

72
core/logx/util_test.go Normal file
View File

@@ -0,0 +1,72 @@
package logx
import (
"path/filepath"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestGetCaller(t *testing.T) {
_, file, _, _ := runtime.Caller(0)
assert.Contains(t, getCaller(1), filepath.Base(file))
assert.True(t, len(getCaller(1<<10)) == 0)
}
func TestGetTimestamp(t *testing.T) {
ts := getTimestamp()
tm, err := time.Parse(timeFormat, ts)
assert.Nil(t, err)
assert.True(t, time.Since(tm) < time.Minute)
}
func TestPrettyCaller(t *testing.T) {
tests := []struct {
name string
file string
line int
want string
}{
{
name: "regular",
file: "logx_test.go",
line: 123,
want: "logx_test.go:123",
},
{
name: "relative",
file: "adhoc/logx_test.go",
line: 123,
want: "adhoc/logx_test.go:123",
},
{
name: "long path",
file: "github.com/zeromicro/go-zero/core/logx/util_test.go",
line: 12,
want: "logx/util_test.go:12",
},
{
name: "local path",
file: "/Users/kevin/go-zero/core/logx/util_test.go",
line: 1234,
want: "logx/util_test.go:1234",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.want, prettyCaller(test.file, test.line))
})
}
}
func BenchmarkGetCaller(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
getCaller(1)
}
}

61
core/logx/vars.go Normal file
View File

@@ -0,0 +1,61 @@
package logx
import "errors"
const (
// InfoLevel logs everything
InfoLevel uint32 = iota
// ErrorLevel includes errors, slows, stacks
ErrorLevel
// SevereLevel only log severe messages
SevereLevel
)
const (
jsonEncodingType = iota
plainEncodingType
jsonEncoding = "json"
plainEncoding = "plain"
plainEncodingSep = '\t'
)
const (
accessFilename = "access.log"
errorFilename = "error.log"
severeFilename = "severe.log"
slowFilename = "slow.log"
statFilename = "stat.log"
consoleMode = "console"
fileMode = "file"
volumeMode = "volume"
levelAlert = "alert"
levelInfo = "info"
levelError = "error"
levelSevere = "severe"
levelFatal = "fatal"
levelSlow = "slow"
levelStat = "stat"
backupFileDelimiter = "-"
flags = 0x0
)
const (
callerKey = "caller"
contentKey = "content"
durationKey = "duration"
levelKey = "level"
spanKey = "span"
timestampKey = "@timestamp"
traceKey = "trace"
)
var (
// ErrLogPathNotSet is an error that indicates the log path is not set.
ErrLogPathNotSet = errors.New("log path must be set")
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
ErrLogServiceNameNotSet = errors.New("log service name must be set")
)

348
core/logx/writer.go Normal file
View File

@@ -0,0 +1,348 @@
package logx
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"path"
"strings"
"sync"
"sync/atomic"
"github.com/zeromicro/go-zero/core/color"
)
type (
Writer interface {
Alert(v interface{})
Close() error
Error(v interface{}, fields ...LogField)
Info(v interface{}, fields ...LogField)
Severe(v interface{})
Slow(v interface{}, fields ...LogField)
Stack(v interface{})
Stat(v interface{}, fields ...LogField)
}
atomicWriter struct {
writer Writer
lock sync.RWMutex
}
concreteWriter struct {
infoLog io.WriteCloser
errorLog io.WriteCloser
severeLog io.WriteCloser
slowLog io.WriteCloser
statLog io.WriteCloser
stackLog io.Writer
}
)
// NewWriter creates a new Writer with the given io.Writer.
func NewWriter(w io.Writer) Writer {
lw := newLogWriter(log.New(w, "", flags))
return &concreteWriter{
infoLog: lw,
errorLog: lw,
severeLog: lw,
slowLog: lw,
statLog: lw,
stackLog: lw,
}
}
func (w *atomicWriter) Load() Writer {
w.lock.RLock()
defer w.lock.RUnlock()
return w.writer
}
func (w *atomicWriter) Store(v Writer) {
w.lock.Lock()
w.writer = v
w.lock.Unlock()
}
func (w *atomicWriter) Swap(v Writer) Writer {
w.lock.Lock()
old := w.writer
w.writer = v
w.lock.Unlock()
return old
}
func newConsoleWriter() Writer {
outLog := newLogWriter(log.New(os.Stdout, "", flags))
errLog := newLogWriter(log.New(os.Stderr, "", flags))
return &concreteWriter{
infoLog: outLog,
errorLog: errLog,
severeLog: errLog,
slowLog: errLog,
stackLog: newLessWriter(errLog, options.logStackCooldownMills),
statLog: outLog,
}
}
func newFileWriter(c LogConf) (Writer, error) {
var err error
var opts []LogOption
var infoLog io.WriteCloser
var errorLog io.WriteCloser
var severeLog io.WriteCloser
var slowLog io.WriteCloser
var statLog io.WriteCloser
var stackLog io.Writer
if len(c.Path) == 0 {
return nil, ErrLogPathNotSet
}
opts = append(opts, WithCooldownMillis(c.StackCooldownMillis))
if c.Compress {
opts = append(opts, WithGzip())
}
if c.KeepDays > 0 {
opts = append(opts, WithKeepDays(c.KeepDays))
}
accessFile := path.Join(c.Path, accessFilename)
errorFile := path.Join(c.Path, errorFilename)
severeFile := path.Join(c.Path, severeFilename)
slowFile := path.Join(c.Path, slowFilename)
statFile := path.Join(c.Path, statFilename)
handleOptions(opts)
setupLogLevel(c)
if infoLog, err = createOutput(accessFile); err != nil {
return nil, err
}
if errorLog, err = createOutput(errorFile); err != nil {
return nil, err
}
if severeLog, err = createOutput(severeFile); err != nil {
return nil, err
}
if slowLog, err = createOutput(slowFile); err != nil {
return nil, err
}
if statLog, err = createOutput(statFile); err != nil {
return nil, err
}
stackLog = newLessWriter(errorLog, options.logStackCooldownMills)
return &concreteWriter{
infoLog: infoLog,
errorLog: errorLog,
severeLog: severeLog,
slowLog: slowLog,
statLog: statLog,
stackLog: stackLog,
}, nil
}
func (w *concreteWriter) Alert(v interface{}) {
output(w.errorLog, levelAlert, v)
}
func (w *concreteWriter) Close() error {
if err := w.infoLog.Close(); err != nil {
return err
}
if err := w.errorLog.Close(); err != nil {
return err
}
if err := w.severeLog.Close(); err != nil {
return err
}
if err := w.slowLog.Close(); err != nil {
return err
}
return w.statLog.Close()
}
func (w *concreteWriter) Error(v interface{}, fields ...LogField) {
output(w.errorLog, levelError, v, fields...)
}
func (w *concreteWriter) Info(v interface{}, fields ...LogField) {
output(w.infoLog, levelInfo, v, fields...)
}
func (w *concreteWriter) Severe(v interface{}) {
output(w.severeLog, levelFatal, v)
}
func (w *concreteWriter) Slow(v interface{}, fields ...LogField) {
output(w.slowLog, levelSlow, v, fields...)
}
func (w *concreteWriter) Stack(v interface{}) {
output(w.stackLog, levelError, v)
}
func (w *concreteWriter) Stat(v interface{}, fields ...LogField) {
output(w.statLog, levelStat, v, fields...)
}
type nopWriter struct{}
func (n nopWriter) Alert(_ interface{}) {
}
func (n nopWriter) Close() error {
return nil
}
func (n nopWriter) Error(_ interface{}, _ ...LogField) {
}
func (n nopWriter) Info(_ interface{}, _ ...LogField) {
}
func (n nopWriter) Severe(_ interface{}) {
}
func (n nopWriter) Slow(_ interface{}, _ ...LogField) {
}
func (n nopWriter) Stack(_ interface{}) {
}
func (n nopWriter) Stat(_ interface{}, _ ...LogField) {
}
func buildFields(fields ...LogField) []string {
var items []string
for _, field := range fields {
items = append(items, fmt.Sprintf("%s=%v", field.Key, field.Value))
}
return items
}
func output(writer io.Writer, level string, val interface{}, fields ...LogField) {
fields = append(fields, Field(callerKey, getCaller(callerDepth)))
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainAny(writer, level, val, buildFields(fields...)...)
default:
entry := make(logEntryWithFields)
for _, field := range fields {
entry[field.Key] = field.Value
}
entry[timestampKey] = getTimestamp()
entry[levelKey] = level
entry[contentKey] = val
writeJson(writer, entry)
}
}
func wrapLevelWithColor(level string) string {
var colour color.Color
switch level {
case levelAlert:
colour = color.FgRed
case levelError:
colour = color.FgRed
case levelFatal:
colour = color.FgRed
case levelInfo:
colour = color.FgBlue
case levelSlow:
colour = color.FgYellow
case levelStat:
colour = color.FgGreen
}
if colour == color.NoColor {
return level
}
return color.WithColorPadding(level, colour)
}
func writeJson(writer io.Writer, info interface{}) {
if content, err := json.Marshal(info); err != nil {
log.Println(err.Error())
} else if writer == nil {
log.Println(string(content))
} else {
writer.Write(append(content, '\n'))
}
}
func writePlainAny(writer io.Writer, level string, val interface{}, fields ...string) {
level = wrapLevelWithColor(level)
switch v := val.(type) {
case string:
writePlainText(writer, level, v, fields...)
case error:
writePlainText(writer, level, v.Error(), fields...)
case fmt.Stringer:
writePlainText(writer, level, v.String(), fields...)
default:
var buf strings.Builder
buf.WriteString(getTimestamp())
buf.WriteByte(plainEncodingSep)
buf.WriteString(level)
buf.WriteByte(plainEncodingSep)
if err := json.NewEncoder(&buf).Encode(val); err != nil {
log.Println(err.Error())
return
}
for _, item := range fields {
buf.WriteByte(plainEncodingSep)
buf.WriteString(item)
}
buf.WriteByte('\n')
if writer == nil {
log.Println(buf.String())
return
}
if _, err := fmt.Fprint(writer, buf.String()); err != nil {
log.Println(err.Error())
}
}
}
func writePlainText(writer io.Writer, level, msg string, fields ...string) {
var buf strings.Builder
buf.WriteString(getTimestamp())
buf.WriteByte(plainEncodingSep)
buf.WriteString(level)
buf.WriteByte(plainEncodingSep)
buf.WriteString(msg)
for _, item := range fields {
buf.WriteByte(plainEncodingSep)
buf.WriteString(item)
}
buf.WriteByte('\n')
if writer == nil {
log.Println(buf.String())
return
}
if _, err := fmt.Fprint(writer, buf.String()); err != nil {
log.Println(err.Error())
}
}

179
core/logx/writer_test.go Normal file
View File

@@ -0,0 +1,179 @@
package logx
import (
"bytes"
"encoding/json"
"errors"
"log"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewWriter(t *testing.T) {
const literal = "foo bar"
var buf bytes.Buffer
w := NewWriter(&buf)
w.Info(literal)
assert.Contains(t, buf.String(), literal)
}
func TestConsoleWriter(t *testing.T) {
var buf bytes.Buffer
w := newConsoleWriter()
lw := newLogWriter(log.New(&buf, "", 0))
w.(*concreteWriter).errorLog = lw
w.Alert("foo bar 1")
var val mockedEntry
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelAlert, val.Level)
assert.Equal(t, "foo bar 1", val.Content)
buf.Reset()
w.(*concreteWriter).errorLog = lw
w.Error("foo bar 2")
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelError, val.Level)
assert.Equal(t, "foo bar 2", val.Content)
buf.Reset()
w.(*concreteWriter).infoLog = lw
w.Info("foo bar 3")
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelInfo, val.Level)
assert.Equal(t, "foo bar 3", val.Content)
buf.Reset()
w.(*concreteWriter).severeLog = lw
w.Severe("foo bar 4")
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelFatal, val.Level)
assert.Equal(t, "foo bar 4", val.Content)
buf.Reset()
w.(*concreteWriter).slowLog = lw
w.Slow("foo bar 5")
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelSlow, val.Level)
assert.Equal(t, "foo bar 5", val.Content)
buf.Reset()
w.(*concreteWriter).statLog = lw
w.Stat("foo bar 6")
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelStat, val.Level)
assert.Equal(t, "foo bar 6", val.Content)
w.(*concreteWriter).infoLog = hardToCloseWriter{}
assert.NotNil(t, w.Close())
w.(*concreteWriter).infoLog = easyToCloseWriter{}
w.(*concreteWriter).errorLog = hardToCloseWriter{}
assert.NotNil(t, w.Close())
w.(*concreteWriter).errorLog = easyToCloseWriter{}
w.(*concreteWriter).severeLog = hardToCloseWriter{}
assert.NotNil(t, w.Close())
w.(*concreteWriter).severeLog = easyToCloseWriter{}
w.(*concreteWriter).slowLog = hardToCloseWriter{}
assert.NotNil(t, w.Close())
w.(*concreteWriter).slowLog = easyToCloseWriter{}
w.(*concreteWriter).statLog = hardToCloseWriter{}
assert.NotNil(t, w.Close())
w.(*concreteWriter).statLog = easyToCloseWriter{}
}
func TestNopWriter(t *testing.T) {
assert.NotPanics(t, func() {
var w nopWriter
w.Alert("foo")
w.Error("foo")
w.Info("foo")
w.Severe("foo")
w.Stack("foo")
w.Stat("foo")
w.Slow("foo")
w.Close()
})
}
func TestWriteJson(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
writeJson(nil, "foo")
assert.Contains(t, buf.String(), "foo")
buf.Reset()
writeJson(nil, make(chan int))
assert.Contains(t, buf.String(), "unsupported type")
}
func TestWritePlainAny(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
writePlainAny(nil, levelInfo, "foo")
assert.Contains(t, buf.String(), "foo")
buf.Reset()
writePlainAny(nil, levelError, make(chan int))
assert.Contains(t, buf.String(), "unsupported type")
writePlainAny(nil, levelSlow, 100)
assert.Contains(t, buf.String(), "100")
buf.Reset()
writePlainAny(hardToWriteWriter{}, levelStat, 100)
assert.Contains(t, buf.String(), "write error")
buf.Reset()
writePlainAny(hardToWriteWriter{}, levelSevere, "foo")
assert.Contains(t, buf.String(), "write error")
buf.Reset()
writePlainAny(hardToWriteWriter{}, levelAlert, "foo")
assert.Contains(t, buf.String(), "write error")
buf.Reset()
writePlainAny(hardToWriteWriter{}, levelFatal, "foo")
assert.Contains(t, buf.String(), "write error")
}
type mockedEntry struct {
Level string `json:"level"`
Content string `json:"content"`
}
type easyToCloseWriter struct{}
func (h easyToCloseWriter) Write(_ []byte) (_ int, _ error) {
return
}
func (h easyToCloseWriter) Close() error {
return nil
}
type hardToCloseWriter struct{}
func (h hardToCloseWriter) Write(_ []byte) (_ int, _ error) {
return
}
func (h hardToCloseWriter) Close() error {
return errors.New("close error")
}
type hardToWriteWriter struct{}
func (h hardToWriteWriter) Write(_ []byte) (_ int, _ error) {
return 0, errors.New("write error")
}

View File

@@ -0,0 +1,29 @@
package mapping
import (
"bytes"
"encoding/json"
"io"
"github.com/pelletier/go-toml/v2"
)
// UnmarshalTomlBytes unmarshals TOML bytes into the given v.
func UnmarshalTomlBytes(content []byte, v interface{}) error {
return UnmarshalTomlReader(bytes.NewReader(content), v)
}
// UnmarshalTomlReader unmarshals TOML from the given io.Reader into the given v.
func UnmarshalTomlReader(r io.Reader, v interface{}) error {
var val interface{}
if err := toml.NewDecoder(r).Decode(&val); err != nil {
return err
}
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(val); err != nil {
return err
}
return UnmarshalJsonReader(&buf, v)
}

View File

@@ -0,0 +1,41 @@
package mapping
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnmarshalToml(t *testing.T) {
const input = `a = "foo"
b = 1
c = "${FOO}"
d = "abcd!@#$112"
`
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
assert.Nil(t, UnmarshalTomlBytes([]byte(input), &val))
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C)
assert.Equal(t, "abcd!@#$112", val.D)
}
func TestUnmarshalTomlErrorToml(t *testing.T) {
const input = `foo"
b = 1
c = "${FOO}"
d = "abcd!@#$112"
`
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
assert.NotNil(t, UnmarshalTomlBytes([]byte(input), &val))
}

View File

@@ -496,10 +496,20 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
return nil return nil
} }
func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.Value, mapValue interface{}) error { func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.Value,
mapValue interface{}) error {
var slice []interface{} var slice []interface{}
if err := jsonx.UnmarshalFromString(mapValue.(string), &slice); err != nil { switch v := mapValue.(type) {
return err case fmt.Stringer:
if err := jsonx.UnmarshalFromString(v.String(), &slice); err != nil {
return err
}
case string:
if err := jsonx.UnmarshalFromString(v, &slice); err != nil {
return err
}
default:
return errUnsupportedType
} }
baseFieldType := Deref(fieldType.Elem()) baseFieldType := Deref(fieldType.Elem())

View File

@@ -2777,6 +2777,36 @@ func TestUnmarshalJsonReaderComplex(t *testing.T) {
assert.Equal(t, "txt", req.Txt) assert.Equal(t, "txt", req.Txt)
} }
func TestUnmarshalJsonReaderArrayBool(t *testing.T) {
payload := `{"id": false}`
var res struct {
ID []string `json:"id"`
}
reader := strings.NewReader(payload)
err := UnmarshalJsonReader(reader, &res)
assert.NotNil(t, err)
}
func TestUnmarshalJsonReaderArrayInt(t *testing.T) {
payload := `{"id": 123}`
var res struct {
ID []string `json:"id"`
}
reader := strings.NewReader(payload)
err := UnmarshalJsonReader(reader, &res)
assert.NotNil(t, err)
}
func TestUnmarshalJsonReaderArrayString(t *testing.T) {
payload := `{"id": "123"}`
var res struct {
ID []string `json:"id"`
}
reader := strings.NewReader(payload)
err := UnmarshalJsonReader(reader, &res)
assert.NotNil(t, err)
}
func BenchmarkDefaultValue(b *testing.B) { func BenchmarkDefaultValue(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var a struct { var a struct {

View File

@@ -102,12 +102,12 @@ func ForEach(generate GenerateFunc, mapper ForEachFunc, opts ...Option) {
options := buildOptions(opts...) options := buildOptions(opts...)
panicChan := &onceChan{channel: make(chan interface{})} panicChan := &onceChan{channel: make(chan interface{})}
source := buildSource(generate, panicChan) source := buildSource(generate, panicChan)
collector := make(chan interface{}, options.workers) collector := make(chan interface{})
done := make(chan lang.PlaceholderType) done := make(chan lang.PlaceholderType)
go executeMappers(mapperContext{ go executeMappers(mapperContext{
ctx: options.ctx, ctx: options.ctx,
mapper: func(item interface{}, writer Writer) { mapper: func(item interface{}, _ Writer) {
mapper(item) mapper(item)
}, },
source: source, source: source,

View File

@@ -1,16 +1,23 @@
package proc package proc
import ( import (
"log"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
) )
func TestDumpGoroutines(t *testing.T) { func TestDumpGoroutines(t *testing.T) {
var buf strings.Builder var buf strings.Builder
log.SetOutput(&buf) w := logx.NewWriter(&buf)
o := logx.Reset()
logx.SetWriter(w)
defer func() {
logx.Reset()
logx.SetWriter(o)
}()
dumpGoroutines() dumpGoroutines()
assert.True(t, strings.Contains(buf.String(), ".dump")) assert.True(t, strings.Contains(buf.String(), ".dump"))
} }

View File

@@ -1,16 +1,24 @@
package proc package proc
import ( import (
"log"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
) )
func TestProfile(t *testing.T) { func TestProfile(t *testing.T) {
var buf strings.Builder var buf strings.Builder
log.SetOutput(&buf) w := logx.NewWriter(&buf)
o := logx.Reset()
logx.SetWriter(w)
defer func() {
logx.Reset()
logx.SetWriter(o)
}()
profiler := StartProfile() profiler := StartProfile()
// start again should not work // start again should not work
assert.NotNil(t, StartProfile()) assert.NotNil(t, StartProfile())

View File

@@ -15,7 +15,6 @@ import (
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/proc" "github.com/zeromicro/go-zero/core/proc"
"github.com/zeromicro/go-zero/core/sysx" "github.com/zeromicro/go-zero/core/sysx"
"github.com/zeromicro/go-zero/core/timex"
) )
const ( const (
@@ -47,7 +46,7 @@ func Report(msg string) {
if fn != nil { if fn != nil {
reported := lessExecutor.DoOrDiscard(func() { reported := lessExecutor.DoOrDiscard(func() {
var builder strings.Builder var builder strings.Builder
fmt.Fprintf(&builder, "%s\n", timex.Time().Format(timeFormat)) fmt.Fprintf(&builder, "%s\n", time.Now().Format(timeFormat))
if len(clusterName) > 0 { if len(clusterName) > 0 {
fmt.Fprintf(&builder, "cluster: %s\n", clusterName) fmt.Fprintf(&builder, "cluster: %s\n", clusterName)
} }

View File

@@ -123,7 +123,8 @@ func (c cacheNode) SetWithExpire(key string, val interface{}, expire time.Durati
} }
// SetWithExpireCtx sets the cache with key and v, using given expire. // SetWithExpireCtx sets the cache with key and v, using given expire.
func (c cacheNode) SetWithExpireCtx(ctx context.Context, key string, val interface{}, expire time.Duration) error { func (c cacheNode) SetWithExpireCtx(ctx context.Context, key string, val interface{},
expire time.Duration) error {
data, err := jsonx.Marshal(val) data, err := jsonx.Marshal(val)
if err != nil { if err != nil {
return err return err
@@ -145,7 +146,8 @@ func (c cacheNode) Take(val interface{}, key string, query func(val interface{})
// TakeCtx takes the result from cache first, if not found, // TakeCtx takes the result from cache first, if not found,
// query from DB and set cache using c.expiry, then return the result. // query from DB and set cache using c.expiry, then return the result.
func (c cacheNode) TakeCtx(ctx context.Context, val interface{}, key string, query func(val interface{}) error) error { func (c cacheNode) TakeCtx(ctx context.Context, val interface{}, key string,
query func(val interface{}) error) error {
return c.doTake(ctx, val, key, query, func(v interface{}) error { return c.doTake(ctx, val, key, query, func(v interface{}) error {
return c.SetCtx(ctx, key, v) return c.SetCtx(ctx, key, v)
}) })
@@ -153,13 +155,15 @@ func (c cacheNode) TakeCtx(ctx context.Context, val interface{}, key string, que
// TakeWithExpire takes the result from cache first, if not found, // TakeWithExpire takes the result from cache first, if not found,
// query from DB and set cache using given expire, then return the result. // query from DB and set cache using given expire, then return the result.
func (c cacheNode) TakeWithExpire(val interface{}, key string, query func(val interface{}, expire time.Duration) error) error { func (c cacheNode) TakeWithExpire(val interface{}, key string, query func(val interface{},
expire time.Duration) error) error {
return c.TakeWithExpireCtx(context.Background(), val, key, query) return c.TakeWithExpireCtx(context.Background(), val, key, query)
} }
// TakeWithExpireCtx takes the result from cache first, if not found, // TakeWithExpireCtx takes the result from cache first, if not found,
// query from DB and set cache using given expire, then return the result. // query from DB and set cache using given expire, then return the result.
func (c cacheNode) TakeWithExpireCtx(ctx context.Context, val interface{}, key string, query func(val interface{}, expire time.Duration) error) error { func (c cacheNode) TakeWithExpireCtx(ctx context.Context, val interface{}, key string,
query func(val interface{}, expire time.Duration) error) error {
expire := c.aroundDuration(c.expiry) expire := c.aroundDuration(c.expiry)
return c.doTake(ctx, val, key, func(v interface{}) error { return c.doTake(ctx, val, key, func(v interface{}) error {
return query(v, expire) return query(v, expire)
@@ -239,7 +243,11 @@ func (c cacheNode) doTake(ctx context.Context, v interface{}, key string,
return nil return nil
} }
// got the result from previous ongoing query // got the result from previous ongoing query.
// why not call IncrementTotal at the beginning of this function?
// because a shared error is returned, and we don't want to count.
// for example, if the db is down, the query will be failed, we count
// the shared errors with one db failure.
c.stat.IncrementTotal() c.stat.IncrementTotal()
c.stat.IncrementHit() c.stat.IncrementHit()

View File

@@ -88,7 +88,7 @@ func TestCacheNode_InvalidCache(t *testing.T) {
assert.Equal(t, miniredis.ErrKeyNotFound, err) assert.Equal(t, miniredis.ErrKeyNotFound, err)
} }
func TestCacheNode_Take(t *testing.T) { func TestCacheNode_SetWithExpire(t *testing.T) {
store, clean, err := redistest.CreateRedis() store, clean, err := redistest.CreateRedis()
assert.Nil(t, err) assert.Nil(t, err)
defer clean() defer clean()
@@ -100,8 +100,18 @@ func TestCacheNode_Take(t *testing.T) {
lock: new(sync.Mutex), lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation), unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewStat("any"), stat: NewStat("any"),
errNotFound: errTestNotFound, errNotFound: errors.New("any"),
} }
assert.NotNil(t, cn.SetWithExpire("key", make(chan int), time.Second))
}
func TestCacheNode_Take(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := NewNode(store, syncx.NewSingleFlight(), NewStat("any"), errTestNotFound,
WithExpiry(time.Second), WithNotFoundExpiry(time.Second))
var str string var str string
err = cn.Take(&str, "any", func(v interface{}) error { err = cn.Take(&str, "any", func(v interface{}) error {
*v.(*string) = "value" *v.(*string) = "value"

View File

@@ -2,7 +2,7 @@ package clickhouse
import ( import (
// imports the driver, don't remove this comment, golint requires. // imports the driver, don't remove this comment, golint requires.
_ "github.com/ClickHouse/clickhouse-go" _ "github.com/ClickHouse/clickhouse-go/v2"
"github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlx"
) )

View File

@@ -8,18 +8,35 @@ import (
"github.com/zeromicro/go-zero/core/breaker" "github.com/zeromicro/go-zero/core/breaker"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
"github.com/zeromicro/go-zero/core/trace"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
mopt "go.mongodb.org/mongo-driver/mongo/options" mopt "go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/mongo/driver/session" "go.mongodb.org/mongo-driver/x/mongo/driver/session"
"go.opentelemetry.io/otel"
tracesdk "go.opentelemetry.io/otel/trace"
) )
const ( const (
defaultSlowThreshold = time.Millisecond * 500 defaultSlowThreshold = time.Millisecond * 500
// spanName is the span name of the mongo calls. // spanName is the span name of the mongo calls.
spanName = "mongo" spanName = "mongo"
// mongodb method names
aggregate = "Aggregate"
bulkWrite = "BulkWrite"
countDocuments = "CountDocuments"
deleteMany = "DeleteMany"
deleteOne = "DeleteOne"
distinct = "Distinct"
estimatedDocumentCount = "EstimatedDocumentCount"
find = "Find"
findOne = "FindOne"
findOneAndDelete = "FindOneAndDelete"
findOneAndReplace = "FindOneAndReplace"
findOneAndUpdate = "FindOneAndUpdate"
insertMany = "InsertMany"
insertOne = "InsertOne"
replaceOne = "ReplaceOne"
updateByID = "UpdateByID"
updateMany = "UpdateMany"
updateOne = "UpdateOne"
) )
// ErrNotFound is an alias of mongo.ErrNoDocuments // ErrNotFound is an alias of mongo.ErrNoDocuments
@@ -120,341 +137,397 @@ func newCollection(collection *mongo.Collection, brk breaker.Breaker) Collection
func (c *decoratedCollection) Aggregate(ctx context.Context, pipeline interface{}, func (c *decoratedCollection) Aggregate(ctx context.Context, pipeline interface{},
opts ...*mopt.AggregateOptions) (cur *mongo.Cursor, err error) { opts ...*mopt.AggregateOptions) (cur *mongo.Cursor, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, aggregate)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
starTime := timex.Now() starTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple("Aggregate", starTime, err) c.logDurationSimple(ctx, aggregate, starTime, err)
}() }()
cur, err = c.Collection.Aggregate(ctx, pipeline, opts...) cur, err = c.Collection.Aggregate(ctx, pipeline, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) BulkWrite(ctx context.Context, models []mongo.WriteModel, func (c *decoratedCollection) BulkWrite(ctx context.Context, models []mongo.WriteModel,
opts ...*mopt.BulkWriteOptions) (res *mongo.BulkWriteResult, err error) { opts ...*mopt.BulkWriteOptions) (res *mongo.BulkWriteResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, bulkWrite)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple("BulkWrite", startTime, err) c.logDurationSimple(ctx, bulkWrite, startTime, err)
}() }()
res, err = c.Collection.BulkWrite(ctx, models, opts...) res, err = c.Collection.BulkWrite(ctx, models, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) CountDocuments(ctx context.Context, filter interface{}, func (c *decoratedCollection) CountDocuments(ctx context.Context, filter interface{},
opts ...*mopt.CountOptions) (count int64, err error) { opts ...*mopt.CountOptions) (count int64, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, countDocuments)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple("CountDocuments", startTime, err) c.logDurationSimple(ctx, countDocuments, startTime, err)
}() }()
count, err = c.Collection.CountDocuments(ctx, filter, opts...) count, err = c.Collection.CountDocuments(ctx, filter, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) DeleteMany(ctx context.Context, filter interface{}, func (c *decoratedCollection) DeleteMany(ctx context.Context, filter interface{},
opts ...*mopt.DeleteOptions) (res *mongo.DeleteResult, err error) { opts ...*mopt.DeleteOptions) (res *mongo.DeleteResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, deleteMany)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple("DeleteMany", startTime, err) c.logDurationSimple(ctx, deleteMany, startTime, err)
}() }()
res, err = c.Collection.DeleteMany(ctx, filter, opts...) res, err = c.Collection.DeleteMany(ctx, filter, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) DeleteOne(ctx context.Context, filter interface{}, func (c *decoratedCollection) DeleteOne(ctx context.Context, filter interface{},
opts ...*mopt.DeleteOptions) (res *mongo.DeleteResult, err error) { opts ...*mopt.DeleteOptions) (res *mongo.DeleteResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, deleteOne)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration("DeleteOne", startTime, err, filter) c.logDuration(ctx, deleteOne, startTime, err, filter)
}() }()
res, err = c.Collection.DeleteOne(ctx, filter, opts...) res, err = c.Collection.DeleteOne(ctx, filter, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) Distinct(ctx context.Context, fieldName string, filter interface{}, func (c *decoratedCollection) Distinct(ctx context.Context, fieldName string, filter interface{},
opts ...*mopt.DistinctOptions) (val []interface{}, err error) { opts ...*mopt.DistinctOptions) (val []interface{}, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, distinct)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple("Distinct", startTime, err) c.logDurationSimple(ctx, distinct, startTime, err)
}() }()
val, err = c.Collection.Distinct(ctx, fieldName, filter, opts...) val, err = c.Collection.Distinct(ctx, fieldName, filter, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) EstimatedDocumentCount(ctx context.Context, func (c *decoratedCollection) EstimatedDocumentCount(ctx context.Context,
opts ...*mopt.EstimatedDocumentCountOptions) (val int64, err error) { opts ...*mopt.EstimatedDocumentCountOptions) (val int64, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, estimatedDocumentCount)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple("EstimatedDocumentCount", startTime, err) c.logDurationSimple(ctx, estimatedDocumentCount, startTime, err)
}() }()
val, err = c.Collection.EstimatedDocumentCount(ctx, opts...) val, err = c.Collection.EstimatedDocumentCount(ctx, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) Find(ctx context.Context, filter interface{}, func (c *decoratedCollection) Find(ctx context.Context, filter interface{},
opts ...*mopt.FindOptions) (cur *mongo.Cursor, err error) { opts ...*mopt.FindOptions) (cur *mongo.Cursor, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, find)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration("Find", startTime, err, filter) c.logDuration(ctx, find, startTime, err, filter)
}() }()
cur, err = c.Collection.Find(ctx, filter, opts...) cur, err = c.Collection.Find(ctx, filter, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) FindOne(ctx context.Context, filter interface{}, func (c *decoratedCollection) FindOne(ctx context.Context, filter interface{},
opts ...*mopt.FindOneOptions) (res *mongo.SingleResult, err error) { opts ...*mopt.FindOneOptions) (res *mongo.SingleResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, findOne)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration("FindOne", startTime, err, filter) c.logDuration(ctx, findOne, startTime, err, filter)
}() }()
res = c.Collection.FindOne(ctx, filter, opts...) res = c.Collection.FindOne(ctx, filter, opts...)
err = res.Err() err = res.Err()
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) FindOneAndDelete(ctx context.Context, filter interface{}, func (c *decoratedCollection) FindOneAndDelete(ctx context.Context, filter interface{},
opts ...*mopt.FindOneAndDeleteOptions) (res *mongo.SingleResult, err error) { opts ...*mopt.FindOneAndDeleteOptions) (res *mongo.SingleResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, findOneAndDelete)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration("FindOneAndDelete", startTime, err, filter) c.logDuration(ctx, findOneAndDelete, startTime, err, filter)
}() }()
res = c.Collection.FindOneAndDelete(ctx, filter, opts...) res = c.Collection.FindOneAndDelete(ctx, filter, opts...)
err = res.Err() err = res.Err()
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) FindOneAndReplace(ctx context.Context, filter interface{}, func (c *decoratedCollection) FindOneAndReplace(ctx context.Context, filter interface{},
replacement interface{}, opts ...*mopt.FindOneAndReplaceOptions) ( replacement interface{}, opts ...*mopt.FindOneAndReplaceOptions) (
res *mongo.SingleResult, err error) { res *mongo.SingleResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, findOneAndReplace)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration("FindOneAndReplace", startTime, err, filter, replacement) c.logDuration(ctx, findOneAndReplace, startTime, err, filter, replacement)
}() }()
res = c.Collection.FindOneAndReplace(ctx, filter, replacement, opts...) res = c.Collection.FindOneAndReplace(ctx, filter, replacement, opts...)
err = res.Err() err = res.Err()
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{}, func (c *decoratedCollection) FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{},
opts ...*mopt.FindOneAndUpdateOptions) (res *mongo.SingleResult, err error) { opts ...*mopt.FindOneAndUpdateOptions) (res *mongo.SingleResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, findOneAndUpdate)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration("FindOneAndUpdate", startTime, err, filter, update) c.logDuration(ctx, findOneAndUpdate, startTime, err, filter, update)
}() }()
res = c.Collection.FindOneAndUpdate(ctx, filter, update, opts...) res = c.Collection.FindOneAndUpdate(ctx, filter, update, opts...)
err = res.Err() err = res.Err()
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) InsertMany(ctx context.Context, documents []interface{}, func (c *decoratedCollection) InsertMany(ctx context.Context, documents []interface{},
opts ...*mopt.InsertManyOptions) (res *mongo.InsertManyResult, err error) { opts ...*mopt.InsertManyOptions) (res *mongo.InsertManyResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, insertMany)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple("InsertMany", startTime, err) c.logDurationSimple(ctx, insertMany, startTime, err)
}() }()
res, err = c.Collection.InsertMany(ctx, documents, opts...) res, err = c.Collection.InsertMany(ctx, documents, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) InsertOne(ctx context.Context, document interface{}, func (c *decoratedCollection) InsertOne(ctx context.Context, document interface{},
opts ...*mopt.InsertOneOptions) (res *mongo.InsertOneResult, err error) { opts ...*mopt.InsertOneOptions) (res *mongo.InsertOneResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, insertOne)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration("InsertOne", startTime, err, document) c.logDuration(ctx, insertOne, startTime, err, document)
}() }()
res, err = c.Collection.InsertOne(ctx, document, opts...) res, err = c.Collection.InsertOne(ctx, document, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter interface{}, replacement interface{}, func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter interface{}, replacement interface{},
opts ...*mopt.ReplaceOptions) (res *mongo.UpdateResult, err error) { opts ...*mopt.ReplaceOptions) (res *mongo.UpdateResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, replaceOne)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration("ReplaceOne", startTime, err, filter, replacement) c.logDuration(ctx, replaceOne, startTime, err, filter, replacement)
}() }()
res, err = c.Collection.ReplaceOne(ctx, filter, replacement, opts...) res, err = c.Collection.ReplaceOne(ctx, filter, replacement, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) UpdateByID(ctx context.Context, id interface{}, update interface{}, func (c *decoratedCollection) UpdateByID(ctx context.Context, id interface{}, update interface{},
opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) { opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, updateByID)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration("UpdateByID", startTime, err, id, update) c.logDuration(ctx, updateByID, startTime, err, id, update)
}() }()
res, err = c.Collection.UpdateByID(ctx, id, update, opts...) res, err = c.Collection.UpdateByID(ctx, id, update, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) UpdateMany(ctx context.Context, filter interface{}, update interface{}, func (c *decoratedCollection) UpdateMany(ctx context.Context, filter interface{}, update interface{},
opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) { opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, updateMany)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple("UpdateMany", startTime, err) c.logDurationSimple(ctx, updateMany, startTime, err)
}() }()
res, err = c.Collection.UpdateMany(ctx, filter, update, opts...) res, err = c.Collection.UpdateMany(ctx, filter, update, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) UpdateOne(ctx context.Context, filter interface{}, update interface{}, func (c *decoratedCollection) UpdateOne(ctx context.Context, filter interface{}, update interface{},
opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) { opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, updateOne)
defer span.End() defer func() {
endSpan(span, err)
}()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptable(func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration("UpdateOne", startTime, err, filter, update) c.logDuration(ctx, updateOne, startTime, err, filter, update)
}() }()
res, err = c.Collection.UpdateOne(ctx, filter, update, opts...) res, err = c.Collection.UpdateOne(ctx, filter, update, opts...)
return err return err
}, acceptable) }, acceptable)
return return
} }
func (c *decoratedCollection) logDuration(method string, startTime time.Duration, err error, func (c *decoratedCollection) logDuration(ctx context.Context, method string, startTime time.Duration, err error,
docs ...interface{}) { docs ...interface{}) {
duration := timex.Since(startTime) duration := timex.Since(startTime)
logger := logx.WithContext(ctx).WithDuration(duration)
content, e := json.Marshal(docs) content, e := json.Marshal(docs)
if e != nil { if e != nil {
logx.Error(err) logger.Error(err)
} else if err != nil { } else if err != nil {
if duration > slowThreshold.Load() { if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s", logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s",
c.name, method, err.Error(), string(content)) c.name, method, err.Error(), string(content))
} else { } else {
logx.WithDuration(duration).Infof("mongo(%s) - %s - fail(%s) - %s", logger.Infof("mongo(%s) - %s - fail(%s) - %s",
c.name, method, err.Error(), string(content)) c.name, method, err.Error(), string(content))
} }
} else { } else {
if duration > slowThreshold.Load() { if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s", logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
c.name, method, string(content)) c.name, method, string(content))
} else { } else {
logx.WithDuration(duration).Infof("mongo(%s) - %s - ok - %s", c.name, method, string(content)) logger.Infof("mongo(%s) - %s - ok - %s", c.name, method, string(content))
} }
} }
} }
func (c *decoratedCollection) logDurationSimple(method string, startTime time.Duration, err error) { func (c *decoratedCollection) logDurationSimple(ctx context.Context, method string, startTime time.Duration, err error) {
logDuration(c.name, method, startTime, err) logDuration(ctx, c.name, method, startTime, err)
} }
func (p keepablePromise) accept(err error) error { func (p keepablePromise) accept(err error) error {
@@ -483,8 +556,3 @@ func acceptable(err error) bool {
err == session.ErrAbortTwice || err == session.ErrCommitAfterAbort || err == session.ErrAbortTwice || err == session.ErrCommitAfterAbort ||
err == session.ErrUnackWCUnsupported || err == session.ErrSnapshotTransaction err == session.ErrUnackWCUnsupported || err == session.ErrSnapshotTransaction
} }
func startSpan(ctx context.Context) (context.Context, tracesdk.Span) {
tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
return tracer.Start(ctx, spanName)
}

View File

@@ -3,7 +3,9 @@ package mon
import ( import (
"context" "context"
"errors" "errors"
"strings"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/breaker" "github.com/zeromicro/go-zero/core/breaker"
@@ -567,6 +569,44 @@ func TestCollection_UpdateMany(t *testing.T) {
}) })
} }
func Test_DecoratedCollectionLogDuration(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
defer mt.Close()
c := decoratedCollection{
Collection: mt.Coll,
brk: breaker.NewBreaker(),
}
var buf strings.Builder
w := logx.NewWriter(&buf)
o := logx.Reset()
logx.SetWriter(w)
defer func() {
logx.Reset()
logx.SetWriter(o)
}()
buf.Reset()
c.logDuration(context.Background(), "foo", time.Millisecond, nil, "bar")
assert.Contains(t, buf.String(), "foo")
assert.Contains(t, buf.String(), "bar")
buf.Reset()
c.logDuration(context.Background(), "foo", time.Millisecond, errors.New("bar"), make(chan int))
assert.Contains(t, buf.String(), "bar")
buf.Reset()
c.logDuration(context.Background(), "foo", slowThreshold.Load()+time.Millisecond, errors.New("bar"))
assert.Contains(t, buf.String(), "foo")
assert.Contains(t, buf.String(), "slowcall")
buf.Reset()
c.logDuration(context.Background(), "foo", slowThreshold.Load()+time.Millisecond, nil)
assert.Contains(t, buf.String(), "foo")
assert.Contains(t, buf.String(), "slowcall")
}
type mockPromise struct { type mockPromise struct {
accepted bool accepted bool
reason string reason string
@@ -590,15 +630,15 @@ func (d *dropBreaker) Allow() (breaker.Promise, error) {
return nil, errDummy return nil, errDummy
} }
func (d *dropBreaker) Do(req func() error) error { func (d *dropBreaker) Do(_ func() error) error {
return nil return nil
} }
func (d *dropBreaker) DoWithAcceptable(req func() error, acceptable breaker.Acceptable) error { func (d *dropBreaker) DoWithAcceptable(_ func() error, _ breaker.Acceptable) error {
return errDummy return errDummy
} }
func (d *dropBreaker) DoWithFallback(req func() error, fallback func(err error) error) error { func (d *dropBreaker) DoWithFallback(_ func() error, _ func(err error) error) error {
return nil return nil
} }

View File

@@ -11,6 +11,14 @@ import (
mopt "go.mongodb.org/mongo-driver/mongo/options" mopt "go.mongodb.org/mongo-driver/mongo/options"
) )
const (
startSession = "StartSession"
abortTransaction = "AbortTransaction"
commitTransaction = "CommitTransaction"
withTransaction = "WithTransaction"
endSession = "EndSession"
)
type ( type (
// Model is a mongodb store model that represents a collection. // Model is a mongodb store model that represents a collection.
Model struct { Model struct {
@@ -23,7 +31,8 @@ type (
wrappedSession struct { wrappedSession struct {
mongo.Session mongo.Session
brk breaker.Breaker name string
brk breaker.Breaker
} }
) )
@@ -66,7 +75,7 @@ func (m *Model) StartSession(opts ...*mopt.SessionOptions) (sess mongo.Session,
err = m.brk.DoWithAcceptable(func() error { err = m.brk.DoWithAcceptable(func() error {
starTime := timex.Now() starTime := timex.Now()
defer func() { defer func() {
logDuration(m.name, "StartSession", starTime, err) logDuration(context.Background(), m.name, startSession, starTime, err)
}() }()
session, sessionErr := m.cli.StartSession(opts...) session, sessionErr := m.cli.StartSession(opts...)
@@ -76,11 +85,13 @@ func (m *Model) StartSession(opts ...*mopt.SessionOptions) (sess mongo.Session,
sess = &wrappedSession{ sess = &wrappedSession{
Session: session, Session: session,
name: m.name,
brk: m.brk, brk: m.brk,
} }
return nil return nil
}, acceptable) }, acceptable)
return return
} }
@@ -169,33 +180,57 @@ func (m *Model) FindOneAndUpdate(ctx context.Context, v, filter interface{}, upd
return res.Decode(v) return res.Decode(v)
} }
func (w *wrappedSession) AbortTransaction(ctx context.Context) error { // AbortTransaction implements the mongo.Session interface.
ctx, span := startSpan(ctx) func (w *wrappedSession) AbortTransaction(ctx context.Context) (err error) {
defer span.End() ctx, span := startSpan(ctx, abortTransaction)
defer func() {
endSpan(span, err)
}()
return w.brk.DoWithAcceptable(func() error { return w.brk.DoWithAcceptable(func() error {
starTime := timex.Now()
defer func() {
logDuration(ctx, w.name, abortTransaction, starTime, err)
}()
return w.Session.AbortTransaction(ctx) return w.Session.AbortTransaction(ctx)
}, acceptable) }, acceptable)
} }
func (w *wrappedSession) CommitTransaction(ctx context.Context) error { // CommitTransaction implements the mongo.Session interface.
ctx, span := startSpan(ctx) func (w *wrappedSession) CommitTransaction(ctx context.Context) (err error) {
defer span.End() ctx, span := startSpan(ctx, commitTransaction)
defer func() {
endSpan(span, err)
}()
return w.brk.DoWithAcceptable(func() error { return w.brk.DoWithAcceptable(func() error {
starTime := timex.Now()
defer func() {
logDuration(ctx, w.name, commitTransaction, starTime, err)
}()
return w.Session.CommitTransaction(ctx) return w.Session.CommitTransaction(ctx)
}, acceptable) }, acceptable)
} }
// WithTransaction implements the mongo.Session interface.
func (w *wrappedSession) WithTransaction( func (w *wrappedSession) WithTransaction(
ctx context.Context, ctx context.Context,
fn func(sessCtx mongo.SessionContext) (interface{}, error), fn func(sessCtx mongo.SessionContext) (interface{}, error),
opts ...*mopt.TransactionOptions, opts ...*mopt.TransactionOptions,
) (res interface{}, err error) { ) (res interface{}, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, withTransaction)
defer span.End() defer func() {
endSpan(span, err)
}()
err = w.brk.DoWithAcceptable(func() error { err = w.brk.DoWithAcceptable(func() error {
starTime := timex.Now()
defer func() {
logDuration(ctx, w.name, withTransaction, starTime, err)
}()
res, err = w.Session.WithTransaction(ctx, fn, opts...) res, err = w.Session.WithTransaction(ctx, fn, opts...)
return err return err
}, acceptable) }, acceptable)
@@ -203,11 +238,20 @@ func (w *wrappedSession) WithTransaction(
return return
} }
// EndSession implements the mongo.Session interface.
func (w *wrappedSession) EndSession(ctx context.Context) { func (w *wrappedSession) EndSession(ctx context.Context) {
ctx, span := startSpan(ctx) var err error
defer span.End() ctx, span := startSpan(ctx, endSession)
defer func() {
endSpan(span, err)
}()
err = w.brk.DoWithAcceptable(func() error {
starTime := timex.Now()
defer func() {
logDuration(ctx, w.name, endSession, starTime, err)
}()
_ = w.brk.DoWithAcceptable(func() error {
w.Session.EndSession(ctx) w.Session.EndSession(ctx)
return nil return nil
}, acceptable) }, acceptable)

37
core/stores/mon/trace.go Normal file
View File

@@ -0,0 +1,37 @@
package mon
import (
"context"
"github.com/zeromicro/go-zero/core/trace"
"go.mongodb.org/mongo-driver/mongo"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
oteltrace "go.opentelemetry.io/otel/trace"
)
var mongoCmdAttributeKey = attribute.Key("mongo.cmd")
func startSpan(ctx context.Context, cmd string) (context.Context, oteltrace.Span) {
tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
ctx, span := tracer.Start(ctx,
spanName,
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
span.SetAttributes(mongoCmdAttributeKey.String(cmd))
return ctx, span
}
func endSpan(span oteltrace.Span, err error) {
defer span.End()
if err == nil || err == mongo.ErrNoDocuments ||
err == mongo.ErrNilValue || err == mongo.ErrNilDocument {
span.SetStatus(codes.Ok, "")
return
}
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
}

View File

@@ -1,6 +1,7 @@
package mon package mon
import ( import (
"context"
"strings" "strings"
"time" "time"
@@ -15,11 +16,12 @@ func FormatAddr(hosts []string) string {
return strings.Join(hosts, mongoAddrSep) return strings.Join(hosts, mongoAddrSep)
} }
func logDuration(name, method string, startTime time.Duration, err error) { func logDuration(ctx context.Context, name, method string, startTime time.Duration, err error) {
duration := timex.Since(startTime) duration := timex.Since(startTime)
logger := logx.WithContext(ctx).WithDuration(duration)
if err != nil { if err != nil {
logx.WithDuration(duration).Infof("mongo(%s) - %s - fail(%s)", name, method, err.Error()) logger.Infof("mongo(%s) - %s - fail(%s)", name, method, err.Error())
} else { } else {
logx.WithDuration(duration).Infof("mongo(%s) - %s - ok", name, method) logger.Infof("mongo(%s) - %s - ok", name, method)
} }
} }

View File

@@ -1,9 +1,14 @@
package mon package mon
import ( import (
"context"
"errors"
"strings"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
) )
func TestFormatAddrs(t *testing.T) { func TestFormatAddrs(t *testing.T) {
@@ -33,3 +38,26 @@ func TestFormatAddrs(t *testing.T) {
assert.Equal(t, test.expect, FormatAddr(test.addrs)) assert.Equal(t, test.expect, FormatAddr(test.addrs))
} }
} }
func Test_logDuration(t *testing.T) {
var buf strings.Builder
w := logx.NewWriter(&buf)
o := logx.Reset()
logx.SetWriter(w)
defer func() {
logx.Reset()
logx.SetWriter(o)
}()
buf.Reset()
logDuration(context.Background(), "foo", "bar", time.Millisecond, nil)
assert.Contains(t, buf.String(), "foo")
assert.Contains(t, buf.String(), "bar")
buf.Reset()
logDuration(context.Background(), "foo", "bar", time.Millisecond, errors.New("bar"))
assert.Contains(t, buf.String(), "foo")
assert.Contains(t, buf.String(), "bar")
assert.Contains(t, buf.String(), "fail")
}

View File

@@ -2,7 +2,9 @@ package mongo
import ( import (
"errors" "errors"
"strings"
"testing" "testing"
"time"
"github.com/globalsign/mgo" "github.com/globalsign/mgo"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
@@ -266,6 +268,46 @@ func TestCollectionUpsert(t *testing.T) {
assert.Equal(t, errDummy, err) assert.Equal(t, errDummy, err)
} }
func Test_logDuration(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
col := internal.NewMockMgoCollection(ctrl)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
var buf strings.Builder
w := logx.NewWriter(&buf)
o := logx.Reset()
logx.SetWriter(w)
defer func() {
logx.Reset()
logx.SetWriter(o)
}()
buf.Reset()
c.logDuration("foo", time.Millisecond, nil, "bar")
assert.Contains(t, buf.String(), "foo")
assert.Contains(t, buf.String(), "bar")
buf.Reset()
c.logDuration("foo", time.Millisecond, errors.New("bar"), make(chan int))
assert.Contains(t, buf.String(), "bar")
buf.Reset()
c.logDuration("foo", slowThreshold.Load()+time.Millisecond, errors.New("bar"))
assert.Contains(t, buf.String(), "bar")
assert.Contains(t, buf.String(), "slowcall")
buf.Reset()
c.logDuration("foo", slowThreshold.Load()+time.Millisecond, nil)
assert.Contains(t, buf.String(), "foo")
assert.Contains(t, buf.String(), "slowcall")
}
type mockPromise struct { type mockPromise struct {
accepted bool accepted bool
reason string reason string

View File

@@ -6,35 +6,40 @@ import (
"time" "time"
red "github.com/go-redis/redis/v8" red "github.com/go-redis/redis/v8"
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mapping" "github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
"github.com/zeromicro/go-zero/core/trace" "github.com/zeromicro/go-zero/core/trace"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
tracestd "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
oteltrace "go.opentelemetry.io/otel/trace"
) )
// spanName is the span name of the redis calls. // spanName is the span name of the redis calls.
const spanName = "redis" const spanName = "redis"
var ( var (
startTimeKey = contextKey("startTime") startTimeKey = contextKey("startTime")
durationHook = hook{tracer: otel.GetTracerProvider().Tracer(trace.TraceName)} durationHook = hook{tracer: otel.GetTracerProvider().Tracer(trace.TraceName)}
redisCmdsAttributeKey = attribute.Key("redis.cmds")
) )
type ( type (
contextKey string contextKey string
hook struct { hook struct {
tracer tracestd.Tracer tracer oteltrace.Tracer
} }
) )
func (h hook) BeforeProcess(ctx context.Context, _ red.Cmder) (context.Context, error) { func (h hook) BeforeProcess(ctx context.Context, cmd red.Cmder) (context.Context, error) {
return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now())), nil return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now()), cmd), nil
} }
func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error { func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
h.endSpan(ctx) err := cmd.Err()
h.endSpan(ctx, err)
val := ctx.Value(startTimeKey) val := ctx.Value(startTimeKey)
if val == nil { if val == nil {
@@ -54,17 +59,30 @@ func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
return nil return nil
} }
func (h hook) BeforeProcessPipeline(ctx context.Context, _ []red.Cmder) (context.Context, error) { func (h hook) BeforeProcessPipeline(ctx context.Context, cmds []red.Cmder) (context.Context, error) {
return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now())), nil if len(cmds) == 0 {
return ctx, nil
}
return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now()), cmds...), nil
} }
func (h hook) AfterProcessPipeline(ctx context.Context, cmds []red.Cmder) error { func (h hook) AfterProcessPipeline(ctx context.Context, cmds []red.Cmder) error {
h.endSpan(ctx)
if len(cmds) == 0 { if len(cmds) == 0 {
return nil return nil
} }
batchError := errorx.BatchError{}
for _, cmd := range cmds {
err := cmd.Err()
if err == nil {
continue
}
batchError.Add(err)
}
h.endSpan(ctx, batchError.Err())
val := ctx.Value(startTimeKey) val := ctx.Value(startTimeKey)
if val == nil { if val == nil {
return nil return nil
@@ -94,11 +112,30 @@ func logDuration(ctx context.Context, cmd red.Cmder, duration time.Duration) {
logx.WithContext(ctx).WithDuration(duration).Slowf("[REDIS] slowcall on executing: %s", buf.String()) logx.WithContext(ctx).WithDuration(duration).Slowf("[REDIS] slowcall on executing: %s", buf.String())
} }
func (h hook) startSpan(ctx context.Context) context.Context { func (h hook) startSpan(ctx context.Context, cmds ...red.Cmder) context.Context {
ctx, _ = h.tracer.Start(ctx, spanName) ctx, span := h.tracer.Start(ctx,
spanName,
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
cmdStrs := make([]string, 0, len(cmds))
for _, cmd := range cmds {
cmdStrs = append(cmdStrs, cmd.Name())
}
span.SetAttributes(redisCmdsAttributeKey.StringSlice(cmdStrs))
return ctx return ctx
} }
func (h hook) endSpan(ctx context.Context) { func (h hook) endSpan(ctx context.Context, err error) {
tracestd.SpanFromContext(ctx).End() span := oteltrace.SpanFromContext(ctx)
defer span.End()
if err == nil || err == red.Nil {
span.SetStatus(codes.Ok, "")
return
}
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
} }

View File

@@ -9,6 +9,7 @@ import (
red "github.com/go-redis/redis/v8" red "github.com/go-redis/redis/v8"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
ztrace "github.com/zeromicro/go-zero/core/trace" ztrace "github.com/zeromicro/go-zero/core/trace"
tracesdk "go.opentelemetry.io/otel/trace" tracesdk "go.opentelemetry.io/otel/trace"
) )
@@ -26,7 +27,7 @@ func TestHookProcessCase1(t *testing.T) {
log.SetOutput(&buf) log.SetOutput(&buf)
defer log.SetOutput(writer) defer log.SetOutput(writer)
ctx, err := durationHook.BeforeProcess(context.Background(), nil) ctx, err := durationHook.BeforeProcess(context.Background(), red.NewCmd(context.Background()))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -44,12 +45,10 @@ func TestHookProcessCase2(t *testing.T) {
Sampler: 1.0, Sampler: 1.0,
}) })
writer := log.Writer() w, restore := injectLog()
var buf strings.Builder defer restore()
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx, err := durationHook.BeforeProcess(context.Background(), nil) ctx, err := durationHook.BeforeProcess(context.Background(), red.NewCmd(context.Background()))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -58,9 +57,9 @@ func TestHookProcessCase2(t *testing.T) {
time.Sleep(slowThreshold.Load() + time.Millisecond) time.Sleep(slowThreshold.Load() + time.Millisecond)
assert.Nil(t, durationHook.AfterProcess(ctx, red.NewCmd(context.Background(), "foo", "bar"))) assert.Nil(t, durationHook.AfterProcess(ctx, red.NewCmd(context.Background(), "foo", "bar")))
assert.True(t, strings.Contains(buf.String(), "slow")) assert.True(t, strings.Contains(w.String(), "slow"))
assert.True(t, strings.Contains(buf.String(), "trace")) assert.True(t, strings.Contains(w.String(), "trace"))
assert.True(t, strings.Contains(buf.String(), "span")) assert.True(t, strings.Contains(w.String(), "span"))
} }
func TestHookProcessCase3(t *testing.T) { func TestHookProcessCase3(t *testing.T) {
@@ -90,7 +89,7 @@ func TestHookProcessPipelineCase1(t *testing.T) {
log.SetOutput(&buf) log.SetOutput(&buf)
defer log.SetOutput(writer) defer log.SetOutput(writer)
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), nil) ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{red.NewCmd(context.Background())})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -110,12 +109,10 @@ func TestHookProcessPipelineCase2(t *testing.T) {
Sampler: 1.0, Sampler: 1.0,
}) })
writer := log.Writer() w, restore := injectLog()
var buf strings.Builder defer restore()
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), nil) ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{red.NewCmd(context.Background())})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -126,34 +123,30 @@ func TestHookProcessPipelineCase2(t *testing.T) {
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{ assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
red.NewCmd(context.Background(), "foo", "bar"), red.NewCmd(context.Background(), "foo", "bar"),
})) }))
assert.True(t, strings.Contains(buf.String(), "slow")) assert.True(t, strings.Contains(w.String(), "slow"))
assert.True(t, strings.Contains(buf.String(), "trace")) assert.True(t, strings.Contains(w.String(), "trace"))
assert.True(t, strings.Contains(buf.String(), "span")) assert.True(t, strings.Contains(w.String(), "span"))
} }
func TestHookProcessPipelineCase3(t *testing.T) { func TestHookProcessPipelineCase3(t *testing.T) {
writer := log.Writer() w, restore := injectLog()
var buf strings.Builder defer restore()
log.SetOutput(&buf)
defer log.SetOutput(writer)
assert.Nil(t, durationHook.AfterProcessPipeline(context.Background(), []red.Cmder{ assert.Nil(t, durationHook.AfterProcessPipeline(context.Background(), []red.Cmder{
red.NewCmd(context.Background()), red.NewCmd(context.Background()),
})) }))
assert.True(t, buf.Len() == 0) assert.True(t, len(w.String()) == 0)
} }
func TestHookProcessPipelineCase4(t *testing.T) { func TestHookProcessPipelineCase4(t *testing.T) {
writer := log.Writer() w, restore := injectLog()
var buf strings.Builder defer restore()
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx := context.WithValue(context.Background(), startTimeKey, "foo") ctx := context.WithValue(context.Background(), startTimeKey, "foo")
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{ assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
red.NewCmd(context.Background()), red.NewCmd(context.Background()),
})) }))
assert.True(t, buf.Len() == 0) assert.True(t, len(w.String()) == 0)
} }
func TestHookProcessPipelineCase5(t *testing.T) { func TestHookProcessPipelineCase5(t *testing.T) {
@@ -163,6 +156,18 @@ func TestHookProcessPipelineCase5(t *testing.T) {
defer log.SetOutput(writer) defer log.SetOutput(writer)
ctx := context.WithValue(context.Background(), startTimeKey, "foo") ctx := context.WithValue(context.Background(), startTimeKey, "foo")
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, nil)) assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{red.NewCmd(context.Background())}))
assert.True(t, buf.Len() == 0) assert.True(t, buf.Len() == 0)
} }
func injectLog() (r *strings.Builder, restore func()) {
var buf strings.Builder
w := logx.NewWriter(&buf)
o := logx.Reset()
logx.SetWriter(w)
return &buf, func() {
logx.Reset()
logx.SetWriter(o)
}
}

View File

@@ -97,7 +97,8 @@ func (cc CachedConn) Exec(exec ExecFn, keys ...string) (sql.Result, error) {
} }
// ExecCtx runs given exec on given keys, and returns execution result. // ExecCtx runs given exec on given keys, and returns execution result.
func (cc CachedConn) ExecCtx(ctx context.Context, exec ExecCtxFn, keys ...string) (sql.Result, error) { func (cc CachedConn) ExecCtx(ctx context.Context, exec ExecCtxFn, keys ...string) (
sql.Result, error) {
res, err := exec(ctx, cc.db) res, err := exec(ctx, cc.db)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -6,9 +6,6 @@ import (
"github.com/zeromicro/go-zero/core/breaker" "github.com/zeromicro/go-zero/core/breaker"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/trace"
"go.opentelemetry.io/otel"
tracesdk "go.opentelemetry.io/otel/trace"
) )
// spanName is used to identify the span name for the SQL execution. // spanName is used to identify the span name for the SQL execution.
@@ -140,8 +137,10 @@ func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result,
func (db *commonSqlConn) ExecCtx(ctx context.Context, q string, args ...interface{}) ( func (db *commonSqlConn) ExecCtx(ctx context.Context, q string, args ...interface{}) (
result sql.Result, err error) { result sql.Result, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "Exec")
defer span.End() defer func() {
endSpan(span, err)
}()
err = db.brk.DoWithAcceptable(func() error { err = db.brk.DoWithAcceptable(func() error {
var conn *sql.DB var conn *sql.DB
@@ -163,8 +162,10 @@ func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
} }
func (db *commonSqlConn) PrepareCtx(ctx context.Context, query string) (stmt StmtSession, err error) { func (db *commonSqlConn) PrepareCtx(ctx context.Context, query string) (stmt StmtSession, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "Prepare")
defer span.End() defer func() {
endSpan(span, err)
}()
err = db.brk.DoWithAcceptable(func() error { err = db.brk.DoWithAcceptable(func() error {
var conn *sql.DB var conn *sql.DB
@@ -194,9 +195,11 @@ func (db *commonSqlConn) QueryRow(v interface{}, q string, args ...interface{})
} }
func (db *commonSqlConn) QueryRowCtx(ctx context.Context, v interface{}, q string, func (db *commonSqlConn) QueryRowCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error { args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRow")
defer span.End() defer func() {
endSpan(span, err)
}()
return db.queryRows(ctx, func(rows *sql.Rows) error { return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, true) return unmarshalRow(v, rows, true)
@@ -208,9 +211,11 @@ func (db *commonSqlConn) QueryRowPartial(v interface{}, q string, args ...interf
} }
func (db *commonSqlConn) QueryRowPartialCtx(ctx context.Context, v interface{}, func (db *commonSqlConn) QueryRowPartialCtx(ctx context.Context, v interface{},
q string, args ...interface{}) error { q string, args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRowPartial")
defer span.End() defer func() {
endSpan(span, err)
}()
return db.queryRows(ctx, func(rows *sql.Rows) error { return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, false) return unmarshalRow(v, rows, false)
@@ -222,9 +227,11 @@ func (db *commonSqlConn) QueryRows(v interface{}, q string, args ...interface{})
} }
func (db *commonSqlConn) QueryRowsCtx(ctx context.Context, v interface{}, q string, func (db *commonSqlConn) QueryRowsCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error { args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRows")
defer span.End() defer func() {
endSpan(span, err)
}()
return db.queryRows(ctx, func(rows *sql.Rows) error { return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, true) return unmarshalRows(v, rows, true)
@@ -236,9 +243,11 @@ func (db *commonSqlConn) QueryRowsPartial(v interface{}, q string, args ...inter
} }
func (db *commonSqlConn) QueryRowsPartialCtx(ctx context.Context, v interface{}, func (db *commonSqlConn) QueryRowsPartialCtx(ctx context.Context, v interface{},
q string, args ...interface{}) error { q string, args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRowsPartial")
defer span.End() defer func() {
endSpan(span, err)
}()
return db.queryRows(ctx, func(rows *sql.Rows) error { return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, false) return unmarshalRows(v, rows, false)
@@ -255,9 +264,11 @@ func (db *commonSqlConn) Transact(fn func(Session) error) error {
}) })
} }
func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Context, Session) error) error { func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Context, Session) error) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "Transact")
defer span.End() defer func() {
endSpan(span, err)
}()
return db.brk.DoWithAcceptable(func() error { return db.brk.DoWithAcceptable(func() error {
return transact(ctx, db, db.beginTx, fn) return transact(ctx, db, db.beginTx, fn)
@@ -274,10 +285,7 @@ func (db *commonSqlConn) acceptable(err error) bool {
} }
func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error, func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error,
q string, args ...interface{}) error { q string, args ...interface{}) (err error) {
ctx, span := startSpan(ctx)
defer span.End()
var qerr error var qerr error
return db.brk.DoWithAcceptable(func() error { return db.brk.DoWithAcceptable(func() error {
conn, err := db.connProv() conn, err := db.connProv()
@@ -303,9 +311,11 @@ func (s statement) Exec(args ...interface{}) (sql.Result, error) {
return s.ExecCtx(context.Background(), args...) return s.ExecCtx(context.Background(), args...)
} }
func (s statement) ExecCtx(ctx context.Context, args ...interface{}) (sql.Result, error) { func (s statement) ExecCtx(ctx context.Context, args ...interface{}) (result sql.Result, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "Exec")
defer span.End() defer func() {
endSpan(span, err)
}()
return execStmt(ctx, s.stmt, s.query, args...) return execStmt(ctx, s.stmt, s.query, args...)
} }
@@ -314,9 +324,11 @@ func (s statement) QueryRow(v interface{}, args ...interface{}) error {
return s.QueryRowCtx(context.Background(), v, args...) return s.QueryRowCtx(context.Background(), v, args...)
} }
func (s statement) QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) error { func (s statement) QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRow")
defer span.End() defer func() {
endSpan(span, err)
}()
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error { return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, true) return unmarshalRow(v, rows, true)
@@ -327,9 +339,11 @@ func (s statement) QueryRowPartial(v interface{}, args ...interface{}) error {
return s.QueryRowPartialCtx(context.Background(), v, args...) return s.QueryRowPartialCtx(context.Background(), v, args...)
} }
func (s statement) QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error { func (s statement) QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRowPartial")
defer span.End() defer func() {
endSpan(span, err)
}()
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error { return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, false) return unmarshalRow(v, rows, false)
@@ -340,9 +354,11 @@ func (s statement) QueryRows(v interface{}, args ...interface{}) error {
return s.QueryRowsCtx(context.Background(), v, args...) return s.QueryRowsCtx(context.Background(), v, args...)
} }
func (s statement) QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) error { func (s statement) QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRows")
defer span.End() defer func() {
endSpan(span, err)
}()
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error { return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, true) return unmarshalRows(v, rows, true)
@@ -353,16 +369,13 @@ func (s statement) QueryRowsPartial(v interface{}, args ...interface{}) error {
return s.QueryRowsPartialCtx(context.Background(), v, args...) return s.QueryRowsPartialCtx(context.Background(), v, args...)
} }
func (s statement) QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error { func (s statement) QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRowsPartial")
defer span.End() defer func() {
endSpan(span, err)
}()
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error { return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, false) return unmarshalRows(v, rows, false)
}, s.query, args...) }, s.query, args...)
} }
func startSpan(ctx context.Context) (context.Context, tracesdk.Span) {
tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
return tracer.Start(ctx, spanName)
}

37
core/stores/sqlx/trace.go Normal file
View File

@@ -0,0 +1,37 @@
package sqlx
import (
"context"
"database/sql"
"github.com/zeromicro/go-zero/core/trace"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
oteltrace "go.opentelemetry.io/otel/trace"
)
var sqlAttributeKey = attribute.Key("sql.method")
func startSpan(ctx context.Context, method string) (context.Context, oteltrace.Span) {
tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
start, span := tracer.Start(ctx,
spanName,
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
span.SetAttributes(sqlAttributeKey.String(method))
return start, span
}
func endSpan(span oteltrace.Span, err error) {
defer span.End()
if err == nil || err == sql.ErrNoRows {
span.SetStatus(codes.Ok, "")
return
}
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
}

View File

@@ -30,20 +30,26 @@ func (t txSession) Exec(q string, args ...interface{}) (sql.Result, error) {
return t.ExecCtx(context.Background(), q, args...) return t.ExecCtx(context.Background(), q, args...)
} }
func (t txSession) ExecCtx(ctx context.Context, q string, args ...interface{}) (sql.Result, error) { func (t txSession) ExecCtx(ctx context.Context, q string, args ...interface{}) (result sql.Result, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "Exec")
defer span.End() defer func() {
endSpan(span, err)
}()
return exec(ctx, t.Tx, q, args...) result, err = exec(ctx, t.Tx, q, args...)
return
} }
func (t txSession) Prepare(q string) (StmtSession, error) { func (t txSession) Prepare(q string) (StmtSession, error) {
return t.PrepareCtx(context.Background(), q) return t.PrepareCtx(context.Background(), q)
} }
func (t txSession) PrepareCtx(ctx context.Context, q string) (StmtSession, error) { func (t txSession) PrepareCtx(ctx context.Context, q string) (stmtSession StmtSession, err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "Prepare")
defer span.End() defer func() {
endSpan(span, err)
}()
stmt, err := t.Tx.PrepareContext(ctx, q) stmt, err := t.Tx.PrepareContext(ctx, q)
if err != nil { if err != nil {
@@ -60,9 +66,11 @@ func (t txSession) QueryRow(v interface{}, q string, args ...interface{}) error
return t.QueryRowCtx(context.Background(), v, q, args...) return t.QueryRowCtx(context.Background(), v, q, args...)
} }
func (t txSession) QueryRowCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error { func (t txSession) QueryRowCtx(ctx context.Context, v interface{}, q string, args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRow")
defer span.End() defer func() {
endSpan(span, err)
}()
return query(ctx, t.Tx, func(rows *sql.Rows) error { return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, true) return unmarshalRow(v, rows, true)
@@ -74,9 +82,11 @@ func (t txSession) QueryRowPartial(v interface{}, q string, args ...interface{})
} }
func (t txSession) QueryRowPartialCtx(ctx context.Context, v interface{}, q string, func (t txSession) QueryRowPartialCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error { args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRowPartial")
defer span.End() defer func() {
endSpan(span, err)
}()
return query(ctx, t.Tx, func(rows *sql.Rows) error { return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, false) return unmarshalRow(v, rows, false)
@@ -87,9 +97,11 @@ func (t txSession) QueryRows(v interface{}, q string, args ...interface{}) error
return t.QueryRowsCtx(context.Background(), v, q, args...) return t.QueryRowsCtx(context.Background(), v, q, args...)
} }
func (t txSession) QueryRowsCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error { func (t txSession) QueryRowsCtx(ctx context.Context, v interface{}, q string, args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRows")
defer span.End() defer func() {
endSpan(span, err)
}()
return query(ctx, t.Tx, func(rows *sql.Rows) error { return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, true) return unmarshalRows(v, rows, true)
@@ -101,9 +113,11 @@ func (t txSession) QueryRowsPartial(v interface{}, q string, args ...interface{}
} }
func (t txSession) QueryRowsPartialCtx(ctx context.Context, v interface{}, q string, func (t txSession) QueryRowsPartialCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error { args ...interface{}) (err error) {
ctx, span := startSpan(ctx) ctx, span := startSpan(ctx, "QueryRowsPartial")
defer span.End() defer func() {
endSpan(span, err)
}()
return query(ctx, t.Tx, func(rows *sql.Rows) error { return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, false) return unmarshalRows(v, rows, false)

View File

@@ -103,7 +103,8 @@ func Reverse(s string) string {
return string(runes) return string(runes)
} }
// Substr returns runes between start and stop [start, stop) regardless of the chars are ascii or utf8. // Substr returns runes between start and stop [start, stop)
// regardless of the chars are ascii or utf8.
func Substr(str string, start, stop int) (string, error) { func Substr(str string, start, stop int) (string, error) {
rs := []rune(str) rs := []rune(str)
length := len(rs) length := len(rs)

View File

@@ -15,8 +15,3 @@ func Now() time.Duration {
func Since(d time.Duration) time.Duration { func Since(d time.Duration) time.Duration {
return time.Since(initTime) - d return time.Since(initTime) - d
} }
// Time returns current time, the same as time.Now().
func Time() time.Time {
return initTime.Add(Now())
}

View File

@@ -15,11 +15,18 @@ func TestRelativeTime(t *testing.T) {
assert.True(t, Since(now) > 0) assert.True(t, Since(now) > 0)
} }
func TestRelativeTime_Time(t *testing.T) { func BenchmarkTimeSince(b *testing.B) {
diff := time.Until(Time()) b.ReportAllocs()
if diff > 0 {
assert.True(t, diff < time.Second) for i := 0; i < b.N; i++ {
} else { _ = time.Since(time.Now())
assert.True(t, -diff < time.Second) }
}
func BenchmarkTimexSince(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = Since(Now())
} }
} }

View File

@@ -67,7 +67,7 @@ func (ft *fakeTicker) Stop() {
} }
func (ft *fakeTicker) Tick() { func (ft *fakeTicker) Tick() {
ft.c <- Time() ft.c <- time.Now()
} }
func (ft *fakeTicker) Wait(d time.Duration) error { func (ft *fakeTicker) Wait(d time.Duration) error {

58
go.mod
View File

@@ -1,62 +1,56 @@
module github.com/zeromicro/go-zero module github.com/zeromicro/go-zero
go 1.15 go 1.16
require ( require (
github.com/ClickHouse/clickhouse-go v1.5.1 github.com/ClickHouse/clickhouse-go/v2 v2.0.14
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/alicebob/miniredis/v2 v2.17.0 github.com/alicebob/miniredis/v2 v2.21.0
github.com/fatih/color v1.13.0
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
github.com/go-redis/redis/v8 v8.11.4 github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.2.0 github.com/golang-jwt/jwt/v4 v4.4.1
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/justinas/alice v1.2.0 github.com/justinas/alice v1.2.0
github.com/lib/pq v1.10.4 github.com/lib/pq v1.10.6
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/prometheus/client_golang v1.11.0 github.com/pelletier/go-toml/v2 v2.0.1
github.com/prometheus/client_golang v1.12.2
github.com/spaolacci/murmur3 v1.1.0 github.com/spaolacci/murmur3 v1.1.0
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.1
go.etcd.io/etcd/api/v3 v3.5.2 go.etcd.io/etcd/api/v3 v3.5.4
go.etcd.io/etcd/client/v3 v3.5.2 go.etcd.io/etcd/client/v3 v3.5.4
go.mongodb.org/mongo-driver v1.9.0 go.mongodb.org/mongo-driver v1.9.1
go.opentelemetry.io/otel v1.3.0 go.opentelemetry.io/otel v1.7.0
go.opentelemetry.io/otel/exporters/jaeger v1.3.0 go.opentelemetry.io/otel/exporters/jaeger v1.7.0
go.opentelemetry.io/otel/exporters/zipkin v1.3.0 go.opentelemetry.io/otel/exporters/zipkin v1.7.0
go.opentelemetry.io/otel/sdk v1.3.0 go.opentelemetry.io/otel/sdk v1.7.0
go.opentelemetry.io/otel/trace v1.3.0 go.opentelemetry.io/otel/trace v1.7.0
go.uber.org/automaxprocs v1.4.0 go.uber.org/automaxprocs v1.5.1
go.uber.org/goleak v1.1.12 go.uber.org/goleak v1.1.12
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 golang.org/x/time v0.0.0-20220411224347-583f2d630306
google.golang.org/grpc v1.46.0 google.golang.org/grpc v1.46.2
google.golang.org/protobuf v1.28.0 google.golang.org/protobuf v1.28.0
gopkg.in/cheggaaa/pb.v1 v1.0.28 gopkg.in/cheggaaa/pb.v1 v1.0.28
gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/h2non/gock.v1 v1.1.2
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.20.12 k8s.io/api v0.22.9
k8s.io/apimachinery v0.20.12 k8s.io/apimachinery v0.22.9
k8s.io/client-go v0.20.12 k8s.io/client-go v0.22.9
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
) )
require ( require (
github.com/fatih/color v1.10.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/openzipkin/zipkin-go v0.4.0 // indirect
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect go.uber.org/zap v1.21.0 // indirect
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 // indirect golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731 // indirect google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731 // indirect
k8s.io/klog/v2 v2.40.1 // indirect k8s.io/klog/v2 v2.40.1 // indirect
) )

232
go.sum
View File

@@ -32,18 +32,18 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/clickhouse-go v1.5.1 h1:I8zVFZTz80crCs0FFEBJooIxsPcV0xfthzK1YrkpJTc= github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0=
github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/ClickHouse/clickhouse-go/v2 v2.0.14 h1:7HW+MXPaQfVyCzPGEn/LciMc8K6cG58FZMUc7DXQmro=
github.com/ClickHouse/clickhouse-go/v2 v2.0.14/go.mod h1:iq2DUGgpA4BBki2CVwrF8x43zqBjdgHtbexkFkh5a6M=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
@@ -51,6 +51,7 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs= github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -58,8 +59,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.17.0 h1:EwLdrIS50uczw71Jc7iVSxZluTKj5nfSP8n7ARRnJy0= github.com/alicebob/miniredis/v2 v2.21.0 h1:CdmwIlKUWFBDS+4464GtQiQ0R1vpzOgu4Vnd74rBL7M=
github.com/alicebob/miniredis/v2 v2.17.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I= github.com/alicebob/miniredis/v2 v2.21.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
@@ -68,7 +69,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -78,7 +78,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -96,10 +95,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@@ -115,17 +112,16 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
@@ -139,23 +135,19 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -166,13 +158,13 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -204,6 +196,7 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -214,8 +207,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
@@ -229,6 +223,7 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -236,11 +231,15 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
@@ -248,10 +247,10 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslC
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
@@ -284,19 +283,19 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
@@ -305,6 +304,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -319,6 +320,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@@ -326,35 +328,47 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/openzipkin/zipkin-go v0.4.0 h1:CtfRrOVZtbDj8rt1WXjklw0kqqJQwICrCKmlfUuBUUw= github.com/openzipkin/zipkin-go v0.4.0 h1:CtfRrOVZtbDj8rt1WXjklw0kqqJQwICrCKmlfUuBUUw=
github.com/openzipkin/zipkin-go v0.4.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ= github.com/openzipkin/zipkin-go v0.4.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
github.com/paulmach/orb v0.5.0 h1:sNhJV5ML+mv1F077ljOck/9inorF4ahDO8iNNpHbKHY=
github.com/paulmach/orb v0.5.0/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -363,8 +377,8 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@@ -378,6 +392,11 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -389,19 +408,22 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@@ -416,37 +438,37 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
go.etcd.io/etcd/api/v3 v3.5.2 h1:tXok5yLlKyuQ/SXSjtqHc4uzNaMqZi2XsoSPr/LlJXI= go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc=
go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.2 h1:4hzqQ6hIb3blLyQ8usCU4h3NghkqcsohEQ3o3VetYxE= go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg=
go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.2 h1:WdnejrUtQC4nCxK0/dLTMqKOB+U5TP/2Ya0BJL+1otA= go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
go.mongodb.org/mongo-driver v1.9.0 h1:f3aLGJvQmBl8d9S40IL+jEyBC6hfLPbJjv9t5hEM9ck= go.mongodb.org/mongo-driver v1.9.1 h1:m078y9v7sBItkt1aaoe2YlvWEXcD263e1a4E1fBrJ1c=
go.mongodb.org/mongo-driver v1.9.0/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y= go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
go.opentelemetry.io/otel/exporters/jaeger v1.3.0 h1:HfydzioALdtcB26H5WHc4K47iTETJCdloL7VN579/L0= go.opentelemetry.io/otel/exporters/jaeger v1.7.0 h1:wXgjiRldljksZkZrldGVe6XrG9u3kYDyQmkZwmm5dI0=
go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc= go.opentelemetry.io/otel/exporters/jaeger v1.7.0/go.mod h1:PwQAOqBgqbLQRKlj466DuD2qyMjbtcPpfPfj+AqbSBs=
go.opentelemetry.io/otel/exporters/zipkin v1.3.0 h1:uOD28dZ7yIKITTcUS6MeAGNHYy3uhP7DTkhcJM6onlQ= go.opentelemetry.io/otel/exporters/zipkin v1.7.0 h1:X0FZj+kaIdLi29UiyrEGDhRTYsEXj9GdEW5Y39UQFEE=
go.opentelemetry.io/otel/exporters/zipkin v1.3.0/go.mod h1:LxGGfHIYbvsFnrJtBcazb0yG24xHdDGrT/H6RB9r3+8= go.opentelemetry.io/otel/exporters/zipkin v1.7.0/go.mod h1:9YBXeOMFLQGwNEjsxMRiWPGoJX83usGMhbCmxUbNe5I=
go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0=
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU=
go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY= go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0= go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
@@ -460,12 +482,12 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8= golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -500,7 +522,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -534,12 +555,12 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8= golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8=
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -547,9 +568,8 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -574,7 +594,6 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -583,6 +602,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -597,12 +617,12 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -614,11 +634,16 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 h1:Js08h5hqB5xyWR789+QqueR6sDE8mk+YvpETZ+F6X9Y=
golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -633,9 +658,9 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -647,7 +672,6 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -711,9 +735,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -744,6 +767,7 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731 h1:nquqdM9+ps0JZcIiI70+tqoaIFS5Ql4ZuK8UXnz3HfE= google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731 h1:nquqdM9+ps0JZcIiI70+tqoaIFS5Ql4ZuK8UXnz3HfE=
google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
@@ -764,8 +788,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -785,6 +809,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
@@ -807,6 +832,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -816,26 +842,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.20.12 h1:LfRpmRkJLwPP8eaYehsVVmIIfg1yCBIIUHaSsdqCgHA= k8s.io/api v0.22.9 h1:PidjRtgd0zDa6SvyooBLH/SP62uOhEBY0kx0UYRGr1o=
k8s.io/api v0.20.12/go.mod h1:A2brwyEkVLM3wQGNnzoAa5JsQRzHK0uoOQ+bsnv7V68= k8s.io/api v0.22.9/go.mod h1:rcjO/FPOuvc3x7nQWx29UcDrFJMx82RxDob71ntNH4A=
k8s.io/apimachinery v0.20.12 h1:2c0LIVNMvB8k2Ozstmhl2zGeCEcPazznuLYEwxFdNjM= k8s.io/apimachinery v0.22.9 h1:5qjnpBk6eC9me0SAzokCUMI0KVF2PENK1PnykF8/Gjo=
k8s.io/apimachinery v0.20.12/go.mod h1:uM7hCI0NyBymUwgshMgZyte475lxhr+QH6h3cvdnzEc= k8s.io/apimachinery v0.22.9/go.mod h1:ZvVLP5iLhwVFg2Yx9Gh5W0um0DUauExbRhe+2Z8I1EU=
k8s.io/client-go v0.20.12 h1:U75SxTC31BHT9i7CbX/hL4v+U1Wkzy/E1vt5ClDPp3I= k8s.io/client-go v0.22.9 h1:5p2R2LsoBfaE6QnXfWFmyyvxrFXtfegUGRMZSpTI+Q8=
k8s.io/client-go v0.20.12/go.mod h1:NBJj6Evp73Xy/4v/O/RDRaH0+3JoxNfjRxkyRgrdbsA= k8s.io/client-go v0.22.9/go.mod h1:IoH7exYnoH/zgvHOuVxh2c4yJepcCBt72FzCTisOc4k=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4= k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4=
k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc=
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -1,4 +1,4 @@
<img align="right" width="150px" src="https://gitee.com/kevwan/static/raw/master/doc/images/go-zero.png"> <img align="right" width="150px" src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/go-zero.png">
# go-zero # go-zero
@@ -11,15 +11,19 @@
[![goproxy](https://goproxy.cn/stats/github.com/tal-tech/go-zero/badges/download-count.svg)](https://goproxy.cn/stats/github.com/tal-tech/go-zero/badges/download-count.svg) [![goproxy](https://goproxy.cn/stats/github.com/tal-tech/go-zero/badges/download-count.svg)](https://goproxy.cn/stats/github.com/tal-tech/go-zero/badges/download-count.svg)
[![codecov](https://codecov.io/gh/zeromicro/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/zeromicro/go-zero) [![codecov](https://codecov.io/gh/zeromicro/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/zeromicro/go-zero)
[![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero) [![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero)
[![Go Reference](https://pkg.go.dev/badge/github.com/zeromicro/go-zero.svg)](https://pkg.go.dev/github.com/zeromicro/go-zero)
[![Awesome Go](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/avelino/awesome-go)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
> ***注意:*** > ***注意:***
> >
> 从 v1.3.0 之前版本升级请执行以下命令: > 从 v1.3.0 之前版本升级请执行以下命令:
> >
> `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest` > `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
> >
> `goctl migrate —verbose —version v1.3.2` > `goctl migrate —verbose —version v1.3.3`
## 0. go-zero 介绍 ## 0. go-zero 介绍
@@ -88,9 +92,13 @@ go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有
![弹性设计](https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/resilience.jpg) ![弹性设计](https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/resilience.jpg)
## 4. 我们使用 go-zero 的基本架构图
<img width="973" alt="image" src="https://user-images.githubusercontent.com/1918356/170813549-f6a40438-c8c3-4c66-9348-bb85f96b91f5.png">
觉得不错的话,别忘 **star** 👏 觉得不错的话,别忘 **star** 👏
## 4. Installation ## 5. Installation
在项目目录下通过如下命令安装: 在项目目录下通过如下命令安装:
@@ -98,7 +106,7 @@ go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro/go-zero GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro/go-zero
``` ```
## 5. Quick Start ## 6. Quick Start
0. 完整示例请查看 0. 完整示例请查看
@@ -116,6 +124,9 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
# Go 1.16 及以后版本 # Go 1.16 及以后版本
GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest
# For Mac
brew install goctl
# docker for amd64 architecture # docker for amd64 architecture
docker pull kevinwan/goctl docker pull kevinwan/goctl
@@ -171,13 +182,13 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
... ...
``` ```
## 6. Benchmark ## 7. Benchmark
![benchmark](https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/benchmark.png) ![benchmark](https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/benchmark.png)
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark) [测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
## 7. 文档 ## 8. 文档
* API 文档 * API 文档
@@ -198,7 +209,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
| [goctl-android](https://github.com/zeromicro/goctl-android) | 生成 `java (android)` 端 `http client` 请求代码 | | [goctl-android](https://github.com/zeromicro/goctl-android) | 生成 `java (android)` 端 `http client` 请求代码 |
| [goctl-go-compact](https://github.com/zeromicro/goctl-go-compact) | 合并 `api` 里同一个 `group` 里的 `handler` 到一个 `go` 文件 | | [goctl-go-compact](https://github.com/zeromicro/goctl-go-compact) | 合并 `api` 里同一个 `group` 里的 `handler` 到一个 `go` 文件 |
## 8. go-zero 用户 ## 9. go-zero 用户
go-zero 已被许多公司用于生产部署,接入场景如在线教育、电商业务、游戏、区块链等,目前为止,已使用 go-zero 的公司包括但不限于: go-zero 已被许多公司用于生产部署,接入场景如在线教育、电商业务、游戏、区块链等,目前为止,已使用 go-zero 的公司包括但不限于:
@@ -263,10 +274,19 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
>59. 费芮网络 >59. 费芮网络
>60. 51CTO >60. 51CTO
>61. 聿旌科技 >61. 聿旌科技
>62. 山东胜软科技股份有限公司
>63. 上海芯果科技有限公司(好特卖)
>64. 成都高鹿科技有限公司
>65. 飞视(苏州)数字技术有限公司
>66. 上海幻析信息科技有限公司
>67. 统信软件技术有限公司
>68. 得物
>69. 鼎翰文化股份有限公司
>70. 茶码纹化(云南)科技发展有限公司
如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。 如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
## 9. CNCF 云原生技术全景图 ## 10. CNCF 云原生技术全景图
<p float="left"> <p float="left">
<img src="https://landscape.cncf.io/images/left-logo.svg" width="150"/>&nbsp;&nbsp;&nbsp; <img src="https://landscape.cncf.io/images/left-logo.svg" width="150"/>&nbsp;&nbsp;&nbsp;
@@ -275,13 +295,13 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape.cncf.io/?selected=go-zero)。 go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape.cncf.io/?selected=go-zero)。
## 10. 微信公众号 ## 11. 微信公众号
`go-zero` 相关文章和视频都会在 `微服务实践` 公众号整理呈现,欢迎扫码关注 👏 `go-zero` 相关文章和视频都会在 `微服务实践` 公众号整理呈现,欢迎扫码关注 👏
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/zeromicro.jpg" alt="wechat" width="600" /> <img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/zeromicro.jpg" alt="wechat" width="600" />
## 11. 微信交流群 ## 12. 微信交流群
如果文档中未能覆盖的任何疑问,欢迎您在群里提出,我们会尽快答复。 如果文档中未能覆盖的任何疑问,欢迎您在群里提出,我们会尽快答复。
@@ -293,7 +313,7 @@ go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" /> <img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
## 12. 赞助一下👍 ## 13. 赞助一下👍
如果觉得项目有帮助,可以请作者喝杯咖啡 🍹 如果觉得项目有帮助,可以请作者喝杯咖啡 🍹

View File

@@ -8,8 +8,11 @@ English | [简体中文](readme-cn.md)
[![codecov](https://codecov.io/gh/zeromicro/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/zeromicro/go-zero) [![codecov](https://codecov.io/gh/zeromicro/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/zeromicro/go-zero)
[![Go Report Card](https://goreportcard.com/badge/github.com/zeromicro/go-zero)](https://goreportcard.com/report/github.com/zeromicro/go-zero) [![Go Report Card](https://goreportcard.com/badge/github.com/zeromicro/go-zero)](https://goreportcard.com/report/github.com/zeromicro/go-zero)
[![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero) [![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero)
[![Go Reference](https://pkg.go.dev/badge/github.com/zeromicro/go-zero.svg)](https://pkg.go.dev/github.com/zeromicro/go-zero)
[![Awesome Go](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/avelino/awesome-go)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Discord](https://img.shields.io/discord/794530774463414292?label=chat&logo=discord)](https://discord.gg/4JQvC5A4Fe) [![Discord](https://img.shields.io/discord/794530774463414292?label=chat&logo=discord)](https://discord.gg/4JQvC5A4Fe)
<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
> ***Important!*** > ***Important!***
@@ -18,7 +21,7 @@ English | [简体中文](readme-cn.md)
> >
> `go install github.com/zeromicro/go-zero/tools/goctl@latest` > `go install github.com/zeromicro/go-zero/tools/goctl@latest`
> >
> `goctl migrate —verbose —version v1.3.2` > `goctl migrate —verbose —version v1.3.3`
## 0. what is go-zero ## 0. what is go-zero
@@ -88,10 +91,9 @@ As below, go-zero protects the system with a couple of layers and mechanisms:
![Resilience](https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/resilience-en.png) ![Resilience](https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/resilience-en.png)
## 4. Future development plans of go-zero ## 4. The simplified architecture that we use with go-zero
* auto-generate API mock server, make the client debugging easier <img width="973" alt="image" src="https://user-images.githubusercontent.com/1918356/170813500-035e13f0-9775-4dc5-a33f-cd9d389219d1.png">
* auto-generate the simple integration test for the server-side just from the .api files
## 5. Installation ## 5. Installation
@@ -119,6 +121,9 @@ go get -u github.com/zeromicro/go-zero
# for Go 1.16 and later # for Go 1.16 and later
go install github.com/zeromicro/go-zero/tools/goctl@latest go install github.com/zeromicro/go-zero/tools/goctl@latest
# For Mac
brew install goctl
# docker for amd64 architecture # docker for amd64 architecture
docker pull kevinwan/goctl docker pull kevinwan/goctl
@@ -230,7 +235,7 @@ go get -u github.com/zeromicro/go-zero
[Checkout the test code](https://github.com/smallnest/go-web-framework-benchmark) [Checkout the test code](https://github.com/smallnest/go-web-framework-benchmark)
## 8. Documents (adding) ## 8. Documents
* [Documents](https://go-zero.dev/en/) * [Documents](https://go-zero.dev/en/)
* [Rapid development of microservice systems](https://github.com/zeromicro/zero-doc/blob/main/doc/shorturl-en.md) * [Rapid development of microservice systems](https://github.com/zeromicro/zero-doc/blob/main/doc/shorturl-en.md)

View File

@@ -242,7 +242,7 @@ func (ng *engine) start(router httpx.Router) error {
} }
if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 { if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 {
return internal.StartHttp(ng.conf.Host, ng.conf.Port, router) return internal.StartHttp(ng.conf.Host, ng.conf.Port, router, ng.withTimeout())
} }
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile, return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
@@ -250,13 +250,29 @@ func (ng *engine) start(router httpx.Router) error {
if ng.tlsConfig != nil { if ng.tlsConfig != nil {
svr.TLSConfig = ng.tlsConfig svr.TLSConfig = ng.tlsConfig
} }
}) }, ng.withTimeout())
} }
func (ng *engine) use(middleware Middleware) { func (ng *engine) use(middleware Middleware) {
ng.middlewares = append(ng.middlewares, middleware) ng.middlewares = append(ng.middlewares, middleware)
} }
func (ng *engine) withTimeout() internal.StartOption {
return func(svr *http.Server) {
timeout := ng.conf.Timeout
if timeout > 0 {
// factor 0.8, to avoid clients send longer content-length than the actual content,
// without this timeout setting, the server will time out and respond 503 Service Unavailable,
// which triggers the circuit breaker.
svr.ReadTimeout = 4 * time.Duration(timeout) * time.Millisecond / 5
// factor 0.9, to avoid clients not reading the response
// without this timeout setting, the server will time out and respond 503 Service Unavailable,
// which triggers the circuit breaker.
svr.WriteTimeout = 9 * time.Duration(timeout) * time.Millisecond / 10
}
}
}
func convertMiddleware(ware Middleware) func(http.Handler) http.Handler { func convertMiddleware(ware Middleware) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return ware(next.ServeHTTP) return ware(next.ServeHTTP)

View File

@@ -146,7 +146,7 @@ Verbose: true
for _, yaml := range yamls { for _, yaml := range yamls {
for _, route := range routes { for _, route := range routes {
var cnf RestConf var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(yaml), &cnf)) assert.Nil(t, conf.LoadFromYamlBytes([]byte(yaml), &cnf))
ng := newEngine(cnf) ng := newEngine(cnf)
ng.addRoutes(route) ng.addRoutes(route)
ng.use(func(next http.HandlerFunc) http.HandlerFunc { ng.use(func(next http.HandlerFunc) http.HandlerFunc {
@@ -298,17 +298,48 @@ func TestEngine_notFoundHandlerNotNilWriteHeader(t *testing.T) {
assert.Equal(t, int32(1), atomic.LoadInt32(&called)) assert.Equal(t, int32(1), atomic.LoadInt32(&called))
} }
type mockedRouter struct{} func TestEngine_withTimeout(t *testing.T) {
logx.Disable()
func (m mockedRouter) ServeHTTP(writer http.ResponseWriter, request *http.Request) { tests := []struct {
name string
timeout int64
}{
{
name: "not set",
},
{
name: "set",
timeout: 1000,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
ng := newEngine(RestConf{Timeout: test.timeout})
svr := &http.Server{}
ng.withTimeout()(svr)
assert.Equal(t, time.Duration(test.timeout)*time.Millisecond*4/5, svr.ReadTimeout)
assert.Equal(t, time.Duration(0), svr.ReadHeaderTimeout)
assert.Equal(t, time.Duration(test.timeout)*time.Millisecond*9/10, svr.WriteTimeout)
assert.Equal(t, time.Duration(0), svr.IdleTimeout)
})
}
} }
func (m mockedRouter) Handle(method, path string, handler http.Handler) error { type mockedRouter struct{}
func (m mockedRouter) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
}
func (m mockedRouter) Handle(_, _ string, _ http.Handler) error {
return errors.New("foo") return errors.New("foo")
} }
func (m mockedRouter) SetNotFoundHandler(handler http.Handler) { func (m mockedRouter) SetNotFoundHandler(_ http.Handler) {
} }
func (m mockedRouter) SetNotAllowedHandler(handler http.Handler) { func (m mockedRouter) SetNotAllowedHandler(_ http.Handler) {
} }

View File

@@ -11,9 +11,11 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"strconv"
"strings" "strings"
"time" "time"
"github.com/zeromicro/go-zero/core/color"
"github.com/zeromicro/go-zero/core/iox" "github.com/zeromicro/go-zero/core/iox"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
@@ -157,15 +159,21 @@ func dumpRequest(r *http.Request) string {
return string(reqContent) return string(reqContent)
} }
func isOkResponse(code int) bool {
// not server error
return code < http.StatusInternalServerError
}
func logBrief(r *http.Request, code int, timer *utils.ElapsedTimer, logs *internal.LogCollector) { func logBrief(r *http.Request, code int, timer *utils.ElapsedTimer, logs *internal.LogCollector) {
var buf bytes.Buffer var buf bytes.Buffer
duration := timer.Duration() duration := timer.Duration()
logger := logx.WithContext(r.Context()).WithDuration(duration) logger := logx.WithContext(r.Context()).WithDuration(duration)
buf.WriteString(fmt.Sprintf("[HTTP] %s - %d - %s - %s - %s", buf.WriteString(fmt.Sprintf("[HTTP] %s - %s %s - %s - %s",
r.Method, code, r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent())) wrapStatusCode(code), wrapMethod(r.Method), r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent()))
if duration > slowThreshold.Load() { if duration > slowThreshold.Load() {
logger.Slowf("[HTTP] %s - %d - %s - %s - %s - slowcall(%s)", logger.Slowf("[HTTP] %s - %s - %s %s - %s - slowcall(%s)",
r.Method, code, r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(), timex.ReprOfDuration(duration)) wrapStatusCode(code), wrapMethod(r.Method), r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(),
fmt.Sprintf("slowcall(%s)", timex.ReprOfDuration(duration)))
} }
ok := isOkResponse(code) ok := isOkResponse(code)
@@ -201,8 +209,8 @@ func logDetails(r *http.Request, response *detailLoggedResponseWriter, timer *ut
buf.WriteString(fmt.Sprintf("[HTTP] %s - %d - %s - %s\n=> %s\n", buf.WriteString(fmt.Sprintf("[HTTP] %s - %d - %s - %s\n=> %s\n",
r.Method, code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r))) r.Method, code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r)))
if duration > defaultSlowThreshold { if duration > defaultSlowThreshold {
logger.Slowf("[HTTP] %s - %d - %s - slowcall(%s)\n=> %s\n", logger.Slowf("[HTTP] %s - %d - %s - slowcall(%s)\n=> %s\n", r.Method, code, r.RemoteAddr,
r.Method, code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r)) fmt.Sprintf("slowcall(%s)", timex.ReprOfDuration(duration)), dumpRequest(r))
} }
body := logs.Flush() body := logs.Flush()
@@ -222,7 +230,44 @@ func logDetails(r *http.Request, response *detailLoggedResponseWriter, timer *ut
} }
} }
func isOkResponse(code int) bool { func wrapMethod(method string) string {
// not server error var colour color.Color
return code < http.StatusInternalServerError switch method {
case http.MethodGet:
colour = color.BgBlue
case http.MethodPost:
colour = color.BgCyan
case http.MethodPut:
colour = color.BgYellow
case http.MethodDelete:
colour = color.BgRed
case http.MethodPatch:
colour = color.BgGreen
case http.MethodHead:
colour = color.BgMagenta
case http.MethodOptions:
colour = color.BgWhite
}
if colour == color.NoColor {
return method
}
return logx.WithColorPadding(method, colour)
}
func wrapStatusCode(code int) string {
var colour color.Color
switch {
case code >= http.StatusOK && code < http.StatusMultipleChoices:
colour = color.BgGreen
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
colour = color.BgBlue
case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
colour = color.BgMagenta
default:
colour = color.BgYellow
}
return logx.WithColorPadding(strconv.Itoa(code), colour)
} }

View File

@@ -1,6 +1,8 @@
package handler package handler
import ( import (
"bytes"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
@@ -44,6 +46,33 @@ func TestLogHandler(t *testing.T) {
} }
} }
func TestLogHandlerVeryLong(t *testing.T) {
var buf bytes.Buffer
for i := 0; i < limitBodyBytes<<1; i++ {
buf.WriteByte('a')
}
req := httptest.NewRequest(http.MethodPost, "http://localhost", &buf)
handler := LogHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Context().Value(internal.LogContext).(*internal.LogCollector).Append("anything")
io.Copy(ioutil.Discard, r.Body)
w.Header().Set("X-Test", "test")
w.WriteHeader(http.StatusServiceUnavailable)
_, err := w.Write([]byte("content"))
assert.Nil(t, err)
flusher, ok := w.(http.Flusher)
assert.True(t, ok)
flusher.Flush()
}))
resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req)
assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
assert.Equal(t, "test", resp.Header().Get("X-Test"))
assert.Equal(t, "content", resp.Body.String())
}
func TestLogHandlerSlow(t *testing.T) { func TestLogHandlerSlow(t *testing.T) {
handlers := []func(handler http.Handler) http.Handler{ handlers := []func(handler http.Handler) http.Handler{
LogHandler, LogHandler,
@@ -106,6 +135,28 @@ func TestSetSlowThreshold(t *testing.T) {
assert.Equal(t, time.Second, slowThreshold.Load()) assert.Equal(t, time.Second, slowThreshold.Load())
} }
func TestWrapMethodWithColor(t *testing.T) {
// no tty
assert.Equal(t, http.MethodGet, wrapMethod(http.MethodGet))
assert.Equal(t, http.MethodPost, wrapMethod(http.MethodPost))
assert.Equal(t, http.MethodPut, wrapMethod(http.MethodPut))
assert.Equal(t, http.MethodDelete, wrapMethod(http.MethodDelete))
assert.Equal(t, http.MethodPatch, wrapMethod(http.MethodPatch))
assert.Equal(t, http.MethodHead, wrapMethod(http.MethodHead))
assert.Equal(t, http.MethodOptions, wrapMethod(http.MethodOptions))
assert.Equal(t, http.MethodConnect, wrapMethod(http.MethodConnect))
assert.Equal(t, http.MethodTrace, wrapMethod(http.MethodTrace))
}
func TestWrapStatusCodeWithColor(t *testing.T) {
// no tty
assert.Equal(t, "200", wrapStatusCode(http.StatusOK))
assert.Equal(t, "302", wrapStatusCode(http.StatusFound))
assert.Equal(t, "404", wrapStatusCode(http.StatusNotFound))
assert.Equal(t, "500", wrapStatusCode(http.StatusInternalServerError))
assert.Equal(t, "503", wrapStatusCode(http.StatusServiceUnavailable))
}
func BenchmarkLogHandler(b *testing.B) { func BenchmarkLogHandler(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()

View File

@@ -158,3 +158,32 @@ func TestDo_BadRequest(t *testing.T) {
_, err = Do(context.Background(), http.MethodPost, "/nodes/:val", val5) _, err = Do(context.Background(), http.MethodPost, "/nodes/:val", val5)
assert.NotNil(t, err) assert.NotNil(t, err)
} }
func TestDo_Json(t *testing.T) {
type Data struct {
Key string `path:"key"`
Value int `form:"value"`
Header string `header:"X-Header"`
Body chan int `json:"body"`
}
rt := router.NewRouter()
err := rt.Handle(http.MethodPost, "/nodes/:key",
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req Data
assert.Nil(t, httpx.Parse(r, &req))
}))
assert.Nil(t, err)
svr := httptest.NewServer(http.HandlerFunc(rt.ServeHTTP))
defer svr.Close()
data := Data{
Key: "foo",
Value: 10,
Header: "my-header",
Body: make(chan int),
}
_, err = Do(context.Background(), http.MethodPost, svr.URL+"/nodes/:key", data)
assert.NotNil(t, err)
}

View File

@@ -129,7 +129,18 @@ func TestWriteJsonTimeout(t *testing.T) {
// only log it and ignore // only log it and ignore
w := tracedResponseWriter{ w := tracedResponseWriter{
headers: make(map[string][]string), headers: make(map[string][]string),
timeout: true, err: http.ErrHandlerTimeout,
}
msg := message{Name: "anyone"}
WriteJson(&w, http.StatusOK, msg)
assert.Equal(t, http.StatusOK, w.code)
}
func TestWriteJsonError(t *testing.T) {
// only log it and ignore
w := tracedResponseWriter{
headers: make(map[string][]string),
err: errors.New("foo"),
} }
msg := message{Name: "anyone"} msg := message{Name: "anyone"}
WriteJson(&w, http.StatusOK, msg) WriteJson(&w, http.StatusOK, msg)
@@ -162,8 +173,8 @@ type tracedResponseWriter struct {
hasBody bool hasBody bool
code int code int
lessWritten bool lessWritten bool
timeout bool
wroteHeader bool wroteHeader bool
err error
} }
func (w *tracedResponseWriter) Header() http.Header { func (w *tracedResponseWriter) Header() http.Header {
@@ -171,8 +182,8 @@ func (w *tracedResponseWriter) Header() http.Header {
} }
func (w *tracedResponseWriter) Write(bytes []byte) (n int, err error) { func (w *tracedResponseWriter) Write(bytes []byte) (n int, err error) {
if w.timeout { if w.err != nil {
return 0, http.ErrHandlerTimeout return 0, w.err
} }
n, err = w.builder.Write(bytes) n, err = w.builder.Write(bytes)

View File

@@ -2,13 +2,13 @@ package internal
import ( import (
"context" "context"
"log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
) )
func TestInfo(t *testing.T) { func TestInfo(t *testing.T) {
@@ -25,16 +25,23 @@ func TestInfo(t *testing.T) {
} }
func TestError(t *testing.T) { func TestError(t *testing.T) {
var writer strings.Builder var buf strings.Builder
log.SetOutput(&writer) w := logx.NewWriter(&buf)
o := logx.Reset()
logx.SetWriter(w)
defer func() {
logx.Reset()
logx.SetWriter(o)
}()
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil) req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
Error(req, "first") Error(req, "first")
Errorf(req, "second %s", "third") Errorf(req, "second %s", "third")
val := writer.String() val := buf.String()
assert.True(t, strings.Contains(val, "first")) assert.True(t, strings.Contains(val, "first"))
assert.True(t, strings.Contains(val, "second")) assert.True(t, strings.Contains(val, "second"))
assert.True(t, strings.Contains(val, "third")) assert.True(t, strings.Contains(val, "third"))
assert.True(t, strings.Contains(val, "\n"))
} }
func TestContextKey_String(t *testing.T) { func TestContextKey_String(t *testing.T) {

View File

@@ -4,6 +4,7 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
@@ -11,17 +12,22 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/router" "github.com/zeromicro/go-zero/rest/router"
) )
func TestNewServer(t *testing.T) { func TestNewServer(t *testing.T) {
writer := logx.Reset()
defer logx.SetWriter(writer)
logx.SetWriter(logx.NewWriter(ioutil.Discard))
const configYaml = ` const configYaml = `
Name: foo Name: foo
Port: 54321 Port: 54321
` `
var cnf RestConf var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf)) assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
tests := []struct { tests := []struct {
c RestConf c RestConf
@@ -31,7 +37,6 @@ Port: 54321
{ {
c: RestConf{}, c: RestConf{},
opts: []RunOption{WithRouter(mockedRouter{}), WithCors()}, opts: []RunOption{WithRouter(mockedRouter{}), WithCors()},
fail: true,
}, },
{ {
c: cnf, c: cnf,
@@ -271,7 +276,7 @@ Name: foo
Port: 54321 Port: 54321
` `
var cnf RestConf var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf)) assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
testConfig := &tls.Config{ testConfig := &tls.Config{
CipherSuites: []uint16{ CipherSuites: []uint16{
@@ -309,7 +314,7 @@ Name: foo
Port: 54321 Port: 54321
` `
var cnf RestConf var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf)) assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
rt := router.NewRouter() rt := router.NewRouter()
svr, err := NewServer(cnf, WithRouter(rt)) svr, err := NewServer(cnf, WithRouter(rt))
assert.Nil(t, err) assert.Nil(t, err)
@@ -324,7 +329,7 @@ Name: foo
Port: 54321 Port: 54321
` `
var cnf RestConf var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf)) assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
rt := router.NewRouter() rt := router.NewRouter()
svr, err := NewServer(cnf, WithRouter(rt)) svr, err := NewServer(cnf, WithRouter(rt))
assert.Nil(t, err) assert.Nil(t, err)

View File

@@ -9,7 +9,7 @@ import (
"strings" "strings"
"github.com/logrusorgru/aurora" "github.com/logrusorgru/aurora"
"github.com/urfave/cli" "github.com/spf13/cobra"
"github.com/zeromicro/go-zero/tools/goctl/util" "github.com/zeromicro/go-zero/tools/goctl/util"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx"
) )
@@ -17,13 +17,20 @@ import (
//go:embed api.tpl //go:embed api.tpl
var apiTemplate string var apiTemplate string
// ApiCommand create api template file var (
func ApiCommand(c *cli.Context) error { // VarStringOutput describes the output.
if c.NumFlags() == 0 { VarStringOutput string
cli.ShowAppHelpAndExit(c, 1) // VarStringHome describes the goctl home.
} VarStringHome string
// VarStringRemote describes the remote git repository.
VarStringRemote string
// VarStringBranch describes the git branch.
VarStringBranch string
)
apiFile := c.String("o") // CreateApiTemplate create api template file
func CreateApiTemplate(_ *cobra.Command, _ []string) error {
apiFile := VarStringOutput
if len(apiFile) == 0 { if len(apiFile) == 0 {
return errors.New("missing -o") return errors.New("missing -o")
} }
@@ -34,18 +41,15 @@ func ApiCommand(c *cli.Context) error {
} }
defer fp.Close() defer fp.Close()
home := c.String("home") if len(VarStringRemote) > 0 {
remote := c.String("remote") repo, _ := util.CloneIntoGitHome(VarStringRemote, VarStringBranch)
branch := c.String("branch")
if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 { if len(repo) > 0 {
home = repo VarStringHome = repo
} }
} }
if len(home) > 0 { if len(VarStringHome) > 0 {
pathx.RegisterGoctlHome(home) pathx.RegisterGoctlHome(VarStringHome)
} }
text, err := pathx.LoadTemplate(category, apiTemplateFile, apiTemplate) text, err := pathx.LoadTemplate(category, apiTemplateFile, apiTemplate)

View File

@@ -3,7 +3,6 @@ package apigen
import ( import (
"fmt" "fmt"
"github.com/urfave/cli"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx"
) )
@@ -27,7 +26,7 @@ func Clean() error {
} }
// GenTemplates generates api template files. // GenTemplates generates api template files.
func GenTemplates(_ *cli.Context) error { func GenTemplates() error {
return pathx.InitTemplates(category, templates) return pathx.InitTemplates(category, templates)
} }

176
tools/goctl/api/cmd.go Normal file
View File

@@ -0,0 +1,176 @@
package api
import (
"github.com/spf13/cobra"
"github.com/zeromicro/go-zero/tools/goctl/api/apigen"
"github.com/zeromicro/go-zero/tools/goctl/api/dartgen"
"github.com/zeromicro/go-zero/tools/goctl/api/docgen"
"github.com/zeromicro/go-zero/tools/goctl/api/format"
"github.com/zeromicro/go-zero/tools/goctl/api/gogen"
"github.com/zeromicro/go-zero/tools/goctl/api/javagen"
"github.com/zeromicro/go-zero/tools/goctl/api/ktgen"
"github.com/zeromicro/go-zero/tools/goctl/api/new"
"github.com/zeromicro/go-zero/tools/goctl/api/tsgen"
"github.com/zeromicro/go-zero/tools/goctl/api/validate"
"github.com/zeromicro/go-zero/tools/goctl/plugin"
)
var (
// Cmd describes a api command.
Cmd = &cobra.Command{
Use: "api",
Short: "Generate api related files",
RunE: apigen.CreateApiTemplate,
}
dartCmd = &cobra.Command{
Use: "dart",
Short: "Generate dart files for provided api in api file",
RunE: dartgen.DartCommand,
}
docCmd = &cobra.Command{
Use: "doc",
Short: "Generate doc files",
RunE: docgen.DocCommand,
}
formatCmd = &cobra.Command{
Use: "format",
Short: "Format api files",
RunE: format.GoFormatApi,
}
goCmd = &cobra.Command{
Use: "go",
Short: "Generate go files for provided api in yaml file",
RunE: gogen.GoCommand,
}
newCmd = &cobra.Command{
Use: "new",
Short: "Fast create api service",
Example: "goctl api new [options] service-name",
Args: cobra.ExactValidArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return new.CreateServiceCommand(args)
},
}
validateCmd = &cobra.Command{
Use: "validate",
Short: "Validate api file",
RunE: validate.GoValidateApi,
}
javaCmd = &cobra.Command{
Use: "java",
Short: "Generate java files for provided api in api file",
RunE: javagen.JavaCommand,
}
ktCmd = &cobra.Command{
Use: "kt",
Short: "Generate kotlin code for provided api file",
RunE: ktgen.KtCommand,
}
pluginCmd = &cobra.Command{
Use: "plugin",
Short: "Custom file generator",
RunE: plugin.PluginCommand,
}
tsCmd = &cobra.Command{
Use: "ts",
Short: "Generate ts files for provided api in api file",
RunE: tsgen.TsCommand,
}
)
func init() {
Cmd.Flags().StringVar(&apigen.VarStringOutput, "o", "", "Output a sample api file")
Cmd.Flags().StringVar(&apigen.VarStringHome, "home", "", "The goctl home path of the"+
" template, --home and --remote cannot be set at the same time, if they are, --remote has "+
"higher priority")
Cmd.Flags().StringVar(&apigen.VarStringRemote, "remote", "", "The remote git repo of the"+
" template, --home and --remote cannot be set at the same time, if they are, --remote has higher"+
" priority\n\tThe git repo directory must be consistent with the"+
" https://github.com/zeromicro/go-zero-template directory structure")
Cmd.Flags().StringVar(&apigen.VarStringBranch, "branch", "master", "The branch of the "+
"remote repo, it does work with --remote")
dartCmd.Flags().StringVar(&dartgen.VarStringDir, "dir", "", "The target dir")
dartCmd.Flags().StringVar(&dartgen.VarStringAPI, "api", "", "The api file")
dartCmd.Flags().BoolVar(&dartgen.VarStringLegacy, "legacy", false, "Legacy generator for flutter v1")
dartCmd.Flags().StringVar(&dartgen.VarStringHostname, "hostname", "", "hostname of the server")
docCmd.Flags().StringVar(&docgen.VarStringDir, "dir", "", "The target dir")
docCmd.Flags().StringVar(&docgen.VarStringOutput, "o", "", "The output markdown directory")
formatCmd.Flags().StringVar(&format.VarStringDir, "dir", "", "The format target dir")
formatCmd.Flags().BoolVar(&format.VarBoolIgnore, "iu", false, "Ignore update")
formatCmd.Flags().BoolVar(&format.VarBoolUseStdin, "stdin", false, "Use stdin to input api"+
" doc content, press \"ctrl + d\" to send EOF")
formatCmd.Flags().BoolVar(&format.VarBoolSkipCheckDeclare, "declare", false, "Use to skip check "+
"api types already declare")
goCmd.Flags().StringVar(&gogen.VarStringDir, "dir", "", "The target dir")
goCmd.Flags().StringVar(&gogen.VarStringAPI, "api", "", "The api file")
goCmd.Flags().StringVar(&gogen.VarStringHome, "home", "", "The goctl home path of "+
"the template, --home and --remote cannot be set at the same time, if they are, --remote "+
"has higher priority")
goCmd.Flags().StringVar(&gogen.VarStringRemote, "remote", "", "The remote git repo "+
"of the template, --home and --remote cannot be set at the same time, if they are, --remote"+
" has higher priority\n\tThe git repo directory must be consistent with the "+
"https://github.com/zeromicro/go-zero-template directory structure")
goCmd.Flags().StringVar(&gogen.VarStringBranch, "branch", "master", "The branch of "+
"the remote repo, it does work with --remote")
goCmd.Flags().StringVar(&gogen.VarStringStyle, "style", "gozero", "The file naming format,"+
" see [https://github.com/zeromicro/go-zero/blob/master/tools/goctl/config/readme.md]")
javaCmd.Flags().StringVar(&javagen.VarStringDir, "dir", "", "The target dir")
javaCmd.Flags().StringVar(&javagen.VarStringAPI, "api", "", "The api file")
ktCmd.Flags().StringVar(&ktgen.VarStringDir, "dir", "", "The target dir")
ktCmd.Flags().StringVar(&ktgen.VarStringAPI, "api", "", "The api file")
ktCmd.Flags().StringVar(&ktgen.VarStringPKG, "pkg", "", "Define package name for kotlin file")
newCmd.Flags().StringVar(&new.VarStringHome, "home", "", "The goctl home path of "+
"the template, --home and --remote cannot be set at the same time, if they are, --remote "+
"has higher priority")
newCmd.Flags().StringVar(&new.VarStringRemote, "remote", "", "The remote git repo "+
"of the template, --home and --remote cannot be set at the same time, if they are, --remote"+
" has higher priority\n\tThe git repo directory must be consistent with the "+
"https://github.com/zeromicro/go-zero-template directory structure")
newCmd.Flags().StringVar(&new.VarStringBranch, "branch", "master", "The branch of "+
"the remote repo, it does work with --remote")
newCmd.Flags().StringVar(&new.VarStringStyle, "style", "gozero", "The file naming format,"+
" see [https://github.com/zeromicro/go-zero/blob/master/tools/goctl/config/readme.md]")
pluginCmd.Flags().StringVarP(&plugin.VarStringPlugin, "plugin", "p", "", "The plugin file")
pluginCmd.Flags().StringVar(&plugin.VarStringDir, "dir", "", "The target dir")
pluginCmd.Flags().StringVar(&plugin.VarStringAPI, "api", "", "The api file")
pluginCmd.Flags().StringVar(&plugin.VarStringStyle, "style", "",
"The file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]")
tsCmd.Flags().StringVar(&tsgen.VarStringDir, "dir", "", "The target dir")
tsCmd.Flags().StringVar(&tsgen.VarStringAPI, "api", "", "The api file")
tsCmd.Flags().StringVar(&tsgen.VarStringWebAPI, "webapi", "", "The web api file path")
tsCmd.Flags().StringVar(&tsgen.VarStringCaller, "caller", "", "The web api caller")
tsCmd.Flags().BoolVar(&tsgen.VarBoolUnWrap, "unwrap", false, "Unwrap the webapi caller for import")
validateCmd.Flags().StringVar(&validate.VarStringAPI, "api", "", "Validate target api file")
// Add sub-commands
Cmd.AddCommand(dartCmd)
Cmd.AddCommand(docCmd)
Cmd.AddCommand(formatCmd)
Cmd.AddCommand(goCmd)
Cmd.AddCommand(javaCmd)
Cmd.AddCommand(ktCmd)
Cmd.AddCommand(newCmd)
Cmd.AddCommand(pluginCmd)
Cmd.AddCommand(tsCmd)
Cmd.AddCommand(validateCmd)
}

View File

@@ -5,17 +5,28 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/urfave/cli" "github.com/spf13/cobra"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/tools/goctl/api/parser" "github.com/zeromicro/go-zero/tools/goctl/api/parser"
) )
var (
// VarStringDir describes the directory.
VarStringDir string
// VarStringAPI defines the API.
VarStringAPI string
// VarStringLegacy describes whether legacy.
VarStringLegacy bool
// VarStringHostname defines the hostname.
VarStringHostname string
)
// DartCommand create dart network request code // DartCommand create dart network request code
func DartCommand(c *cli.Context) error { func DartCommand(_ *cobra.Command, _ []string) error {
apiFile := c.String("api") apiFile := VarStringAPI
dir := c.String("dir") dir := VarStringDir
isLegacy := c.Bool("legacy") isLegacy := VarStringLegacy
hostname := c.String("hostname") hostname := VarStringHostname
if len(apiFile) == 0 { if len(apiFile) == 0 {
return errors.New("missing -api") return errors.New("missing -api")
} }

View File

@@ -7,19 +7,26 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/urfave/cli" "github.com/spf13/cobra"
"github.com/zeromicro/go-zero/tools/goctl/api/parser" "github.com/zeromicro/go-zero/tools/goctl/api/parser"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx"
) )
var (
// VarStringDir describes a directory.
VarStringDir string
// VarStringOutput describes an output directory.
VarStringOutput string
)
// DocCommand generate Markdown doc file // DocCommand generate Markdown doc file
func DocCommand(c *cli.Context) error { func DocCommand(_ *cobra.Command, _ []string) error {
dir := c.String("dir") dir := VarStringDir
if len(dir) == 0 { if len(dir) == 0 {
return errors.New("missing -dir") return errors.New("missing -dir")
} }
outputDir := c.String("o") outputDir := VarStringOutput
if len(outputDir) == 0 { if len(outputDir) == 0 {
var err error var err error
outputDir, err = os.Getwd() outputDir, err = os.Getwd()

View File

@@ -12,7 +12,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/urfave/cli" "github.com/spf13/cobra"
"github.com/zeromicro/go-zero/core/errorx" "github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/tools/goctl/api/parser" "github.com/zeromicro/go-zero/tools/goctl/api/parser"
"github.com/zeromicro/go-zero/tools/goctl/api/util" "github.com/zeromicro/go-zero/tools/goctl/api/util"
@@ -26,30 +26,37 @@ const (
rightBrace = "}" rightBrace = "}"
) )
// GoFormatApi format api file var (
func GoFormatApi(c *cli.Context) error { // VarBoolUseStdin describes whether to use stdin or not.
useStdin := c.Bool("stdin") VarBoolUseStdin bool
skipCheckDeclare := c.Bool("declare") // VarBoolSkipCheckDeclare describes whether to skip.
dir := c.String("dir") VarBoolSkipCheckDeclare bool
// VarStringDir describes the directory.
VarStringDir string
// VarBoolIgnore describes whether to ignore.
VarBoolIgnore bool
)
// GoFormatApi format api file
func GoFormatApi(_ *cobra.Command, _ []string) error {
var be errorx.BatchError var be errorx.BatchError
if useStdin { if VarBoolUseStdin {
if err := apiFormatReader(os.Stdin, dir, skipCheckDeclare); err != nil { if err := apiFormatReader(os.Stdin, VarStringDir, VarBoolSkipCheckDeclare); err != nil {
be.Add(err) be.Add(err)
} }
} else { } else {
if len(dir) == 0 { if len(VarStringDir) == 0 {
return errors.New("missing -dir") return errors.New("missing -dir")
} }
_, err := os.Lstat(dir) _, err := os.Lstat(VarStringDir)
if err != nil { if err != nil {
return errors.New(dir + ": No such file or directory") return errors.New(VarStringDir + ": No such file or directory")
} }
err = filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) { err = filepath.Walk(VarStringDir, func(path string, fi os.FileInfo, errBack error) (err error) {
if strings.HasSuffix(path, ".api") { if strings.HasSuffix(path, ".api") {
if err := ApiFormatByPath(path, skipCheckDeclare); err != nil { if err := ApiFormatByPath(path, VarBoolSkipCheckDeclare); err != nil {
be.Add(util.WrapErr(err, fi.Name())) be.Add(util.WrapErr(err, fi.Name()))
} }
} }

View File

@@ -12,7 +12,7 @@ import (
"time" "time"
"github.com/logrusorgru/aurora" "github.com/logrusorgru/aurora"
"github.com/urfave/cli" "github.com/spf13/cobra"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
apiformat "github.com/zeromicro/go-zero/tools/goctl/api/format" apiformat "github.com/zeromicro/go-zero/tools/goctl/api/format"
"github.com/zeromicro/go-zero/tools/goctl/api/parser" "github.com/zeromicro/go-zero/tools/goctl/api/parser"
@@ -24,16 +24,30 @@ import (
const tmpFile = "%s-%d" const tmpFile = "%s-%d"
var tmpDir = path.Join(os.TempDir(), "goctl") var (
tmpDir = path.Join(os.TempDir(), "goctl")
// VarStringDir describes the directory.
VarStringDir string
// VarStringAPI describes the API.
VarStringAPI string
// VarStringHome describes the go home.
VarStringHome string
// VarStringRemote describes the remote git repository.
VarStringRemote string
// VarStringBranch describes the branch.
VarStringBranch string
// VarStringStyle describes the style of output files.
VarStringStyle string
)
// GoCommand gen go project files from command line // GoCommand gen go project files from command line
func GoCommand(c *cli.Context) error { func GoCommand(_ *cobra.Command, _ []string) error {
apiFile := c.String("api") apiFile := VarStringAPI
dir := c.String("dir") dir := VarStringDir
namingStyle := c.String("style") namingStyle := VarStringStyle
home := c.String("home") home := VarStringHome
remote := c.String("remote") remote := VarStringRemote
branch := c.String("branch") branch := VarStringBranch
if len(remote) > 0 { if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote, branch) repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 { if len(repo) > 0 {

View File

@@ -15,10 +15,10 @@ func main() {
var c config.Config var c config.Config
conf.MustLoad(*configFile, &c) conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
server := rest.MustNewServer(c.RestConf) server := rest.MustNewServer(c.RestConf)
defer server.Stop() defer server.Stop()
ctx := svc.NewServiceContext(c)
handler.RegisterHandlers(server, ctx) handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)

View File

@@ -3,7 +3,6 @@ package gogen
import ( import (
"fmt" "fmt"
"github.com/urfave/cli"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx"
) )
@@ -45,7 +44,7 @@ func Clean() error {
} }
// GenTemplates generates api template files. // GenTemplates generates api template files.
func GenTemplates(_ *cli.Context) error { func GenTemplates() error {
return pathx.InitTemplates(category, templates) return pathx.InitTemplates(category, templates)
} }

View File

@@ -6,16 +6,23 @@ import (
"strings" "strings"
"github.com/logrusorgru/aurora" "github.com/logrusorgru/aurora"
"github.com/urfave/cli" "github.com/spf13/cobra"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/tools/goctl/api/parser" "github.com/zeromicro/go-zero/tools/goctl/api/parser"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx"
) )
var (
// VarStringDir describes a directory.
VarStringDir string
// VarStringAPI describes an API.
VarStringAPI string
)
// JavaCommand generates java code command entrance. // JavaCommand generates java code command entrance.
func JavaCommand(c *cli.Context) error { func JavaCommand(_ *cobra.Command, _ []string) error {
apiFile := c.String("api") apiFile := VarStringAPI
dir := c.String("dir") dir := VarStringDir
if len(apiFile) == 0 { if len(apiFile) == 0 {
return errors.New("missing -api") return errors.New("missing -api")
} }

View File

@@ -3,21 +3,30 @@ package ktgen
import ( import (
"errors" "errors"
"github.com/urfave/cli" "github.com/spf13/cobra"
"github.com/zeromicro/go-zero/tools/goctl/api/parser" "github.com/zeromicro/go-zero/tools/goctl/api/parser"
) )
var (
// VarStringDir describes a directory.
VarStringDir string
// VarStringAPI describes an API.
VarStringAPI string
// VarStringPKG describes a package.
VarStringPKG string
)
// KtCommand generates kotlin code command entrance // KtCommand generates kotlin code command entrance
func KtCommand(c *cli.Context) error { func KtCommand(_ *cobra.Command, _ []string) error {
apiFile := c.String("api") apiFile := VarStringAPI
if apiFile == "" { if apiFile == "" {
return errors.New("missing -api") return errors.New("missing -api")
} }
dir := c.String("dir") dir := VarStringDir
if dir == "" { if dir == "" {
return errors.New("missing -dir") return errors.New("missing -dir")
} }
pkg := c.String("pkg") pkg := VarStringPKG
if pkg == "" { if pkg == "" {
return errors.New("missing -pkg") return errors.New("missing -pkg")
} }

View File

@@ -8,7 +8,6 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/urfave/cli"
"github.com/zeromicro/go-zero/tools/goctl/api/gogen" "github.com/zeromicro/go-zero/tools/goctl/api/gogen"
conf "github.com/zeromicro/go-zero/tools/goctl/config" conf "github.com/zeromicro/go-zero/tools/goctl/config"
"github.com/zeromicro/go-zero/tools/goctl/util" "github.com/zeromicro/go-zero/tools/goctl/util"
@@ -18,18 +17,22 @@ import (
//go:embed api.tpl //go:embed api.tpl
var apiTemplate string var apiTemplate string
var (
// VarStringHome describes the goctl home.
VarStringHome string
// VarStringRemote describes the remote git repository.
VarStringRemote string
// VarStringBranch describes the git branch.
VarStringBranch string
// VarStringStyle describes the style of output files.
VarStringStyle string
)
// CreateServiceCommand fast create service // CreateServiceCommand fast create service
func CreateServiceCommand(c *cli.Context) error { func CreateServiceCommand(args []string) error {
if c.NArg() == 0 { dirName := args[0]
cli.ShowCommandHelpAndExit(c, "new", 1) if len(VarStringStyle) == 0 {
} VarStringStyle = conf.DefaultFormat
args := c.Args()
dirName := args.First()
dirStyle := c.String("style")
if len(dirStyle) == 0 {
dirStyle = conf.DefaultFormat
} }
if strings.Contains(dirName, "-") { if strings.Contains(dirName, "-") {
return errors.New("api new command service name not support strikethrough, because this will used by function name") return errors.New("api new command service name not support strikethrough, because this will used by function name")
@@ -55,18 +58,15 @@ func CreateServiceCommand(c *cli.Context) error {
defer fp.Close() defer fp.Close()
home := c.String("home") if len(VarStringRemote) > 0 {
remote := c.String("remote") repo, _ := util.CloneIntoGitHome(VarStringRemote, VarStringBranch)
branch := c.String("branch")
if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 { if len(repo) > 0 {
home = repo VarStringHome = repo
} }
} }
if len(home) > 0 { if len(VarStringHome) > 0 {
pathx.RegisterGoctlHome(home) pathx.RegisterGoctlHome(VarStringHome)
} }
text, err := pathx.LoadTemplate(category, apiTemplateFile, apiTemplate) text, err := pathx.LoadTemplate(category, apiTemplateFile, apiTemplate)
@@ -82,6 +82,6 @@ func CreateServiceCommand(c *cli.Context) error {
return err return err
} }
err = gogen.DoGenProject(apiFilePath, abs, dirStyle) err = gogen.DoGenProject(apiFilePath, abs, VarStringStyle)
return err return err
} }

View File

@@ -3,7 +3,6 @@ package new
import ( import (
"fmt" "fmt"
"github.com/urfave/cli"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx"
) )
@@ -27,7 +26,7 @@ func Clean() error {
} }
// GenTemplates generates api template files. // GenTemplates generates api template files.
func GenTemplates(_ *cli.Context) error { func GenTemplates() error {
return pathx.InitTemplates(category, templates) return pathx.InitTemplates(category, templates)
} }

View File

@@ -1,3 +1,3 @@
/ Code generated by goctl. DO NOT EDIT. // Code generated by goctl. DO NOT EDIT.
{{.componentTypes}} {{.componentTypes}}

View File

@@ -5,19 +5,32 @@ import (
"fmt" "fmt"
"github.com/logrusorgru/aurora" "github.com/logrusorgru/aurora"
"github.com/urfave/cli" "github.com/spf13/cobra"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/tools/goctl/api/parser" "github.com/zeromicro/go-zero/tools/goctl/api/parser"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx"
) )
var (
// VarStringDir describes a directory.
VarStringDir string
// VarStringAPI describes an API file.
VarStringAPI string
// VarStringWebAPI describes a web API file.
VarStringWebAPI string
// VarStringCaller describes a caller.
VarStringCaller string
// VarBoolUnWrap describes whether wrap or not.
VarBoolUnWrap bool
)
// TsCommand provides the entry to generate typescript codes // TsCommand provides the entry to generate typescript codes
func TsCommand(c *cli.Context) error { func TsCommand(_ *cobra.Command, _ []string) error {
apiFile := c.String("api") apiFile := VarStringAPI
dir := c.String("dir") dir := VarStringDir
webAPI := c.String("webapi") webAPI := VarStringWebAPI
caller := c.String("caller") caller := VarStringCaller
unwrapAPI := c.Bool("unwrap") unwrapAPI := VarBoolUnWrap
if len(apiFile) == 0 { if len(apiFile) == 0 {
return errors.New("missing -api") return errors.New("missing -api")
} }

View File

@@ -5,13 +5,16 @@ import (
"fmt" "fmt"
"github.com/logrusorgru/aurora" "github.com/logrusorgru/aurora"
"github.com/urfave/cli" "github.com/spf13/cobra"
"github.com/zeromicro/go-zero/tools/goctl/api/parser" "github.com/zeromicro/go-zero/tools/goctl/api/parser"
) )
// VarStringAPI describes an API.
var VarStringAPI string
// GoValidateApi verifies whether the api has a syntax error // GoValidateApi verifies whether the api has a syntax error
func GoValidateApi(c *cli.Context) error { func GoValidateApi(_ *cobra.Command, _ []string) error {
apiFile := c.String("api") apiFile := VarStringAPI
if len(apiFile) == 0 { if len(apiFile) == 0 {
return errors.New("missing -api") return errors.New("missing -api")

View File

@@ -6,7 +6,7 @@ import (
"os/exec" "os/exec"
"runtime" "runtime"
"github.com/urfave/cli" "github.com/spf13/cobra"
"github.com/zeromicro/go-zero/tools/goctl/internal/version" "github.com/zeromicro/go-zero/tools/goctl/internal/version"
) )
@@ -29,7 +29,7 @@ var openCmd = map[string]string{
darwin: darwinOpen, darwin: darwinOpen,
} }
func Action(_ *cli.Context) error { func runE(_ *cobra.Command, _ []string) error {
env := getEnv() env := getEnv()
content := fmt.Sprintf(issueTemplate, version.BuildVersion, env.string()) content := fmt.Sprintf(issueTemplate, version.BuildVersion, env.string())
content = url.QueryEscape(content) content = url.QueryEscape(content)

11
tools/goctl/bug/cmd.go Normal file
View File

@@ -0,0 +1,11 @@
package bug
import "github.com/spf13/cobra"
// Cmd describes a bug command.
var Cmd = &cobra.Command{
Use: "bug",
Short: "Report a bug",
Args: cobra.NoArgs,
RunE: runE,
}

112
tools/goctl/cmd/root.go Normal file
View File

@@ -0,0 +1,112 @@
package cmd
import (
"fmt"
"os"
"runtime"
"strings"
"github.com/logrusorgru/aurora"
"github.com/spf13/cobra"
"github.com/zeromicro/go-zero/tools/goctl/api"
"github.com/zeromicro/go-zero/tools/goctl/bug"
"github.com/zeromicro/go-zero/tools/goctl/docker"
"github.com/zeromicro/go-zero/tools/goctl/env"
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
"github.com/zeromicro/go-zero/tools/goctl/kube"
"github.com/zeromicro/go-zero/tools/goctl/migrate"
"github.com/zeromicro/go-zero/tools/goctl/model"
"github.com/zeromicro/go-zero/tools/goctl/quickstart"
"github.com/zeromicro/go-zero/tools/goctl/rpc"
"github.com/zeromicro/go-zero/tools/goctl/tpl"
"github.com/zeromicro/go-zero/tools/goctl/upgrade"
)
const (
codeFailure = 1
dash = "-"
doubleDash = "--"
assign = "="
)
var rootCmd = &cobra.Command{
Use: "goctl",
Short: "A cli tool to generate go-zero code",
Long: "A cli tool to generate api, zrpc, model code",
}
// Execute executes the given command
func Execute() {
os.Args = supportGoStdFlag(os.Args)
if err := rootCmd.Execute(); err != nil {
fmt.Println(aurora.Red(err.Error()))
os.Exit(codeFailure)
}
}
func supportGoStdFlag(args []string) []string {
copyArgs := append([]string(nil), args...)
parentCmd, _, err := rootCmd.Traverse(args[:1])
if err != nil { // ignore it to let cobra handle the error.
return copyArgs
}
for idx, arg := range copyArgs[0:] {
parentCmd, _, err = parentCmd.Traverse([]string{arg})
if err != nil { // ignore it to let cobra handle the error.
break
}
if !strings.HasPrefix(arg, dash) {
continue
}
flagExpr := strings.TrimPrefix(arg, doubleDash)
flagExpr = strings.TrimPrefix(flagExpr, dash)
flagName, flagValue := flagExpr, ""
assignIndex := strings.Index(flagExpr, assign)
if assignIndex > 0 {
flagName = flagExpr[:assignIndex]
flagValue = flagExpr[assignIndex:]
}
if !isBuiltin(flagName) {
// The method Flag can only match the user custom flags.
f := parentCmd.Flag(flagName)
if f == nil {
continue
}
if f.Shorthand == flagName {
continue
}
}
goStyleFlag := doubleDash + flagName
if assignIndex > 0 {
goStyleFlag += flagValue
}
copyArgs[idx] = goStyleFlag
}
return copyArgs
}
func isBuiltin(name string) bool {
return name == "version" || name == "help"
}
func init() {
rootCmd.Version = fmt.Sprintf(
"%s %s/%s", version.BuildVersion,
runtime.GOOS, runtime.GOARCH)
rootCmd.AddCommand(api.Cmd)
rootCmd.AddCommand(bug.Cmd)
rootCmd.AddCommand(docker.Cmd)
rootCmd.AddCommand(kube.Cmd)
rootCmd.AddCommand(env.Cmd)
rootCmd.AddCommand(model.Cmd)
rootCmd.AddCommand(migrate.Cmd)
rootCmd.AddCommand(quickstart.Cmd)
rootCmd.AddCommand(rpc.Cmd)
rootCmd.AddCommand(tpl.Cmd)
rootCmd.AddCommand(upgrade.Cmd)
}

View File

@@ -0,0 +1,23 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/zeromicro/go-zero/tools/goctl/compare/testdata"
"github.com/zeromicro/go-zero/tools/goctl/util/console"
)
var rootCmd = &cobra.Command{
Use: "compare",
Short: "Compare the goctl commands generated results between urfave and cobra",
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
dir := args[0]
testdata.MustRun(dir)
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
console.Error("%+v", err)
}
}

View File

@@ -0,0 +1,11 @@
package main
import "github.com/zeromicro/go-zero/tools/goctl/compare/cmd"
// EXPRIMENTAL: compare goctl generated code results between old and new, it will be removed in the feature.
// TODO: BEFORE RUNNING: export DSN=$datasource, the database must be gozero, and there has no limit for tables.
// TODO: AFTER RUNNING: diff --recursive old_fs new_fs
func main() {
cmd.Execute()
}

View File

@@ -0,0 +1,7 @@
#!/bin/bash
wd=`dirname $0`
GOBIN="$GOPATH/bin"
EXE=goctl-compare
go build -o $EXE $wd
mv $EXE $GOBIN

17
tools/goctl/compare/testdata/kotlin.api vendored Normal file
View File

@@ -0,0 +1,17 @@
syntax = "v1"
info(
title: "type title here"
desc: "type desc here"
author: "type author here"
email: "type email here"
version: "type version here"
)
type req{}
type reply{}
service kotlin-api{
@handler ping
post /ping
}

470
tools/goctl/compare/testdata/testcase.go vendored Normal file
View File

@@ -0,0 +1,470 @@
package testdata
import _ "embed"
var (
//go:embed unformat.api
unformatApi string
//go:embed kotlin.api
kotlinApi string
//go:embed user.sql
userSql string
list = Files{
{
IsDir: true,
Path: "version",
Cmd: "goctl --version",
},
{
IsDir: true,
Path: "api/sample_file/local",
Cmd: "goctl api --o sample.api",
},
{
IsDir: true,
Path: "api/sample_file/local/assign",
Cmd: "goctl api --o=sample.api",
},
{
IsDir: true,
Path: "api/sample_file/local/assign/shorthand",
Cmd: "goctl api -o=sample.api",
},
{
IsDir: true,
Path: "api/sample_file/remote",
Cmd: "goctl api --o sample.api --remote https://github.com/zeromicro/go-zero-template --branch main",
},
{
IsDir: true,
Path: "api/sample_file/remote/shorthand",
Cmd: "goctl api -o sample.api -remote https://github.com/zeromicro/go-zero-template -branch main",
},
{
IsDir: true,
Path: "api/sample_file/remote/assign",
Cmd: "goctl api --o=sample.api --remote https://github.com/zeromicro/go-zero-template --branch=main",
},
{
IsDir: true,
Path: "api/sample_file/remote/assign/shorthand",
Cmd: "goctl api -o=sample.api -remote https://github.com/zeromicro/go-zero-template -branch=main",
},
{
IsDir: true,
Path: "api/dart/legacy/true",
Cmd: "goctl api --o sample.api && goctl api dart --api sample.api --dir . --hostname 127.0.0.1 --legacy true",
},
{
IsDir: true,
Path: "api/dart/legacy/true/shorthand",
Cmd: "goctl api -o sample.api && goctl api dart -api sample.api -dir . -hostname 127.0.0.1 -legacy true",
},
{
IsDir: true,
Path: "api/dart/legacy/true/assign",
Cmd: "goctl api --o=sample.api && goctl api dart --api=sample.api --dir=. --hostname=127.0.0.1 --legacy=true",
},
{
IsDir: true,
Path: "api/dart/legacy/true/assign/shorthand",
Cmd: "goctl api -o=sample.api && goctl api dart -api=sample.api -dir=. -hostname=127.0.0.1 -legacy=true",
},
{
IsDir: true,
Path: "api/dart/legacy/false",
Cmd: "goctl api --o sample.api && goctl api dart --api sample.api --dir . --hostname 127.0.0.1 --legacy true",
},
{
IsDir: true,
Path: "api/dart/legacy/false/shorthand",
Cmd: "goctl api -o sample.api && goctl api dart -api sample.api -dir . -hostname 127.0.0.1 -legacy true",
},
{
IsDir: true,
Path: "api/dart/legacy/false/assign",
Cmd: "goctl api --o=sample.api && goctl api dart --api=sample.api --dir=. --hostname=127.0.0.1 --legacy=true",
},
{
IsDir: true,
Path: "api/dart/legacy/false/assign/shorthand",
Cmd: "goctl api -o=sample.api && goctl api dart -api=sample.api -dir=. -hostname=127.0.0.1 -legacy=true",
},
{
IsDir: true,
Path: "api/doc",
Cmd: "goctl api --o sample.api && goctl api doc --dir . --o .",
},
{
IsDir: true,
Path: "api/doc/shorthand",
Cmd: "goctl api -o sample.api && goctl api doc -dir . -o .",
},
{
IsDir: true,
Path: "api/doc/assign",
Cmd: "goctl api --o=sample.api && goctl api doc --dir=. --o=.",
},
{
IsDir: true,
Path: "api/doc/assign/shorthand",
Cmd: "goctl api -o=sample.api && goctl api doc -dir=. -o=.",
},
{
Path: "api/format/unformat.api",
Content: unformatApi,
Cmd: "goctl api format --dir . --iu",
},
{
Path: "api/format/shorthand/unformat.api",
Content: unformatApi,
Cmd: "goctl api format -dir . -iu",
},
{
Path: "api/format/assign/unformat.api",
Content: unformatApi,
Cmd: "goctl api format --dir=. --iu",
},
{
Path: "api/format/assign/shorthand/unformat.api",
Content: unformatApi,
Cmd: "goctl api format -dir=. -iu",
},
{
IsDir: true,
Path: "api/go/style/default",
Cmd: "goctl api --o sample.api && goctl api go --api sample.api --dir .",
},
{
IsDir: true,
Path: "api/go/style/default/shorthand",
Cmd: "goctl api -o sample.api && goctl api go -api sample.api -dir .",
},
{
IsDir: true,
Path: "api/go/style/assign/default",
Cmd: "goctl api --o=sample.api && goctl api go --api=sample.api --dir=.",
},
{
IsDir: true,
Path: "api/go/style/assign/default/shorthand",
Cmd: "goctl api -o=sample.api && goctl api go -api=sample.api -dir=.",
},
{
IsDir: true,
Path: "api/go/style/goZero",
Cmd: "goctl api --o sample.api && goctl api go --api sample.api --dir . --style goZero",
},
{
IsDir: true,
Path: "api/go/style/goZero/shorthand",
Cmd: "goctl api -o sample.api && goctl api go -api sample.api -dir . -style goZero",
},
{
IsDir: true,
Path: "api/go/style/goZero/assign",
Cmd: "goctl api --o=sample.api && goctl api go --api=sample.api --dir=. --style=goZero",
},
{
IsDir: true,
Path: "api/go/style/goZero/assign/shorthand",
Cmd: "goctl api -o=sample.api && goctl api go -api=sample.api -dir=. -style=goZero",
},
{
IsDir: true,
Path: "api/java",
Cmd: "goctl api --o sample.api && goctl api java --api sample.api --dir .",
},
{
IsDir: true,
Path: "api/java/shorthand",
Cmd: "goctl api -o sample.api && goctl api java -api sample.api -dir .",
},
{
IsDir: true,
Path: "api/java/assign",
Cmd: "goctl api --o=sample.api && goctl api java --api=sample.api --dir=.",
},
{
IsDir: true,
Path: "api/java/shorthand/assign",
Cmd: "goctl api -o=sample.api && goctl api java -api=sample.api -dir=.",
},
{
IsDir: true,
Path: "api/new/style/default",
Cmd: "goctl api new greet",
},
{
IsDir: true,
Path: "api/new/style/goZero",
Cmd: "goctl api new greet --style goZero",
},
{
IsDir: true,
Path: "api/new/style/goZero/assign",
Cmd: "goctl api new greet --style=goZero",
},
{
IsDir: true,
Path: "api/new/style/goZero/shorthand",
Cmd: "goctl api new greet -style goZero",
},
{
IsDir: true,
Path: "api/new/style/goZero/shorthand/assign",
Cmd: "goctl api new greet -style=goZero",
},
{
IsDir: true,
Path: "api/ts",
Cmd: "goctl api --o sample.api && goctl api ts --api sample.api --dir . --unwrap --webapi .",
},
{
IsDir: true,
Path: "api/ts/shorthand",
Cmd: "goctl api -o sample.api && goctl api ts -api sample.api -dir . -unwrap -webapi .",
},
{
IsDir: true,
Path: "api/ts/assign",
Cmd: "goctl api --o=sample.api && goctl api ts --api=sample.api --dir=. --unwrap --webapi=.",
},
{
IsDir: true,
Path: "api/ts/shorthand/assign",
Cmd: "goctl api -o=sample.api && goctl api ts -api=sample.api -dir=. -unwrap -webapi=.",
},
{
IsDir: true,
Path: "api/validate",
Cmd: "goctl api --o sample.api && goctl api validate --api sample.api",
},
{
IsDir: true,
Path: "api/validate/shorthand",
Cmd: "goctl api -o sample.api && goctl api validate -api sample.api",
},
{
IsDir: true,
Path: "api/validate/assign",
Cmd: "goctl api --o=sample.api && goctl api validate --api=sample.api",
},
{
IsDir: true,
Path: "api/validate/shorthand/assign",
Cmd: "goctl api -o=sample.api && goctl api validate -api=sample.api",
},
{
IsDir: true,
Path: "env/show",
Cmd: "goctl env > env.txt",
},
{
IsDir: true,
Path: "env/check",
Cmd: "goctl env check -f -v",
},
{
IsDir: true,
Path: "env/install",
Cmd: "goctl env install -v",
},
{
IsDir: true,
Path: "kube",
Cmd: "goctl kube deploy --image alpine --name foo --namespace foo --o foo.yaml --port 8888",
},
{
IsDir: true,
Path: "kube/shorthand",
Cmd: "goctl kube deploy -image alpine -name foo -namespace foo -o foo.yaml -port 8888",
},
{
IsDir: true,
Path: "kube/assign",
Cmd: "goctl kube deploy --image=alpine --name=foo --namespace=foo --o=foo.yaml --port=8888",
},
{
IsDir: true,
Path: "kube/shorthand/assign",
Cmd: "goctl kube deploy -image=alpine -name=foo -namespace=foo -o=foo.yaml -port=8888",
},
{
IsDir: true,
Path: "model/mongo/cache",
Cmd: "goctl model mongo --dir . --type user --style goZero -c",
},
{
IsDir: true,
Path: "model/mongo/cache/shorthand",
Cmd: "goctl model mongo -dir . -type user -style goZero -c",
},
{
IsDir: true,
Path: "model/mongo/cache/assign",
Cmd: "goctl model mongo --dir=. --type=user --style=goZero -c",
},
{
IsDir: true,
Path: "model/mongo/cache/shorthand/assign",
Cmd: "goctl model mongo -dir=. -type=user -style=goZero -c",
},
{
IsDir: true,
Path: "model/mongo/nocache",
Cmd: "goctl model mongo --dir . --type user",
},
{
IsDir: true,
Path: "model/mongo/nocache/shorthand",
Cmd: "goctl model mongo -dir . -type user",
},
{
IsDir: true,
Path: "model/mongo/nocache/assign",
Cmd: "goctl model mongo --dir=. --type=user",
},
{
IsDir: true,
Path: "model/mongo/nocache/shorthand/assign",
Cmd: "goctl model mongo -dir=. -type=user",
},
{
Content: userSql,
Path: "model/mysql/ddl/user.sql",
Cmd: "goctl model mysql ddl --database user --dir cache --src user.sql -c",
},
{
Content: userSql,
Path: "model/mysql/ddl/shorthand/user.sql",
Cmd: "goctl model mysql ddl -database user -dir cache -src user.sql -c",
},
{
Content: userSql,
Path: "model/mysql/ddl/assign/user.sql",
Cmd: "goctl model mysql ddl --database=user --dir=cache --src=user.sql -c",
},
{
Content: userSql,
Path: "model/mysql/ddl/shorthand/assign/user.sql",
Cmd: "goctl model mysql ddl -database=user -dir=cache -src=user.sql -c",
},
{
Content: userSql,
Path: "model/mysql/ddl/user.sql",
Cmd: "goctl model mysql ddl --database user --dir nocache --src user.sql",
},
{
Content: userSql,
Path: "model/mysql/ddl/shorthand/user.sql",
Cmd: "goctl model mysql ddl -database user -dir nocache -src user.sql",
},
{
Content: userSql,
Path: "model/mysql/ddl/assign/user.sql",
Cmd: "goctl model mysql ddl --database=user --dir=nocache --src=user.sql",
},
{
Content: userSql,
Path: "model/mysql/ddl/shorthand/assign/user.sql",
Cmd: "goctl model mysql ddl -database=user -dir=nocache -src=user.sql",
},
{
IsDir: true,
Path: "model/mysql/datasource",
Cmd: `goctl model mysql datasource --url $DSN --dir cache --table "*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource/shorthand",
Cmd: `goctl model mysql datasource -url $DSN -dir cache -table "*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource/shorthand2",
Cmd: `goctl model mysql datasource -url $DSN -dir cache -t "*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource/assign",
Cmd: `goctl model mysql datasource --url=$DSN --dir=cache --table="*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource/shorthand/assign",
Cmd: `goctl model mysql datasource -url=$DSN -dir=cache -table="*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource/shorthand2/assign",
Cmd: `goctl model mysql datasource -url=$DSN -dir=cache -t="*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource",
Cmd: `goctl model mysql datasource --url $DSN --dir nocache --table "*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource/shorthand",
Cmd: `goctl model mysql datasource -url $DSN -dir nocache -table "*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource/shorthand2",
Cmd: `goctl model mysql datasource -url $DSN -dir nocache -t "*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource/assign",
Cmd: `goctl model mysql datasource --url=$DSN --dir=nocache --table="*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource/shorthand/assign",
Cmd: `goctl model mysql datasource -url=$DSN -dir=nocache -table="*" -c`,
},
{
IsDir: true,
Path: "model/mysql/datasource/shorthand2/assign",
Cmd: `goctl model mysql datasource -url=$DSN -dir=nocache -t="*" -c`,
},
{
IsDir: true,
Path: "rpc/new",
Cmd: "goctl rpc new greet",
},
{
IsDir: true,
Path: "rpc/template",
Cmd: "goctl rpc template --o greet.proto",
},
{
IsDir: true,
Path: "rpc/template/shorthand",
Cmd: "goctl rpc template -o greet.proto",
},
{
IsDir: true,
Path: "rpc/template/assign",
Cmd: "goctl rpc template --o=greet.proto",
},
{
IsDir: true,
Path: "rpc/template/shorthand/assign",
Cmd: "goctl rpc template -o=greet.proto",
},
{
IsDir: true,
Path: "rpc/protoc",
Cmd: "goctl rpc template --o greet.proto && goctl rpc protoc greet.proto --go_out . --go-grpc_out . --zrpc_out .",
},
{
IsDir: true,
Path: "rpc/protoc/assign",
Cmd: "goctl rpc template --o=greet.proto && goctl rpc protoc greet.proto --go_out=. --go-grpc_out=. --zrpc_out=.",
},
}
)

120
tools/goctl/compare/testdata/testdata.go vendored Normal file
View File

@@ -0,0 +1,120 @@
package testdata
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/logrusorgru/aurora"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
type (
File struct {
IsDir bool
Path string
AbsolutePath string
Content string
Cmd string
}
Files []File
)
func (f File) execute(goctl string) error {
printDir := f.Path
dir := f.AbsolutePath
if !f.IsDir {
printDir = filepath.Dir(printDir)
dir = filepath.Dir(dir)
}
printCommand := strings.ReplaceAll(fmt.Sprintf("cd %s && %s", printDir, f.Cmd), "goctl", filepath.Base(goctl))
command := strings.ReplaceAll(fmt.Sprintf("cd %s && %s", dir, f.Cmd), "goctl", goctl)
fmt.Println(aurora.BrightGreen(printCommand))
cmd := exec.Command("sh", "-c", command)
cmd.Env = os.Environ()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func (fs Files) execute(goctl string) error {
for _, f := range fs {
err := f.execute(goctl)
if err != nil {
return err
}
}
return nil
}
func mustGetTestData(baseDir string) (Files, Files) {
if len(baseDir) == 0 {
dir, err := os.Getwd()
if err != nil {
log.Fatalln(err)
}
baseDir = dir
}
baseDir, err := filepath.Abs(baseDir)
if err != nil {
return nil, nil
}
createFile := func(baseDir string, data File) (File, error) {
fp := filepath.Join(baseDir, data.Path)
dir := filepath.Dir(fp)
if data.IsDir {
dir = fp
}
if err := pathx.MkdirIfNotExist(dir); err != nil {
return data, err
}
data.AbsolutePath = fp
if data.IsDir {
return data, nil
}
return data, ioutil.WriteFile(fp, []byte(data.Content), os.ModePerm)
}
oldDir := filepath.Join(baseDir, "old_fs")
newDir := filepath.Join(baseDir, "new_fs")
os.RemoveAll(oldDir)
os.RemoveAll(newDir)
var oldFiles, newFiles []File
for _, data := range list {
od, err := createFile(oldDir, data)
if err != nil {
log.Fatalln(err)
}
oldFiles = append(oldFiles, od)
nd, err := createFile(newDir, data)
if err != nil {
log.Fatalln(err)
}
newFiles = append(newFiles, nd)
}
return oldFiles, newFiles
}
func MustRun(baseDir string) {
oldFiles, newFiles := mustGetTestData(baseDir)
goctlOld, err := exec.LookPath("goctl.old")
must(err)
goctlNew, err := exec.LookPath("goctl")
must(err)
fmt.Println(aurora.BrightBlue("========================goctl.old======================="))
must(oldFiles.execute(goctlOld))
fmt.Println()
fmt.Println(aurora.BrightBlue("========================goctl.new======================="))
must(newFiles.execute(goctlNew))
}
func must(err error) {
if err != nil {
log.Fatalln(err)
}
}

View File

@@ -0,0 +1,3 @@
syntax = "v1"
type Foo struct{}

Some files were not shown because too many files have changed in this diff Show More