mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-12 01:10:00 +08:00
Compare commits
132 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97a171441d | ||
|
|
725e6056e1 | ||
|
|
1410f7dc20 | ||
|
|
8afe68f3f1 | ||
|
|
74c41e8c5e | ||
|
|
48f7e01158 | ||
|
|
f6f6ee5c8c | ||
|
|
b364c54940 | ||
|
|
e0e3f97c7c | ||
|
|
6a2d6786c6 | ||
|
|
18035bd4d4 | ||
|
|
f3b8fef34f | ||
|
|
6a4885ba64 | ||
|
|
f2cef2b963 | ||
|
|
bfd0869ee2 | ||
|
|
4e26e0407e | ||
|
|
d200ba4a7b | ||
|
|
ce7e2a2a9a | ||
|
|
c92400ead2 | ||
|
|
0b109c1954 | ||
|
|
d42979f705 | ||
|
|
29d81381c1 | ||
|
|
89f6c97097 | ||
|
|
ff6f109065 | ||
|
|
7da77302f4 | ||
|
|
76086fc717 | ||
|
|
555c4ecd1a | ||
|
|
630dfa0887 | ||
|
|
38cd7b7df0 | ||
|
|
9148f8df2a | ||
|
|
13f051d0e5 | ||
|
|
93b3f5030f | ||
|
|
b44e8f5c75 | ||
|
|
b9eb03e9a9 | ||
|
|
86b531406b | ||
|
|
47c49de94e | ||
|
|
50f16e2892 | ||
|
|
018ca82048 | ||
|
|
6976ba7e13 | ||
|
|
9b6e4c440c | ||
|
|
9eea311a4d | ||
|
|
86d70317bf | ||
|
|
6518eb10b3 | ||
|
|
0147d7a9d1 | ||
|
|
1b2b7647d6 | ||
|
|
af6d37c33d | ||
|
|
3da5c5f530 | ||
|
|
1694a92db0 | ||
|
|
c27e00b45c | ||
|
|
ed1c937998 | ||
|
|
db9a1f3e27 | ||
|
|
392a390a3f | ||
|
|
2a900e1795 | ||
|
|
0f5d8c6be3 | ||
|
|
f2caf9237a | ||
|
|
2f0e4e3ebf | ||
|
|
2c6b422f6b | ||
|
|
4d34998338 | ||
|
|
8be47b9c99 | ||
|
|
1d95e95cf8 | ||
|
|
3fa8c5940d | ||
|
|
c44edd7cac | ||
|
|
af05219b70 | ||
|
|
f366e1d936 | ||
|
|
6c94e4652e | ||
|
|
edfaa6d906 | ||
|
|
b6b96d9dad | ||
|
|
87800419f5 | ||
|
|
50a5fb7715 | ||
|
|
aa8f07d064 | ||
|
|
7868bdf660 | ||
|
|
46078e716d | ||
|
|
bb33a20bc8 | ||
|
|
5536473a08 | ||
|
|
323b35ed2d | ||
|
|
30958a91f7 | ||
|
|
b94b68a427 | ||
|
|
07145b210e | ||
|
|
321a20add6 | ||
|
|
65098d4737 | ||
|
|
35425f6164 | ||
|
|
a0060ff81b | ||
|
|
289a325757 | ||
|
|
3fbe0f87b7 | ||
|
|
ea98d210fd | ||
|
|
b9bc1fdcf8 | ||
|
|
6dc570bcd7 | ||
|
|
e21997f0d7 | ||
|
|
92c0b7c3c5 | ||
|
|
6d3ed98744 | ||
|
|
fb519fa547 | ||
|
|
e9501c3fb3 | ||
|
|
fd12659729 | ||
|
|
72ebbb9774 | ||
|
|
f1fdd55b38 | ||
|
|
58787746db | ||
|
|
ca88b69d24 | ||
|
|
6b1e15cab1 | ||
|
|
6f86e5bff8 | ||
|
|
3f492df74e | ||
|
|
5e7b1f6bfe | ||
|
|
e80a64fa67 | ||
|
|
95282edb78 | ||
|
|
7b82eda993 | ||
|
|
5d09cd0e7c | ||
|
|
1e717f9f5c | ||
|
|
c6e2b4a43a | ||
|
|
e567a0c718 | ||
|
|
52f060caae | ||
|
|
f486685e99 | ||
|
|
3ae874d75d | ||
|
|
c58eb13328 | ||
|
|
14ca39bc86 | ||
|
|
3ea8a2d4b6 | ||
|
|
6d2b9fd904 | ||
|
|
5451d96a81 | ||
|
|
69c2bad410 | ||
|
|
5383e29ce6 | ||
|
|
51472004a3 | ||
|
|
caf5b7b1f1 | ||
|
|
bef9aa55e6 | ||
|
|
d0a59b13a6 | ||
|
|
469e62067c | ||
|
|
a36d58aac9 | ||
|
|
aa5118c2aa | ||
|
|
974ba5c9aa | ||
|
|
ec1de4f48d | ||
|
|
bab72b7630 | ||
|
|
ac321fc146 | ||
|
|
ae2c76765c | ||
|
|
f21970c117 | ||
|
|
d0a58d1f2d |
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -9,4 +9,5 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt 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
|
||||
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -35,11 +35,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -64,4 +64,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
4
.github/workflows/go.yml
vendored
4
.github/workflows/go.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
go-version: ^1.15
|
||||
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
28
.github/workflows/release.yaml
vendored
Normal file
28
.github/workflows/release.yaml
vendored
Normal 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@v3
|
||||
- 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
|
||||
2
.github/workflows/reviewdog.yml
vendored
2
.github/workflows/reviewdog.yml
vendored
@@ -5,7 +5,7 @@ jobs:
|
||||
name: runner / staticcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: reviewdog/action-staticcheck@v1
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,6 +18,7 @@
|
||||
# for test purpose
|
||||
**/adhoc
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# gitlab ci
|
||||
.cache
|
||||
|
||||
@@ -22,7 +22,7 @@ We hope that the items listed below will inspire further engagement from the com
|
||||
## 2022
|
||||
- [x] Support `context` in redis 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.
|
||||
- [ ] Support `goctl doctor` command to report potential issues for given service
|
||||
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file
|
||||
|
||||
@@ -69,11 +69,8 @@ func (f *Filter) Exists(data []byte) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !isSet {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return isSet, nil
|
||||
}
|
||||
|
||||
func (f *Filter) getLocations(data []byte) []uint {
|
||||
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/mathx"
|
||||
"github.com/zeromicro/go-zero/core/proc"
|
||||
"github.com/zeromicro/go-zero/core/stat"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -198,7 +198,7 @@ type errorWindow struct {
|
||||
|
||||
func (ew *errorWindow) add(reason string) {
|
||||
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.count = mathx.MinInt(ew.count+1, numHistoryReasons)
|
||||
ew.lock.Unlock()
|
||||
|
||||
73
core/color/color.go
Normal file
73
core/color/color.go
Normal 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
17
core/color/color_test.go
Normal 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)
|
||||
}
|
||||
@@ -6,24 +6,26 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/mapping"
|
||||
)
|
||||
|
||||
var loaders = map[string]func([]byte, interface{}) error{
|
||||
".json": LoadConfigFromJsonBytes,
|
||||
".yaml": LoadConfigFromYamlBytes,
|
||||
".yml": LoadConfigFromYamlBytes,
|
||||
".json": LoadFromJsonBytes,
|
||||
".toml": LoadFromTomlBytes,
|
||||
".yaml": LoadFromYamlBytes,
|
||||
".yml": LoadFromYamlBytes,
|
||||
}
|
||||
|
||||
// LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
|
||||
func LoadConfig(file string, v interface{}, opts ...Option) error {
|
||||
// Load loads config into v from file, .json, .yaml and .yml are acceptable.
|
||||
func Load(file string, v interface{}, opts ...Option) error {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loader, ok := loaders[path.Ext(file)]
|
||||
loader, ok := loaders[strings.ToLower(path.Ext(file))]
|
||||
if !ok {
|
||||
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)
|
||||
}
|
||||
|
||||
// LoadConfigFromJsonBytes loads config into v from content json bytes.
|
||||
func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
|
||||
// LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
|
||||
// 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)
|
||||
}
|
||||
|
||||
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
|
||||
func LoadConfigFromYamlBytes(content []byte, v interface{}) error {
|
||||
// LoadConfigFromJsonBytes loads config into v from content json bytes.
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ import (
|
||||
)
|
||||
|
||||
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) {
|
||||
filename, err := fs.TempFilenameWithText("hello")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
assert.NotNil(t, LoadConfig(filename, nil))
|
||||
assert.NotNil(t, Load(filename, nil))
|
||||
}
|
||||
|
||||
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) {
|
||||
tests := []string{
|
||||
".json",
|
||||
|
||||
57
core/conf/readme.md
Normal file
57
core/conf/readme.md
Normal file
@@ -0,0 +1,57 @@
|
||||
## How to use
|
||||
|
||||
1. Define a config structure, like below:
|
||||
|
||||
```go
|
||||
type 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, toml or json config file:
|
||||
|
||||
- yaml example
|
||||
|
||||
```yaml
|
||||
# most fields are optional or have default values
|
||||
Port: 8080
|
||||
LogMode: console
|
||||
# you can use env settings
|
||||
MaxBytes: ${MAX_BYTES}
|
||||
```
|
||||
|
||||
- toml example
|
||||
|
||||
```toml
|
||||
# most fields are optional or have default values
|
||||
Port = 8_080
|
||||
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())
|
||||
```
|
||||
|
||||
@@ -191,9 +191,11 @@ func (c *cluster) handleWatchEvents(key string, events []*clientv3.Event) {
|
||||
})
|
||||
}
|
||||
case clientv3.EventTypeDelete:
|
||||
c.lock.Lock()
|
||||
if vals, ok := c.values[key]; ok {
|
||||
delete(vals, string(ev.Kv.Key))
|
||||
}
|
||||
c.lock.Unlock()
|
||||
for _, l := range listeners {
|
||||
l.OnDelete(KV{
|
||||
Key: string(ev.Kv.Key),
|
||||
|
||||
49
core/fs/temps_test.go
Normal file
49
core/fs/temps_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,16 @@ func Marshal(v interface{}) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// MarshalToString marshals v into a string.
|
||||
func MarshalToString(v interface{}) (string, error) {
|
||||
data, err := Marshal(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals data bytes into v.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
|
||||
103
core/jsonx/json_test.go
Normal file
103
core/jsonx/json_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
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 TestMarshalToString(t *testing.T) {
|
||||
var v = struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}{
|
||||
Name: "John",
|
||||
Age: 30,
|
||||
}
|
||||
toString, err := MarshalToString(v)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, `{"name":"John","age":30}`, toString)
|
||||
|
||||
_, err = MarshalToString(make(chan int))
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package limit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -74,7 +75,12 @@ func NewPeriodLimit(period, quota int, limitStore *redis.Redis, keyPrefix string
|
||||
|
||||
// Take requests a permit, it returns the permit state.
|
||||
func (h *PeriodLimit) Take(key string) (int, error) {
|
||||
resp, err := h.limitStore.Eval(periodScript, []string{h.keyPrefix + key}, []string{
|
||||
return h.TakeCtx(context.Background(), key)
|
||||
}
|
||||
|
||||
// TakeCtx requests a permit with context, it returns the permit state.
|
||||
func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
|
||||
resp, err := h.limitStore.EvalCtx(ctx, periodScript, []string{h.keyPrefix + key}, []string{
|
||||
strconv.Itoa(h.quota),
|
||||
strconv.Itoa(h.calcExpireSeconds()),
|
||||
})
|
||||
|
||||
26
core/logx/color.go
Normal file
26
core/logx/color.go
Normal 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
33
core/logx/color_test.go
Normal 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)
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
const durationCallerDepth = 3
|
||||
|
||||
type durationLogger logEntry
|
||||
|
||||
// WithDuration returns a Logger which logs the given duration.
|
||||
func WithDuration(d time.Duration) Logger {
|
||||
return &durationLogger{
|
||||
@@ -20,57 +15,62 @@ func WithDuration(d time.Duration) Logger {
|
||||
}
|
||||
}
|
||||
|
||||
type durationLogger logEntry
|
||||
|
||||
func (l *durationLogger) Error(v ...interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
|
||||
}
|
||||
l.err(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (l *durationLogger) Errorf(format string, v ...interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
|
||||
}
|
||||
l.err(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *durationLogger) Errorv(v interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(errorLog, levelError, v)
|
||||
}
|
||||
l.err(v)
|
||||
}
|
||||
|
||||
func (l *durationLogger) Errorw(msg string, fields ...LogField) {
|
||||
l.err(msg, fields...)
|
||||
}
|
||||
|
||||
func (l *durationLogger) Info(v ...interface{}) {
|
||||
if shallLog(InfoLevel) {
|
||||
l.write(infoLog, levelInfo, fmt.Sprint(v...))
|
||||
}
|
||||
l.info(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (l *durationLogger) Infof(format string, v ...interface{}) {
|
||||
if shallLog(InfoLevel) {
|
||||
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
|
||||
}
|
||||
l.info(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *durationLogger) Infov(v interface{}) {
|
||||
if shallLog(InfoLevel) {
|
||||
l.write(infoLog, levelInfo, v)
|
||||
}
|
||||
l.info(v)
|
||||
}
|
||||
|
||||
func (l *durationLogger) Infow(msg string, fields ...LogField) {
|
||||
l.info(msg, fields...)
|
||||
}
|
||||
|
||||
func (l *durationLogger) Slow(v ...interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(slowLog, levelSlow, fmt.Sprint(v...))
|
||||
}
|
||||
l.slow(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (l *durationLogger) Slowf(format string, v ...interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
|
||||
}
|
||||
l.slow(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *durationLogger) Slowv(v interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(slowLog, levelSlow, v)
|
||||
l.slow(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
|
||||
}
|
||||
|
||||
func (l *durationLogger) write(writer io.Writer, level string, val interface{}) {
|
||||
switch atomic.LoadUint32(&encoding) {
|
||||
case plainEncodingType:
|
||||
writePlainAny(writer, level, val, l.Duration)
|
||||
default:
|
||||
outputJson(writer, &durationLogger{
|
||||
Timestamp: getTimestamp(),
|
||||
Level: level,
|
||||
Content: val,
|
||||
Duration: l.Duration,
|
||||
})
|
||||
func (l *durationLogger) err(v interface{}, fields ...LogField) {
|
||||
if shallLog(ErrorLevel) {
|
||||
fields = append(fields, Field(durationKey, l.Duration))
|
||||
getWriter().Error(v, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *durationLogger) info(v interface{}, fields ...LogField) {
|
||||
if shallLog(InfoLevel) {
|
||||
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...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,62 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"log"
|
||||
"context"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.opentelemetry.io/otel"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
func TestWithDurationError(t *testing.T) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
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) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
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) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
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) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
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) {
|
||||
@@ -45,43 +66,96 @@ func TestWithDurationInfoConsole(t *testing.T) {
|
||||
atomic.StoreUint32(&encoding, old)
|
||||
}()
|
||||
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
o := writer.Swap(w)
|
||||
defer writer.Store(o)
|
||||
|
||||
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) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
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) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
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) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
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) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
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) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -9,23 +8,27 @@ import (
|
||||
)
|
||||
|
||||
func TestLessLogger_Error(t *testing.T) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
l := NewLessLogger(500)
|
||||
for i := 0; i < 100; i++ {
|
||||
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) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
l := NewLessLogger(500)
|
||||
for i := 0; i < 100; i++ {
|
||||
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
38
core/logx/logger.go
Normal 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
|
||||
}
|
||||
@@ -1,93 +1,29 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/iox"
|
||||
"github.com/zeromicro/go-zero/core/sysx"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
const (
|
||||
// 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
|
||||
)
|
||||
const callerDepth = 5
|
||||
|
||||
var (
|
||||
// ErrLogPathNotSet is an error that indicates the log path is not set.
|
||||
ErrLogPathNotSet = errors.New("log path must be set")
|
||||
// ErrLogNotInitialized is an error that log is not initialized.
|
||||
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
|
||||
timeFormat = "2006-01-02T15:04:05.000Z07:00"
|
||||
logLevel uint32
|
||||
encoding uint32 = jsonEncodingType
|
||||
// use uint32 for atomic operations
|
||||
disableStat uint32
|
||||
infoLog io.WriteCloser
|
||||
errorLog io.WriteCloser
|
||||
severeLog io.WriteCloser
|
||||
slowLog io.WriteCloser
|
||||
statLog io.WriteCloser
|
||||
stackLog io.Writer
|
||||
|
||||
once sync.Once
|
||||
initialized uint32
|
||||
options logOptions
|
||||
options logOptions
|
||||
writer = new(atomicWriter)
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -95,109 +31,37 @@ type (
|
||||
Timestamp string `json:"@timestamp"`
|
||||
Level string `json:"level"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
Caller string `json:"caller,omitempty"`
|
||||
Content interface{} `json:"content"`
|
||||
}
|
||||
|
||||
logEntryWithFields map[string]interface{}
|
||||
|
||||
logOptions struct {
|
||||
gzipEnabled bool
|
||||
logStackCooldownMills 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 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.
|
||||
func Alert(v string) {
|
||||
outputText(errorLog, levelAlert, v)
|
||||
getWriter().Alert(v)
|
||||
}
|
||||
|
||||
// Close closes the logging.
|
||||
func Close() error {
|
||||
if writeConsole {
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if w := writer.Swap(nil); w != nil {
|
||||
return w.(io.Closer).Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -205,16 +69,7 @@ func Close() error {
|
||||
|
||||
// Disable disables the logging.
|
||||
func Disable() {
|
||||
once.Do(func() {
|
||||
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
|
||||
})
|
||||
writer.Store(nopWriter{})
|
||||
}
|
||||
|
||||
// DisableStat disables the stat logs.
|
||||
@@ -224,22 +79,12 @@ func DisableStat() {
|
||||
|
||||
// Error writes v into error log.
|
||||
func Error(v ...interface{}) {
|
||||
ErrorCaller(1, 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)
|
||||
errorTextSync(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Errorf writes v with format into error log.
|
||||
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.
|
||||
@@ -260,6 +105,49 @@ func Errorv(v interface{}) {
|
||||
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.
|
||||
func Info(v ...interface{}) {
|
||||
infoTextSync(fmt.Sprint(v...))
|
||||
@@ -275,14 +163,32 @@ func Infov(v interface{}) {
|
||||
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.
|
||||
func Must(err error) {
|
||||
if err != nil {
|
||||
msg := formatWithCaller(err.Error(), 3)
|
||||
log.Print(msg)
|
||||
outputText(severeLog, levelFatal, msg)
|
||||
os.Exit(1)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -290,6 +196,43 @@ func SetLevel(level uint32) {
|
||||
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.
|
||||
func Severe(v ...interface{}) {
|
||||
severeSync(fmt.Sprint(v...))
|
||||
@@ -315,6 +258,11 @@ func Slowv(v interface{}) {
|
||||
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.
|
||||
func Stat(v ...interface{}) {
|
||||
statSync(fmt.Sprint(v...))
|
||||
@@ -357,52 +305,30 @@ func createOutput(path string) (io.WriteCloser, error) {
|
||||
|
||||
func errorAnySync(v interface{}) {
|
||||
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) {
|
||||
outputError(errorLog, msg, callDepth)
|
||||
getWriter().Error(content, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
func formatWithCaller(msg string, callDepth int) string {
|
||||
var buf strings.Builder
|
||||
|
||||
caller := getCaller(callDepth)
|
||||
if len(caller) > 0 {
|
||||
buf.WriteString(caller)
|
||||
buf.WriteByte(' ')
|
||||
func errorTextSync(msg string) {
|
||||
if shallLog(ErrorLevel) {
|
||||
getWriter().Error(msg)
|
||||
}
|
||||
|
||||
buf.WriteString(msg)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func getCaller(callDepth int) string {
|
||||
var buf strings.Builder
|
||||
|
||||
_, file, line, ok := runtime.Caller(callDepth)
|
||||
if ok {
|
||||
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))
|
||||
func getWriter() Writer {
|
||||
w := writer.Load()
|
||||
if w == nil {
|
||||
w = newConsoleWriter()
|
||||
writer.Store(w)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func getTimestamp() string {
|
||||
return timex.Time().Format(timeFormat)
|
||||
return w
|
||||
}
|
||||
|
||||
func handleOptions(opts []LogOption) {
|
||||
@@ -413,56 +339,19 @@ func handleOptions(opts []LogOption) {
|
||||
|
||||
func infoAnySync(val interface{}) {
|
||||
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) {
|
||||
if shallLog(InfoLevel) {
|
||||
outputText(infoLog, levelInfo, 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'))
|
||||
getWriter().Info(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,72 +366,18 @@ func setupLogLevel(c LogConf) {
|
||||
}
|
||||
}
|
||||
|
||||
func setupWithConsole(c LogConf) {
|
||||
once.Do(func() {
|
||||
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 setupWithConsole() {
|
||||
SetWriter(newConsoleWriter())
|
||||
}
|
||||
|
||||
func setupWithFiles(c LogConf) error {
|
||||
var opts []LogOption
|
||||
var err error
|
||||
|
||||
if len(c.Path) == 0 {
|
||||
return ErrLogPathNotSet
|
||||
w, err := newFileWriter(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
SetWriter(w)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupWithVolume(c LogConf) error {
|
||||
@@ -556,7 +391,7 @@ func setupWithVolume(c LogConf) error {
|
||||
|
||||
func severeSync(msg string) {
|
||||
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{}) {
|
||||
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) {
|
||||
if shallLog(ErrorLevel) {
|
||||
outputText(slowLog, levelSlow, msg)
|
||||
getWriter().Slow(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func stackSync(msg string) {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -19,8 +19,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
s = []byte("Sending #11 notification (id: 1451875113812010473) in #1 connection")
|
||||
pool = make(chan []byte, 1)
|
||||
s = []byte("Sending #11 notification (id: 1451875113812010473) in #1 connection")
|
||||
pool = make(chan []byte, 1)
|
||||
_ Writer = (*mockWriter)(nil)
|
||||
)
|
||||
|
||||
type mockWriter struct {
|
||||
@@ -28,10 +29,46 @@ type mockWriter struct {
|
||||
builder strings.Builder
|
||||
}
|
||||
|
||||
func (mw *mockWriter) Write(data []byte) (int, error) {
|
||||
func (mw *mockWriter) Alert(v interface{}) {
|
||||
mw.lock.Lock()
|
||||
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 {
|
||||
@@ -56,95 +93,211 @@ func (mw *mockWriter) String() 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) {
|
||||
writer := new(mockWriter)
|
||||
errorLog = writer
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
file, line := getFileLine()
|
||||
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()
|
||||
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) {
|
||||
writer := new(mockWriter)
|
||||
writeConsole = true
|
||||
errorLog = newLogWriter(log.New(writer, "[ERROR] ", flags))
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
file, line := getFileLine()
|
||||
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()
|
||||
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) {
|
||||
doTestStructedLog(t, levelAlert, func(writer io.WriteCloser) {
|
||||
errorLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelAlert, w, func(v ...interface{}) {
|
||||
Alert(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogError(t *testing.T) {
|
||||
doTestStructedLog(t, levelError, func(writer io.WriteCloser) {
|
||||
errorLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
|
||||
Error(v...)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogErrorf(t *testing.T) {
|
||||
doTestStructedLog(t, levelError, func(writer io.WriteCloser) {
|
||||
errorLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
|
||||
Errorf("%s", fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogErrorv(t *testing.T) {
|
||||
doTestStructedLog(t, levelError, func(writer io.WriteCloser) {
|
||||
errorLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
|
||||
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) {
|
||||
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
|
||||
infoLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
|
||||
Info(v...)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogInfof(t *testing.T) {
|
||||
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
|
||||
infoLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
|
||||
Infof("%s", fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogInfov(t *testing.T) {
|
||||
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
|
||||
infoLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
|
||||
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) {
|
||||
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
|
||||
infoLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
defer func() {
|
||||
@@ -156,9 +309,11 @@ func TestStructedLogInfoConsoleAny(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStructedLogInfoConsoleAnyString(t *testing.T) {
|
||||
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
|
||||
infoLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
defer func() {
|
||||
@@ -170,9 +325,11 @@ func TestStructedLogInfoConsoleAnyString(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStructedLogInfoConsoleAnyError(t *testing.T) {
|
||||
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
|
||||
infoLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
defer func() {
|
||||
@@ -184,9 +341,11 @@ func TestStructedLogInfoConsoleAnyError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
|
||||
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
|
||||
infoLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
defer func() {
|
||||
@@ -200,9 +359,11 @@ func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStructedLogInfoConsoleText(t *testing.T) {
|
||||
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
|
||||
infoLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
defer func() {
|
||||
@@ -214,69 +375,94 @@ func TestStructedLogInfoConsoleText(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStructedLogSlow(t *testing.T) {
|
||||
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) {
|
||||
slowLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
|
||||
Slow(v...)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogSlowf(t *testing.T) {
|
||||
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) {
|
||||
slowLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
|
||||
Slowf(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogSlowv(t *testing.T) {
|
||||
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) {
|
||||
slowLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
|
||||
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) {
|
||||
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
|
||||
statLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelStat, w, func(v ...interface{}) {
|
||||
Stat(v...)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogStatf(t *testing.T) {
|
||||
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
|
||||
statLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelStat, w, func(v ...interface{}) {
|
||||
Statf(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogSevere(t *testing.T) {
|
||||
doTestStructedLog(t, levelSevere, func(writer io.WriteCloser) {
|
||||
severeLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSevere, w, func(v ...interface{}) {
|
||||
Severe(v...)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogSeveref(t *testing.T) {
|
||||
doTestStructedLog(t, levelSevere, func(writer io.WriteCloser) {
|
||||
severeLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSevere, w, func(v ...interface{}) {
|
||||
Severef(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogWithDuration(t *testing.T) {
|
||||
const message = "hello there"
|
||||
writer := new(mockWriter)
|
||||
infoLog = writer
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
WithDuration(time.Second).Info(message)
|
||||
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)
|
||||
}
|
||||
assert.Equal(t, levelInfo, entry.Level)
|
||||
@@ -287,11 +473,12 @@ func TestStructedLogWithDuration(t *testing.T) {
|
||||
func TestSetLevel(t *testing.T) {
|
||||
SetLevel(ErrorLevel)
|
||||
const message = "hello there"
|
||||
writer := new(mockWriter)
|
||||
infoLog = writer
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
Info(message)
|
||||
assert.Equal(t, 0, writer.builder.Len())
|
||||
assert.Equal(t, 0, w.builder.Len())
|
||||
}
|
||||
|
||||
func TestSetLevelTwiceWithMode(t *testing.T) {
|
||||
@@ -300,29 +487,35 @@ func TestSetLevelTwiceWithMode(t *testing.T) {
|
||||
"console",
|
||||
"volumn",
|
||||
}
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
for _, mode := range testModes {
|
||||
testSetLevelTwiceWithMode(t, mode)
|
||||
testSetLevelTwiceWithMode(t, mode, w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetLevelWithDuration(t *testing.T) {
|
||||
SetLevel(ErrorLevel)
|
||||
const message = "hello there"
|
||||
writer := new(mockWriter)
|
||||
infoLog = writer
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
WithDuration(time.Second).Info(message)
|
||||
assert.Equal(t, 0, writer.builder.Len())
|
||||
assert.Equal(t, 0, w.builder.Len())
|
||||
}
|
||||
|
||||
func TestErrorfWithWrappedError(t *testing.T) {
|
||||
SetLevel(ErrorLevel)
|
||||
const message = "there"
|
||||
writer := new(mockWriter)
|
||||
errorLog = writer
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
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) {
|
||||
@@ -330,6 +523,11 @@ func TestMustNil(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
defer func() {
|
||||
SetLevel(InfoLevel)
|
||||
atomic.StoreUint32(&encoding, jsonEncodingType)
|
||||
}()
|
||||
|
||||
MustSetup(LogConf{
|
||||
ServiceName: "any",
|
||||
Mode: "console",
|
||||
@@ -344,6 +542,17 @@ func TestSetup(t *testing.T) {
|
||||
Mode: "volume",
|
||||
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, setupWithFiles(LogConf{}))
|
||||
assert.Nil(t, setupWithFiles(LogConf{
|
||||
@@ -364,6 +573,8 @@ func TestSetup(t *testing.T) {
|
||||
_, err := createOutput("")
|
||||
assert.NotNil(t, err)
|
||||
Disable()
|
||||
SetLevel(InfoLevel)
|
||||
atomic.StoreUint32(&encoding, jsonEncodingType)
|
||||
}
|
||||
|
||||
func TestDisable(t *testing.T) {
|
||||
@@ -373,7 +584,6 @@ func TestDisable(t *testing.T) {
|
||||
WithKeepDays(1)(&opt)
|
||||
WithGzip()(&opt)
|
||||
assert.Nil(t, Close())
|
||||
writeConsole = false
|
||||
assert.Nil(t, Close())
|
||||
}
|
||||
|
||||
@@ -381,11 +591,20 @@ func TestDisableStat(t *testing.T) {
|
||||
DisableStat()
|
||||
|
||||
const message = "hello there"
|
||||
writer := new(mockWriter)
|
||||
statLog = writer
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
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) {
|
||||
@@ -487,15 +706,11 @@ func put(b []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func doTestStructedLog(t *testing.T, level string, setup func(writer io.WriteCloser),
|
||||
write func(...interface{})) {
|
||||
func doTestStructedLog(t *testing.T, level string, w *mockWriter, write func(...interface{})) {
|
||||
const message = "hello there"
|
||||
writer := new(mockWriter)
|
||||
setup(writer)
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
write(message)
|
||||
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)
|
||||
}
|
||||
assert.Equal(t, level, entry.Level)
|
||||
@@ -504,18 +719,14 @@ func doTestStructedLog(t *testing.T, level string, setup func(writer io.WriteClo
|
||||
assert.True(t, strings.Contains(val, message))
|
||||
}
|
||||
|
||||
func doTestStructedLogConsole(t *testing.T, setup func(writer io.WriteCloser),
|
||||
write func(...interface{})) {
|
||||
func doTestStructedLogConsole(t *testing.T, w *mockWriter, write func(...interface{})) {
|
||||
const message = "hello there"
|
||||
writer := new(mockWriter)
|
||||
setup(writer)
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
write(message)
|
||||
println(writer.String())
|
||||
assert.True(t, strings.Contains(writer.String(), message))
|
||||
assert.True(t, strings.Contains(w.String(), message))
|
||||
}
|
||||
|
||||
func testSetLevelTwiceWithMode(t *testing.T, mode string) {
|
||||
func testSetLevelTwiceWithMode(t *testing.T, mode string, w *mockWriter) {
|
||||
writer.Store(nil)
|
||||
SetUp(LogConf{
|
||||
Mode: mode,
|
||||
Level: "error",
|
||||
@@ -527,17 +738,14 @@ func testSetLevelTwiceWithMode(t *testing.T, mode string) {
|
||||
Path: "/dev/null",
|
||||
})
|
||||
const message = "hello there"
|
||||
writer := new(mockWriter)
|
||||
infoLog = writer
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
Info(message)
|
||||
assert.Equal(t, 0, writer.builder.Len())
|
||||
assert.Equal(t, 0, w.builder.Len())
|
||||
Infof(message)
|
||||
assert.Equal(t, 0, writer.builder.Len())
|
||||
assert.Equal(t, 0, w.builder.Len())
|
||||
ErrorStack(message)
|
||||
assert.Equal(t, 0, writer.builder.Len())
|
||||
assert.Equal(t, 0, w.builder.Len())
|
||||
ErrorStackf(message)
|
||||
assert.Equal(t, 0, writer.builder.Len())
|
||||
assert.Equal(t, 0, w.builder.Len())
|
||||
}
|
||||
|
||||
type ValStringer struct {
|
||||
@@ -547,3 +755,18 @@ type ValStringer struct {
|
||||
func (v ValStringer) String() string {
|
||||
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
22
core/logx/logwriter.go
Normal 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
197
core/logx/readme-cn.md
Normal 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
197
core/logx/readme.md
Normal 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. It’s 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!
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
|
||||
"github.com/zeromicro/go-zero/core/fs"
|
||||
"github.com/zeromicro/go-zero/core/lang"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -211,6 +210,12 @@ func (l *RotateLogger) maybeCompressFile(file string) {
|
||||
ErrorStack(r)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
// file not exists or other error, ignore compression
|
||||
return
|
||||
}
|
||||
|
||||
compressLogFile(file)
|
||||
}
|
||||
|
||||
@@ -290,12 +295,12 @@ func (l *RotateLogger) write(v []byte) {
|
||||
}
|
||||
|
||||
func compressLogFile(file string) {
|
||||
start := timex.Now()
|
||||
start := time.Now()
|
||||
Infof("compressing log file: %s", file)
|
||||
if err := gzipFile(file); err != nil {
|
||||
Errorf("compress error: %s", err)
|
||||
} else {
|
||||
Infof("compressed log file: %s, took %s", file, timex.Since(start))
|
||||
Infof("compressed log file: %s, took %s", file, time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,12 @@ func TestRotateLoggerGetBackupFilename(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRotateLoggerMayCompressFile(t *testing.T) {
|
||||
old := os.Stdout
|
||||
os.Stdout = os.NewFile(0, os.DevNull)
|
||||
defer func() {
|
||||
os.Stdout = old
|
||||
}()
|
||||
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
@@ -70,15 +76,18 @@ func TestRotateLoggerMayCompressFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRotateLoggerMayCompressFileTrue(t *testing.T) {
|
||||
old := os.Stdout
|
||||
os.Stdout = os.NewFile(0, os.DevNull)
|
||||
defer func() {
|
||||
os.Stdout = old
|
||||
}()
|
||||
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
logger, err := NewLogger(filename, new(DailyRotateRule), true)
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer func() {
|
||||
os.Remove(filename)
|
||||
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
|
||||
}()
|
||||
defer os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
|
||||
}
|
||||
logger.maybeCompressFile(filename)
|
||||
_, err = os.Stat(filename)
|
||||
@@ -92,7 +101,6 @@ func TestRotateLoggerRotate(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer func() {
|
||||
os.Remove(filename)
|
||||
os.Remove(logger.getBackupFilename())
|
||||
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
|
||||
}()
|
||||
@@ -102,6 +110,10 @@ func TestRotateLoggerRotate(t *testing.T) {
|
||||
case *os.LinkError:
|
||||
// avoid rename error on docker container
|
||||
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:
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -115,12 +127,18 @@ func TestRotateLoggerWrite(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer func() {
|
||||
os.Remove(filename)
|
||||
os.Remove(logger.getBackupFilename())
|
||||
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
|
||||
}()
|
||||
}
|
||||
// the following write calls cannot be changed to Write, because of DATA RACE.
|
||||
logger.write([]byte(`foo`))
|
||||
rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
|
||||
logger.write([]byte(`bar`))
|
||||
logger.Close()
|
||||
logger.write([]byte(`baz`))
|
||||
}
|
||||
|
||||
func TestLogWriterClose(t *testing.T) {
|
||||
assert.Nil(t, newLogWriter(nil).Close())
|
||||
}
|
||||
|
||||
@@ -29,16 +29,16 @@ func TestRedirector(t *testing.T) {
|
||||
}
|
||||
|
||||
func captureOutput(f func()) string {
|
||||
writer := new(mockWriter)
|
||||
infoLog = writer
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
prevLevel := atomic.LoadUint32(&logLevel)
|
||||
SetLevel(InfoLevel)
|
||||
f()
|
||||
SetLevel(prevLevel)
|
||||
|
||||
return writer.builder.String()
|
||||
return w.String()
|
||||
}
|
||||
|
||||
func getContent(jsonStr string) string {
|
||||
|
||||
@@ -3,73 +3,79 @@ package logx
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
"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 {
|
||||
logEntry
|
||||
Trace string `json:"trace,omitempty"`
|
||||
Span string `json:"span,omitempty"`
|
||||
ctx context.Context
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (l *traceLogger) Error(v ...interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
|
||||
}
|
||||
l.err(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (l *traceLogger) Errorf(format string, v ...interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
|
||||
}
|
||||
l.err(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *traceLogger) Errorv(v interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(errorLog, levelError, v)
|
||||
}
|
||||
l.err(fmt.Sprint(v))
|
||||
}
|
||||
|
||||
func (l *traceLogger) Errorw(msg string, fields ...LogField) {
|
||||
l.err(msg, fields...)
|
||||
}
|
||||
|
||||
func (l *traceLogger) Info(v ...interface{}) {
|
||||
if shallLog(InfoLevel) {
|
||||
l.write(infoLog, levelInfo, fmt.Sprint(v...))
|
||||
}
|
||||
l.info(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (l *traceLogger) Infof(format string, v ...interface{}) {
|
||||
if shallLog(InfoLevel) {
|
||||
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
|
||||
}
|
||||
l.info(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *traceLogger) Infov(v interface{}) {
|
||||
if shallLog(InfoLevel) {
|
||||
l.write(infoLog, levelInfo, v)
|
||||
}
|
||||
l.info(v)
|
||||
}
|
||||
|
||||
func (l *traceLogger) Infow(msg string, fields ...LogField) {
|
||||
l.info(msg, fields...)
|
||||
}
|
||||
|
||||
func (l *traceLogger) Slow(v ...interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(slowLog, levelSlow, fmt.Sprint(v...))
|
||||
}
|
||||
l.slow(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (l *traceLogger) Slowf(format string, v ...interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
|
||||
}
|
||||
l.slow(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *traceLogger) Slowv(v interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(slowLog, levelSlow, v)
|
||||
l.slow(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 {
|
||||
@@ -77,31 +83,37 @@ func (l *traceLogger) WithDuration(duration time.Duration) Logger {
|
||||
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)
|
||||
if len(traceID) > 0 {
|
||||
fields = append(fields, Field(traceKey, traceID))
|
||||
}
|
||||
spanID := spanIdFromContext(l.ctx)
|
||||
if len(spanID) > 0 {
|
||||
fields = append(fields, Field(spanKey, spanID))
|
||||
}
|
||||
|
||||
switch atomic.LoadUint32(&encoding) {
|
||||
case plainEncodingType:
|
||||
writePlainAny(writer, level, val, l.Duration, traceID, spanID)
|
||||
default:
|
||||
outputJson(writer, &traceLogger{
|
||||
logEntry: logEntry{
|
||||
Timestamp: getTimestamp(),
|
||||
Level: level,
|
||||
Duration: l.Duration,
|
||||
Content: val,
|
||||
},
|
||||
Trace: traceID,
|
||||
Span: spanID,
|
||||
})
|
||||
return fields
|
||||
}
|
||||
|
||||
func (l *traceLogger) err(v interface{}, fields ...LogField) {
|
||||
if shallLog(ErrorLevel) {
|
||||
getWriter().Error(v, l.buildFields(fields...)...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext sets ctx to log, for keeping tracing information.
|
||||
func WithContext(ctx context.Context) Logger {
|
||||
return &traceLogger{
|
||||
ctx: ctx,
|
||||
func (l *traceLogger) info(v interface{}, fields ...LogField) {
|
||||
if shallLog(InfoLevel) {
|
||||
getWriter().Info(v, l.buildFields(fields...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *traceLogger) slow(v interface{}, fields ...LogField) {
|
||||
if shallLog(ErrorLevel) {
|
||||
getWriter().Slow(v, l.buildFields(fields...)...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ package logx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -13,142 +14,207 @@ import (
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
traceKey = "trace"
|
||||
spanKey = "span"
|
||||
)
|
||||
|
||||
func TestTraceLog(t *testing.T) {
|
||||
var buf mockWriter
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
SetLevel(InfoLevel)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
writer.lock.RLock()
|
||||
defer func() {
|
||||
writer.lock.RUnlock()
|
||||
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")
|
||||
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
|
||||
defer span.End()
|
||||
|
||||
WithContext(ctx).Info(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
}
|
||||
|
||||
func TestTraceError(t *testing.T) {
|
||||
var buf mockWriter
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
errorLog = newLogWriter(log.New(&buf, "", flags))
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
writer.lock.RLock()
|
||||
defer func() {
|
||||
writer.lock.RUnlock()
|
||||
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")
|
||||
l := WithContext(ctx).(*traceLogger)
|
||||
SetLevel(InfoLevel)
|
||||
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
|
||||
defer span.End()
|
||||
|
||||
var nilCtx context.Context
|
||||
l := WithContext(context.Background())
|
||||
l = l.WithContext(nilCtx)
|
||||
l = l.WithContext(ctx)
|
||||
SetLevel(ErrorLevel)
|
||||
l.WithDuration(time.Second).Error(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Errorf(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Errorv(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
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"))
|
||||
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) {
|
||||
var buf mockWriter
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
infoLog = newLogWriter(log.New(&buf, "", flags))
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
writer.lock.RLock()
|
||||
defer func() {
|
||||
writer.lock.RUnlock()
|
||||
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")
|
||||
l := WithContext(ctx).(*traceLogger)
|
||||
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
|
||||
defer span.End()
|
||||
|
||||
SetLevel(InfoLevel)
|
||||
l := WithContext(ctx)
|
||||
l.WithDuration(time.Second).Info(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infof(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infov(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
validate(t, w.String(), true, true)
|
||||
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) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, jsonEncodingType)
|
||||
old := atomic.SwapUint32(&encoding, jsonEncodingType)
|
||||
defer atomic.StoreUint32(&encoding, old)
|
||||
|
||||
w := new(mockWriter)
|
||||
o := writer.Swap(w)
|
||||
writer.lock.RLock()
|
||||
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()
|
||||
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
|
||||
otel.SetTracerProvider(tp)
|
||||
defer otel.SetTracerProvider(otp)
|
||||
|
||||
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar")
|
||||
l := WithContext(ctx).(*traceLogger)
|
||||
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
|
||||
defer span.End()
|
||||
|
||||
l := WithContext(ctx)
|
||||
SetLevel(InfoLevel)
|
||||
l.WithDuration(time.Second).Info(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceIdFromContext(ctx)))
|
||||
assert.True(t, strings.Contains(buf.String(), spanIdFromContext(ctx)))
|
||||
buf.Reset()
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infof(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceIdFromContext(ctx)))
|
||||
assert.True(t, strings.Contains(buf.String(), spanIdFromContext(ctx)))
|
||||
buf.Reset()
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infov(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceIdFromContext(ctx)))
|
||||
assert.True(t, strings.Contains(buf.String(), spanIdFromContext(ctx)))
|
||||
validate(t, w.String(), true, true)
|
||||
}
|
||||
|
||||
func TestTraceSlow(t *testing.T) {
|
||||
var buf mockWriter
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
slowLog = newLogWriter(log.New(&buf, "", flags))
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
writer.lock.RLock()
|
||||
defer func() {
|
||||
writer.lock.RUnlock()
|
||||
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")
|
||||
l := WithContext(ctx).(*traceLogger)
|
||||
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
|
||||
defer span.End()
|
||||
|
||||
l := WithContext(ctx)
|
||||
SetLevel(InfoLevel)
|
||||
l.WithDuration(time.Second).Slow(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
assert.True(t, strings.Contains(w.String(), traceKey))
|
||||
assert.True(t, strings.Contains(w.String(), spanKey))
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Slowf(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Slowv(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
validate(t, w.String(), true, true)
|
||||
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) {
|
||||
var buf mockWriter
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
infoLog = newLogWriter(log.New(&buf, "", flags))
|
||||
l := WithContext(context.Background()).(*traceLogger)
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
writer.lock.RLock()
|
||||
defer func() {
|
||||
writer.lock.RUnlock()
|
||||
writer.Store(old)
|
||||
}()
|
||||
|
||||
l := WithContext(context.Background())
|
||||
SetLevel(InfoLevel)
|
||||
l.WithDuration(time.Second).Info(testlog)
|
||||
assert.False(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.False(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
validate(t, w.String(), false, false)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infof(testlog)
|
||||
assert.False(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.False(t, strings.Contains(buf.String(), spanKey))
|
||||
validate(t, w.String(), false, false)
|
||||
}
|
||||
|
||||
func validate(t *testing.T, body string, expectedTrace, expectedSpan bool) {
|
||||
var val mockValue
|
||||
dec := json.NewDecoder(strings.NewReader(body))
|
||||
|
||||
for {
|
||||
var doc mockValue
|
||||
err := dec.Decode(&doc)
|
||||
if err == io.EOF {
|
||||
// all done
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
val = doc
|
||||
}
|
||||
|
||||
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
35
core/logx/util.go
Normal 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
72
core/logx/util_test.go
Normal 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
61
core/logx/vars.go
Normal 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
348
core/logx/writer.go
Normal 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
179
core/logx/writer_test.go
Normal 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")
|
||||
}
|
||||
29
core/mapping/tomlunmarshaler.go
Normal file
29
core/mapping/tomlunmarshaler.go
Normal 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)
|
||||
}
|
||||
41
core/mapping/tomlunmarshaler_test.go
Normal file
41
core/mapping/tomlunmarshaler_test.go
Normal 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))
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func (u *Unmarshaler) processFieldPrimitive(field reflect.StructField, value ref
|
||||
return u.processFieldPrimitiveWithJSONNumber(field, value, v, opts, fullName)
|
||||
default:
|
||||
if typeKind == valueKind {
|
||||
if err := validateValueInOptions(opts.options(), mapValue); err != nil {
|
||||
if err := validateValueInOptions(mapValue, opts.options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -253,6 +253,10 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(field reflect.StructFi
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateValueInOptions(v, opts.options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fieldKind == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
@@ -496,10 +500,20 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
|
||||
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{}
|
||||
if err := jsonx.UnmarshalFromString(mapValue.(string), &slice); err != nil {
|
||||
return err
|
||||
switch v := mapValue.(type) {
|
||||
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())
|
||||
@@ -520,8 +534,10 @@ func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int,
|
||||
baseKind reflect.Kind, value interface{}) error {
|
||||
ithVal := slice.Index(index)
|
||||
switch v := value.(type) {
|
||||
case json.Number:
|
||||
case fmt.Stringer:
|
||||
return setValue(baseKind, ithVal, v.String())
|
||||
case string:
|
||||
return setValue(baseKind, ithVal, v)
|
||||
default:
|
||||
// don't need to consider the difference between int, int8, int16, int32, int64,
|
||||
// uint, uint8, uint16, uint32, uint64, because they're handled as json.Number.
|
||||
|
||||
@@ -1170,6 +1170,28 @@ func TestUnmarshalWithIntOptionsIncorrect(t *testing.T) {
|
||||
assert.NotNil(t, UnmarshalKey(m, &in))
|
||||
}
|
||||
|
||||
func TestUnmarshalWithJsonNumberOptionsIncorrect(t *testing.T) {
|
||||
type inner struct {
|
||||
Value string `key:"value,options=first|second"`
|
||||
Incorrect int `key:"incorrect,options=1|2"`
|
||||
}
|
||||
m := map[string]interface{}{
|
||||
"value": "first",
|
||||
"incorrect": json.Number("3"),
|
||||
}
|
||||
|
||||
var in inner
|
||||
assert.NotNil(t, UnmarshalKey(m, &in))
|
||||
}
|
||||
|
||||
func TestUnmarshaler_UnmarshalIntOptions(t *testing.T) {
|
||||
var val struct {
|
||||
Sex int `json:"sex,options=0|1"`
|
||||
}
|
||||
input := []byte(`{"sex": 2}`)
|
||||
assert.NotNil(t, UnmarshalJsonBytes(input, &val))
|
||||
}
|
||||
|
||||
func TestUnmarshalWithUintOptionsCorrect(t *testing.T) {
|
||||
type inner struct {
|
||||
Value string `key:"value,options=first|second"`
|
||||
@@ -2659,7 +2681,7 @@ func TestUnmarshalJsonReaderMultiArray(t *testing.T) {
|
||||
assert.Equal(t, 2, len(res.B))
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonReaderPtrMultiArray(t *testing.T) {
|
||||
func TestUnmarshalJsonReaderPtrMultiArrayString(t *testing.T) {
|
||||
payload := `{"a": "133", "b": [["add", "cccd"], ["eeee"]]}`
|
||||
var res struct {
|
||||
A string `json:"a"`
|
||||
@@ -2672,6 +2694,32 @@ func TestUnmarshalJsonReaderPtrMultiArray(t *testing.T) {
|
||||
assert.Equal(t, 2, len(res.B[0]))
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonReaderPtrMultiArrayString_Int(t *testing.T) {
|
||||
payload := `{"a": "133", "b": [[11, 22], [33]]}`
|
||||
var res struct {
|
||||
A string `json:"a"`
|
||||
B [][]*string `json:"b"`
|
||||
}
|
||||
reader := strings.NewReader(payload)
|
||||
err := UnmarshalJsonReader(reader, &res)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(res.B))
|
||||
assert.Equal(t, 2, len(res.B[0]))
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonReaderPtrMultiArrayInt(t *testing.T) {
|
||||
payload := `{"a": "133", "b": [[11, 22], [33]]}`
|
||||
var res struct {
|
||||
A string `json:"a"`
|
||||
B [][]*int `json:"b"`
|
||||
}
|
||||
reader := strings.NewReader(payload)
|
||||
err := UnmarshalJsonReader(reader, &res)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(res.B))
|
||||
assert.Equal(t, 2, len(res.B[0]))
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonReaderPtrArray(t *testing.T) {
|
||||
payload := `{"a": "133", "b": ["add", "cccd", "eeee"]}`
|
||||
var res struct {
|
||||
@@ -2684,6 +2732,30 @@ func TestUnmarshalJsonReaderPtrArray(t *testing.T) {
|
||||
assert.Equal(t, 3, len(res.B))
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonReaderPtrArray_Int(t *testing.T) {
|
||||
payload := `{"a": "133", "b": [11, 22, 33]}`
|
||||
var res struct {
|
||||
A string `json:"a"`
|
||||
B []*string `json:"b"`
|
||||
}
|
||||
reader := strings.NewReader(payload)
|
||||
err := UnmarshalJsonReader(reader, &res)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(res.B))
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonReaderPtrInt(t *testing.T) {
|
||||
payload := `{"a": "133", "b": [11, 22, 33]}`
|
||||
var res struct {
|
||||
A string `json:"a"`
|
||||
B []*string `json:"b"`
|
||||
}
|
||||
reader := strings.NewReader(payload)
|
||||
err := UnmarshalJsonReader(reader, &res)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(res.B))
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonWithoutKey(t *testing.T) {
|
||||
payload := `{"A": "1", "B": "2"}`
|
||||
var res struct {
|
||||
@@ -2777,6 +2849,36 @@ func TestUnmarshalJsonReaderComplex(t *testing.T) {
|
||||
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) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var a struct {
|
||||
|
||||
@@ -143,6 +143,23 @@ func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fie
|
||||
return key, &fieldOpts, nil
|
||||
}
|
||||
|
||||
// ensureValue ensures nested members not to be nil.
|
||||
// If pointer value is nil, set to a new value.
|
||||
func ensureValue(v reflect.Value) reflect.Value {
|
||||
for {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
break
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {
|
||||
numFields := tp.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
@@ -478,6 +495,7 @@ func setValue(kind reflect.Kind, value reflect.Value, str string) error {
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
value = ensureValue(value)
|
||||
v, err := convertType(kind, str)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -592,16 +610,16 @@ func validateNumberRange(fv float64, nr *numberRange) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateValueInOptions(options []string, value interface{}) error {
|
||||
func validateValueInOptions(val interface{}, options []string) error {
|
||||
if len(options) > 0 {
|
||||
switch v := value.(type) {
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
if !stringx.Contains(options, v) {
|
||||
return fmt.Errorf(`error: value "%s" is not defined in options "%v"`, v, options)
|
||||
}
|
||||
default:
|
||||
if !stringx.Contains(options, Repr(v)) {
|
||||
return fmt.Errorf(`error: value "%v" is not defined in options "%v"`, value, options)
|
||||
return fmt.Errorf(`error: value "%v" is not defined in options "%v"`, val, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ type Foo struct {
|
||||
StrWithTagAndOption string `key:"stringwithtag,string"`
|
||||
}
|
||||
|
||||
func TestDeferInt(t *testing.T) {
|
||||
func TestDerefInt(t *testing.T) {
|
||||
i := 1
|
||||
s := "hello"
|
||||
number := struct {
|
||||
@@ -60,6 +60,51 @@ func TestDeferInt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDerefValInt(t *testing.T) {
|
||||
i := 1
|
||||
s := "hello"
|
||||
number := struct {
|
||||
f float64
|
||||
}{
|
||||
f: 6.4,
|
||||
}
|
||||
cases := []struct {
|
||||
t reflect.Value
|
||||
expect reflect.Kind
|
||||
}{
|
||||
{
|
||||
t: reflect.ValueOf(i),
|
||||
expect: reflect.Int,
|
||||
},
|
||||
{
|
||||
t: reflect.ValueOf(&i),
|
||||
expect: reflect.Int,
|
||||
},
|
||||
{
|
||||
t: reflect.ValueOf(s),
|
||||
expect: reflect.String,
|
||||
},
|
||||
{
|
||||
t: reflect.ValueOf(&s),
|
||||
expect: reflect.String,
|
||||
},
|
||||
{
|
||||
t: reflect.ValueOf(number.f),
|
||||
expect: reflect.Float64,
|
||||
},
|
||||
{
|
||||
t: reflect.ValueOf(&number.f),
|
||||
expect: reflect.Float64,
|
||||
},
|
||||
}
|
||||
|
||||
for _, each := range cases {
|
||||
t.Run(each.t.String(), func(t *testing.T) {
|
||||
assert.Equal(t, each.expect, ensureValue(each.t).Kind())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKeyAndOptionWithoutTag(t *testing.T) {
|
||||
var foo Foo
|
||||
rte := reflect.TypeOf(&foo).Elem()
|
||||
|
||||
@@ -102,12 +102,12 @@ func ForEach(generate GenerateFunc, mapper ForEachFunc, opts ...Option) {
|
||||
options := buildOptions(opts...)
|
||||
panicChan := &onceChan{channel: make(chan interface{})}
|
||||
source := buildSource(generate, panicChan)
|
||||
collector := make(chan interface{}, options.workers)
|
||||
collector := make(chan interface{})
|
||||
done := make(chan lang.PlaceholderType)
|
||||
|
||||
go executeMappers(mapperContext{
|
||||
ctx: options.ctx,
|
||||
mapper: func(item interface{}, writer Writer) {
|
||||
mapper: func(item interface{}, _ Writer) {
|
||||
mapper(item)
|
||||
},
|
||||
source: source,
|
||||
@@ -376,9 +376,7 @@ type onceChan struct {
|
||||
}
|
||||
|
||||
func (oc *onceChan) write(val interface{}) {
|
||||
if atomic.AddInt32(&oc.wrote, 1) > 1 {
|
||||
return
|
||||
if atomic.CompareAndSwapInt32(&oc.wrote, 0, 1) {
|
||||
oc.channel <- val
|
||||
}
|
||||
|
||||
oc.channel <- val
|
||||
}
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
func TestDumpGoroutines(t *testing.T) {
|
||||
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()
|
||||
assert.True(t, strings.Contains(buf.String(), ".dump"))
|
||||
}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
func TestProfile(t *testing.T) {
|
||||
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()
|
||||
// start again should not work
|
||||
assert.NotNil(t, StartProfile())
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/proc"
|
||||
"github.com/zeromicro/go-zero/core/sysx"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,7 +46,7 @@ func Report(msg string) {
|
||||
if fn != nil {
|
||||
reported := lessExecutor.DoOrDiscard(func() {
|
||||
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 {
|
||||
fmt.Fprintf(&builder, "cluster: %s\n", clusterName)
|
||||
}
|
||||
|
||||
18
core/stores/cache/cachenode.go
vendored
18
core/stores/cache/cachenode.go
vendored
@@ -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.
|
||||
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)
|
||||
if err != nil {
|
||||
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,
|
||||
// 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.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,
|
||||
// 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)
|
||||
}
|
||||
|
||||
// TakeWithExpireCtx takes the result from cache first, if not found,
|
||||
// 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)
|
||||
return c.doTake(ctx, val, key, func(v interface{}) error {
|
||||
return query(v, expire)
|
||||
@@ -239,7 +243,11 @@ func (c cacheNode) doTake(ctx context.Context, v interface{}, key string,
|
||||
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.IncrementHit()
|
||||
|
||||
|
||||
14
core/stores/cache/cachenode_test.go
vendored
14
core/stores/cache/cachenode_test.go
vendored
@@ -88,7 +88,7 @@ func TestCacheNode_InvalidCache(t *testing.T) {
|
||||
assert.Equal(t, miniredis.ErrKeyNotFound, err)
|
||||
}
|
||||
|
||||
func TestCacheNode_Take(t *testing.T) {
|
||||
func TestCacheNode_SetWithExpire(t *testing.T) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
@@ -100,8 +100,18 @@ func TestCacheNode_Take(t *testing.T) {
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
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
|
||||
err = cn.Take(&str, "any", func(v interface{}) error {
|
||||
*v.(*string) = "value"
|
||||
|
||||
@@ -2,7 +2,7 @@ package clickhouse
|
||||
|
||||
import (
|
||||
// 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"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,18 +8,35 @@ import (
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
"github.com/zeromicro/go-zero/core/trace"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/session"
|
||||
"go.opentelemetry.io/otel"
|
||||
tracesdk "go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSlowThreshold = time.Millisecond * 500
|
||||
// spanName is the span name of the mongo calls.
|
||||
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
|
||||
@@ -120,341 +137,411 @@ func newCollection(collection *mongo.Collection, brk breaker.Breaker) Collection
|
||||
|
||||
func (c *decoratedCollection) Aggregate(ctx context.Context, pipeline interface{},
|
||||
opts ...*mopt.AggregateOptions) (cur *mongo.Cursor, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, aggregate)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
starTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDurationSimple("Aggregate", starTime, err)
|
||||
c.logDurationSimple(ctx, aggregate, starTime, err)
|
||||
}()
|
||||
|
||||
cur, err = c.Collection.Aggregate(ctx, pipeline, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) BulkWrite(ctx context.Context, models []mongo.WriteModel,
|
||||
opts ...*mopt.BulkWriteOptions) (res *mongo.BulkWriteResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, bulkWrite)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDurationSimple("BulkWrite", startTime, err)
|
||||
c.logDurationSimple(ctx, bulkWrite, startTime, err)
|
||||
}()
|
||||
|
||||
res, err = c.Collection.BulkWrite(ctx, models, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) CountDocuments(ctx context.Context, filter interface{},
|
||||
opts ...*mopt.CountOptions) (count int64, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, countDocuments)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDurationSimple("CountDocuments", startTime, err)
|
||||
c.logDurationSimple(ctx, countDocuments, startTime, err)
|
||||
}()
|
||||
|
||||
count, err = c.Collection.CountDocuments(ctx, filter, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) DeleteMany(ctx context.Context, filter interface{},
|
||||
opts ...*mopt.DeleteOptions) (res *mongo.DeleteResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, deleteMany)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDurationSimple("DeleteMany", startTime, err)
|
||||
c.logDurationSimple(ctx, deleteMany, startTime, err)
|
||||
}()
|
||||
|
||||
res, err = c.Collection.DeleteMany(ctx, filter, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) DeleteOne(ctx context.Context, filter interface{},
|
||||
opts ...*mopt.DeleteOptions) (res *mongo.DeleteResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, deleteOne)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDuration("DeleteOne", startTime, err, filter)
|
||||
c.logDuration(ctx, deleteOne, startTime, err, filter)
|
||||
}()
|
||||
|
||||
res, err = c.Collection.DeleteOne(ctx, filter, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) Distinct(ctx context.Context, fieldName string, filter interface{},
|
||||
opts ...*mopt.DistinctOptions) (val []interface{}, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, distinct)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDurationSimple("Distinct", startTime, err)
|
||||
c.logDurationSimple(ctx, distinct, startTime, err)
|
||||
}()
|
||||
|
||||
val, err = c.Collection.Distinct(ctx, fieldName, filter, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) EstimatedDocumentCount(ctx context.Context,
|
||||
opts ...*mopt.EstimatedDocumentCountOptions) (val int64, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, estimatedDocumentCount)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDurationSimple("EstimatedDocumentCount", startTime, err)
|
||||
c.logDurationSimple(ctx, estimatedDocumentCount, startTime, err)
|
||||
}()
|
||||
|
||||
val, err = c.Collection.EstimatedDocumentCount(ctx, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) Find(ctx context.Context, filter interface{},
|
||||
opts ...*mopt.FindOptions) (cur *mongo.Cursor, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, find)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDuration("Find", startTime, err, filter)
|
||||
c.logDuration(ctx, find, startTime, err, filter)
|
||||
}()
|
||||
|
||||
cur, err = c.Collection.Find(ctx, filter, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) FindOne(ctx context.Context, filter interface{},
|
||||
opts ...*mopt.FindOneOptions) (res *mongo.SingleResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, findOne)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDuration("FindOne", startTime, err, filter)
|
||||
c.logDuration(ctx, findOne, startTime, err, filter)
|
||||
}()
|
||||
|
||||
res = c.Collection.FindOne(ctx, filter, opts...)
|
||||
err = res.Err()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) FindOneAndDelete(ctx context.Context, filter interface{},
|
||||
opts ...*mopt.FindOneAndDeleteOptions) (res *mongo.SingleResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, findOneAndDelete)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDuration("FindOneAndDelete", startTime, err, filter)
|
||||
c.logDuration(ctx, findOneAndDelete, startTime, err, filter)
|
||||
}()
|
||||
|
||||
res = c.Collection.FindOneAndDelete(ctx, filter, opts...)
|
||||
err = res.Err()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) FindOneAndReplace(ctx context.Context, filter interface{},
|
||||
replacement interface{}, opts ...*mopt.FindOneAndReplaceOptions) (
|
||||
res *mongo.SingleResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, findOneAndReplace)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
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...)
|
||||
err = res.Err()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{},
|
||||
opts ...*mopt.FindOneAndUpdateOptions) (res *mongo.SingleResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, findOneAndUpdate)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
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...)
|
||||
err = res.Err()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) InsertMany(ctx context.Context, documents []interface{},
|
||||
opts ...*mopt.InsertManyOptions) (res *mongo.InsertManyResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, insertMany)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDurationSimple("InsertMany", startTime, err)
|
||||
c.logDurationSimple(ctx, insertMany, startTime, err)
|
||||
}()
|
||||
|
||||
res, err = c.Collection.InsertMany(ctx, documents, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) InsertOne(ctx context.Context, document interface{},
|
||||
opts ...*mopt.InsertOneOptions) (res *mongo.InsertOneResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, insertOne)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDuration("InsertOne", startTime, err, document)
|
||||
c.logDuration(ctx, insertOne, startTime, err, document)
|
||||
}()
|
||||
|
||||
res, err = c.Collection.InsertOne(ctx, document, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter interface{}, replacement interface{},
|
||||
opts ...*mopt.ReplaceOptions) (res *mongo.UpdateResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, replaceOne)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
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...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) UpdateByID(ctx context.Context, id interface{}, update interface{},
|
||||
opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, updateByID)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
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...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) UpdateMany(ctx context.Context, filter interface{}, update interface{},
|
||||
opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, updateMany)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
c.logDurationSimple("UpdateMany", startTime, err)
|
||||
c.logDurationSimple(ctx, updateMany, startTime, err)
|
||||
}()
|
||||
|
||||
res, err = c.Collection.UpdateMany(ctx, filter, update, opts...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) UpdateOne(ctx context.Context, filter interface{}, update interface{},
|
||||
opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, updateOne)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
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...)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) logDuration(method string, startTime time.Duration, err error,
|
||||
docs ...interface{}) {
|
||||
func (c *decoratedCollection) logDuration(ctx context.Context, method string,
|
||||
startTime time.Duration, err error, docs ...interface{}) {
|
||||
duration := timex.Since(startTime)
|
||||
content, e := json.Marshal(docs)
|
||||
if e != nil {
|
||||
logx.Error(err)
|
||||
logger := logx.WithContext(ctx).WithDuration(duration)
|
||||
|
||||
content, jerr := json.Marshal(docs)
|
||||
// jerr should not be non-nil, but we don't care much on this,
|
||||
// if non-nil, we just log without docs.
|
||||
if jerr != nil {
|
||||
if err != nil {
|
||||
if duration > slowThreshold.Load() {
|
||||
logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s)", c.name, method, err.Error())
|
||||
} else {
|
||||
logger.Infof("mongo(%s) - %s - fail(%s)", c.name, method, err.Error())
|
||||
}
|
||||
} else {
|
||||
if duration > slowThreshold.Load() {
|
||||
logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - ok", c.name, method)
|
||||
} else {
|
||||
logger.Infof("mongo(%s) - %s - ok", c.name, method)
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
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))
|
||||
} 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))
|
||||
}
|
||||
} else {
|
||||
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))
|
||||
} 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) {
|
||||
logDuration(c.name, method, startTime, err)
|
||||
func (c *decoratedCollection) logDurationSimple(ctx context.Context, method string, startTime time.Duration, err error) {
|
||||
logDuration(ctx, c.name, method, startTime, err)
|
||||
}
|
||||
|
||||
func (p keepablePromise) accept(err error) error {
|
||||
@@ -483,8 +570,3 @@ func acceptable(err error) bool {
|
||||
err == session.ErrAbortTwice || err == session.ErrCommitAfterAbort ||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ package mon
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
@@ -567,6 +569,63 @@ 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", timex.Now(), nil, "bar")
|
||||
assert.Contains(t, buf.String(), "foo")
|
||||
assert.Contains(t, buf.String(), "bar")
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration(context.Background(), "foo", timex.Now(), errors.New("bar"), make(chan int))
|
||||
assert.Contains(t, buf.String(), "foo")
|
||||
assert.Contains(t, buf.String(), "bar")
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration(context.Background(), "foo", timex.Now(), nil, make(chan int))
|
||||
assert.Contains(t, buf.String(), "foo")
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration(context.Background(), "foo", timex.Now()-slowThreshold.Load()*2,
|
||||
nil, make(chan int))
|
||||
assert.Contains(t, buf.String(), "foo")
|
||||
assert.Contains(t, buf.String(), "slowcall")
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration(context.Background(), "foo", timex.Now()-slowThreshold.Load()*2,
|
||||
errors.New("bar"), make(chan int))
|
||||
assert.Contains(t, buf.String(), "foo")
|
||||
assert.Contains(t, buf.String(), "bar")
|
||||
assert.Contains(t, buf.String(), "slowcall")
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration(context.Background(), "foo", timex.Now()-slowThreshold.Load()*2,
|
||||
errors.New("bar"))
|
||||
assert.Contains(t, buf.String(), "foo")
|
||||
assert.Contains(t, buf.String(), "slowcall")
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration(context.Background(), "foo", timex.Now()-slowThreshold.Load()*2, nil)
|
||||
assert.Contains(t, buf.String(), "foo")
|
||||
assert.Contains(t, buf.String(), "slowcall")
|
||||
}
|
||||
|
||||
type mockPromise struct {
|
||||
accepted bool
|
||||
reason string
|
||||
@@ -590,15 +649,15 @@ func (d *dropBreaker) Allow() (breaker.Promise, error) {
|
||||
return nil, errDummy
|
||||
}
|
||||
|
||||
func (d *dropBreaker) Do(req func() error) error {
|
||||
func (d *dropBreaker) Do(_ func() error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dropBreaker) DoWithAcceptable(req func() error, acceptable breaker.Acceptable) error {
|
||||
func (d *dropBreaker) DoWithAcceptable(_ func() error, _ breaker.Acceptable) error {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,14 @@ import (
|
||||
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
const (
|
||||
startSession = "StartSession"
|
||||
abortTransaction = "AbortTransaction"
|
||||
commitTransaction = "CommitTransaction"
|
||||
withTransaction = "WithTransaction"
|
||||
endSession = "EndSession"
|
||||
)
|
||||
|
||||
type (
|
||||
// Model is a mongodb store model that represents a collection.
|
||||
Model struct {
|
||||
@@ -23,7 +31,8 @@ type (
|
||||
|
||||
wrappedSession struct {
|
||||
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 {
|
||||
starTime := timex.Now()
|
||||
defer func() {
|
||||
logDuration(m.name, "StartSession", starTime, err)
|
||||
logDuration(context.Background(), m.name, startSession, starTime, err)
|
||||
}()
|
||||
|
||||
session, sessionErr := m.cli.StartSession(opts...)
|
||||
@@ -76,11 +85,13 @@ func (m *Model) StartSession(opts ...*mopt.SessionOptions) (sess mongo.Session,
|
||||
|
||||
sess = &wrappedSession{
|
||||
Session: session,
|
||||
name: m.name,
|
||||
brk: m.brk,
|
||||
}
|
||||
|
||||
return nil
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -169,33 +180,57 @@ func (m *Model) FindOneAndUpdate(ctx context.Context, v, filter interface{}, upd
|
||||
return res.Decode(v)
|
||||
}
|
||||
|
||||
func (w *wrappedSession) AbortTransaction(ctx context.Context) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
// AbortTransaction implements the mongo.Session interface.
|
||||
func (w *wrappedSession) AbortTransaction(ctx context.Context) (err error) {
|
||||
ctx, span := startSpan(ctx, abortTransaction)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return w.brk.DoWithAcceptable(func() error {
|
||||
starTime := timex.Now()
|
||||
defer func() {
|
||||
logDuration(ctx, w.name, abortTransaction, starTime, err)
|
||||
}()
|
||||
|
||||
return w.Session.AbortTransaction(ctx)
|
||||
}, acceptable)
|
||||
}
|
||||
|
||||
func (w *wrappedSession) CommitTransaction(ctx context.Context) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
// CommitTransaction implements the mongo.Session interface.
|
||||
func (w *wrappedSession) CommitTransaction(ctx context.Context) (err error) {
|
||||
ctx, span := startSpan(ctx, commitTransaction)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return w.brk.DoWithAcceptable(func() error {
|
||||
starTime := timex.Now()
|
||||
defer func() {
|
||||
logDuration(ctx, w.name, commitTransaction, starTime, err)
|
||||
}()
|
||||
|
||||
return w.Session.CommitTransaction(ctx)
|
||||
}, acceptable)
|
||||
}
|
||||
|
||||
// WithTransaction implements the mongo.Session interface.
|
||||
func (w *wrappedSession) WithTransaction(
|
||||
ctx context.Context,
|
||||
fn func(sessCtx mongo.SessionContext) (interface{}, error),
|
||||
opts ...*mopt.TransactionOptions,
|
||||
) (res interface{}, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, withTransaction)
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
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...)
|
||||
return err
|
||||
}, acceptable)
|
||||
@@ -203,11 +238,20 @@ func (w *wrappedSession) WithTransaction(
|
||||
return
|
||||
}
|
||||
|
||||
// EndSession implements the mongo.Session interface.
|
||||
func (w *wrappedSession) EndSession(ctx context.Context) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
var err error
|
||||
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)
|
||||
return nil
|
||||
}, acceptable)
|
||||
|
||||
37
core/stores/mon/trace.go
Normal file
37
core/stores/mon/trace.go
Normal 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)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package mon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -15,11 +16,12 @@ func FormatAddr(hosts []string) string {
|
||||
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)
|
||||
logger := logx.WithContext(ctx).WithDuration(duration)
|
||||
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 {
|
||||
logx.WithDuration(duration).Infof("mongo(%s) - %s - ok", name, method)
|
||||
logger.Infof("mongo(%s) - %s - ok", name, method)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package mon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
func TestFormatAddrs(t *testing.T) {
|
||||
@@ -33,3 +38,26 @@ func TestFormatAddrs(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package mongo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/golang/mock/gomock"
|
||||
@@ -266,6 +268,46 @@ func TestCollectionUpsert(t *testing.T) {
|
||||
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 {
|
||||
accepted bool
|
||||
reason string
|
||||
|
||||
@@ -6,35 +6,40 @@ import (
|
||||
"time"
|
||||
|
||||
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/mapping"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
"github.com/zeromicro/go-zero/core/trace"
|
||||
"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.
|
||||
const spanName = "redis"
|
||||
|
||||
var (
|
||||
startTimeKey = contextKey("startTime")
|
||||
durationHook = hook{tracer: otel.GetTracerProvider().Tracer(trace.TraceName)}
|
||||
startTimeKey = contextKey("startTime")
|
||||
durationHook = hook{tracer: otel.GetTracerProvider().Tracer(trace.TraceName)}
|
||||
redisCmdsAttributeKey = attribute.Key("redis.cmds")
|
||||
)
|
||||
|
||||
type (
|
||||
contextKey string
|
||||
hook struct {
|
||||
tracer tracestd.Tracer
|
||||
tracer oteltrace.Tracer
|
||||
}
|
||||
)
|
||||
|
||||
func (h hook) BeforeProcess(ctx context.Context, _ red.Cmder) (context.Context, error) {
|
||||
return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now())), nil
|
||||
func (h hook) BeforeProcess(ctx context.Context, cmd red.Cmder) (context.Context, error) {
|
||||
return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now()), cmd), nil
|
||||
}
|
||||
|
||||
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)
|
||||
if val == nil {
|
||||
@@ -54,17 +59,30 @@ func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h hook) BeforeProcessPipeline(ctx context.Context, _ []red.Cmder) (context.Context, error) {
|
||||
return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now())), nil
|
||||
func (h hook) BeforeProcessPipeline(ctx context.Context, cmds []red.Cmder) (context.Context, error) {
|
||||
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 {
|
||||
h.endSpan(ctx)
|
||||
|
||||
if len(cmds) == 0 {
|
||||
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)
|
||||
if val == 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())
|
||||
}
|
||||
|
||||
func (h hook) startSpan(ctx context.Context) context.Context {
|
||||
ctx, _ = h.tracer.Start(ctx, spanName)
|
||||
func (h hook) startSpan(ctx context.Context, cmds ...red.Cmder) context.Context {
|
||||
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
|
||||
}
|
||||
|
||||
func (h hook) endSpan(ctx context.Context) {
|
||||
tracestd.SpanFromContext(ctx).End()
|
||||
func (h hook) endSpan(ctx context.Context, err error) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
red "github.com/go-redis/redis/v8"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
ztrace "github.com/zeromicro/go-zero/core/trace"
|
||||
tracesdk "go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@@ -26,7 +27,7 @@ func TestHookProcessCase1(t *testing.T) {
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -44,12 +45,10 @@ func TestHookProcessCase2(t *testing.T) {
|
||||
Sampler: 1.0,
|
||||
})
|
||||
|
||||
writer := log.Writer()
|
||||
var buf strings.Builder
|
||||
log.SetOutput(&buf)
|
||||
defer log.SetOutput(writer)
|
||||
w, restore := injectLog()
|
||||
defer restore()
|
||||
|
||||
ctx, err := durationHook.BeforeProcess(context.Background(), nil)
|
||||
ctx, err := durationHook.BeforeProcess(context.Background(), red.NewCmd(context.Background()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -58,9 +57,9 @@ func TestHookProcessCase2(t *testing.T) {
|
||||
time.Sleep(slowThreshold.Load() + time.Millisecond)
|
||||
|
||||
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(buf.String(), "trace"))
|
||||
assert.True(t, strings.Contains(buf.String(), "span"))
|
||||
assert.True(t, strings.Contains(w.String(), "slow"))
|
||||
assert.True(t, strings.Contains(w.String(), "trace"))
|
||||
assert.True(t, strings.Contains(w.String(), "span"))
|
||||
}
|
||||
|
||||
func TestHookProcessCase3(t *testing.T) {
|
||||
@@ -90,7 +89,7 @@ func TestHookProcessPipelineCase1(t *testing.T) {
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -110,12 +109,10 @@ func TestHookProcessPipelineCase2(t *testing.T) {
|
||||
Sampler: 1.0,
|
||||
})
|
||||
|
||||
writer := log.Writer()
|
||||
var buf strings.Builder
|
||||
log.SetOutput(&buf)
|
||||
defer log.SetOutput(writer)
|
||||
w, restore := injectLog()
|
||||
defer restore()
|
||||
|
||||
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), nil)
|
||||
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{red.NewCmd(context.Background())})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -126,34 +123,30 @@ func TestHookProcessPipelineCase2(t *testing.T) {
|
||||
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
|
||||
red.NewCmd(context.Background(), "foo", "bar"),
|
||||
}))
|
||||
assert.True(t, strings.Contains(buf.String(), "slow"))
|
||||
assert.True(t, strings.Contains(buf.String(), "trace"))
|
||||
assert.True(t, strings.Contains(buf.String(), "span"))
|
||||
assert.True(t, strings.Contains(w.String(), "slow"))
|
||||
assert.True(t, strings.Contains(w.String(), "trace"))
|
||||
assert.True(t, strings.Contains(w.String(), "span"))
|
||||
}
|
||||
|
||||
func TestHookProcessPipelineCase3(t *testing.T) {
|
||||
writer := log.Writer()
|
||||
var buf strings.Builder
|
||||
log.SetOutput(&buf)
|
||||
defer log.SetOutput(writer)
|
||||
w, restore := injectLog()
|
||||
defer restore()
|
||||
|
||||
assert.Nil(t, durationHook.AfterProcessPipeline(context.Background(), []red.Cmder{
|
||||
red.NewCmd(context.Background()),
|
||||
}))
|
||||
assert.True(t, buf.Len() == 0)
|
||||
assert.True(t, len(w.String()) == 0)
|
||||
}
|
||||
|
||||
func TestHookProcessPipelineCase4(t *testing.T) {
|
||||
writer := log.Writer()
|
||||
var buf strings.Builder
|
||||
log.SetOutput(&buf)
|
||||
defer log.SetOutput(writer)
|
||||
w, restore := injectLog()
|
||||
defer restore()
|
||||
|
||||
ctx := context.WithValue(context.Background(), startTimeKey, "foo")
|
||||
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
|
||||
red.NewCmd(context.Background()),
|
||||
}))
|
||||
assert.True(t, buf.Len() == 0)
|
||||
assert.True(t, len(w.String()) == 0)
|
||||
}
|
||||
|
||||
func TestHookProcessPipelineCase5(t *testing.T) {
|
||||
@@ -163,6 +156,18 @@ func TestHookProcessPipelineCase5(t *testing.T) {
|
||||
defer log.SetOutput(writer)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -6,9 +6,6 @@ import (
|
||||
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"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.
|
||||
@@ -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{}) (
|
||||
result sql.Result, err error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, "Exec")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = db.brk.DoWithAcceptable(func() error {
|
||||
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) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
ctx, span := startSpan(ctx, "Prepare")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
err = db.brk.DoWithAcceptable(func() error {
|
||||
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,
|
||||
args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRow")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return db.queryRows(ctx, func(rows *sql.Rows) error {
|
||||
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{},
|
||||
q string, args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
q string, args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRowPartial")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return db.queryRows(ctx, func(rows *sql.Rows) error {
|
||||
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,
|
||||
args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRows")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return db.queryRows(ctx, func(rows *sql.Rows) error {
|
||||
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{},
|
||||
q string, args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
q string, args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRowsPartial")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return db.queryRows(ctx, func(rows *sql.Rows) error {
|
||||
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 {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Context, Session) error) (err error) {
|
||||
ctx, span := startSpan(ctx, "Transact")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return db.brk.DoWithAcceptable(func() error {
|
||||
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,
|
||||
q string, args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
|
||||
q string, args ...interface{}) (err error) {
|
||||
var qerr error
|
||||
return db.brk.DoWithAcceptable(func() error {
|
||||
conn, err := db.connProv()
|
||||
@@ -303,9 +311,11 @@ func (s statement) Exec(args ...interface{}) (sql.Result, error) {
|
||||
return s.ExecCtx(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (s statement) ExecCtx(ctx context.Context, args ...interface{}) (sql.Result, error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
func (s statement) ExecCtx(ctx context.Context, args ...interface{}) (result sql.Result, err error) {
|
||||
ctx, span := startSpan(ctx, "Exec")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
func (s statement) QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
func (s statement) QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRow")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
|
||||
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...)
|
||||
}
|
||||
|
||||
func (s statement) QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
func (s statement) QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRowPartial")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
|
||||
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...)
|
||||
}
|
||||
|
||||
func (s statement) QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
func (s statement) QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRows")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
|
||||
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...)
|
||||
}
|
||||
|
||||
func (s statement) QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
func (s statement) QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRowsPartial")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
|
||||
return unmarshalRows(v, rows, false)
|
||||
}, 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
37
core/stores/sqlx/trace.go
Normal 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)
|
||||
}
|
||||
@@ -30,20 +30,26 @@ func (t txSession) Exec(q string, args ...interface{}) (sql.Result, error) {
|
||||
return t.ExecCtx(context.Background(), q, args...)
|
||||
}
|
||||
|
||||
func (t txSession) ExecCtx(ctx context.Context, q string, args ...interface{}) (sql.Result, error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
func (t txSession) ExecCtx(ctx context.Context, q string, args ...interface{}) (result sql.Result, err error) {
|
||||
ctx, span := startSpan(ctx, "Exec")
|
||||
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) {
|
||||
return t.PrepareCtx(context.Background(), q)
|
||||
}
|
||||
|
||||
func (t txSession) PrepareCtx(ctx context.Context, q string) (StmtSession, error) {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
func (t txSession) PrepareCtx(ctx context.Context, q string) (stmtSession StmtSession, err error) {
|
||||
ctx, span := startSpan(ctx, "Prepare")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
stmt, err := t.Tx.PrepareContext(ctx, q)
|
||||
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...)
|
||||
}
|
||||
|
||||
func (t txSession) QueryRowCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
func (t txSession) QueryRowCtx(ctx context.Context, v interface{}, q string, args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRow")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return query(ctx, t.Tx, func(rows *sql.Rows) error {
|
||||
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,
|
||||
args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRowPartial")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return query(ctx, t.Tx, func(rows *sql.Rows) error {
|
||||
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...)
|
||||
}
|
||||
|
||||
func (t txSession) QueryRowsCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
func (t txSession) QueryRowsCtx(ctx context.Context, v interface{}, q string, args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRows")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return query(ctx, t.Tx, func(rows *sql.Rows) error {
|
||||
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,
|
||||
args ...interface{}) error {
|
||||
ctx, span := startSpan(ctx)
|
||||
defer span.End()
|
||||
args ...interface{}) (err error) {
|
||||
ctx, span := startSpan(ctx, "QueryRowsPartial")
|
||||
defer func() {
|
||||
endSpan(span, err)
|
||||
}()
|
||||
|
||||
return query(ctx, t.Tx, func(rows *sql.Rows) error {
|
||||
return unmarshalRows(v, rows, false)
|
||||
|
||||
@@ -103,7 +103,8 @@ func Reverse(s string) string {
|
||||
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) {
|
||||
rs := []rune(str)
|
||||
length := len(rs)
|
||||
|
||||
@@ -15,8 +15,3 @@ func Now() time.Duration {
|
||||
func Since(d time.Duration) time.Duration {
|
||||
return time.Since(initTime) - d
|
||||
}
|
||||
|
||||
// Time returns current time, the same as time.Now().
|
||||
func Time() time.Time {
|
||||
return initTime.Add(Now())
|
||||
}
|
||||
|
||||
@@ -15,11 +15,18 @@ func TestRelativeTime(t *testing.T) {
|
||||
assert.True(t, Since(now) > 0)
|
||||
}
|
||||
|
||||
func TestRelativeTime_Time(t *testing.T) {
|
||||
diff := time.Until(Time())
|
||||
if diff > 0 {
|
||||
assert.True(t, diff < time.Second)
|
||||
} else {
|
||||
assert.True(t, -diff < time.Second)
|
||||
func BenchmarkTimeSince(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = time.Since(time.Now())
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTimexSince(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Since(Now())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (ft *fakeTicker) Stop() {
|
||||
}
|
||||
|
||||
func (ft *fakeTicker) Tick() {
|
||||
ft.c <- Time()
|
||||
ft.c <- time.Now()
|
||||
}
|
||||
|
||||
func (ft *fakeTicker) Wait(d time.Duration) error {
|
||||
|
||||
@@ -35,13 +35,11 @@ func CompareVersions(v1, op, v2 string) bool {
|
||||
|
||||
// return -1 if v1<v2, 0 if they are equal, and 1 if v1>v2
|
||||
func compare(v1, v2 string) int {
|
||||
v1 = replacer.Replace(v1)
|
||||
v2 = replacer.Replace(v2)
|
||||
fields1 := strings.Split(v1, ".")
|
||||
fields2 := strings.Split(v2, ".")
|
||||
ver1 := strsToInts(fields1)
|
||||
ver2 := strsToInts(fields2)
|
||||
shorter := mathx.MinInt(len(ver1), len(ver2))
|
||||
v1, v2 = replacer.Replace(v1), replacer.Replace(v2)
|
||||
fields1, fields2 := strings.Split(v1, "."), strings.Split(v2, ".")
|
||||
ver1, ver2 := strsToInts(fields1), strsToInts(fields2)
|
||||
ver1len, ver2len := len(ver1), len(ver2)
|
||||
shorter := mathx.MinInt(ver1len, ver2len)
|
||||
|
||||
for i := 0; i < shorter; i++ {
|
||||
if ver1[i] == ver2[i] {
|
||||
@@ -53,9 +51,9 @@ func compare(v1, v2 string) int {
|
||||
}
|
||||
}
|
||||
|
||||
if len(ver1) < len(ver2) {
|
||||
if ver1len < ver2len {
|
||||
return -1
|
||||
} else if len(ver1) == len(ver2) {
|
||||
} else if ver1len == ver2len {
|
||||
return 0
|
||||
} else {
|
||||
return 1
|
||||
|
||||
62
go.mod
62
go.mod
@@ -1,62 +1,56 @@
|
||||
module github.com/zeromicro/go-zero
|
||||
|
||||
go 1.15
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/ClickHouse/clickhouse-go v1.5.1
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.2.0
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/alicebob/miniredis/v2 v2.17.0
|
||||
github.com/alicebob/miniredis/v2 v2.22.0
|
||||
github.com/fatih/color v1.13.0
|
||||
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/golang-jwt/jwt/v4 v4.2.0
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/uuid v1.3.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/prometheus/client_golang v1.11.0
|
||||
github.com/pelletier/go-toml/v2 v2.0.2
|
||||
github.com/prometheus/client_golang v1.12.2
|
||||
github.com/spaolacci/murmur3 v1.1.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.etcd.io/etcd/api/v3 v3.5.2
|
||||
go.etcd.io/etcd/client/v3 v3.5.2
|
||||
go.mongodb.org/mongo-driver v1.9.0
|
||||
go.opentelemetry.io/otel v1.3.0
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.3.0
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.3.0
|
||||
go.opentelemetry.io/otel/sdk v1.3.0
|
||||
go.opentelemetry.io/otel/trace v1.3.0
|
||||
go.uber.org/automaxprocs v1.4.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
go.etcd.io/etcd/api/v3 v3.5.4
|
||||
go.etcd.io/etcd/client/v3 v3.5.4
|
||||
go.mongodb.org/mongo-driver v1.9.1
|
||||
go.opentelemetry.io/otel v1.8.0
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.8.0
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.8.0
|
||||
go.opentelemetry.io/otel/sdk v1.8.0
|
||||
go.opentelemetry.io/otel/trace v1.8.0
|
||||
go.uber.org/automaxprocs v1.5.1
|
||||
go.uber.org/goleak v1.1.12
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
|
||||
google.golang.org/grpc v1.46.0
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
||||
google.golang.org/grpc v1.47.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.20.12
|
||||
k8s.io/apimachinery v0.20.12
|
||||
k8s.io/client-go v0.20.12
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
|
||||
k8s.io/api v0.22.9
|
||||
k8s.io/apimachinery v0.22.9
|
||||
k8s.io/client-go v0.22.9
|
||||
k8s.io/utils v0.0.0-20220706174534-f6158b442e7c
|
||||
)
|
||||
|
||||
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/json-iterator/go v1.1.12 // 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/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/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // 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
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 // indirect
|
||||
k8s.io/klog/v2 v2.40.1 // indirect
|
||||
)
|
||||
|
||||
259
go.sum
259
go.sum
@@ -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=
|
||||
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/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
|
||||
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.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
|
||||
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
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/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/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/ClickHouse/clickhouse-go v1.5.1 h1:I8zVFZTz80crCs0FFEBJooIxsPcV0xfthzK1YrkpJTc=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.2.0 h1:dj00TDKY+xwuTJdbpspCSmTLFyWzRJerTHwaBxut1C0=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.2.0/go.mod h1:8f2XZUi7XoeU+uPIytSi1cvx8fmJxi7vIgqpvYTF1+o=
|
||||
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/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/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/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-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
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/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/miniredis/v2 v2.17.0 h1:EwLdrIS50uczw71Jc7iVSxZluTKj5nfSP8n7ARRnJy0=
|
||||
github.com/alicebob/miniredis/v2 v2.17.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I=
|
||||
github.com/alicebob/miniredis/v2 v2.22.0 h1:lIHHiSkEyS1MkKHCHzN+0mWrA4YdbGdimE5iZ2sHSzo=
|
||||
github.com/alicebob/miniredis/v2 v2.22.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88=
|
||||
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/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.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
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/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=
|
||||
@@ -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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
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.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/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
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.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
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/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/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/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
@@ -139,23 +135,20 @@ 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.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.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.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/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/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-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
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/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-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
|
||||
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
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.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
@@ -166,13 +159,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.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
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.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
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-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-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.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
@@ -204,6 +197,7 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
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 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.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -214,8 +208,10 @@ 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.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.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.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
@@ -229,6 +225,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-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-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
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.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -236,11 +233,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/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/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
|
||||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
||||
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/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/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/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=
|
||||
@@ -248,10 +249,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/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.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
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/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/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=
|
||||
@@ -284,19 +285,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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
|
||||
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
||||
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-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.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
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.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
@@ -305,6 +306,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/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -319,6 +322,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/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/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.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
@@ -326,35 +330,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/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.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.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.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
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 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.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/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/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
|
||||
github.com/paulmach/orb v0.7.1 h1:Zha++Z5OX/l168sqHK3k4z18LDvr+YAO/VjK0ReQ9rU=
|
||||
github.com/paulmach/orb v0.7.1/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.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
|
||||
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.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/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/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 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.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.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-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@@ -363,8 +379,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.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
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.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
||||
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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
@@ -378,6 +394,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/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/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/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
@@ -389,19 +410,26 @@ 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 v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
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.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/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
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.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.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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
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/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
@@ -416,37 +444,40 @@ 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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
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-20200816102855-ee81675732da/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.2/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.2/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.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
|
||||
go.mongodb.org/mongo-driver v1.9.0 h1:f3aLGJvQmBl8d9S40IL+jEyBC6hfLPbJjv9t5hEM9ck=
|
||||
go.mongodb.org/mongo-driver v1.9.0/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw=
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc=
|
||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
|
||||
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
||||
go.mongodb.org/mongo-driver v1.9.1 h1:m078y9v7sBItkt1aaoe2YlvWEXcD263e1a4E1fBrJ1c=
|
||||
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.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
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.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y=
|
||||
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.3.0 h1:HfydzioALdtcB26H5WHc4K47iTETJCdloL7VN579/L0=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.3.0 h1:uOD28dZ7yIKITTcUS6MeAGNHYy3uhP7DTkhcJM6onlQ=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.3.0/go.mod h1:LxGGfHIYbvsFnrJtBcazb0yG24xHdDGrT/H6RB9r3+8=
|
||||
go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI=
|
||||
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
|
||||
go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY=
|
||||
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
|
||||
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
|
||||
go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg=
|
||||
go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.8.0 h1:TLLqD6kDhLPziEC7pgPrMvP9lAqdk3n1gf8DiFSnfW8=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.8.0/go.mod h1:GbWg+ng88rDtx+id26C34QLqw2erqJeAjsCx9AFeHfE=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.8.0 h1:PIAiDdROZzATAFfxr5ASYuSOG0JIJxRq3GwlpJGbSYQ=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.8.0/go.mod h1:0uYAyCuGT67MFV9Z/Mmx93wGuugHw0FbxMc74fs3LNo=
|
||||
go.opentelemetry.io/otel/sdk v1.8.0 h1:xwu69/fNuwbSHWe/0PGS888RmjWY181OmcXDQKu7ZQk=
|
||||
go.opentelemetry.io/otel/sdk v1.8.0/go.mod h1:uPSfc+yfDH2StDM/Rm35WE8gXSNdvCg023J6HeGNO0c=
|
||||
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
|
||||
go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY=
|
||||
go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4=
|
||||
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.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
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.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
||||
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
|
||||
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.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
@@ -460,12 +491,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-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-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-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-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-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/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -500,7 +531,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.2.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/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=
|
||||
@@ -534,22 +564,21 @@ 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-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-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-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-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-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-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/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-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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -574,15 +603,16 @@ 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-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-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-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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/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-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-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -597,12 +627,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-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-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-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-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-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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -614,11 +644,17 @@ 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-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-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-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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-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/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -633,9 +669,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-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-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/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-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
||||
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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -647,7 +683,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-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-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-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -688,7 +723,6 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
@@ -711,9 +745,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.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.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
|
||||
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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@@ -744,9 +777,10 @@ 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-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-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-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-20220602131408-e326c6e8e9c8 h1:qRu95HZ148xXw+XeZ3dvqe85PxH4X8+jIo0iRPKcEnM=
|
||||
google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -763,9 +797,9 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
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.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8=
|
||||
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/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
|
||||
google.golang.org/grpc v1.47.0/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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -785,6 +819,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 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-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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
|
||||
@@ -807,8 +842,10 @@ 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/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-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -816,26 +853,28 @@ 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-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=
|
||||
k8s.io/api v0.20.12 h1:LfRpmRkJLwPP8eaYehsVVmIIfg1yCBIIUHaSsdqCgHA=
|
||||
k8s.io/api v0.20.12/go.mod h1:A2brwyEkVLM3wQGNnzoAa5JsQRzHK0uoOQ+bsnv7V68=
|
||||
k8s.io/apimachinery v0.20.12 h1:2c0LIVNMvB8k2Ozstmhl2zGeCEcPazznuLYEwxFdNjM=
|
||||
k8s.io/apimachinery v0.20.12/go.mod h1:uM7hCI0NyBymUwgshMgZyte475lxhr+QH6h3cvdnzEc=
|
||||
k8s.io/client-go v0.20.12 h1:U75SxTC31BHT9i7CbX/hL4v+U1Wkzy/E1vt5ClDPp3I=
|
||||
k8s.io/client-go v0.20.12/go.mod h1:NBJj6Evp73Xy/4v/O/RDRaH0+3JoxNfjRxkyRgrdbsA=
|
||||
k8s.io/api v0.22.9 h1:PidjRtgd0zDa6SvyooBLH/SP62uOhEBY0kx0UYRGr1o=
|
||||
k8s.io/api v0.22.9/go.mod h1:rcjO/FPOuvc3x7nQWx29UcDrFJMx82RxDob71ntNH4A=
|
||||
k8s.io/apimachinery v0.22.9 h1:5qjnpBk6eC9me0SAzokCUMI0KVF2PENK1PnykF8/Gjo=
|
||||
k8s.io/apimachinery v0.22.9/go.mod h1:ZvVLP5iLhwVFg2Yx9Gh5W0um0DUauExbRhe+2Z8I1EU=
|
||||
k8s.io/client-go v0.22.9 h1:5p2R2LsoBfaE6QnXfWFmyyvxrFXtfegUGRMZSpTI+Q8=
|
||||
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/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/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw=
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed/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=
|
||||
k8s.io/utils v0.0.0-20220706174534-f6158b442e7c h1:hFZO68mv/0xe8+V0gRT9BAq3/31cKjjeVv4nScriuBk=
|
||||
k8s.io/utils v0.0.0-20220706174534-f6158b442e7c/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
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/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.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
||||
48
readme-cn.md
48
readme-cn.md
@@ -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
|
||||
|
||||
@@ -11,15 +11,19 @@
|
||||
[](https://goproxy.cn/stats/github.com/tal-tech/go-zero/badges/download-count.svg)
|
||||
[](https://codecov.io/gh/zeromicro/go-zero)
|
||||
[](https://github.com/zeromicro/go-zero)
|
||||
[](https://pkg.go.dev/github.com/zeromicro/go-zero)
|
||||
[](https://github.com/avelino/awesome-go)
|
||||
[](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-zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go-zero - A web & rpc framework written in Go. | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
> ***注意:***
|
||||
>
|
||||
> 从 v1.3.0 之前版本升级请执行以下命令:
|
||||
>
|
||||
> `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.4`
|
||||
|
||||
## 0. go-zero 介绍
|
||||
|
||||
@@ -88,9 +92,13 @@ go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有
|
||||
|
||||

|
||||
|
||||
## 4. 我们使用 go-zero 的基本架构图
|
||||
|
||||
<img width="1067" alt="image" src="https://user-images.githubusercontent.com/1918356/171880582-11a86658-41c3-466c-95e7-7b1220eecc52.png">
|
||||
|
||||
觉得不错的话,别忘 **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
|
||||
```
|
||||
|
||||
## 5. Quick Start
|
||||
## 6. Quick Start
|
||||
|
||||
0. 完整示例请查看
|
||||
|
||||
@@ -108,7 +116,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
|
||||
|
||||
1. 安装 goctl 工具
|
||||
|
||||
`goctl` 读作 `go control`,不要读成 `go C-T-L`。`goctl` 的意思是不要被代码控制,而是要去控制它。其中的 `go` 不是指 `golang`。在设计 `goctl` 之初,我就希望通过 ` 她 ` 来解放我们的双手👈
|
||||
`goctl` 读作 `go control`,不要读成 `go C-T-L`。`goctl` 的意思是不要被代码控制,而是要去控制它。其中的 `go` 不是指 `golang`。在设计 `goctl` 之初,我就希望通过 `工具` 来解放我们的双手👈
|
||||
|
||||
```shell
|
||||
# Go 1.15 及之前版本
|
||||
@@ -116,6 +124,9 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
|
||||
|
||||
# Go 1.16 及以后版本
|
||||
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 pull kevinwan/goctl
|
||||
@@ -171,13 +182,13 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
|
||||
...
|
||||
```
|
||||
|
||||
## 6. Benchmark
|
||||
## 7. Benchmark
|
||||
|
||||

|
||||
|
||||
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
|
||||
|
||||
## 7. 文档
|
||||
## 8. 文档
|
||||
|
||||
* 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-go-compact](https://github.com/zeromicro/goctl-go-compact) | 合并 `api` 里同一个 `group` 里的 `handler` 到一个 `go` 文件 |
|
||||
|
||||
## 8. go-zero 用户
|
||||
## 9. go-zero 用户
|
||||
|
||||
go-zero 已被许多公司用于生产部署,接入场景如在线教育、电商业务、游戏、区块链等,目前为止,已使用 go-zero 的公司包括但不限于:
|
||||
|
||||
@@ -263,10 +274,23 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
|
||||
>59. 费芮网络
|
||||
>60. 51CTO
|
||||
>61. 聿旌科技
|
||||
>62. 山东胜软科技股份有限公司
|
||||
>63. 上海芯果科技有限公司(好特卖)
|
||||
>64. 成都高鹿科技有限公司
|
||||
>65. 飞视(苏州)数字技术有限公司
|
||||
>66. 上海幻析信息科技有限公司
|
||||
>67. 统信软件技术有限公司
|
||||
>68. 得物
|
||||
>69. 鼎翰文化股份有限公司
|
||||
>70. 茶码纹化(云南)科技发展有限公司
|
||||
>71. 湖南度思信息技术有限公司
|
||||
>72. 深圳圆度
|
||||
>73. 武汉沃柒科技有限公司(茄椒)
|
||||
>74. 驭势科技
|
||||
|
||||
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
||||
|
||||
## 9. CNCF 云原生技术全景图
|
||||
## 10. CNCF 云原生技术全景图
|
||||
|
||||
<p float="left">
|
||||
<img src="https://landscape.cncf.io/images/left-logo.svg" width="150"/>
|
||||
@@ -275,13 +299,13 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
|
||||
|
||||
go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape.cncf.io/?selected=go-zero)。
|
||||
|
||||
## 10. 微信公众号
|
||||
## 11. 微信公众号
|
||||
|
||||
`go-zero` 相关文章和视频都会在 `微服务实践` 公众号整理呈现,欢迎扫码关注 👏
|
||||
|
||||
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/zeromicro.jpg" alt="wechat" width="600" />
|
||||
|
||||
## 11. 微信交流群
|
||||
## 12. 微信交流群
|
||||
|
||||
如果文档中未能覆盖的任何疑问,欢迎您在群里提出,我们会尽快答复。
|
||||
|
||||
@@ -293,7 +317,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" />
|
||||
|
||||
## 12. 赞助一下👍
|
||||
## 13. 赞助一下👍
|
||||
|
||||
如果觉得项目有帮助,可以请作者喝杯咖啡 🍹
|
||||
|
||||
|
||||
21
readme.md
21
readme.md
@@ -8,8 +8,11 @@ English | [简体中文](readme-cn.md)
|
||||
[](https://codecov.io/gh/zeromicro/go-zero)
|
||||
[](https://goreportcard.com/report/github.com/zeromicro/go-zero)
|
||||
[](https://github.com/zeromicro/go-zero)
|
||||
[](https://pkg.go.dev/github.com/zeromicro/go-zero)
|
||||
[](https://github.com/avelino/awesome-go)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://discord.gg/4JQvC5A4Fe)
|
||||
|
||||
<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go-zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go-zero - A web & rpc framework written in Go. | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
> ***Important!***
|
||||
@@ -18,7 +21,7 @@ English | [简体中文](readme-cn.md)
|
||||
>
|
||||
> `go install github.com/zeromicro/go-zero/tools/goctl@latest`
|
||||
>
|
||||
> `goctl migrate —verbose —version v1.3.2`
|
||||
> `goctl migrate —verbose —version v1.3.4`
|
||||
|
||||
## 0. what is go-zero
|
||||
|
||||
@@ -88,10 +91,9 @@ As below, go-zero protects the system with a couple of layers and mechanisms:
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
* auto-generate the simple integration test for the server-side just from the .api files
|
||||
<img width="1067" alt="image" src="https://user-images.githubusercontent.com/1918356/171880372-5010d846-e8b1-4942-8fe2-e2bbb584f762.png">
|
||||
|
||||
## 5. Installation
|
||||
|
||||
@@ -119,6 +121,9 @@ go get -u github.com/zeromicro/go-zero
|
||||
|
||||
# for Go 1.16 and later
|
||||
go install github.com/zeromicro/go-zero/tools/goctl@latest
|
||||
|
||||
# For Mac
|
||||
brew install goctl
|
||||
|
||||
# docker for amd64 architecture
|
||||
docker pull kevinwan/goctl
|
||||
@@ -230,9 +235,9 @@ go get -u github.com/zeromicro/go-zero
|
||||
|
||||
[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/)
|
||||
* [Rapid development of microservice systems](https://github.com/zeromicro/zero-doc/blob/main/doc/shorturl-en.md)
|
||||
* [Rapid development of microservice systems - multiple RPCs](https://github.com/zeromicro/zero-doc/blob/main/docs/zero/bookstore-en.md)
|
||||
* [Examples](https://github.com/zeromicro/zero-examples)
|
||||
@@ -253,3 +258,7 @@ go-zero enlisted in the [CNCF Cloud Native Landscape](https://landscape.cncf.io/
|
||||
## Give a Star! ⭐
|
||||
|
||||
If you like or are using this project to learn or start your solution, please give it a star. Thanks!
|
||||
|
||||
## Buy me a coffee
|
||||
|
||||
<a href="https://www.buymeacoffee.com/kevwan" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
||||
|
||||
109
rest/chain/chain.go
Normal file
109
rest/chain/chain.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package chain
|
||||
|
||||
// This is a modified version of https://github.com/justinas/alice
|
||||
// The original code is licensed under the MIT license.
|
||||
// It's modified for couple reasons:
|
||||
// - Added the Chain interface
|
||||
// - Added support for the Chain.Prepend(...) method
|
||||
|
||||
import "net/http"
|
||||
|
||||
type (
|
||||
// Chain defines a chain of middleware.
|
||||
Chain interface {
|
||||
Append(middlewares ...Middleware) Chain
|
||||
Prepend(middlewares ...Middleware) Chain
|
||||
Then(h http.Handler) http.Handler
|
||||
ThenFunc(fn http.HandlerFunc) http.Handler
|
||||
}
|
||||
|
||||
// Middleware is an HTTP middleware.
|
||||
Middleware func(http.Handler) http.Handler
|
||||
|
||||
// chain acts as a list of http.Handler middlewares.
|
||||
// chain is effectively immutable:
|
||||
// once created, it will always hold
|
||||
// the same set of middlewares in the same order.
|
||||
chain struct {
|
||||
middlewares []Middleware
|
||||
}
|
||||
)
|
||||
|
||||
// New creates a new Chain, memorizing the given list of middleware middlewares.
|
||||
// New serves no other function, middlewares are only called upon a call to Then() or ThenFunc().
|
||||
func New(middlewares ...Middleware) Chain {
|
||||
return chain{middlewares: append(([]Middleware)(nil), middlewares...)}
|
||||
}
|
||||
|
||||
// Append extends a chain, adding the specified middlewares as the last ones in the request flow.
|
||||
//
|
||||
// c := chain.New(m1, m2)
|
||||
// c.Append(m3, m4)
|
||||
// // requests in c go m1 -> m2 -> m3 -> m4
|
||||
func (c chain) Append(middlewares ...Middleware) Chain {
|
||||
return chain{middlewares: join(c.middlewares, middlewares)}
|
||||
}
|
||||
|
||||
// Prepend extends a chain by adding the specified chain as the first one in the request flow.
|
||||
//
|
||||
// c := chain.New(m3, m4)
|
||||
// c1 := chain.New(m1, m2)
|
||||
// c.Prepend(c1)
|
||||
// // requests in c go m1 -> m2 -> m3 -> m4
|
||||
func (c chain) Prepend(middlewares ...Middleware) Chain {
|
||||
return chain{middlewares: join(middlewares, c.middlewares)}
|
||||
}
|
||||
|
||||
// Then chains the middleware and returns the final http.Handler.
|
||||
// New(m1, m2, m3).Then(h)
|
||||
// is equivalent to:
|
||||
// m1(m2(m3(h)))
|
||||
// When the request comes in, it will be passed to m1, then m2, then m3
|
||||
// and finally, the given handler
|
||||
// (assuming every middleware calls the following one).
|
||||
//
|
||||
// A chain can be safely reused by calling Then() several times.
|
||||
// stdStack := chain.New(ratelimitHandler, csrfHandler)
|
||||
// indexPipe = stdStack.Then(indexHandler)
|
||||
// authPipe = stdStack.Then(authHandler)
|
||||
// Note that middlewares are called on every call to Then() or ThenFunc()
|
||||
// and thus several instances of the same middleware will be created
|
||||
// when a chain is reused in this way.
|
||||
// For proper middleware, this should cause no problems.
|
||||
//
|
||||
// Then() treats nil as http.DefaultServeMux.
|
||||
func (c chain) Then(h http.Handler) http.Handler {
|
||||
if h == nil {
|
||||
h = http.DefaultServeMux
|
||||
}
|
||||
|
||||
for i := range c.middlewares {
|
||||
h = c.middlewares[len(c.middlewares)-1-i](h)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// ThenFunc works identically to Then, but takes
|
||||
// a HandlerFunc instead of a Handler.
|
||||
//
|
||||
// The following two statements are equivalent:
|
||||
// c.Then(http.HandlerFunc(fn))
|
||||
// c.ThenFunc(fn)
|
||||
//
|
||||
// ThenFunc provides all the guarantees of Then.
|
||||
func (c chain) ThenFunc(fn http.HandlerFunc) http.Handler {
|
||||
// This nil check cannot be removed due to the "nil is not nil" common mistake in Go.
|
||||
// Required due to: https://stackoverflow.com/questions/33426977/how-to-golang-check-a-variable-is-nil
|
||||
if fn == nil {
|
||||
return c.Then(nil)
|
||||
}
|
||||
return c.Then(fn)
|
||||
}
|
||||
|
||||
func join(a, b []Middleware) []Middleware {
|
||||
mids := make([]Middleware, 0, len(a)+len(b))
|
||||
mids = append(mids, a...)
|
||||
mids = append(mids, b...)
|
||||
return mids
|
||||
}
|
||||
126
rest/chain/chain_test.go
Normal file
126
rest/chain/chain_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// A constructor for middleware
|
||||
// that writes its own "tag" into the RW and does nothing else.
|
||||
// Useful in checking if a chain is behaving in the right order.
|
||||
func tagMiddleware(tag string) Middleware {
|
||||
return func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(tag))
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Not recommended (https://golang.org/pkg/reflect/#Value.Pointer),
|
||||
// but the best we can do.
|
||||
func funcsEqual(f1, f2 interface{}) bool {
|
||||
val1 := reflect.ValueOf(f1)
|
||||
val2 := reflect.ValueOf(f2)
|
||||
return val1.Pointer() == val2.Pointer()
|
||||
}
|
||||
|
||||
var testApp = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("app\n"))
|
||||
})
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
c1 := func(h http.Handler) http.Handler {
|
||||
return nil
|
||||
}
|
||||
|
||||
c2 := func(h http.Handler) http.Handler {
|
||||
return http.StripPrefix("potato", nil)
|
||||
}
|
||||
|
||||
slice := []Middleware{c1, c2}
|
||||
c := New(slice...)
|
||||
for k := range slice {
|
||||
assert.True(t, funcsEqual(c.(chain).middlewares[k], slice[k]),
|
||||
"New does not add constructors correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestThenWorksWithNoMiddleware(t *testing.T) {
|
||||
assert.True(t, funcsEqual(New().Then(testApp), testApp),
|
||||
"Then does not work with no middleware")
|
||||
}
|
||||
|
||||
func TestThenTreatsNilAsDefaultServeMux(t *testing.T) {
|
||||
assert.Equal(t, http.DefaultServeMux, New().Then(nil),
|
||||
"Then does not treat nil as DefaultServeMux")
|
||||
}
|
||||
|
||||
func TestThenFuncTreatsNilAsDefaultServeMux(t *testing.T) {
|
||||
assert.Equal(t, http.DefaultServeMux, New().ThenFunc(nil),
|
||||
"ThenFunc does not treat nil as DefaultServeMux")
|
||||
}
|
||||
|
||||
func TestThenFuncConstructsHandlerFunc(t *testing.T) {
|
||||
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
})
|
||||
chained := New().ThenFunc(fn)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
chained.ServeHTTP(rec, (*http.Request)(nil))
|
||||
|
||||
assert.Equal(t, reflect.TypeOf((http.HandlerFunc)(nil)), reflect.TypeOf(chained),
|
||||
"ThenFunc does not construct HandlerFunc")
|
||||
}
|
||||
|
||||
func TestThenOrdersHandlersCorrectly(t *testing.T) {
|
||||
t1 := tagMiddleware("t1\n")
|
||||
t2 := tagMiddleware("t2\n")
|
||||
t3 := tagMiddleware("t3\n")
|
||||
|
||||
chained := New(t1, t2, t3).Then(testApp)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest("GET", "/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
chained.ServeHTTP(w, r)
|
||||
|
||||
assert.Equal(t, "t1\nt2\nt3\napp\n", w.Body.String(),
|
||||
"Then does not order handlers correctly")
|
||||
}
|
||||
|
||||
func TestAppendAddsHandlersCorrectly(t *testing.T) {
|
||||
c := New(tagMiddleware("t1\n"), tagMiddleware("t2\n"))
|
||||
c = c.Append(tagMiddleware("t3\n"), tagMiddleware("t4\n"))
|
||||
h := c.Then(testApp)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest("GET", "/", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
assert.Equal(t, "t1\nt2\nt3\nt4\napp\n", w.Body.String(),
|
||||
"Append does not add handlers correctly")
|
||||
}
|
||||
|
||||
func TestExtendAddsHandlersCorrectly(t *testing.T) {
|
||||
c := New(tagMiddleware("t3\n"), tagMiddleware("t4\n"))
|
||||
c = c.Prepend(tagMiddleware("t1\n"), tagMiddleware("t2\n"))
|
||||
h := c.Then(testApp)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest("GET", "/", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
assert.Equal(t, "t1\nt2\nt3\nt4\napp\n", w.Body.String(),
|
||||
"Extend does not add handlers in correctly")
|
||||
}
|
||||
112
rest/engine.go
112
rest/engine.go
@@ -5,12 +5,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/justinas/alice"
|
||||
"github.com/zeromicro/go-zero/core/codec"
|
||||
"github.com/zeromicro/go-zero/core/load"
|
||||
"github.com/zeromicro/go-zero/core/stat"
|
||||
"github.com/zeromicro/go-zero/rest/chain"
|
||||
"github.com/zeromicro/go-zero/rest/handler"
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"github.com/zeromicro/go-zero/rest/internal"
|
||||
@@ -28,6 +29,7 @@ type engine struct {
|
||||
routes []featuredRoutes
|
||||
unauthorizedCallback handler.UnauthorizedCallback
|
||||
unsignedCallback handler.UnsignedCallback
|
||||
chain chain.Chain
|
||||
middlewares []Middleware
|
||||
shedder load.Shedder
|
||||
priorityShedder load.Shedder
|
||||
@@ -51,20 +53,20 @@ func (ng *engine) addRoutes(r featuredRoutes) {
|
||||
ng.routes = append(ng.routes, r)
|
||||
}
|
||||
|
||||
func (ng *engine) appendAuthHandler(fr featuredRoutes, chain alice.Chain,
|
||||
verifier func(alice.Chain) alice.Chain) alice.Chain {
|
||||
func (ng *engine) appendAuthHandler(fr featuredRoutes, chn chain.Chain,
|
||||
verifier func(chain.Chain) chain.Chain) chain.Chain {
|
||||
if fr.jwt.enabled {
|
||||
if len(fr.jwt.prevSecret) == 0 {
|
||||
chain = chain.Append(handler.Authorize(fr.jwt.secret,
|
||||
chn = chn.Append(handler.Authorize(fr.jwt.secret,
|
||||
handler.WithUnauthorizedCallback(ng.unauthorizedCallback)))
|
||||
} else {
|
||||
chain = chain.Append(handler.Authorize(fr.jwt.secret,
|
||||
chn = chn.Append(handler.Authorize(fr.jwt.secret,
|
||||
handler.WithPrevSecret(fr.jwt.prevSecret),
|
||||
handler.WithUnauthorizedCallback(ng.unauthorizedCallback)))
|
||||
}
|
||||
}
|
||||
|
||||
return verifier(chain)
|
||||
return verifier(chn)
|
||||
}
|
||||
|
||||
func (ng *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {
|
||||
@@ -83,26 +85,30 @@ func (ng *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, met
|
||||
}
|
||||
|
||||
func (ng *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,
|
||||
route Route, verifier func(chain alice.Chain) alice.Chain) error {
|
||||
chain := alice.New(
|
||||
handler.TracingHandler(ng.conf.Name, route.Path),
|
||||
ng.getLogHandler(),
|
||||
handler.PrometheusHandler(route.Path),
|
||||
handler.MaxConns(ng.conf.MaxConns),
|
||||
handler.BreakerHandler(route.Method, route.Path, metrics),
|
||||
handler.SheddingHandler(ng.getShedder(fr.priority), metrics),
|
||||
handler.TimeoutHandler(ng.checkedTimeout(fr.timeout)),
|
||||
handler.RecoverHandler,
|
||||
handler.MetricHandler(metrics),
|
||||
handler.MaxBytesHandler(ng.checkedMaxBytes(fr.maxBytes)),
|
||||
handler.GunzipHandler,
|
||||
)
|
||||
chain = ng.appendAuthHandler(fr, chain, verifier)
|
||||
route Route, verifier func(chain.Chain) chain.Chain) error {
|
||||
chn := ng.chain
|
||||
if chn == nil {
|
||||
chn = chain.New(
|
||||
handler.TracingHandler(ng.conf.Name, route.Path),
|
||||
ng.getLogHandler(),
|
||||
handler.PrometheusHandler(route.Path),
|
||||
handler.MaxConns(ng.conf.MaxConns),
|
||||
handler.BreakerHandler(route.Method, route.Path, metrics),
|
||||
handler.SheddingHandler(ng.getShedder(fr.priority), metrics),
|
||||
handler.TimeoutHandler(ng.checkedTimeout(fr.timeout)),
|
||||
handler.RecoverHandler,
|
||||
handler.MetricHandler(metrics),
|
||||
handler.MaxBytesHandler(ng.checkedMaxBytes(fr.maxBytes)),
|
||||
handler.GunzipHandler,
|
||||
)
|
||||
}
|
||||
|
||||
chn = ng.appendAuthHandler(fr, chn, verifier)
|
||||
|
||||
for _, middleware := range ng.middlewares {
|
||||
chain = chain.Append(convertMiddleware(middleware))
|
||||
chn = chn.Append(convertMiddleware(middleware))
|
||||
}
|
||||
handle := chain.ThenFunc(route.Handler)
|
||||
handle := chn.ThenFunc(route.Handler)
|
||||
|
||||
return router.Handle(route.Method, route.Path, handle)
|
||||
}
|
||||
@@ -166,16 +172,16 @@ func (ng *engine) getShedder(priority bool) load.Shedder {
|
||||
// notFoundHandler returns a middleware that handles 404 not found requests.
|
||||
func (ng *engine) notFoundHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
chain := alice.New(
|
||||
chn := chain.New(
|
||||
handler.TracingHandler(ng.conf.Name, ""),
|
||||
ng.getLogHandler(),
|
||||
)
|
||||
|
||||
var h http.Handler
|
||||
if next != nil {
|
||||
h = chain.Then(next)
|
||||
h = chn.Then(next)
|
||||
} else {
|
||||
h = chain.Then(http.NotFoundHandler())
|
||||
h = chn.Then(http.NotFoundHandler())
|
||||
}
|
||||
|
||||
cw := response.NewHeaderOnceResponseWriter(w)
|
||||
@@ -184,6 +190,23 @@ func (ng *engine) notFoundHandler(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func (ng *engine) print() {
|
||||
var routes []string
|
||||
|
||||
for _, fr := range ng.routes {
|
||||
for _, route := range fr.routes {
|
||||
routes = append(routes, fmt.Sprintf("%s %s", route.Method, route.Path))
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(routes)
|
||||
|
||||
fmt.Println("Routes:")
|
||||
for _, route := range routes {
|
||||
fmt.Printf(" %s\n", route)
|
||||
}
|
||||
}
|
||||
|
||||
func (ng *engine) setTlsConfig(cfg *tls.Config) {
|
||||
ng.tlsConfig = cfg
|
||||
}
|
||||
@@ -196,10 +219,10 @@ func (ng *engine) setUnsignedCallback(callback handler.UnsignedCallback) {
|
||||
ng.unsignedCallback = callback
|
||||
}
|
||||
|
||||
func (ng *engine) signatureVerifier(signature signatureSetting) (func(chain alice.Chain) alice.Chain, error) {
|
||||
func (ng *engine) signatureVerifier(signature signatureSetting) (func(chain.Chain) chain.Chain, error) {
|
||||
if !signature.enabled {
|
||||
return func(chain alice.Chain) alice.Chain {
|
||||
return chain
|
||||
return func(chn chain.Chain) chain.Chain {
|
||||
return chn
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -208,8 +231,8 @@ func (ng *engine) signatureVerifier(signature signatureSetting) (func(chain alic
|
||||
return nil, ErrSignatureConfig
|
||||
}
|
||||
|
||||
return func(chain alice.Chain) alice.Chain {
|
||||
return chain
|
||||
return func(chn chain.Chain) chain.Chain {
|
||||
return chn
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -225,14 +248,13 @@ func (ng *engine) signatureVerifier(signature signatureSetting) (func(chain alic
|
||||
decrypters[fingerprint] = decrypter
|
||||
}
|
||||
|
||||
return func(chain alice.Chain) alice.Chain {
|
||||
return func(chn chain.Chain) chain.Chain {
|
||||
if ng.unsignedCallback != nil {
|
||||
return chain.Append(handler.ContentSecurityHandler(
|
||||
return chn.Append(handler.ContentSecurityHandler(
|
||||
decrypters, signature.Expiry, signature.Strict, ng.unsignedCallback))
|
||||
}
|
||||
|
||||
return chain.Append(handler.ContentSecurityHandler(
|
||||
decrypters, signature.Expiry, signature.Strict))
|
||||
return chn.Append(handler.ContentSecurityHandler(decrypters, signature.Expiry, signature.Strict))
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -242,7 +264,7 @@ func (ng *engine) start(router httpx.Router) error {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -250,13 +272,29 @@ func (ng *engine) start(router httpx.Router) error {
|
||||
if ng.tlsConfig != nil {
|
||||
svr.TLSConfig = ng.tlsConfig
|
||||
}
|
||||
})
|
||||
}, ng.withTimeout())
|
||||
}
|
||||
|
||||
func (ng *engine) use(middleware 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 {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return ware(next.ServeHTTP)
|
||||
|
||||
@@ -146,7 +146,7 @@ Verbose: true
|
||||
for _, yaml := range yamls {
|
||||
for _, route := range routes {
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(yaml), &cnf))
|
||||
assert.Nil(t, conf.LoadFromYamlBytes([]byte(yaml), &cnf))
|
||||
ng := newEngine(cnf)
|
||||
ng.addRoutes(route)
|
||||
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))
|
||||
}
|
||||
|
||||
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, handler http.Handler) error {
|
||||
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) {
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/color"
|
||||
"github.com/zeromicro/go-zero/core/iox"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
@@ -157,15 +159,21 @@ func dumpRequest(r *http.Request) string {
|
||||
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) {
|
||||
var buf bytes.Buffer
|
||||
duration := timer.Duration()
|
||||
logger := logx.WithContext(r.Context()).WithDuration(duration)
|
||||
buf.WriteString(fmt.Sprintf("[HTTP] %s - %d - %s - %s - %s",
|
||||
r.Method, code, r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent()))
|
||||
buf.WriteString(fmt.Sprintf("[HTTP] %s - %s %s - %s - %s",
|
||||
wrapStatusCode(code), wrapMethod(r.Method), r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent()))
|
||||
if duration > slowThreshold.Load() {
|
||||
logger.Slowf("[HTTP] %s - %d - %s - %s - %s - slowcall(%s)",
|
||||
r.Method, code, r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(), timex.ReprOfDuration(duration))
|
||||
logger.Slowf("[HTTP] %s - %s - %s %s - %s - slowcall(%s)",
|
||||
wrapStatusCode(code), wrapMethod(r.Method), r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(),
|
||||
fmt.Sprintf("slowcall(%s)", timex.ReprOfDuration(duration)))
|
||||
}
|
||||
|
||||
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",
|
||||
r.Method, code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r)))
|
||||
if duration > defaultSlowThreshold {
|
||||
logger.Slowf("[HTTP] %s - %d - %s - slowcall(%s)\n=> %s\n",
|
||||
r.Method, code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r))
|
||||
logger.Slowf("[HTTP] %s - %d - %s - slowcall(%s)\n=> %s\n", r.Method, code, r.RemoteAddr,
|
||||
fmt.Sprintf("slowcall(%s)", timex.ReprOfDuration(duration)), dumpRequest(r))
|
||||
}
|
||||
|
||||
body := logs.Flush()
|
||||
@@ -222,7 +230,44 @@ func logDetails(r *http.Request, response *detailLoggedResponseWriter, timer *ut
|
||||
}
|
||||
}
|
||||
|
||||
func isOkResponse(code int) bool {
|
||||
// not server error
|
||||
return code < http.StatusInternalServerError
|
||||
func wrapMethod(method string) string {
|
||||
var colour color.Color
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"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) {
|
||||
handlers := []func(handler http.Handler) http.Handler{
|
||||
LogHandler,
|
||||
@@ -106,6 +135,28 @@ func TestSetSlowThreshold(t *testing.T) {
|
||||
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) {
|
||||
b.ReportAllocs()
|
||||
|
||||
|
||||
@@ -7,13 +7,20 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
nurl "net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/lang"
|
||||
"github.com/zeromicro/go-zero/core/mapping"
|
||||
"github.com/zeromicro/go-zero/core/trace"
|
||||
"github.com/zeromicro/go-zero/rest/httpc/internal"
|
||||
"github.com/zeromicro/go-zero/rest/internal/header"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||
oteltrace "go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
var interceptors = []internal.Interceptor{
|
||||
@@ -150,17 +157,47 @@ func fillPath(u *nurl.URL, val map[string]interface{}) error {
|
||||
}
|
||||
|
||||
func request(r *http.Request, cli client) (*http.Response, error) {
|
||||
var respHandlers []internal.ResponseHandler
|
||||
for _, interceptor := range interceptors {
|
||||
tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
|
||||
propagator := otel.GetTextMapPropagator()
|
||||
|
||||
spanName := r.URL.Path
|
||||
ctx, span := tracer.Start(
|
||||
r.Context(),
|
||||
spanName,
|
||||
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
|
||||
oteltrace.WithAttributes(semconv.HTTPClientAttributesFromHTTPRequest(r)...),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
respHandlers := make([]internal.ResponseHandler, len(interceptors))
|
||||
for i, interceptor := range interceptors {
|
||||
var h internal.ResponseHandler
|
||||
r, h = interceptor(r)
|
||||
respHandlers = append(respHandlers, h)
|
||||
respHandlers[i] = h
|
||||
}
|
||||
|
||||
clientTrace := httptrace.ContextClientTrace(ctx)
|
||||
if clientTrace != nil {
|
||||
ctx = httptrace.WithClientTrace(ctx, clientTrace)
|
||||
}
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(r)...)
|
||||
propagator.Inject(ctx, propagation.HeaderCarrier(r.Header))
|
||||
|
||||
resp, err := cli.do(r)
|
||||
for i := len(respHandlers) - 1; i >= 0; i-- {
|
||||
respHandlers[i](resp, err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return resp, err
|
||||
}
|
||||
|
||||
span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(resp.StatusCode)...)
|
||||
span.SetStatus(semconv.SpanStatusFromHTTPStatusCode(resp.StatusCode))
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
@@ -4,15 +4,25 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httptrace"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
ztrace "github.com/zeromicro/go-zero/core/trace"
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"github.com/zeromicro/go-zero/rest/internal/header"
|
||||
"github.com/zeromicro/go-zero/rest/router"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
func TestDoRequest(t *testing.T) {
|
||||
ztrace.StartAgent(ztrace.Config{
|
||||
Name: "go-zero-test",
|
||||
Endpoint: "http://localhost:14268/api/traces",
|
||||
Batcher: "jaeger",
|
||||
Sampler: 1.0,
|
||||
})
|
||||
|
||||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
defer svr.Close()
|
||||
@@ -21,6 +31,8 @@ func TestDoRequest(t *testing.T) {
|
||||
resp, err := DoRequest(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
spanContext := trace.SpanContextFromContext(resp.Request.Context())
|
||||
assert.True(t, spanContext.IsValid())
|
||||
}
|
||||
|
||||
func TestDoRequest_NotFound(t *testing.T) {
|
||||
@@ -158,3 +170,46 @@ func TestDo_BadRequest(t *testing.T) {
|
||||
_, err = Do(context.Background(), http.MethodPost, "/nodes/:val", val5)
|
||||
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)
|
||||
}
|
||||
|
||||
func TestDo_WithClientHttpTrace(t *testing.T) {
|
||||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
defer svr.Close()
|
||||
|
||||
_, err := Do(httptrace.WithClientTrace(context.Background(),
|
||||
&httptrace.ClientTrace{
|
||||
DNSStart: func(info httptrace.DNSStartInfo) {
|
||||
assert.Equal(t, "localhost", info.Host)
|
||||
},
|
||||
}), http.MethodGet, svr.URL, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/rest/internal/errcode"
|
||||
"github.com/zeromicro/go-zero/rest/internal/header"
|
||||
)
|
||||
|
||||
@@ -23,9 +24,14 @@ func Error(w http.ResponseWriter, err error, fns ...func(w http.ResponseWriter,
|
||||
if handler == nil {
|
||||
if len(fns) > 0 {
|
||||
fns[0](w, err)
|
||||
} else if errcode.IsGrpcError(err) {
|
||||
// don't unwrap error and get status.Message(),
|
||||
// it hides the rpc error headers.
|
||||
http.Error(w, err.Error(), errcode.CodeFromGrpcError(err))
|
||||
} else {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
@@ -95,6 +97,16 @@ func TestError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorWithGrpcError(t *testing.T) {
|
||||
w := tracedResponseWriter{
|
||||
headers: make(map[string][]string),
|
||||
}
|
||||
Error(&w, status.Error(codes.Unavailable, "foo"))
|
||||
assert.Equal(t, http.StatusServiceUnavailable, w.code)
|
||||
assert.True(t, w.hasBody)
|
||||
assert.True(t, strings.Contains(w.builder.String(), "foo"))
|
||||
}
|
||||
|
||||
func TestErrorWithHandler(t *testing.T) {
|
||||
w := tracedResponseWriter{
|
||||
headers: make(map[string][]string),
|
||||
@@ -129,7 +141,18 @@ func TestWriteJsonTimeout(t *testing.T) {
|
||||
// only log it and ignore
|
||||
w := tracedResponseWriter{
|
||||
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"}
|
||||
WriteJson(&w, http.StatusOK, msg)
|
||||
@@ -162,8 +185,8 @@ type tracedResponseWriter struct {
|
||||
hasBody bool
|
||||
code int
|
||||
lessWritten bool
|
||||
timeout bool
|
||||
wroteHeader bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *tracedResponseWriter) Header() http.Header {
|
||||
@@ -171,8 +194,8 @@ func (w *tracedResponseWriter) Header() http.Header {
|
||||
}
|
||||
|
||||
func (w *tracedResponseWriter) Write(bytes []byte) (n int, err error) {
|
||||
if w.timeout {
|
||||
return 0, http.ErrHandlerTimeout
|
||||
if w.err != nil {
|
||||
return 0, w.err
|
||||
}
|
||||
|
||||
n, err = w.builder.Write(bytes)
|
||||
|
||||
@@ -38,3 +38,14 @@ func TestParseHeadersMulti(t *testing.T) {
|
||||
assert.Equal(t, 1, val.Baz)
|
||||
assert.True(t, val.Qux)
|
||||
}
|
||||
|
||||
func TestParseHeadersArrayInt(t *testing.T) {
|
||||
var val struct {
|
||||
Foo []int `header:"foo"`
|
||||
}
|
||||
r := httptest.NewRequest(http.MethodGet, "/any", nil)
|
||||
r.Header.Set("foo", "1")
|
||||
r.Header.Add("foo", "2")
|
||||
assert.Nil(t, ParseHeaders(r.Header, &val))
|
||||
assert.Equal(t, []int{1, 2}, val.Foo)
|
||||
}
|
||||
55
rest/internal/errcode/grpc.go
Normal file
55
rest/internal/errcode/grpc.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package errcode
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// CodeFromGrpcError converts the gRPC error to an HTTP status code.
|
||||
// See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
|
||||
func CodeFromGrpcError(err error) int {
|
||||
code := status.Code(err)
|
||||
switch code {
|
||||
case codes.OK:
|
||||
return http.StatusOK
|
||||
case codes.InvalidArgument, codes.FailedPrecondition, codes.OutOfRange:
|
||||
return http.StatusBadRequest
|
||||
case codes.Unauthenticated:
|
||||
return http.StatusUnauthorized
|
||||
case codes.PermissionDenied:
|
||||
return http.StatusForbidden
|
||||
case codes.NotFound:
|
||||
return http.StatusNotFound
|
||||
case codes.Canceled:
|
||||
return http.StatusRequestTimeout
|
||||
case codes.AlreadyExists, codes.Aborted:
|
||||
return http.StatusConflict
|
||||
case codes.ResourceExhausted:
|
||||
return http.StatusTooManyRequests
|
||||
case codes.Internal, codes.DataLoss, codes.Unknown:
|
||||
return http.StatusInternalServerError
|
||||
case codes.Unimplemented:
|
||||
return http.StatusNotImplemented
|
||||
case codes.Unavailable:
|
||||
return http.StatusServiceUnavailable
|
||||
case codes.DeadlineExceeded:
|
||||
return http.StatusGatewayTimeout
|
||||
}
|
||||
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
|
||||
// IsGrpcError checks if the error is a gRPC error.
|
||||
func IsGrpcError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := err.(interface {
|
||||
GRPCStatus() *status.Status
|
||||
})
|
||||
|
||||
return ok
|
||||
}
|
||||
123
rest/internal/errcode/grpc_test.go
Normal file
123
rest/internal/errcode/grpc_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package errcode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestCodeFromGrpcError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code codes.Code
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
code: codes.OK,
|
||||
want: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "Invalid argument",
|
||||
code: codes.InvalidArgument,
|
||||
want: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "Failed precondition",
|
||||
code: codes.FailedPrecondition,
|
||||
want: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "Out of range",
|
||||
code: codes.OutOfRange,
|
||||
want: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "Unauthorized",
|
||||
code: codes.Unauthenticated,
|
||||
want: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "Permission denied",
|
||||
code: codes.PermissionDenied,
|
||||
want: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "Not found",
|
||||
code: codes.NotFound,
|
||||
want: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "Canceled",
|
||||
code: codes.Canceled,
|
||||
want: http.StatusRequestTimeout,
|
||||
},
|
||||
{
|
||||
name: "Already exists",
|
||||
code: codes.AlreadyExists,
|
||||
want: http.StatusConflict,
|
||||
},
|
||||
{
|
||||
name: "Aborted",
|
||||
code: codes.Aborted,
|
||||
want: http.StatusConflict,
|
||||
},
|
||||
{
|
||||
name: "Resource exhausted",
|
||||
code: codes.ResourceExhausted,
|
||||
want: http.StatusTooManyRequests,
|
||||
},
|
||||
{
|
||||
name: "Internal",
|
||||
code: codes.Internal,
|
||||
want: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
name: "Data loss",
|
||||
code: codes.DataLoss,
|
||||
want: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
name: "Unknown",
|
||||
code: codes.Unknown,
|
||||
want: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
name: "Unimplemented",
|
||||
code: codes.Unimplemented,
|
||||
want: http.StatusNotImplemented,
|
||||
},
|
||||
{
|
||||
name: "Unavailable",
|
||||
code: codes.Unavailable,
|
||||
want: http.StatusServiceUnavailable,
|
||||
},
|
||||
{
|
||||
name: "Deadline exceeded",
|
||||
code: codes.DeadlineExceeded,
|
||||
want: http.StatusGatewayTimeout,
|
||||
},
|
||||
{
|
||||
name: "Beyond defined error",
|
||||
code: codes.Code(^uint32(0)),
|
||||
want: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.want, CodeFromGrpcError(status.Error(test.code, "foo")))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsGrpcError(t *testing.T) {
|
||||
assert.True(t, IsGrpcError(status.Error(codes.Unknown, "foo")))
|
||||
assert.False(t, IsGrpcError(errors.New("foo")))
|
||||
assert.False(t, IsGrpcError(nil))
|
||||
}
|
||||
@@ -2,13 +2,13 @@ package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
@@ -25,16 +25,23 @@ func TestInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
var writer strings.Builder
|
||||
log.SetOutput(&writer)
|
||||
var buf strings.Builder
|
||||
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)
|
||||
Error(req, "first")
|
||||
Errorf(req, "second %s", "third")
|
||||
val := writer.String()
|
||||
val := buf.String()
|
||||
assert.True(t, strings.Contains(val, "first"))
|
||||
assert.True(t, strings.Contains(val, "second"))
|
||||
assert.True(t, strings.Contains(val, "third"))
|
||||
assert.True(t, strings.Contains(val, "\n"))
|
||||
}
|
||||
|
||||
func TestContextKey_String(t *testing.T) {
|
||||
|
||||
@@ -39,7 +39,7 @@ func start(host string, port int, handler http.Handler, run func(svr *http.Serve
|
||||
}
|
||||
|
||||
waitForCalled := proc.AddWrapUpListener(func() {
|
||||
if e := server.Shutdown(context.Background()); err != nil {
|
||||
if e := server.Shutdown(context.Background()); e != nil {
|
||||
logx.Error(e)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/rest/chain"
|
||||
"github.com/zeromicro/go-zero/rest/handler"
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"github.com/zeromicro/go-zero/rest/internal/cors"
|
||||
@@ -73,6 +74,11 @@ func (s *Server) AddRoute(r Route, opts ...RouteOption) {
|
||||
s.AddRoutes([]Route{r}, opts...)
|
||||
}
|
||||
|
||||
// PrintRoutes prints the added routes to stdout.
|
||||
func (s *Server) PrintRoutes() {
|
||||
s.ngin.print()
|
||||
}
|
||||
|
||||
// Start starts the Server.
|
||||
// Graceful shutdown is enabled by default.
|
||||
// Use proc.SetTimeToForceQuit to customize the graceful shutdown period.
|
||||
@@ -97,6 +103,14 @@ func ToMiddleware(handler func(next http.Handler) http.Handler) Middleware {
|
||||
}
|
||||
}
|
||||
|
||||
// WithChain returns a RunOption that uses the given chain to replace the default chain.
|
||||
// JWT auth middleware and the middlewares that added by svr.Use() will be appended.
|
||||
func WithChain(chn chain.Chain) RunOption {
|
||||
return func(svr *Server) {
|
||||
svr.ngin.chain = chn
|
||||
}
|
||||
}
|
||||
|
||||
// WithCors returns a func to enable CORS for given origin, or default to all origins (*).
|
||||
func WithCors(origin ...string) RunOption {
|
||||
return func(server *Server) {
|
||||
|
||||
@@ -4,24 +4,35 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/conf"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/service"
|
||||
"github.com/zeromicro/go-zero/rest/chain"
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"github.com/zeromicro/go-zero/rest/router"
|
||||
)
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
writer := logx.Reset()
|
||||
defer logx.SetWriter(writer)
|
||||
logx.SetWriter(logx.NewWriter(ioutil.Discard))
|
||||
|
||||
const configYaml = `
|
||||
Name: foo
|
||||
Port: 54321
|
||||
`
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
|
||||
assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
|
||||
|
||||
tests := []struct {
|
||||
c RestConf
|
||||
@@ -31,7 +42,6 @@ Port: 54321
|
||||
{
|
||||
c: RestConf{},
|
||||
opts: []RunOption{WithRouter(mockedRouter{}), WithCors()},
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
c: cnf,
|
||||
@@ -95,6 +105,18 @@ Port: 54321
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServerError(t *testing.T) {
|
||||
_, err := NewServer(RestConf{
|
||||
ServiceConf: service.ServiceConf{
|
||||
Log: logx.LogConf{
|
||||
// file mode, no path specified
|
||||
Mode: "file",
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestWithMaxBytes(t *testing.T) {
|
||||
const maxBytes = 1000
|
||||
var fr featuredRoutes
|
||||
@@ -271,7 +293,7 @@ Name: foo
|
||||
Port: 54321
|
||||
`
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
|
||||
assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
|
||||
|
||||
testConfig := &tls.Config{
|
||||
CipherSuites: []uint16{
|
||||
@@ -309,10 +331,11 @@ Name: foo
|
||||
Port: 54321
|
||||
`
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
|
||||
assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
|
||||
rt := router.NewRouter()
|
||||
svr, err := NewServer(cnf, WithRouter(rt))
|
||||
assert.Nil(t, err)
|
||||
defer svr.Stop()
|
||||
|
||||
opt := WithCors("local")
|
||||
opt(svr)
|
||||
@@ -324,7 +347,7 @@ Name: foo
|
||||
Port: 54321
|
||||
`
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
|
||||
assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
|
||||
rt := router.NewRouter()
|
||||
svr, err := NewServer(cnf, WithRouter(rt))
|
||||
assert.Nil(t, err)
|
||||
@@ -336,3 +359,122 @@ Port: 54321
|
||||
}, "local")
|
||||
opt(svr)
|
||||
}
|
||||
|
||||
func TestServer_PrintRoutes(t *testing.T) {
|
||||
const (
|
||||
configYaml = `
|
||||
Name: foo
|
||||
Port: 54321
|
||||
`
|
||||
expect = `Routes:
|
||||
GET /bar
|
||||
GET /foo
|
||||
GET /foo/:bar
|
||||
GET /foo/:bar/baz
|
||||
`
|
||||
)
|
||||
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
|
||||
|
||||
svr, err := NewServer(cnf)
|
||||
assert.Nil(t, err)
|
||||
|
||||
svr.AddRoutes([]Route{
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/foo",
|
||||
Handler: http.NotFound,
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/bar",
|
||||
Handler: http.NotFound,
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/foo/:bar",
|
||||
Handler: http.NotFound,
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/foo/:bar/baz",
|
||||
Handler: http.NotFound,
|
||||
},
|
||||
})
|
||||
|
||||
old := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
assert.Nil(t, err)
|
||||
os.Stdout = w
|
||||
defer func() {
|
||||
os.Stdout = old
|
||||
}()
|
||||
|
||||
svr.PrintRoutes()
|
||||
ch := make(chan string)
|
||||
|
||||
go func() {
|
||||
var buf strings.Builder
|
||||
io.Copy(&buf, r)
|
||||
ch <- buf.String()
|
||||
}()
|
||||
|
||||
w.Close()
|
||||
out := <-ch
|
||||
assert.Equal(t, expect, out)
|
||||
}
|
||||
|
||||
func TestHandleError(t *testing.T) {
|
||||
assert.NotPanics(t, func() {
|
||||
handleError(nil)
|
||||
handleError(http.ErrServerClosed)
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateSecret(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
validateSecret("short")
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_WithChain(t *testing.T) {
|
||||
var called int32
|
||||
middleware1 := func() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
atomic.AddInt32(&called, 1)
|
||||
next.ServeHTTP(w, r)
|
||||
atomic.AddInt32(&called, 1)
|
||||
})
|
||||
}
|
||||
}
|
||||
middleware2 := func() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
atomic.AddInt32(&called, 1)
|
||||
next.ServeHTTP(w, r)
|
||||
atomic.AddInt32(&called, 1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
server := MustNewServer(RestConf{}, WithChain(chain.New(middleware1(), middleware2())))
|
||||
server.AddRoutes(
|
||||
[]Route{
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/",
|
||||
Handler: func(_ http.ResponseWriter, _ *http.Request) {
|
||||
atomic.AddInt32(&called, 1)
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
rt := router.NewRouter()
|
||||
assert.Nil(t, server.ngin.bindRoutes(rt))
|
||||
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||
assert.Nil(t, err)
|
||||
rt.ServeHTTP(httptest.NewRecorder(), req)
|
||||
assert.Equal(t, int32(5), atomic.LoadInt32(&called))
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"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/pathx"
|
||||
)
|
||||
@@ -17,13 +17,20 @@ import (
|
||||
//go:embed api.tpl
|
||||
var apiTemplate string
|
||||
|
||||
// ApiCommand create api template file
|
||||
func ApiCommand(c *cli.Context) error {
|
||||
if c.NumFlags() == 0 {
|
||||
cli.ShowAppHelpAndExit(c, 1)
|
||||
}
|
||||
var (
|
||||
// VarStringOutput describes the output.
|
||||
VarStringOutput string
|
||||
// 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 {
|
||||
return errors.New("missing -o")
|
||||
}
|
||||
@@ -34,18 +41,15 @@ func ApiCommand(c *cli.Context) error {
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
home := c.String("home")
|
||||
remote := c.String("remote")
|
||||
branch := c.String("branch")
|
||||
if len(remote) > 0 {
|
||||
repo, _ := util.CloneIntoGitHome(remote, branch)
|
||||
if len(VarStringRemote) > 0 {
|
||||
repo, _ := util.CloneIntoGitHome(VarStringRemote, VarStringBranch)
|
||||
if len(repo) > 0 {
|
||||
home = repo
|
||||
VarStringHome = repo
|
||||
}
|
||||
}
|
||||
|
||||
if len(home) > 0 {
|
||||
pathx.RegisterGoctlHome(home)
|
||||
if len(VarStringHome) > 0 {
|
||||
pathx.RegisterGoctlHome(VarStringHome)
|
||||
}
|
||||
|
||||
text, err := pathx.LoadTemplate(category, apiTemplateFile, apiTemplate)
|
||||
|
||||
@@ -3,7 +3,6 @@ package apigen
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
|
||||
)
|
||||
|
||||
@@ -27,7 +26,7 @@ func Clean() error {
|
||||
}
|
||||
|
||||
// GenTemplates generates api template files.
|
||||
func GenTemplates(_ *cli.Context) error {
|
||||
func GenTemplates() error {
|
||||
return pathx.InitTemplates(category, templates)
|
||||
}
|
||||
|
||||
|
||||
176
tools/goctl/api/cmd.go
Normal file
176
tools/goctl/api/cmd.go
Normal 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\nThe git repo directory must be consistent with the"+
|
||||
" https://github.com/zeromicro/go-zero-template directory structure")
|
||||
Cmd.Flags().StringVar(&apigen.VarStringBranch, "branch", "", "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\nThe git repo directory must be consistent with the "+
|
||||
"https://github.com/zeromicro/go-zero-template directory structure")
|
||||
goCmd.Flags().StringVar(&gogen.VarStringBranch, "branch", "", "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", "", "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)
|
||||
}
|
||||
@@ -5,17 +5,28 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"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
|
||||
func DartCommand(c *cli.Context) error {
|
||||
apiFile := c.String("api")
|
||||
dir := c.String("dir")
|
||||
isLegacy := c.Bool("legacy")
|
||||
hostname := c.String("hostname")
|
||||
func DartCommand(_ *cobra.Command, _ []string) error {
|
||||
apiFile := VarStringAPI
|
||||
dir := VarStringDir
|
||||
isLegacy := VarStringLegacy
|
||||
hostname := VarStringHostname
|
||||
if len(apiFile) == 0 {
|
||||
return errors.New("missing -api")
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ import (
|
||||
var markdownTemplate string
|
||||
|
||||
func genDoc(api *spec.ApiSpec, dir, filename string) error {
|
||||
if len(api.Service.Routes()) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
fp, _, err := util.MaybeCreateFile(dir, "", filename)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -59,6 +63,7 @@ func genDoc(api *spec.ApiSpec, dir, filename string) error {
|
||||
|
||||
builder.Write(tmplBytes.Bytes())
|
||||
}
|
||||
|
||||
_, err = fp.WriteString(strings.Replace(builder.String(), """, `"`, -1))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,19 +7,26 @@ import (
|
||||
"path/filepath"
|
||||
"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/util/pathx"
|
||||
)
|
||||
|
||||
var (
|
||||
// VarStringDir describes a directory.
|
||||
VarStringDir string
|
||||
// VarStringOutput describes an output directory.
|
||||
VarStringOutput string
|
||||
)
|
||||
|
||||
// DocCommand generate Markdown doc file
|
||||
func DocCommand(c *cli.Context) error {
|
||||
dir := c.String("dir")
|
||||
func DocCommand(_ *cobra.Command, _ []string) error {
|
||||
dir := VarStringDir
|
||||
if len(dir) == 0 {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
outputDir := c.String("o")
|
||||
outputDir := VarStringOutput
|
||||
if len(outputDir) == 0 {
|
||||
var err error
|
||||
outputDir, err = os.Getwd()
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zeromicro/go-zero/core/errorx"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/api/parser"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/api/util"
|
||||
@@ -26,30 +26,37 @@ const (
|
||||
rightBrace = "}"
|
||||
)
|
||||
|
||||
// GoFormatApi format api file
|
||||
func GoFormatApi(c *cli.Context) error {
|
||||
useStdin := c.Bool("stdin")
|
||||
skipCheckDeclare := c.Bool("declare")
|
||||
dir := c.String("dir")
|
||||
var (
|
||||
// VarBoolUseStdin describes whether to use stdin or not.
|
||||
VarBoolUseStdin bool
|
||||
// VarBoolSkipCheckDeclare describes whether to skip.
|
||||
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
|
||||
if useStdin {
|
||||
if err := apiFormatReader(os.Stdin, dir, skipCheckDeclare); err != nil {
|
||||
if VarBoolUseStdin {
|
||||
if err := apiFormatReader(os.Stdin, VarStringDir, VarBoolSkipCheckDeclare); err != nil {
|
||||
be.Add(err)
|
||||
}
|
||||
} else {
|
||||
if len(dir) == 0 {
|
||||
if len(VarStringDir) == 0 {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
_, err := os.Lstat(dir)
|
||||
_, err := os.Lstat(VarStringDir)
|
||||
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 err := ApiFormatByPath(path, skipCheckDeclare); err != nil {
|
||||
if err := ApiFormatByPath(path, VarBoolSkipCheckDeclare); err != nil {
|
||||
be.Add(util.WrapErr(err, fi.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user