Compare commits

..

1 Commits

Author SHA1 Message Date
spectatorMrZ
304fb182bb Fix pg model generation without tag (#1407)
1. fix pg model struct haven't tag
2. add pg command test from datasource
2022-01-08 21:48:09 +08:00
785 changed files with 9876 additions and 31209 deletions

3
.github/FUNDING.yml vendored
View File

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

View File

@@ -1,11 +0,0 @@
# 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"

View File

@@ -35,11 +35,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # 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). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -64,4 +64,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v1

View File

@@ -7,50 +7,32 @@ on:
branches: [ master ] branches: [ master ]
jobs: jobs:
test-linux: build:
name: Linux name: Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.16
id: go
- name: Check out code into the Go module directory - name: Set up Go 1.x
uses: actions/checkout@v3 uses: actions/setup-go@v2
with:
go-version: ^1.14
id: go
- name: Get dependencies - name: Check out code into the Go module directory
run: | uses: actions/checkout@v2
go get -v -t -d ./...
- name: Lint - name: Get dependencies
run: | run: |
go vet -stdmethods=false $(go list ./...) go get -v -t -d ./...
go install mvdan.cc/gofumpt@latest
test -z "$(gofumpt -l -extra .)" || echo "Please run 'gofumpt -l -w -extra .'"
- name: Test - name: Lint
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./... run: |
go vet -stdmethods=false $(go list ./...)
go install mvdan.cc/gofumpt@latest
test -z "$(gofumpt -s -l -extra .)" || echo "Please run 'gofumpt -l -w -extra .'"
- name: Codecov - name: Test
uses: codecov/codecov-action@v2 run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
test-win: - name: Codecov
name: Windows uses: codecov/codecov-action@v2
runs-on: windows-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.16
- name: Checkout codebase
uses: actions/checkout@v3
- name: Test
run: |
go mod verify
go mod download
go test -v -race ./...
cd tools/goctl && go build -v goctl.go

View File

@@ -1,18 +0,0 @@
name: 'issue-translator'
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: tomsun28/issues-translate-action@v2.6
with:
IS_MODIFY_TITLE: true
# not require, default false, . Decide whether to modify the issue title
# if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot.
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿
# not require. Customize the translation robot prefix message.

View File

@@ -9,8 +9,8 @@ jobs:
steps: steps:
- uses: actions/stale@v3 - uses: actions/stale@v3
with: with:
days-before-issue-stale: 365 days-before-issue-stale: 30
days-before-issue-close: 90 days-before-issue-close: 14
stale-issue-label: "stale" stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."

View File

@@ -1,28 +0,0 @@
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/readme.md tools/goctl/readme-cn.md

View File

@@ -5,7 +5,7 @@ jobs:
name: runner / staticcheck name: runner / staticcheck
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: reviewdog/action-staticcheck@v1 - uses: reviewdog/action-staticcheck@v1
with: with:
github_token: ${{ secrets.github_token }} github_token: ${{ secrets.github_token }}

4
.gitignore vendored
View File

@@ -16,9 +16,7 @@
**/logs **/logs
# for test purpose # for test purpose
**/adhoc adhoc
go.work
go.work.sum
# gitlab ci # gitlab ci
.cache .cache

View File

@@ -40,7 +40,7 @@ We will help you to contribute in different areas like filing issues, developing
getting your work reviewed and merged. getting your work reviewed and merged.
If you have questions about the development process, If you have questions about the development process,
feel free to [file an issue](https://github.com/zeromicro/go-zero/issues/new/choose). feel free to [file an issue](https://github.com/tal-tech/go-zero/issues/new/choose).
## Find something to work on ## Find something to work on
@@ -50,10 +50,10 @@ Here is how you get started.
### Find a good first topic ### Find a good first topic
[go-zero](https://github.com/zeromicro/go-zero) has beginner-friendly issues that provide a good first issue. [go-zero](https://github.com/tal-tech/go-zero) has beginner-friendly issues that provide a good first issue.
For example, [go-zero](https://github.com/zeromicro/go-zero) has For example, [go-zero](https://github.com/tal-tech/go-zero) has
[help wanted](https://github.com/zeromicro/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) and [help wanted](https://github.com/tal-tech/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) and
[good first issue](https://github.com/zeromicro/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) [good first issue](https://github.com/tal-tech/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
labels for issues that should not need deep knowledge of the system. labels for issues that should not need deep knowledge of the system.
We can help new contributors who wish to work on such issues. We can help new contributors who wish to work on such issues.
@@ -79,7 +79,7 @@ This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where to base the contribution. This is usually master. - Create a topic branch from where to base the contribution. This is usually master.
- Make commits of logical units. - Make commits of logical units.
- Push changes in a topic branch to a personal fork of the repository. - Push changes in a topic branch to a personal fork of the repository.
- Submit a pull request to [go-zero](https://github.com/zeromicro/go-zero). - Submit a pull request to [go-zero](https://github.com/tal-tech/go-zero).
## Creating Pull Requests ## Creating Pull Requests

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2022 zeromicro Copyright (c) 2020 xiaoheiban_server_go
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -20,9 +20,9 @@ We hope that the items listed below will inspire further engagement from the com
- [x] Support `goctl bug` to report bugs conveniently - [x] Support `goctl bug` to report bugs conveniently
## 2022 ## 2022
- [x] Support `context` in redis related methods for timeout and tracing
- [x] Support `context` in sql related methods for timeout and tracing
- [x] Support `context` in 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 - [ ] Support `goctl mock` command to start a mocking server with given `.api` file
- [ ] Add `httpx.Client` with governance, like circuit breaker etc.
- [ ] Support `goctl doctor` command to report potential issues for given service
- [ ] Support `context` in redis related methods for timeout and tracing
- [ ] Support `context` in sql related methods for timeout and tracing
- [ ] Support `context` in mongodb related methods for timeout and tracing

View File

@@ -4,8 +4,8 @@ import (
"errors" "errors"
"strconv" "strconv"
"github.com/zeromicro/go-zero/core/hash" "github.com/tal-tech/go-zero/core/hash"
"github.com/zeromicro/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
) )
const ( const (
@@ -69,8 +69,11 @@ func (f *Filter) Exists(data []byte) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
if !isSet {
return false, nil
}
return isSet, nil return true, nil
} }
func (f *Filter) getLocations(data []byte) []uint { func (f *Filter) getLocations(data []byte) []uint {

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stores/redis/redistest" "github.com/tal-tech/go-zero/core/stores/redis/redistest"
) )
func TestRedisBitSet_New_Set_Test(t *testing.T) { func TestRedisBitSet_New_Set_Test(t *testing.T) {

View File

@@ -5,12 +5,12 @@ import (
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"time"
"github.com/zeromicro/go-zero/core/mathx" "github.com/tal-tech/go-zero/core/mathx"
"github.com/zeromicro/go-zero/core/proc" "github.com/tal-tech/go-zero/core/proc"
"github.com/zeromicro/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/core/timex"
) )
const ( const (
@@ -171,7 +171,7 @@ func (lt loggedThrottle) allow() (Promise, error) {
func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error { func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool { return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool {
accept := acceptable(err) accept := acceptable(err)
if !accept && err != nil { if !accept {
lt.errWin.add(err.Error()) lt.errWin.add(err.Error())
} }
return accept return accept
@@ -198,7 +198,7 @@ type errorWindow struct {
func (ew *errorWindow) add(reason string) { func (ew *errorWindow) add(reason string) {
ew.lock.Lock() ew.lock.Lock()
ew.reasons[ew.index] = fmt.Sprintf("%s %s", time.Now().Format(timeFormat), reason) ew.reasons[ew.index] = fmt.Sprintf("%s %s", timex.Time().Format(timeFormat), reason)
ew.index = (ew.index + 1) % numHistoryReasons ew.index = (ew.index + 1) % numHistoryReasons
ew.count = mathx.MinInt(ew.count+1, numHistoryReasons) ew.count = mathx.MinInt(ew.count+1, numHistoryReasons)
ew.lock.Unlock() ew.lock.Unlock()

View File

@@ -8,7 +8,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
) )
func init() { func init() {

View File

@@ -6,7 +6,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
) )
func init() { func init() {

View File

@@ -4,8 +4,8 @@ import (
"math" "math"
"time" "time"
"github.com/zeromicro/go-zero/core/collection" "github.com/tal-tech/go-zero/core/collection"
"github.com/zeromicro/go-zero/core/mathx" "github.com/tal-tech/go-zero/core/mathx"
) )
const ( const (

View File

@@ -7,9 +7,9 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/collection" "github.com/tal-tech/go-zero/core/collection"
"github.com/zeromicro/go-zero/core/mathx" "github.com/tal-tech/go-zero/core/mathx"
"github.com/zeromicro/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
) )
const ( const (

View File

@@ -8,8 +8,8 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/iox" "github.com/tal-tech/go-zero/core/iox"
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
) )
func TestEnterToContinue(t *testing.T) { func TestEnterToContinue(t *testing.T) {

View File

@@ -7,7 +7,7 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
) )
// ErrPaddingSize indicates bad padding size. // ErrPaddingSize indicates bad padding size.

View File

@@ -7,7 +7,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/pem" "encoding/pem"
"errors" "errors"
"os" "io/ioutil"
) )
var ( var (
@@ -48,7 +48,7 @@ type (
// NewRsaDecrypter returns a RsaDecrypter with the given file. // NewRsaDecrypter returns a RsaDecrypter with the given file.
func NewRsaDecrypter(file string) (RsaDecrypter, error) { func NewRsaDecrypter(file string) (RsaDecrypter, error) {
content, err := os.ReadFile(file) content, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/fs" "github.com/tal-tech/go-zero/core/fs"
) )
const ( const (

View File

@@ -6,9 +6,9 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mathx" "github.com/tal-tech/go-zero/core/mathx"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
) )
const ( const (
@@ -26,7 +26,7 @@ type (
// CacheOption defines the method to customize a Cache. // CacheOption defines the method to customize a Cache.
CacheOption func(cache *Cache) CacheOption func(cache *Cache)
// A Cache object is an in-memory cache. // A Cache object is a in-memory cache.
Cache struct { Cache struct {
name string name string
lock sync.Mutex lock sync.Mutex
@@ -98,18 +98,13 @@ func (c *Cache) Get(key string) (interface{}, bool) {
// Set sets value into c with key. // Set sets value into c with key.
func (c *Cache) Set(key string, value interface{}) { func (c *Cache) Set(key string, value interface{}) {
c.SetWithExpire(key, value, c.expire)
}
// SetWithExpire sets value into c with key and expire with the given value.
func (c *Cache) SetWithExpire(key string, value interface{}, expire time.Duration) {
c.lock.Lock() c.lock.Lock()
_, ok := c.data[key] _, ok := c.data[key]
c.data[key] = value c.data[key] = value
c.lruCache.add(key) c.lruCache.add(key)
c.lock.Unlock() c.lock.Unlock()
expiry := c.unstableExpiry.AroundDuration(expire) expiry := c.unstableExpiry.AroundDuration(c.expire)
if ok { if ok {
c.timingWheel.MoveTimer(key, expiry) c.timingWheel.MoveTimer(key, expiry)
} else { } else {

View File

@@ -18,7 +18,7 @@ func TestCacheSet(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
cache.Set("first", "first element") cache.Set("first", "first element")
cache.SetWithExpire("second", "second element", time.Second*3) cache.Set("second", "second element")
value, ok := cache.Get("first") value, ok := cache.Get("first")
assert.True(t, ok) assert.True(t, ok)

View File

@@ -61,41 +61,3 @@ func TestPutMore(t *testing.T) {
assert.Equal(t, string(element), string(body.([]byte))) assert.Equal(t, string(element), string(body.([]byte)))
} }
} }
func TestPutMoreWithHeaderNotZero(t *testing.T) {
elements := [][]byte{
[]byte("hello"),
[]byte("world"),
[]byte("again"),
}
queue := NewQueue(4)
for i := range elements {
queue.Put(elements[i])
}
// take 1
body, ok := queue.Take()
assert.True(t, ok)
element, ok := body.([]byte)
assert.True(t, ok)
assert.Equal(t, element, []byte("hello"))
// put more
queue.Put([]byte("b4"))
queue.Put([]byte("b5")) // will store in elements[0]
queue.Put([]byte("b6")) // cause expansion
results := [][]byte{
[]byte("world"),
[]byte("again"),
[]byte("b4"),
[]byte("b5"),
[]byte("b6"),
}
for _, element := range results {
body, ok := queue.Take()
assert.True(t, ok)
assert.Equal(t, string(element), string(body.([]byte)))
}
}

View File

@@ -4,7 +4,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/zeromicro/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
type ( type (

View File

@@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
) )
const duration = time.Millisecond * 50 const duration = time.Millisecond * 50

View File

@@ -68,24 +68,6 @@ func (m *SafeMap) Get(key interface{}) (interface{}, bool) {
return val, ok return val, ok
} }
// Range calls f sequentially for each key and value present in the map.
// If f returns false, range stops the iteration.
func (m *SafeMap) Range(f func(key, val interface{}) bool) {
m.lock.RLock()
defer m.lock.RUnlock()
for k, v := range m.dirtyOld {
if !f(k, v) {
return
}
}
for k, v := range m.dirtyNew {
if !f(k, v) {
return
}
}
}
// Set sets the value into m with the given key. // Set sets the value into m with the given key.
func (m *SafeMap) Set(key, value interface{}) { func (m *SafeMap) Set(key, value interface{}) {
m.lock.Lock() m.lock.Lock()

View File

@@ -1,11 +1,10 @@
package collection package collection
import ( import (
"sync/atomic"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
) )
func TestSafeMap(t *testing.T) { func TestSafeMap(t *testing.T) {
@@ -108,42 +107,3 @@ func testSafeMapWithParameters(t *testing.T, size, exception int) {
} }
} }
} }
func TestSafeMap_Range(t *testing.T) {
const (
size = 100000
exception1 = 5
exception2 = 500
)
m := NewSafeMap()
newMap := NewSafeMap()
for i := 0; i < size; i++ {
m.Set(i, i)
}
for i := 0; i < size; i++ {
if i%exception1 == 0 {
m.Del(i)
}
}
for i := size; i < size<<1; i++ {
m.Set(i, i)
}
for i := size; i < size<<1; i++ {
if i%exception2 != 0 {
m.Del(i)
}
}
var count int32
m.Range(func(k, v interface{}) bool {
atomic.AddInt32(&count, 1)
newMap.Set(k, v)
return true
})
assert.Equal(t, int(atomic.LoadInt32(&count)), m.Size())
assert.Equal(t, m.dirtyNew, newMap.dirtyNew)
assert.Equal(t, m.dirtyOld, newMap.dirtyOld)
}

View File

@@ -1,8 +1,8 @@
package collection package collection
import ( import (
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
) )
const ( const (

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
) )
func init() { func init() {

View File

@@ -2,22 +2,16 @@ package collection
import ( import (
"container/list" "container/list"
"errors"
"fmt" "fmt"
"time" "time"
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/threading" "github.com/tal-tech/go-zero/core/threading"
"github.com/zeromicro/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
const drainWorkers = 8 const drainWorkers = 8
var (
ErrClosed = errors.New("TimingWheel is closed already")
ErrArgument = errors.New("incorrect task argument")
)
type ( type (
// Execute defines the method to execute the task. // Execute defines the method to execute the task.
Execute func(key, value interface{}) Execute func(key, value interface{})
@@ -65,15 +59,14 @@ type (
// NewTimingWheel returns a TimingWheel. // NewTimingWheel returns a TimingWheel.
func NewTimingWheel(interval time.Duration, numSlots int, execute Execute) (*TimingWheel, error) { func NewTimingWheel(interval time.Duration, numSlots int, execute Execute) (*TimingWheel, error) {
if interval <= 0 || numSlots <= 0 || execute == nil { if interval <= 0 || numSlots <= 0 || execute == nil {
return nil, fmt.Errorf("interval: %v, slots: %d, execute: %p", return nil, fmt.Errorf("interval: %v, slots: %d, execute: %p", interval, numSlots, execute)
interval, numSlots, execute)
} }
return newTimingWheelWithClock(interval, numSlots, execute, timex.NewTicker(interval)) return newTimingWheelWithClock(interval, numSlots, execute, timex.NewTicker(interval))
} }
func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execute, func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execute, ticker timex.Ticker) (
ticker timex.Ticker) (*TimingWheel, error) { *TimingWheel, error) {
tw := &TimingWheel{ tw := &TimingWheel{
interval: interval, interval: interval,
ticker: ticker, ticker: ticker,
@@ -96,67 +89,47 @@ func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execu
} }
// Drain drains all items and executes them. // Drain drains all items and executes them.
func (tw *TimingWheel) Drain(fn func(key, value interface{})) error { func (tw *TimingWheel) Drain(fn func(key, value interface{})) {
select { tw.drainChannel <- fn
case tw.drainChannel <- fn:
return nil
case <-tw.stopChannel:
return ErrClosed
}
} }
// MoveTimer moves the task with the given key to the given delay. // MoveTimer moves the task with the given key to the given delay.
func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) error { func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) {
if delay <= 0 || key == nil { if delay <= 0 || key == nil {
return ErrArgument return
} }
select { tw.moveChannel <- baseEntry{
case tw.moveChannel <- baseEntry{
delay: delay, delay: delay,
key: key, key: key,
}:
return nil
case <-tw.stopChannel:
return ErrClosed
} }
} }
// RemoveTimer removes the task with the given key. // RemoveTimer removes the task with the given key.
func (tw *TimingWheel) RemoveTimer(key interface{}) error { func (tw *TimingWheel) RemoveTimer(key interface{}) {
if key == nil { if key == nil {
return ErrArgument return
} }
select { tw.removeChannel <- key
case tw.removeChannel <- key:
return nil
case <-tw.stopChannel:
return ErrClosed
}
} }
// SetTimer sets the task value with the given key to the delay. // SetTimer sets the task value with the given key to the delay.
func (tw *TimingWheel) SetTimer(key, value interface{}, delay time.Duration) error { func (tw *TimingWheel) SetTimer(key, value interface{}, delay time.Duration) {
if delay <= 0 || key == nil { if delay <= 0 || key == nil {
return ErrArgument return
} }
select { tw.setChannel <- timingEntry{
case tw.setChannel <- timingEntry{
baseEntry: baseEntry{ baseEntry: baseEntry{
delay: delay, delay: delay,
key: key, key: key,
}, },
value: value, value: value,
}:
return nil
case <-tw.stopChannel:
return ErrClosed
} }
} }
// Stop stops tw. No more actions after stopping a TimingWheel. // Stop stops tw.
func (tw *TimingWheel) Stop() { func (tw *TimingWheel) Stop() {
close(tw.stopChannel) close(tw.stopChannel)
} }

View File

@@ -8,10 +8,10 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
"github.com/zeromicro/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
const ( const (
@@ -28,6 +28,7 @@ func TestTimingWheel_Drain(t *testing.T) {
ticker := timex.NewFakeTicker() ticker := timex.NewFakeTicker()
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) { tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
}, ticker) }, ticker)
defer tw.Stop()
tw.SetTimer("first", 3, testStep*4) tw.SetTimer("first", 3, testStep*4)
tw.SetTimer("second", 5, testStep*7) tw.SetTimer("second", 5, testStep*7)
tw.SetTimer("third", 7, testStep*7) tw.SetTimer("third", 7, testStep*7)
@@ -55,8 +56,6 @@ func TestTimingWheel_Drain(t *testing.T) {
}) })
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
assert.Equal(t, 0, count) assert.Equal(t, 0, count)
tw.Stop()
assert.Equal(t, ErrClosed, tw.Drain(func(key, value interface{}) {}))
} }
func TestTimingWheel_SetTimerSoon(t *testing.T) { func TestTimingWheel_SetTimerSoon(t *testing.T) {
@@ -103,13 +102,6 @@ func TestTimingWheel_SetTimerWrongDelay(t *testing.T) {
}) })
} }
func TestTimingWheel_SetTimerAfterClose(t *testing.T) {
ticker := timex.NewFakeTicker()
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
tw.Stop()
assert.Equal(t, ErrClosed, tw.SetTimer("any", 3, testStep))
}
func TestTimingWheel_MoveTimer(t *testing.T) { func TestTimingWheel_MoveTimer(t *testing.T) {
run := syncx.NewAtomicBool() run := syncx.NewAtomicBool()
ticker := timex.NewFakeTicker() ticker := timex.NewFakeTicker()
@@ -119,6 +111,7 @@ func TestTimingWheel_MoveTimer(t *testing.T) {
assert.Equal(t, 3, v.(int)) assert.Equal(t, 3, v.(int))
ticker.Done() ticker.Done()
}, ticker) }, ticker)
defer tw.Stop()
tw.SetTimer("any", 3, testStep*4) tw.SetTimer("any", 3, testStep*4)
tw.MoveTimer("any", testStep*7) tw.MoveTimer("any", testStep*7)
tw.MoveTimer("any", -testStep) tw.MoveTimer("any", -testStep)
@@ -132,8 +125,6 @@ func TestTimingWheel_MoveTimer(t *testing.T) {
} }
assert.Nil(t, ticker.Wait(waitTime)) assert.Nil(t, ticker.Wait(waitTime))
assert.True(t, run.True()) assert.True(t, run.True())
tw.Stop()
assert.Equal(t, ErrClosed, tw.MoveTimer("any", time.Millisecond))
} }
func TestTimingWheel_MoveTimerSoon(t *testing.T) { func TestTimingWheel_MoveTimerSoon(t *testing.T) {
@@ -184,7 +175,6 @@ func TestTimingWheel_RemoveTimer(t *testing.T) {
ticker.Tick() ticker.Tick()
} }
tw.Stop() tw.Stop()
assert.Equal(t, ErrClosed, tw.RemoveTimer("any"))
} }
func TestTimingWheel_SetTimer(t *testing.T) { func TestTimingWheel_SetTimer(t *testing.T) {

View File

@@ -1,73 +0,0 @@
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)
}

View File

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

View File

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

View File

@@ -1,23 +1,24 @@
package conf package conf
import ( import (
"io/ioutil"
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/fs" "github.com/tal-tech/go-zero/core/fs"
"github.com/zeromicro/go-zero/core/hash" "github.com/tal-tech/go-zero/core/hash"
) )
func TestLoadConfig_notExists(t *testing.T) { func TestLoadConfig_notExists(t *testing.T) {
assert.NotNil(t, Load("not_a_file", nil)) assert.NotNil(t, LoadConfig("not_a_file", nil))
} }
func TestLoadConfig_notRecogFile(t *testing.T) { func TestLoadConfig_notRecogFile(t *testing.T) {
filename, err := fs.TempFilenameWithText("hello") filename, err := fs.TempFilenameWithText("hello")
assert.Nil(t, err) assert.Nil(t, err)
defer os.Remove(filename) defer os.Remove(filename)
assert.NotNil(t, Load(filename, nil)) assert.NotNil(t, LoadConfig(filename, nil))
} }
func TestConfigJson(t *testing.T) { func TestConfigJson(t *testing.T) {
@@ -56,58 +57,6 @@ func TestConfigJson(t *testing.T) {
} }
} }
func TestConfigToml(t *testing.T) {
text := `a = "foo"
b = 1
c = "${FOO}"
d = "abcd!@#$112"
`
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(".toml", text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val)
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C)
assert.Equal(t, "abcd!@#$112", val.D)
}
func TestConfigTomlEnv(t *testing.T) {
text := `a = "foo"
b = 1
c = "${FOO}"
d = "abcd!@#112"
`
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(".toml", text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val, UseEnv())
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "2", val.C)
assert.Equal(t, "abcd!@#112", val.D)
}
func TestConfigJsonEnv(t *testing.T) { func TestConfigJsonEnv(t *testing.T) {
tests := []string{ tests := []string{
".json", ".json",
@@ -145,12 +94,12 @@ func TestConfigJsonEnv(t *testing.T) {
} }
func createTempFile(ext, text string) (string, error) { func createTempFile(ext, text string) (string, error) {
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext) tmpfile, err := ioutil.TempFile(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
if err != nil { if err != nil {
return "", err return "", err
} }
if err := os.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil { if err := ioutil.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
return "", err return "", err
} }

View File

@@ -7,7 +7,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/zeromicro/go-zero/core/iox" "github.com/tal-tech/go-zero/core/iox"
) )
// PropertyError represents a configuration error message. // PropertyError represents a configuration error message.

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/fs" "github.com/tal-tech/go-zero/core/fs"
) )
func TestProperties(t *testing.T) { func TestProperties(t *testing.T) {

View File

@@ -1,57 +0,0 @@
## 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())
```

View File

@@ -3,7 +3,7 @@ package contextx
import ( import (
"context" "context"
"github.com/zeromicro/go-zero/core/mapping" "github.com/tal-tech/go-zero/core/mapping"
) )
const contextTagKey = "ctx" const contextTagKey = "ctx"

View File

@@ -1,6 +1,6 @@
package discov package discov
import "github.com/zeromicro/go-zero/core/discov/internal" import "github.com/tal-tech/go-zero/core/discov/internal"
// RegisterAccount registers the username/password to the given etcd cluster. // RegisterAccount registers the username/password to the given etcd cluster.
func RegisterAccount(endpoints []string, user, pass string) { func RegisterAccount(endpoints []string, user, pass string) {

View File

@@ -4,8 +4,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/discov/internal" "github.com/tal-tech/go-zero/core/discov/internal"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
) )
func TestRegisterAccount(t *testing.T) { func TestRegisterAccount(t *testing.T) {

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/zeromicro/go-zero/core/discov/internal" "github.com/tal-tech/go-zero/core/discov/internal"
) )
const ( const (

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/discov/internal" "github.com/tal-tech/go-zero/core/discov/internal"
) )
var mockLock sync.Mutex var mockLock sync.Mutex

View File

@@ -2,13 +2,6 @@ package discov
import "errors" import "errors"
var (
// errEmptyEtcdHosts indicates that etcd hosts are empty.
errEmptyEtcdHosts = errors.New("empty etcd hosts")
// errEmptyEtcdKey indicates that etcd key is empty.
errEmptyEtcdKey = errors.New("empty etcd key")
)
// EtcdConf is the config item with the given key on etcd. // EtcdConf is the config item with the given key on etcd.
type EtcdConf struct { type EtcdConf struct {
Hosts []string Hosts []string
@@ -34,9 +27,9 @@ func (c EtcdConf) HasTLS() bool {
// Validate validates c. // Validate validates c.
func (c EtcdConf) Validate() error { func (c EtcdConf) Validate() error {
if len(c.Hosts) == 0 { if len(c.Hosts) == 0 {
return errEmptyEtcdHosts return errors.New("empty etcd hosts")
} else if len(c.Key) == 0 { } else if len(c.Key) == 0 {
return errEmptyEtcdKey return errors.New("empty etcd key")
} else { } else {
return nil return nil
} }

View File

@@ -3,7 +3,7 @@ package internal
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"os" "io/ioutil"
"sync" "sync"
) )
@@ -37,7 +37,7 @@ func AddTLS(endpoints []string, certFile, certKeyFile, caFile string, insecureSk
return err return err
} }
caData, err := os.ReadFile(caFile) caData, err := ioutil.ReadFile(caFile)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
) )
func TestAccount(t *testing.T) { func TestAccount(t *testing.T) {

View File

@@ -9,11 +9,11 @@ import (
"sync" "sync"
"time" "time"
"github.com/zeromicro/go-zero/core/contextx" "github.com/tal-tech/go-zero/core/contextx"
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
"github.com/zeromicro/go-zero/core/threading" "github.com/tal-tech/go-zero/core/threading"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
@@ -191,11 +191,9 @@ func (c *cluster) handleWatchEvents(key string, events []*clientv3.Event) {
}) })
} }
case clientv3.EventTypeDelete: case clientv3.EventTypeDelete:
c.lock.Lock()
if vals, ok := c.values[key]; ok { if vals, ok := c.values[key]; ok {
delete(vals, string(ev.Kv.Key)) delete(vals, string(ev.Kv.Key))
} }
c.lock.Unlock()
for _, l := range listeners { for _, l := range listeners {
l.OnDelete(KV{ l.OnDelete(KV{
Key: string(ev.Kv.Key), Key: string(ev.Kv.Key),
@@ -208,7 +206,7 @@ func (c *cluster) handleWatchEvents(key string, events []*clientv3.Event) {
} }
} }
func (c *cluster) load(cli EtcdClient, key string) int64 { func (c *cluster) load(cli EtcdClient, key string) {
var resp *clientv3.GetResponse var resp *clientv3.GetResponse
for { for {
var err error var err error
@@ -232,8 +230,6 @@ func (c *cluster) load(cli EtcdClient, key string) int64 {
} }
c.handleChanges(key, kvs) c.handleChanges(key, kvs)
return resp.Header.Revision
} }
func (c *cluster) monitor(key string, l UpdateListener) error { func (c *cluster) monitor(key string, l UpdateListener) error {
@@ -246,9 +242,9 @@ func (c *cluster) monitor(key string, l UpdateListener) error {
return err return err
} }
rev := c.load(cli, key) c.load(cli, key)
c.watchGroup.Run(func() { c.watchGroup.Run(func() {
c.watch(cli, key, rev) c.watch(cli, key)
}) })
return nil return nil
@@ -280,28 +276,22 @@ func (c *cluster) reload(cli EtcdClient) {
for _, key := range keys { for _, key := range keys {
k := key k := key
c.watchGroup.Run(func() { c.watchGroup.Run(func() {
rev := c.load(cli, k) c.load(cli, k)
c.watch(cli, k, rev) c.watch(cli, k)
}) })
} }
} }
func (c *cluster) watch(cli EtcdClient, key string, rev int64) { func (c *cluster) watch(cli EtcdClient, key string) {
for { for {
if c.watchStream(cli, key, rev) { if c.watchStream(cli, key) {
return return
} }
} }
} }
func (c *cluster) watchStream(cli EtcdClient, key string, rev int64) bool { func (c *cluster) watchStream(cli EtcdClient, key string) bool {
var rch clientv3.WatchChan rch := cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix())
if rev != 0 {
rch = cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix(), clientv3.WithRev(rev+1))
} else {
rch = cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix())
}
for { for {
select { select {
case wresp, ok := <-rch: case wresp, ok := <-rch:

View File

@@ -2,16 +2,15 @@ package internal
import ( import (
"context" "context"
"go.etcd.io/etcd/api/v3/etcdserverpb"
"sync" "sync"
"testing" "testing"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/contextx" "github.com/tal-tech/go-zero/core/contextx"
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
"go.etcd.io/etcd/api/v3/mvccpb" "go.etcd.io/etcd/api/v3/mvccpb"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
@@ -113,7 +112,6 @@ func TestCluster_Load(t *testing.T) {
restore := setMockClient(cli) restore := setMockClient(cli)
defer restore() defer restore()
cli.EXPECT().Get(gomock.Any(), "any/", gomock.Any()).Return(&clientv3.GetResponse{ cli.EXPECT().Get(gomock.Any(), "any/", gomock.Any()).Return(&clientv3.GetResponse{
Header: &etcdserverpb.ResponseHeader{},
Kvs: []*mvccpb.KeyValue{ Kvs: []*mvccpb.KeyValue{
{ {
Key: []byte("hello"), Key: []byte("hello"),
@@ -170,7 +168,7 @@ func TestCluster_Watch(t *testing.T) {
listener.EXPECT().OnDelete(gomock.Any()).Do(func(_ interface{}) { listener.EXPECT().OnDelete(gomock.Any()).Do(func(_ interface{}) {
wg.Done() wg.Done()
}).MaxTimes(1) }).MaxTimes(1)
go c.watch(cli, "any", 0) go c.watch(cli, "any")
ch <- clientv3.WatchResponse{ ch <- clientv3.WatchResponse{
Events: []*clientv3.Event{ Events: []*clientv3.Event{
{ {
@@ -214,7 +212,7 @@ func TestClusterWatch_RespFailures(t *testing.T) {
ch <- resp ch <- resp
close(c.done) close(c.done)
}() }()
c.watch(cli, "any", 0) c.watch(cli, "any")
}) })
} }
} }
@@ -234,7 +232,7 @@ func TestClusterWatch_CloseChan(t *testing.T) {
close(ch) close(ch)
close(c.done) close(c.done)
}() }()
c.watch(cli, "any", 0) c.watch(cli, "any")
} }
func TestValueOnlyContext(t *testing.T) { func TestValueOnlyContext(t *testing.T) {

View File

@@ -1,12 +1,12 @@
package discov package discov
import ( import (
"github.com/zeromicro/go-zero/core/discov/internal" "github.com/tal-tech/go-zero/core/discov/internal"
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/proc" "github.com/tal-tech/go-zero/core/proc"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
"github.com/zeromicro/go-zero/core/threading" "github.com/tal-tech/go-zero/core/threading"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )

View File

@@ -8,10 +8,10 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/discov/internal" "github.com/tal-tech/go-zero/core/discov/internal"
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )

View File

@@ -4,9 +4,9 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/zeromicro/go-zero/core/discov/internal" "github.com/tal-tech/go-zero/core/discov/internal"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
) )
type ( type (

View File

@@ -5,8 +5,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/discov/internal" "github.com/tal-tech/go-zero/core/discov/internal"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
) )
const ( const (

View File

@@ -11,12 +11,10 @@ type (
errorArray []error errorArray []error
) )
// Add adds errs to be, nil errors are ignored. // Add adds err to be.
func (be *BatchError) Add(errs ...error) { func (be *BatchError) Add(err error) {
for _, err := range errs { if err != nil {
if err != nil { be.errs = append(be.errs, err)
be.errs = append(be.errs, err)
}
} }
} }

View File

@@ -1,21 +0,0 @@
package errorx
import "fmt"
// Wrap returns an error that wraps err with given message.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %w", message, err)
}
// Wrapf returns an error that wraps err with given format and args.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err)
}

View File

@@ -1,24 +0,0 @@
package errorx
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWrap(t *testing.T) {
assert.Nil(t, Wrap(nil, "test"))
assert.Equal(t, "foo: bar", Wrap(errors.New("bar"), "foo").Error())
err := errors.New("foo")
assert.True(t, errors.Is(Wrap(err, "bar"), err))
}
func TestWrapf(t *testing.T) {
assert.Nil(t, Wrapf(nil, "%s", "test"))
assert.Equal(t, "foo bar: quz", Wrapf(errors.New("quz"), "foo %s", "bar").Error())
err := errors.New("foo")
assert.True(t, errors.Is(Wrapf(err, "foo %s", "bar"), err))
}

View File

@@ -4,7 +4,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/zeromicro/go-zero/core/threading" "github.com/tal-tech/go-zero/core/threading"
) )
// A DelayExecutor delays a tasks on given delay interval. // A DelayExecutor delays a tasks on given delay interval.

View File

@@ -3,8 +3,8 @@ package executors
import ( import (
"time" "time"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
"github.com/zeromicro/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
// A LessExecutor is an executor to limit execution once within given time interval. // A LessExecutor is an executor to limit execution once within given time interval.

View File

@@ -5,7 +5,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
func TestLessExecutor_DoOrDiscard(t *testing.T) { func TestLessExecutor_DoOrDiscard(t *testing.T) {

View File

@@ -6,11 +6,11 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/proc" "github.com/tal-tech/go-zero/core/proc"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
"github.com/zeromicro/go-zero/core/threading" "github.com/tal-tech/go-zero/core/threading"
"github.com/zeromicro/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
const idleRound = 10 const idleRound = 10

View File

@@ -8,7 +8,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
const threshold = 10 const threshold = 10

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/fs" "github.com/tal-tech/go-zero/core/fs"
) )
const ( const (

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/fs" "github.com/tal-tech/go-zero/core/fs"
) )
func TestSplitLineChunks(t *testing.T) { func TestSplitLineChunks(t *testing.T) {

View File

@@ -5,9 +5,6 @@ import (
"os" "os"
) )
// errExceedFileSize indicates that the file size is exceeded.
var errExceedFileSize = errors.New("exceed file size")
// A RangeReader is used to read a range of content from a file. // A RangeReader is used to read a range of content from a file.
type RangeReader struct { type RangeReader struct {
file *os.File file *os.File
@@ -32,7 +29,7 @@ func (rr *RangeReader) Read(p []byte) (n int, err error) {
} }
if rr.stop < rr.start || rr.start >= stat.Size() { if rr.stop < rr.start || rr.start >= stat.Size() {
return 0, errExceedFileSize return 0, errors.New("exceed file size")
} }
if rr.stop-rr.start < int64(len(p)) { if rr.stop-rr.start < int64(len(p)) {

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/fs" "github.com/tal-tech/go-zero/core/fs"
) )
func TestRangeReader(t *testing.T) { func TestRangeReader(t *testing.T) {

View File

@@ -1,9 +1,10 @@
package fs package fs
import ( import (
"io/ioutil"
"os" "os"
"github.com/zeromicro/go-zero/core/hash" "github.com/tal-tech/go-zero/core/hash"
) )
// TempFileWithText creates the temporary file with the given content, // TempFileWithText creates the temporary file with the given content,
@@ -11,12 +12,12 @@ import (
// The file is kept as open, the caller should close the file handle, // The file is kept as open, the caller should close the file handle,
// and remove the file by name. // and remove the file by name.
func TempFileWithText(text string) (*os.File, error) { func TempFileWithText(text string) (*os.File, error) {
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))) tmpfile, err := ioutil.TempFile(os.TempDir(), hash.Md5Hex([]byte(text)))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := os.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil { if err := ioutil.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
return nil, err return nil, err
} }

View File

@@ -1,49 +0,0 @@
package fs
import (
"io"
"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 := io.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 := os.ReadFile(f)
assert.Nil(t, err)
if len(bs) != 4 {
t.Error("TempFilenameWithText returned wrong file size")
}
}

View File

@@ -1,6 +1,6 @@
package fx package fx
import "github.com/zeromicro/go-zero/core/threading" import "github.com/tal-tech/go-zero/core/threading"
// Parallel runs fns parallelly and waits for done. // Parallel runs fns parallelly and waits for done.
func Parallel(fns ...func()) { func Parallel(fns ...func()) {

View File

@@ -1,6 +1,6 @@
package fx package fx
import "github.com/zeromicro/go-zero/core/errorx" import "github.com/tal-tech/go-zero/core/errorx"
const defaultRetryTimes = 3 const defaultRetryTimes = 3

View File

@@ -4,9 +4,9 @@ import (
"sort" "sort"
"sync" "sync"
"github.com/zeromicro/go-zero/core/collection" "github.com/tal-tech/go-zero/core/collection"
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/threading" "github.com/tal-tech/go-zero/core/threading"
) )
const ( const (

View File

@@ -1,7 +1,7 @@
package fx package fx
import ( import (
"io" "io/ioutil"
"log" "log"
"math/rand" "math/rand"
"reflect" "reflect"
@@ -13,8 +13,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
"go.uber.org/goleak"
) )
func TestBuffer(t *testing.T) { func TestBuffer(t *testing.T) {
@@ -238,7 +237,7 @@ func TestLast(t *testing.T) {
func TestMap(t *testing.T) { func TestMap(t *testing.T) {
runCheckedTest(t, func(t *testing.T) { runCheckedTest(t, func(t *testing.T) {
log.SetOutput(io.Discard) log.SetOutput(ioutil.Discard)
tests := []struct { tests := []struct {
mapper MapFunc mapper MapFunc
@@ -564,6 +563,9 @@ func equal(t *testing.T, stream Stream, data []interface{}) {
} }
func runCheckedTest(t *testing.T, fn func(t *testing.T)) { func runCheckedTest(t *testing.T, fn func(t *testing.T)) {
defer goleak.VerifyNone(t) goroutines := runtime.NumGoroutine()
fn(t) fn(t)
// let scheduler schedule first
time.Sleep(time.Millisecond)
assert.True(t, runtime.NumGoroutine() <= goroutines)
} }

View File

@@ -6,8 +6,8 @@ import (
"strconv" "strconv"
"sync" "sync"
"github.com/zeromicro/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/mapping" "github.com/tal-tech/go-zero/core/mapping"
) )
const ( const (

View File

@@ -6,7 +6,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/mathx" "github.com/tal-tech/go-zero/core/mathx"
) )
const ( const (

View File

@@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"io/ioutil"
"os" "os"
"strings" "strings"
) )
@@ -25,7 +26,7 @@ type (
func DupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) { func DupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) {
var buf bytes.Buffer var buf bytes.Buffer
tee := io.TeeReader(reader, &buf) tee := io.TeeReader(reader, &buf)
return io.NopCloser(tee), io.NopCloser(&buf) return ioutil.NopCloser(tee), ioutil.NopCloser(&buf)
} }
// KeepSpace customizes the reading functions to keep leading and tailing spaces. // KeepSpace customizes the reading functions to keep leading and tailing spaces.
@@ -53,7 +54,7 @@ func ReadBytes(reader io.Reader, buf []byte) error {
// ReadText reads content from the given file with leading and tailing spaces trimmed. // ReadText reads content from the given file with leading and tailing spaces trimmed.
func ReadText(filename string) (string, error) { func ReadText(filename string) (string, error) {
content, err := os.ReadFile(filename) content, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@@ -3,13 +3,14 @@ package iox
import ( import (
"bytes" "bytes"
"io" "io"
"io/ioutil"
"os" "os"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/fs" "github.com/tal-tech/go-zero/core/fs"
"github.com/zeromicro/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
) )
func TestReadText(t *testing.T) { func TestReadText(t *testing.T) {
@@ -96,10 +97,10 @@ func TestReadTextLines(t *testing.T) {
func TestDupReadCloser(t *testing.T) { func TestDupReadCloser(t *testing.T) {
input := "hello" input := "hello"
reader := io.NopCloser(bytes.NewBufferString(input)) reader := ioutil.NopCloser(bytes.NewBufferString(input))
r1, r2 := DupReadCloser(reader) r1, r2 := DupReadCloser(reader)
verify := func(r io.Reader) { verify := func(r io.Reader) {
output, err := io.ReadAll(r) output, err := ioutil.ReadAll(r)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, input, string(output)) assert.Equal(t, input, string(output))
} }
@@ -109,7 +110,7 @@ func TestDupReadCloser(t *testing.T) {
} }
func TestReadBytes(t *testing.T) { func TestReadBytes(t *testing.T) {
reader := io.NopCloser(bytes.NewBufferString("helloworld")) reader := ioutil.NopCloser(bytes.NewBufferString("helloworld"))
buf := make([]byte, 5) buf := make([]byte, 5)
err := ReadBytes(reader, buf) err := ReadBytes(reader, buf)
assert.Nil(t, err) assert.Nil(t, err)
@@ -117,7 +118,7 @@ func TestReadBytes(t *testing.T) {
} }
func TestReadBytesNotEnough(t *testing.T) { func TestReadBytesNotEnough(t *testing.T) {
reader := io.NopCloser(bytes.NewBufferString("hell")) reader := ioutil.NopCloser(bytes.NewBufferString("hell"))
buf := make([]byte, 5) buf := make([]byte, 5)
err := ReadBytes(reader, buf) err := ReadBytes(reader, buf)
assert.Equal(t, io.EOF, err) assert.Equal(t, io.EOF, err)

View File

@@ -1,6 +1,7 @@
package iox package iox
import ( import (
"io/ioutil"
"os" "os"
"testing" "testing"
@@ -12,7 +13,7 @@ func TestCountLines(t *testing.T) {
2 2
3 3
4` 4`
file, err := os.CreateTemp(os.TempDir(), "test-") file, err := ioutil.TempFile(os.TempDir(), "test-")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -13,16 +13,6 @@ func Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v) 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. // Unmarshal unmarshals data bytes into v.
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v interface{}) error {
decoder := json.NewDecoder(bytes.NewReader(data)) decoder := json.NewDecoder(bytes.NewReader(data))
@@ -61,5 +51,5 @@ func unmarshalUseNumber(decoder *json.Decoder, v interface{}) error {
} }
func formatError(v string, err error) error { func formatError(v string, err error) error {
return fmt.Errorf("string: `%s`, error: `%w`", v, err) return fmt.Errorf("string: `%s`, error: `%s`", v, err.Error())
} }

View File

@@ -1,103 +0,0 @@
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)
}

View File

@@ -1,28 +1,30 @@
package limit package limit
import ( import (
"context"
"errors" "errors"
"strconv" "strconv"
"time" "time"
"github.com/zeromicro/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
) )
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key const (
const periodScript = `local limit = tonumber(ARGV[1]) // to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
periodScript = `local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2]) local window = tonumber(ARGV[2])
local current = redis.call("INCRBY", KEYS[1], 1) local current = redis.call("INCRBY", KEYS[1], 1)
if current == 1 then if current == 1 then
redis.call("expire", KEYS[1], window) redis.call("expire", KEYS[1], window)
end return 1
if current < limit then elseif current < limit then
return 1 return 1
elseif current == limit then elseif current == limit then
return 2 return 2
else else
return 0 return 0
end` end`
zoneDiff = 3600 * 8 // GMT+8 for our services
)
const ( const (
// Unknown means not initialized state. // Unknown means not initialized state.
@@ -75,12 +77,7 @@ func NewPeriodLimit(period, quota int, limitStore *redis.Redis, keyPrefix string
// Take requests a permit, it returns the permit state. // Take requests a permit, it returns the permit state.
func (h *PeriodLimit) Take(key string) (int, error) { func (h *PeriodLimit) Take(key string) (int, error) {
return h.TakeCtx(context.Background(), key) resp, err := h.limitStore.Eval(periodScript, []string{h.keyPrefix + key}, []string{
}
// 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.quota),
strconv.Itoa(h.calcExpireSeconds()), strconv.Itoa(h.calcExpireSeconds()),
}) })
@@ -107,9 +104,7 @@ func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
func (h *PeriodLimit) calcExpireSeconds() int { func (h *PeriodLimit) calcExpireSeconds() int {
if h.align { if h.align {
now := time.Now() unix := time.Now().Unix() + zoneDiff
_, offset := now.Zone()
unix := now.Unix() + int64(offset)
return h.period - int(unix%int64(h.period)) return h.period - int(unix%int64(h.period))
} }
@@ -117,8 +112,6 @@ func (h *PeriodLimit) calcExpireSeconds() int {
} }
// Align returns a func to customize a PeriodLimit with alignment. // Align returns a func to customize a PeriodLimit with alignment.
// For example, if we want to limit end users with 5 sms verification messages every day,
// we need to align with the local timezone and the start of the day.
func Align() PeriodOption { func Align() PeriodOption {
return func(l *PeriodLimit) { return func(l *PeriodLimit) {
l.align = true l.align = true

View File

@@ -5,8 +5,8 @@ import (
"github.com/alicebob/miniredis/v2" "github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/redis/redistest" "github.com/tal-tech/go-zero/core/stores/redis/redistest"
) )
func TestPeriodLimit_Take(t *testing.T) { func TestPeriodLimit_Take(t *testing.T) {
@@ -23,9 +23,10 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
const ( const (
seconds = 1 seconds = 1
total = 100
quota = 5 quota = 5
) )
l := NewPeriodLimit(seconds, quota, redis.New(s.Addr()), "periodlimit") l := NewPeriodLimit(seconds, quota, redis.NewRedis(s.Addr(), redis.NodeType), "periodlimit")
s.Close() s.Close()
val, err := l.Take("first") val, err := l.Take("first")
assert.NotNil(t, err) assert.NotNil(t, err)
@@ -65,13 +66,3 @@ func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
assert.Equal(t, 1, hitQuota) assert.Equal(t, 1, hitQuota)
assert.Equal(t, total-quota, overQuota) assert.Equal(t, total-quota, overQuota)
} }
func TestQuotaFull(t *testing.T) {
s, err := miniredis.Run()
assert.Nil(t, err)
l := NewPeriodLimit(1, 1, redis.New(s.Addr()), "periodlimit")
val, err := l.Take("first")
assert.Nil(t, err)
assert.Equal(t, HitQuota, val)
}

View File

@@ -7,8 +7,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
xrate "golang.org/x/time/rate" xrate "golang.org/x/time/rate"
) )
@@ -85,8 +85,8 @@ func (lim *TokenLimiter) Allow() bool {
} }
// AllowN reports whether n events may happen at time now. // AllowN reports whether n events may happen at time now.
// Use this method if you intend to drop / skip events that exceed the rate. // Use this method if you intend to drop / skip events that exceed the rate rate.
// Otherwise, use Reserve or Wait. // Otherwise use Reserve or Wait.
func (lim *TokenLimiter) AllowN(now time.Time, n int) bool { func (lim *TokenLimiter) AllowN(now time.Time, n int) bool {
return lim.reserveN(now, n) return lim.reserveN(now, n)
} }
@@ -112,8 +112,7 @@ func (lim *TokenLimiter) reserveN(now time.Time, n int) bool {
// Lua boolean false -> r Nil bulk reply // Lua boolean false -> r Nil bulk reply
if err == redis.Nil { if err == redis.Nil {
return false return false
} } else if err != nil {
if err != nil {
logx.Errorf("fail to use rate limiter: %s, use in-process limiter for rescue", err) logx.Errorf("fail to use rate limiter: %s, use in-process limiter for rescue", err)
lim.startMonitor() lim.startMonitor()
return lim.rescueLimiter.AllowN(now, n) return lim.rescueLimiter.AllowN(now, n)

View File

@@ -6,9 +6,9 @@ import (
"github.com/alicebob/miniredis/v2" "github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/redis/redistest" "github.com/tal-tech/go-zero/core/stores/redis/redistest"
) )
func init() { func init() {

View File

@@ -7,11 +7,11 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/zeromicro/go-zero/core/collection" "github.com/tal-tech/go-zero/core/collection"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
"github.com/zeromicro/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
const ( const (

View File

@@ -8,11 +8,11 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/collection" "github.com/tal-tech/go-zero/core/collection"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mathx" "github.com/tal-tech/go-zero/core/mathx"
"github.com/zeromicro/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
) )
const ( const (

View File

@@ -3,7 +3,7 @@ package load
import ( import (
"io" "io"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
) )
// A ShedderGroup is a manager to manage key based shedders. // A ShedderGroup is a manager to manage key based shedders.

View File

@@ -4,8 +4,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/zeromicro/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
) )
type ( type (

View File

@@ -1,26 +0,0 @@
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
}

View File

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

View File

@@ -3,24 +3,11 @@ package logx
// A LogConf is a logging config. // A LogConf is a logging config.
type LogConf struct { type LogConf struct {
ServiceName string `json:",optional"` ServiceName string `json:",optional"`
Mode string `json:",default=console,options=[console,file,volume]"` Mode string `json:",default=console,options=console|file|volume"`
Encoding string `json:",default=json,options=[json,plain]"`
TimeFormat string `json:",optional"` TimeFormat string `json:",optional"`
Path string `json:",default=logs"` Path string `json:",default=logs"`
Level string `json:",default=info,options=[info,error,severe]"` Level string `json:",default=info,options=info|error|severe"`
Compress bool `json:",optional"` Compress bool `json:",optional"`
KeepDays int `json:",optional"` KeepDays int `json:",optional"`
StackCooldownMillis int `json:",default=100"` StackCooldownMillis int `json:",default=100"`
// MaxBackups represents how many backup log files will be kept. 0 means all files will be kept forever.
// Only take effect when RotationRuleType is `size`.
// Even thougth `MaxBackups` sets 0, log files will still be removed
// if the `KeepDays` limitation is reached.
MaxBackups int `json:",default=0"`
// MaxSize represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`.
// Only take effect when RotationRuleType is `size`
MaxSize int `json:",default=0"`
// RotationRuleType represents the type of log rotation rule. Default is `daily`.
// daily: daily rotation.
// size: size limited rotation.
Rotation string `json:",default=daily,options=[daily,size]"`
} }

View File

@@ -1,145 +0,0 @@
package logx
import (
"context"
"fmt"
"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 &contextLogger{
ctx: ctx,
}
}
type contextLogger struct {
logEntry
ctx context.Context
}
func (l *contextLogger) Error(v ...interface{}) {
l.err(fmt.Sprint(v...))
}
func (l *contextLogger) Errorf(format string, v ...interface{}) {
l.err(fmt.Sprintf(format, v...))
}
func (l *contextLogger) Errorv(v interface{}) {
l.err(fmt.Sprint(v))
}
func (l *contextLogger) Errorw(msg string, fields ...LogField) {
l.err(msg, fields...)
}
func (l *contextLogger) Info(v ...interface{}) {
l.info(fmt.Sprint(v...))
}
func (l *contextLogger) Infof(format string, v ...interface{}) {
l.info(fmt.Sprintf(format, v...))
}
func (l *contextLogger) Infov(v interface{}) {
l.info(v)
}
func (l *contextLogger) Infow(msg string, fields ...LogField) {
l.info(msg, fields...)
}
func (l *contextLogger) Slow(v ...interface{}) {
l.slow(fmt.Sprint(v...))
}
func (l *contextLogger) Slowf(format string, v ...interface{}) {
l.slow(fmt.Sprintf(format, v...))
}
func (l *contextLogger) Slowv(v interface{}) {
l.slow(v)
}
func (l *contextLogger) Sloww(msg string, fields ...LogField) {
l.slow(msg, fields...)
}
func (l *contextLogger) WithContext(ctx context.Context) Logger {
if ctx == nil {
return l
}
l.ctx = ctx
return l
}
func (l *contextLogger) WithDuration(duration time.Duration) Logger {
l.Duration = timex.ReprOfDuration(duration)
return l
}
func (l *contextLogger) 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))
}
val := l.ctx.Value(fieldsContextKey)
if val != nil {
if arr, ok := val.([]LogField); ok {
fields = append(fields, arr...)
}
}
return fields
}
func (l *contextLogger) err(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Error(v, l.buildFields(fields...)...)
}
}
func (l *contextLogger) info(v interface{}, fields ...LogField) {
if shallLog(InfoLevel) {
getWriter().Info(v, l.buildFields(fields...)...)
}
}
func (l *contextLogger) slow(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Slow(v, l.buildFields(fields...)...)
}
}
func spanIdFromContext(ctx context.Context) string {
spanCtx := trace.SpanContextFromContext(ctx)
if spanCtx.HasSpanID() {
return spanCtx.SpanID().String()
}
return ""
}
func traceIdFromContext(ctx context.Context) string {
spanCtx := trace.SpanContextFromContext(ctx)
if spanCtx.HasTraceID() {
return spanCtx.TraceID().String()
}
return ""
}

View File

@@ -1,240 +0,0 @@
package logx
import (
"context"
"encoding/json"
"io"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func TestTraceLog(t *testing.T) {
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, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
WithContext(ctx).Info(testlog)
validate(t, w.String(), true, true)
}
func TestTraceError(t *testing.T) {
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, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
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)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorf(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorv(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorw(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceInfo(t *testing.T) {
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, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
SetLevel(InfoLevel)
l := WithContext(ctx)
l.WithDuration(time.Second).Info(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infof(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infov(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infow(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceInfoConsole(t *testing.T) {
old := atomic.SwapUint32(&encoding, jsonEncodingType)
defer atomic.StoreUint32(&encoding, old)
w := new(mockWriter)
o := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(o)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
l := WithContext(ctx)
SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infof(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infov(testlog)
validate(t, w.String(), true, true)
}
func TestTraceSlow(t *testing.T) {
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, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
l := WithContext(ctx)
SetLevel(InfoLevel)
l.WithDuration(time.Second).Slow(testlog)
assert.True(t, strings.Contains(w.String(), traceKey))
assert.True(t, strings.Contains(w.String(), spanKey))
w.Reset()
l.WithDuration(time.Second).Slowf(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Slowv(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Sloww(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceWithoutContext(t *testing.T) {
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)
validate(t, w.String(), false, false)
w.Reset()
l.WithDuration(time.Second).Infof(testlog)
validate(t, w.String(), false, false)
}
func TestLogWithFields(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
ctx := WithFields(context.Background(), Field("foo", "bar"))
l := WithContext(ctx)
SetLevel(InfoLevel)
l.Info(testlog)
var val mockValue
assert.Nil(t, json.Unmarshal([]byte(w.String()), &val))
assert.Equal(t, "bar", val.Foo)
}
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"`
Foo string `json:"foo"`
}

View File

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

View File

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

View File

@@ -1,18 +0,0 @@
package logx
import "context"
var fieldsContextKey contextKey
type contextKey struct{}
// WithFields returns a new context with the given fields.
func WithFields(ctx context.Context, fields ...LogField) context.Context {
if val := ctx.Value(fieldsContextKey); val != nil {
if arr, ok := val.([]LogField); ok {
return context.WithValue(ctx, fieldsContextKey, append(arr, fields...))
}
}
return context.WithValue(ctx, fieldsContextKey, fields)
}

View File

@@ -1,35 +0,0 @@
package logx
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithFields(t *testing.T) {
ctx := WithFields(context.Background(), Field("a", 1), Field("b", 2))
vals := ctx.Value(fieldsContextKey)
assert.NotNil(t, vals)
fields, ok := vals.([]LogField)
assert.True(t, ok)
assert.EqualValues(t, []LogField{Field("a", 1), Field("b", 2)}, fields)
}
func TestWithFieldsAppend(t *testing.T) {
var dummyKey struct{}
ctx := context.WithValue(context.Background(), dummyKey, "dummy")
ctx = WithFields(ctx, Field("a", 1), Field("b", 2))
ctx = WithFields(ctx, Field("c", 3), Field("d", 4))
vals := ctx.Value(fieldsContextKey)
assert.NotNil(t, vals)
fields, ok := vals.([]LogField)
assert.True(t, ok)
assert.Equal(t, "dummy", ctx.Value(dummyKey))
assert.EqualValues(t, []LogField{
Field("a", 1),
Field("b", 2),
Field("c", 3),
Field("d", 4),
}, fields)
}

View File

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

View File

@@ -4,8 +4,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/zeromicro/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
"github.com/zeromicro/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
type limitedExecutor struct { type limitedExecutor struct {

View File

@@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
func TestLimitedExecutor_logOrDiscard(t *testing.T) { func TestLimitedExecutor_logOrDiscard(t *testing.T) {

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