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
677 changed files with 6262 additions and 21534 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

@@ -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.15
id: go
- name: Check out code into the Go module directory - name: Set up Go 1.x
uses: actions/checkout@v2 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.15
- name: Checkout codebase
uses: actions/checkout@v2
- 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

@@ -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@v2
- uses: zeromicro/go-zero-release-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
goversion: "https://dl.google.com/go/go1.17.5.linux-amd64.tar.gz"
project_path: "tools/goctl"
binary_name: "goctl"
extra_files: tools/goctl/goctl.md

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

@@ -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 (
@@ -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

@@ -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 TestSafeMap(t *testing.T) { func TestSafeMap(t *testing.T) {

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

@@ -6,26 +6,24 @@ import (
"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 := ioutil.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)
} }
@@ -42,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

@@ -6,19 +6,19 @@ 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"
"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) {
@@ -57,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",

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

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

@@ -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"
) )

View File

@@ -7,10 +7,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/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"
) )

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

@@ -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

@@ -4,7 +4,7 @@ import (
"io/ioutil" "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,

View File

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

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

@@ -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) {
@@ -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

@@ -9,8 +9,8 @@ import (
"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) {

View File

@@ -51,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,87 +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 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

@@ -5,23 +5,26 @@ import (
"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.
@@ -101,9 +104,7 @@ func (h *PeriodLimit) Take(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))
} }
@@ -111,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,11 +3,10 @@ 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"`

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 &traceLogger{
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,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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -29,24 +29,20 @@ func TestRedirector(t *testing.T) {
} }
func captureOutput(f func()) string { func captureOutput(f func()) string {
w := new(mockWriter) atomic.StoreUint32(&initialized, 1)
old := writer.Swap(w) writer := new(mockWriter)
defer writer.Store(old) infoLog = writer
prevLevel := atomic.LoadUint32(&logLevel) prevLevel := atomic.LoadUint32(&logLevel)
SetLevel(InfoLevel) SetLevel(InfoLevel)
f() f()
SetLevel(prevLevel) SetLevel(prevLevel)
return w.String() return writer.builder.String()
} }
func getContent(jsonStr string) string { func getContent(jsonStr string) string {
var entry logEntry var entry logEntry
json.Unmarshal([]byte(jsonStr), &entry) json.Unmarshal([]byte(jsonStr), &entry)
val, ok := entry.Content.(string) return entry.Content.(string)
if ok {
return val
}
return ""
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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