mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-28 00:55:28 +08:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
421e6617b1 | ||
|
|
0ee7a271d3 | ||
|
|
af022b9655 | ||
|
|
98d46261d9 | ||
|
|
4222fd97bc | ||
|
|
814852f0b8 | ||
|
|
ded2888759 | ||
|
|
18d66a795d | ||
|
|
4211672bfd | ||
|
|
68df0c3620 | ||
|
|
5e435b6a76 | ||
|
|
0dcede6457 | ||
|
|
cc21f5fae2 | ||
|
|
b22ad50d59 | ||
|
|
974252980c | ||
|
|
8d83986d27 | ||
|
|
6821b0a7dd | ||
|
|
1ba1724c65 | ||
|
|
ca5a7df5b0 | ||
|
|
69a3024853 | ||
|
|
fd3abf3717 | ||
|
|
99b3750d10 | ||
|
|
33f6d7ebb8 | ||
|
|
c4ef9ceb68 | ||
|
|
e95861f28a | ||
|
|
d3cd7b17c0 | ||
|
|
a50515496c | ||
|
|
0423313d9b | ||
|
|
7bbe7de05f | ||
|
|
83a451f2f4 | ||
|
|
d2a874f21d | ||
|
|
fd85b24b25 | ||
|
|
14fcbd7658 | ||
|
|
cb3ffc76a3 | ||
|
|
45fbd7dc35 | ||
|
|
af821cf794 | ||
|
|
ec69950153 | ||
|
|
ce5e78db53 | ||
|
|
ed75802eaa | ||
|
|
76c92b571d | ||
|
|
a2e703c53e | ||
|
|
ca698deb2a | ||
|
|
a9f4aab86b | ||
|
|
c3f57e9b0a | ||
|
|
ad4cce959d | ||
|
|
279123f4a7 | ||
|
|
457eb1961b | ||
|
|
63df384a4b | ||
|
|
42bfa26e2b | ||
|
|
ff04356704 | ||
|
|
05db706c62 | ||
|
|
ef2e0d859d | ||
|
|
05ec16ae9d | ||
|
|
13e685e0db | ||
|
|
c10f44b74e | ||
|
|
57644420ed | ||
|
|
b245159417 | ||
|
|
c26ea17669 | ||
|
|
a7daff3587 | ||
|
|
6719d06146 | ||
|
|
0c6eaeda9f | ||
|
|
b9c0c0f8b5 | ||
|
|
77da459165 | ||
|
|
13cdbdc98b | ||
|
|
e8c1e6e09b | ||
|
|
f1171e01f2 | ||
|
|
61e562d0c7 | ||
|
|
b71453985c | ||
|
|
31b9ba19a2 | ||
|
|
3170afd57b | ||
|
|
03e365a5d8 | ||
|
|
7d4fce9588 | ||
|
|
916cea858f | ||
|
|
a86942d532 | ||
|
|
f76c70ea9a | ||
|
|
4cbfdb3d74 | ||
|
|
aefa6dfb50 | ||
|
|
9047029475 | ||
|
|
f296c182f7 | ||
|
|
40e7a4cd07 | ||
|
|
92e5819e91 | ||
|
|
8d23ab158b | ||
|
|
bcccfab824 | ||
|
|
f7e701a634 | ||
|
|
7c2d8e5cc2 | ||
|
|
5b622d6265 | ||
|
|
c5510a4e1b | ||
|
|
2a33b74b35 | ||
|
|
45bb547a81 | ||
|
|
f5f5261556 | ||
|
|
b176d5d434 | ||
|
|
92f6c48349 | ||
|
|
71e8230e65 | ||
|
|
018fa8e0a0 | ||
|
|
979fe9718a | ||
|
|
f998803131 | ||
|
|
1262266ac2 | ||
|
|
9c32bf8478 | ||
|
|
37ec7f6443 | ||
|
|
2fdc4dfc0f | ||
|
|
4b2a6ba3de | ||
|
|
7fa3f10f22 | ||
|
|
4a29a0b642 | ||
|
|
a62745a152 | ||
|
|
28314326e7 | ||
|
|
f6bdb6e1de | ||
|
|
efa6940001 | ||
|
|
da81d8f774 | ||
|
|
fd84b27bdc | ||
|
|
6b4d0d89c0 | ||
|
|
d61a55f779 | ||
|
|
8ef4164209 | ||
|
|
50e29e2075 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
# ignore
|
# ignore
|
||||||
**/.idea
|
**/.idea
|
||||||
|
**/.vscode
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/logs
|
**/logs
|
||||||
**/adhoc
|
**/adhoc
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ func NewSafeMap() *SafeMap {
|
|||||||
// Del deletes the value with the given key from m.
|
// Del deletes the value with the given key from m.
|
||||||
func (m *SafeMap) Del(key any) {
|
func (m *SafeMap) Del(key any) {
|
||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
if _, ok := m.dirtyOld[key]; ok {
|
if _, ok := m.dirtyOld[key]; ok {
|
||||||
delete(m.dirtyOld, key)
|
delete(m.dirtyOld, key)
|
||||||
m.deletionOld++
|
m.deletionOld++
|
||||||
@@ -52,7 +54,6 @@ func (m *SafeMap) Del(key any) {
|
|||||||
m.dirtyNew = make(map[any]any)
|
m.dirtyNew = make(map[any]any)
|
||||||
m.deletionNew = 0
|
m.deletionNew = 0
|
||||||
}
|
}
|
||||||
m.lock.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets the value with the given key from m.
|
// Get gets the value with the given key from m.
|
||||||
@@ -89,6 +90,8 @@ func (m *SafeMap) Range(f func(key, val any) bool) {
|
|||||||
// Set sets the value into m with the given key.
|
// Set sets the value into m with the given key.
|
||||||
func (m *SafeMap) Set(key, value any) {
|
func (m *SafeMap) Set(key, value any) {
|
||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
if m.deletionOld <= maxDeletion {
|
if m.deletionOld <= maxDeletion {
|
||||||
if _, ok := m.dirtyNew[key]; ok {
|
if _, ok := m.dirtyNew[key]; ok {
|
||||||
delete(m.dirtyNew, key)
|
delete(m.dirtyNew, key)
|
||||||
@@ -102,7 +105,6 @@ func (m *SafeMap) Set(key, value any) {
|
|||||||
}
|
}
|
||||||
m.dirtyNew[key] = value
|
m.dirtyNew[key] = value
|
||||||
}
|
}
|
||||||
m.lock.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the size of m.
|
// Size returns the size of m.
|
||||||
|
|||||||
@@ -147,3 +147,65 @@ func TestSafeMap_Range(t *testing.T) {
|
|||||||
assert.Equal(t, m.dirtyNew, newMap.dirtyNew)
|
assert.Equal(t, m.dirtyNew, newMap.dirtyNew)
|
||||||
assert.Equal(t, m.dirtyOld, newMap.dirtyOld)
|
assert.Equal(t, m.dirtyOld, newMap.dirtyOld)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetManyTimes(t *testing.T) {
|
||||||
|
const iteration = maxDeletion * 2
|
||||||
|
m := NewSafeMap()
|
||||||
|
for i := 0; i < iteration; i++ {
|
||||||
|
m.Set(i, i)
|
||||||
|
if i%3 == 0 {
|
||||||
|
m.Del(i / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var count int
|
||||||
|
m.Range(func(k, v any) bool {
|
||||||
|
count++
|
||||||
|
return count < maxDeletion/2
|
||||||
|
})
|
||||||
|
assert.Equal(t, maxDeletion/2, count)
|
||||||
|
for i := 0; i < iteration; i++ {
|
||||||
|
m.Set(i, i)
|
||||||
|
if i%3 == 0 {
|
||||||
|
m.Del(i / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < iteration; i++ {
|
||||||
|
m.Set(i, i)
|
||||||
|
if i%3 == 0 {
|
||||||
|
m.Del(i / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < iteration; i++ {
|
||||||
|
m.Set(i, i)
|
||||||
|
if i%3 == 0 {
|
||||||
|
m.Del(i / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
m.Range(func(k, v any) bool {
|
||||||
|
count++
|
||||||
|
return count < maxDeletion
|
||||||
|
})
|
||||||
|
assert.Equal(t, maxDeletion, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetManyTimesNew(t *testing.T) {
|
||||||
|
m := NewSafeMap()
|
||||||
|
for i := 0; i < maxDeletion*3; i++ {
|
||||||
|
m.Set(i, i)
|
||||||
|
}
|
||||||
|
for i := 0; i < maxDeletion*2; i++ {
|
||||||
|
m.Del(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < maxDeletion*3; i++ {
|
||||||
|
m.Set(i+maxDeletion*3, i+maxDeletion*3)
|
||||||
|
}
|
||||||
|
for i := 0; i < maxDeletion*2; i++ {
|
||||||
|
m.Del(i + maxDeletion*2)
|
||||||
|
}
|
||||||
|
for i := 0; i < maxDeletion-copyThreshold+1; i++ {
|
||||||
|
m.Del(i + maxDeletion*2)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 0, len(m.dirtyNew))
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ func (bp *BufferPool) Get() *bytes.Buffer {
|
|||||||
|
|
||||||
// Put returns buf into bp.
|
// Put returns buf into bp.
|
||||||
func (bp *BufferPool) Put(buf *bytes.Buffer) {
|
func (bp *BufferPool) Put(buf *bytes.Buffer) {
|
||||||
|
if buf == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if buf.Cap() < bp.capability {
|
if buf.Cap() < bp.capability {
|
||||||
bp.pool.Put(buf)
|
bp.pool.Put(buf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,3 +13,26 @@ func TestBufferPool(t *testing.T) {
|
|||||||
pool.Put(bytes.NewBuffer(make([]byte, 0, 2*capacity)))
|
pool.Put(bytes.NewBuffer(make([]byte, 0, 2*capacity)))
|
||||||
assert.True(t, pool.Get().Cap() <= capacity)
|
assert.True(t, pool.Get().Cap() <= capacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBufferPool_Put(t *testing.T) {
|
||||||
|
t.Run("with nil buf", func(t *testing.T) {
|
||||||
|
pool := NewBufferPool(1024)
|
||||||
|
pool.Put(nil)
|
||||||
|
val := pool.Get()
|
||||||
|
assert.IsType(t, new(bytes.Buffer), val)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with less-cap buf", func(t *testing.T) {
|
||||||
|
pool := NewBufferPool(1024)
|
||||||
|
pool.Put(bytes.NewBuffer(make([]byte, 0, 512)))
|
||||||
|
val := pool.Get()
|
||||||
|
assert.IsType(t, new(bytes.Buffer), val)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with more-cap buf", func(t *testing.T) {
|
||||||
|
pool := NewBufferPool(1024)
|
||||||
|
pool.Put(bytes.NewBuffer(make([]byte, 0, 1024<<1)))
|
||||||
|
val := pool.Get()
|
||||||
|
assert.IsType(t, new(bytes.Buffer), val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
12
core/iox/nopcloser_test.go
Normal file
12
core/iox/nopcloser_test.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package iox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNopCloser(t *testing.T) {
|
||||||
|
closer := NopCloser(nil)
|
||||||
|
assert.NoError(t, closer.Close())
|
||||||
|
}
|
||||||
@@ -35,6 +35,16 @@ func KeepSpace() TextReadOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LimitDupReadCloser returns two io.ReadCloser that read from the first will be written to the second.
|
||||||
|
// But the second io.ReadCloser is limited to up to n bytes.
|
||||||
|
// The first returned reader needs to be read first, because the content
|
||||||
|
// read from it will be written to the underlying buffer of the second reader.
|
||||||
|
func LimitDupReadCloser(reader io.ReadCloser, n int64) (io.ReadCloser, io.ReadCloser) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
tee := LimitTeeReader(reader, &buf, n)
|
||||||
|
return io.NopCloser(tee), io.NopCloser(&buf)
|
||||||
|
}
|
||||||
|
|
||||||
// ReadBytes reads exactly the bytes with the length of len(buf)
|
// ReadBytes reads exactly the bytes with the length of len(buf)
|
||||||
func ReadBytes(reader io.Reader, buf []byte) error {
|
func ReadBytes(reader io.Reader, buf []byte) error {
|
||||||
var got int
|
var got int
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ b`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadTextError(t *testing.T) {
|
||||||
|
_, err := ReadText("not-exist")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestReadTextLines(t *testing.T) {
|
func TestReadTextLines(t *testing.T) {
|
||||||
text := `1
|
text := `1
|
||||||
|
|
||||||
@@ -94,6 +99,11 @@ func TestReadTextLines(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadTextLinesError(t *testing.T) {
|
||||||
|
_, err := ReadTextLines("not-exist")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDupReadCloser(t *testing.T) {
|
func TestDupReadCloser(t *testing.T) {
|
||||||
input := "hello"
|
input := "hello"
|
||||||
reader := io.NopCloser(bytes.NewBufferString(input))
|
reader := io.NopCloser(bytes.NewBufferString(input))
|
||||||
@@ -108,6 +118,29 @@ func TestDupReadCloser(t *testing.T) {
|
|||||||
verify(r2)
|
verify(r2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLimitDupReadCloser(t *testing.T) {
|
||||||
|
input := "hello world"
|
||||||
|
limitBytes := int64(4)
|
||||||
|
reader := io.NopCloser(bytes.NewBufferString(input))
|
||||||
|
r1, r2 := LimitDupReadCloser(reader, limitBytes)
|
||||||
|
verify := func(r io.Reader) {
|
||||||
|
output, err := io.ReadAll(r)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, input, string(output))
|
||||||
|
}
|
||||||
|
verifyLimit := func(r io.Reader, limit int64) {
|
||||||
|
output, err := io.ReadAll(r)
|
||||||
|
if limit < int64(len(input)) {
|
||||||
|
input = input[:limit]
|
||||||
|
}
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, input, string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(r1)
|
||||||
|
verifyLimit(r2, limitBytes)
|
||||||
|
}
|
||||||
|
|
||||||
func TestReadBytes(t *testing.T) {
|
func TestReadBytes(t *testing.T) {
|
||||||
reader := io.NopCloser(bytes.NewBufferString("helloworld"))
|
reader := io.NopCloser(bytes.NewBufferString("helloworld"))
|
||||||
buf := make([]byte, 5)
|
buf := make([]byte, 5)
|
||||||
|
|||||||
35
core/iox/tee.go
Normal file
35
core/iox/tee.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package iox
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// LimitTeeReader returns a Reader that writes up to n bytes to w what it reads from r.
|
||||||
|
// First n bytes reads from r performed through it are matched with
|
||||||
|
// corresponding writes to w. There is no internal buffering -
|
||||||
|
// the write must complete before the first n bytes read completes.
|
||||||
|
// Any error encountered while writing is reported as a read error.
|
||||||
|
func LimitTeeReader(r io.Reader, w io.Writer, n int64) io.Reader {
|
||||||
|
return &limitTeeReader{r, w, n}
|
||||||
|
}
|
||||||
|
|
||||||
|
type limitTeeReader struct {
|
||||||
|
r io.Reader
|
||||||
|
w io.Writer
|
||||||
|
n int64 // limit bytes remaining
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *limitTeeReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = t.r.Read(p)
|
||||||
|
if n > 0 && t.n > 0 {
|
||||||
|
limit := int64(n)
|
||||||
|
if limit > t.n {
|
||||||
|
limit = t.n
|
||||||
|
}
|
||||||
|
if n, err := t.w.Write(p[:limit]); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.n -= limit
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
40
core/iox/tee_test.go
Normal file
40
core/iox/tee_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package iox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLimitTeeReader(t *testing.T) {
|
||||||
|
limit := int64(4)
|
||||||
|
src := []byte("hello, world")
|
||||||
|
dst := make([]byte, len(src))
|
||||||
|
rb := bytes.NewBuffer(src)
|
||||||
|
wb := new(bytes.Buffer)
|
||||||
|
r := LimitTeeReader(rb, wb, limit)
|
||||||
|
if n, err := io.ReadFull(r, dst); err != nil || n != len(src) {
|
||||||
|
t.Fatalf("ReadFull(r, dst) = %d, %v; want %d, nil", n, err, len(src))
|
||||||
|
}
|
||||||
|
if !bytes.Equal(dst, src) {
|
||||||
|
t.Errorf("bytes read = %q want %q", dst, src)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(wb.Bytes(), src[:limit]) {
|
||||||
|
t.Errorf("bytes written = %q want %q", wb.Bytes(), src)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := r.Read(dst)
|
||||||
|
assert.Equal(t, 0, n)
|
||||||
|
assert.Equal(t, io.EOF, err)
|
||||||
|
|
||||||
|
rb = bytes.NewBuffer(src)
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
if assert.NoError(t, pr.Close()) {
|
||||||
|
r = LimitTeeReader(rb, pw, limit)
|
||||||
|
n, err := io.ReadFull(r, dst)
|
||||||
|
assert.Equal(t, 0, n)
|
||||||
|
assert.Equal(t, io.ErrClosedPipe, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package iox
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@@ -26,7 +27,7 @@ func CountLines(file string) (int, error) {
|
|||||||
count += bytes.Count(buf[:c], lineSep)
|
count += bytes.Count(buf[:c], lineSep)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case err == io.EOF:
|
case errors.Is(err, io.EOF):
|
||||||
if noEol {
|
if noEol {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,3 +24,8 @@ func TestCountLines(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 4, lines)
|
assert.Equal(t, 4, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCountLinesError(t *testing.T) {
|
||||||
|
_, err := CountLines("not-exist")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package iox
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/iotest"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -22,3 +23,10 @@ func TestScanner(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.EqualValues(t, []string{"1", "2", "3", "4"}, lines)
|
assert.EqualValues(t, []string{"1", "2", "3", "4"}, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBadScanner(t *testing.T) {
|
||||||
|
scanner := NewTextLineScanner(iotest.ErrReader(iotest.ErrTimeout))
|
||||||
|
assert.False(t, scanner.Scan())
|
||||||
|
_, err := scanner.Line()
|
||||||
|
assert.ErrorIs(t, err, iotest.ErrTimeout)
|
||||||
|
}
|
||||||
|
|||||||
40
core/logx/fs.go
Normal file
40
core/logx/fs.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package logx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fileSys realFileSystem
|
||||||
|
|
||||||
|
type (
|
||||||
|
fileSystem interface {
|
||||||
|
Close(closer io.Closer) error
|
||||||
|
Copy(writer io.Writer, reader io.Reader) (int64, error)
|
||||||
|
Create(name string) (*os.File, error)
|
||||||
|
Open(name string) (*os.File, error)
|
||||||
|
Remove(name string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
realFileSystem struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (fs realFileSystem) Close(closer io.Closer) error {
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs realFileSystem) Copy(writer io.Writer, reader io.Reader) (int64, error) {
|
||||||
|
return io.Copy(writer, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs realFileSystem) Create(name string) (*os.File, error) {
|
||||||
|
return os.Create(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs realFileSystem) Open(name string) (*os.File, error) {
|
||||||
|
return os.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs realFileSystem) Remove(name string) error {
|
||||||
|
return os.Remove(name)
|
||||||
|
}
|
||||||
@@ -68,22 +68,30 @@ func Close() error {
|
|||||||
|
|
||||||
// Debug writes v into access log.
|
// Debug writes v into access log.
|
||||||
func Debug(v ...any) {
|
func Debug(v ...any) {
|
||||||
writeDebug(fmt.Sprint(v...))
|
if shallLog(DebugLevel) {
|
||||||
|
writeDebug(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugf writes v with format into access log.
|
// Debugf writes v with format into access log.
|
||||||
func Debugf(format string, v ...any) {
|
func Debugf(format string, v ...any) {
|
||||||
writeDebug(fmt.Sprintf(format, v...))
|
if shallLog(DebugLevel) {
|
||||||
|
writeDebug(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugv writes v into access log with json content.
|
// Debugv writes v into access log with json content.
|
||||||
func Debugv(v any) {
|
func Debugv(v any) {
|
||||||
writeDebug(v)
|
if shallLog(DebugLevel) {
|
||||||
|
writeDebug(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugw writes msg along with fields into access log.
|
// Debugw writes msg along with fields into access log.
|
||||||
func Debugw(msg string, fields ...LogField) {
|
func Debugw(msg string, fields ...LogField) {
|
||||||
writeDebug(msg, fields...)
|
if shallLog(DebugLevel) {
|
||||||
|
writeDebug(msg, fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable disables the logging.
|
// Disable disables the logging.
|
||||||
@@ -99,35 +107,47 @@ func DisableStat() {
|
|||||||
|
|
||||||
// Error writes v into error log.
|
// Error writes v into error log.
|
||||||
func Error(v ...any) {
|
func Error(v ...any) {
|
||||||
writeError(fmt.Sprint(v...))
|
if shallLog(ErrorLevel) {
|
||||||
|
writeError(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf writes v with format into error log.
|
// Errorf writes v with format into error log.
|
||||||
func Errorf(format string, v ...any) {
|
func Errorf(format string, v ...any) {
|
||||||
writeError(fmt.Errorf(format, v...).Error())
|
if shallLog(ErrorLevel) {
|
||||||
|
writeError(fmt.Errorf(format, v...).Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorStack writes v along with call stack into error log.
|
// ErrorStack writes v along with call stack into error log.
|
||||||
func ErrorStack(v ...any) {
|
func ErrorStack(v ...any) {
|
||||||
// there is newline in stack string
|
if shallLog(ErrorLevel) {
|
||||||
writeStack(fmt.Sprint(v...))
|
// there is newline in stack string
|
||||||
|
writeStack(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorStackf writes v along with call stack in format into error log.
|
// ErrorStackf writes v along with call stack in format into error log.
|
||||||
func ErrorStackf(format string, v ...any) {
|
func ErrorStackf(format string, v ...any) {
|
||||||
// there is newline in stack string
|
if shallLog(ErrorLevel) {
|
||||||
writeStack(fmt.Sprintf(format, v...))
|
// there is newline in stack string
|
||||||
|
writeStack(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorv writes v into error log with json content.
|
// Errorv writes v into error log with json content.
|
||||||
// No call stack attached, because not elegant to pack the messages.
|
// No call stack attached, because not elegant to pack the messages.
|
||||||
func Errorv(v any) {
|
func Errorv(v any) {
|
||||||
writeError(v)
|
if shallLog(ErrorLevel) {
|
||||||
|
writeError(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorw writes msg along with fields into error log.
|
// Errorw writes msg along with fields into error log.
|
||||||
func Errorw(msg string, fields ...LogField) {
|
func Errorw(msg string, fields ...LogField) {
|
||||||
writeError(msg, fields...)
|
if shallLog(ErrorLevel) {
|
||||||
|
writeError(msg, fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field returns a LogField for the given key and value.
|
// Field returns a LogField for the given key and value.
|
||||||
@@ -170,22 +190,30 @@ func Field(key string, value any) LogField {
|
|||||||
|
|
||||||
// Info writes v into access log.
|
// Info writes v into access log.
|
||||||
func Info(v ...any) {
|
func Info(v ...any) {
|
||||||
writeInfo(fmt.Sprint(v...))
|
if shallLog(InfoLevel) {
|
||||||
|
writeInfo(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infof writes v with format into access log.
|
// Infof writes v with format into access log.
|
||||||
func Infof(format string, v ...any) {
|
func Infof(format string, v ...any) {
|
||||||
writeInfo(fmt.Sprintf(format, v...))
|
if shallLog(InfoLevel) {
|
||||||
|
writeInfo(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infov writes v into access log with json content.
|
// Infov writes v into access log with json content.
|
||||||
func Infov(v any) {
|
func Infov(v any) {
|
||||||
writeInfo(v)
|
if shallLog(InfoLevel) {
|
||||||
|
writeInfo(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infow writes msg along with fields into access log.
|
// Infow writes msg along with fields into access log.
|
||||||
func Infow(msg string, fields ...LogField) {
|
func Infow(msg string, fields ...LogField) {
|
||||||
writeInfo(msg, fields...)
|
if shallLog(InfoLevel) {
|
||||||
|
writeInfo(msg, fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must checks if err is nil, otherwise logs the error and exits.
|
// Must checks if err is nil, otherwise logs the error and exits.
|
||||||
@@ -194,7 +222,7 @@ func Must(err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := err.Error()
|
msg := fmt.Sprintf("%+v\n\n%s", err.Error(), debug.Stack())
|
||||||
log.Print(msg)
|
log.Print(msg)
|
||||||
getWriter().Severe(msg)
|
getWriter().Severe(msg)
|
||||||
|
|
||||||
@@ -269,42 +297,58 @@ func SetUp(c LogConf) (err error) {
|
|||||||
|
|
||||||
// Severe writes v into severe log.
|
// Severe writes v into severe log.
|
||||||
func Severe(v ...any) {
|
func Severe(v ...any) {
|
||||||
writeSevere(fmt.Sprint(v...))
|
if shallLog(SevereLevel) {
|
||||||
|
writeSevere(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Severef writes v with format into severe log.
|
// Severef writes v with format into severe log.
|
||||||
func Severef(format string, v ...any) {
|
func Severef(format string, v ...any) {
|
||||||
writeSevere(fmt.Sprintf(format, v...))
|
if shallLog(SevereLevel) {
|
||||||
|
writeSevere(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slow writes v into slow log.
|
// Slow writes v into slow log.
|
||||||
func Slow(v ...any) {
|
func Slow(v ...any) {
|
||||||
writeSlow(fmt.Sprint(v...))
|
if shallLog(ErrorLevel) {
|
||||||
|
writeSlow(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slowf writes v with format into slow log.
|
// Slowf writes v with format into slow log.
|
||||||
func Slowf(format string, v ...any) {
|
func Slowf(format string, v ...any) {
|
||||||
writeSlow(fmt.Sprintf(format, v...))
|
if shallLog(ErrorLevel) {
|
||||||
|
writeSlow(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slowv writes v into slow log with json content.
|
// Slowv writes v into slow log with json content.
|
||||||
func Slowv(v any) {
|
func Slowv(v any) {
|
||||||
writeSlow(v)
|
if shallLog(ErrorLevel) {
|
||||||
|
writeSlow(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sloww writes msg along with fields into slow log.
|
// Sloww writes msg along with fields into slow log.
|
||||||
func Sloww(msg string, fields ...LogField) {
|
func Sloww(msg string, fields ...LogField) {
|
||||||
writeSlow(msg, fields...)
|
if shallLog(ErrorLevel) {
|
||||||
|
writeSlow(msg, fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat writes v into stat log.
|
// Stat writes v into stat log.
|
||||||
func Stat(v ...any) {
|
func Stat(v ...any) {
|
||||||
writeStat(fmt.Sprint(v...))
|
if shallLogStat() && shallLog(InfoLevel) {
|
||||||
|
writeStat(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statf writes v with format into stat log.
|
// Statf writes v with format into stat log.
|
||||||
func Statf(format string, v ...any) {
|
func Statf(format string, v ...any) {
|
||||||
writeStat(fmt.Sprintf(format, v...))
|
if shallLogStat() && shallLog(InfoLevel) {
|
||||||
|
writeStat(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCooldownMillis customizes logging on writing call stack interval.
|
// WithCooldownMillis customizes logging on writing call stack interval.
|
||||||
@@ -429,44 +473,58 @@ func shallLogStat() bool {
|
|||||||
return atomic.LoadUint32(&disableStat) == 0
|
return atomic.LoadUint32(&disableStat) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeDebug writes v into debug log.
|
||||||
|
// Not checking shallLog here is for performance consideration.
|
||||||
|
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||||
|
// The caller should check shallLog before calling this function.
|
||||||
func writeDebug(val any, fields ...LogField) {
|
func writeDebug(val any, fields ...LogField) {
|
||||||
if shallLog(DebugLevel) {
|
getWriter().Debug(val, addCaller(fields...)...)
|
||||||
getWriter().Debug(val, addCaller(fields...)...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeError writes v into error log.
|
||||||
|
// Not checking shallLog here is for performance consideration.
|
||||||
|
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||||
|
// The caller should check shallLog before calling this function.
|
||||||
func writeError(val any, fields ...LogField) {
|
func writeError(val any, fields ...LogField) {
|
||||||
if shallLog(ErrorLevel) {
|
getWriter().Error(val, addCaller(fields...)...)
|
||||||
getWriter().Error(val, addCaller(fields...)...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeInfo writes v into info log.
|
||||||
|
// Not checking shallLog here is for performance consideration.
|
||||||
|
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||||
|
// The caller should check shallLog before calling this function.
|
||||||
func writeInfo(val any, fields ...LogField) {
|
func writeInfo(val any, fields ...LogField) {
|
||||||
if shallLog(InfoLevel) {
|
getWriter().Info(val, addCaller(fields...)...)
|
||||||
getWriter().Info(val, addCaller(fields...)...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeSevere writes v into severe log.
|
||||||
|
// Not checking shallLog here is for performance consideration.
|
||||||
|
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||||
|
// The caller should check shallLog before calling this function.
|
||||||
func writeSevere(msg string) {
|
func writeSevere(msg string) {
|
||||||
if shallLog(SevereLevel) {
|
getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||||
getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeSlow writes v into slow log.
|
||||||
|
// Not checking shallLog here is for performance consideration.
|
||||||
|
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||||
|
// The caller should check shallLog before calling this function.
|
||||||
func writeSlow(val any, fields ...LogField) {
|
func writeSlow(val any, fields ...LogField) {
|
||||||
if shallLog(ErrorLevel) {
|
getWriter().Slow(val, addCaller(fields...)...)
|
||||||
getWriter().Slow(val, addCaller(fields...)...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeStack writes v into stack log.
|
||||||
|
// Not checking shallLog here is for performance consideration.
|
||||||
|
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||||
|
// The caller should check shallLog before calling this function.
|
||||||
func writeStack(msg string) {
|
func writeStack(msg string) {
|
||||||
if shallLog(ErrorLevel) {
|
getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||||
getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeStat writes v into stat log.
|
||||||
|
// Not checking shallLog here is for performance consideration.
|
||||||
|
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||||
|
// The caller should check shallLog before calling this function.
|
||||||
func writeStat(msg string) {
|
func writeStat(msg string) {
|
||||||
if shallLogStat() && shallLog(InfoLevel) {
|
getWriter().Stat(msg, addCaller()...)
|
||||||
getWriter().Stat(msg, addCaller()...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -299,6 +298,7 @@ func (l *RotateLogger) initialize() error {
|
|||||||
if l.fp, err = os.OpenFile(l.filename, os.O_APPEND|os.O_WRONLY, defaultFileMode); err != nil {
|
if l.fp, err = os.OpenFile(l.filename, os.O_APPEND|os.O_WRONLY, defaultFileMode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
l.currentSize = fileInfo.Size()
|
l.currentSize = fileInfo.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +382,15 @@ func (l *RotateLogger) startWorker() {
|
|||||||
case event := <-l.channel:
|
case event := <-l.channel:
|
||||||
l.write(event)
|
l.write(event)
|
||||||
case <-l.done:
|
case <-l.done:
|
||||||
return
|
// avoid losing logs before closing.
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-l.channel:
|
||||||
|
l.write(event)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -406,7 +414,7 @@ func (l *RotateLogger) write(v []byte) {
|
|||||||
func compressLogFile(file string) {
|
func compressLogFile(file string) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
Infof("compressing log file: %s", file)
|
Infof("compressing log file: %s", file)
|
||||||
if err := gzipFile(file); err != nil {
|
if err := gzipFile(file, fileSys); 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, time.Since(start))
|
||||||
@@ -421,25 +429,37 @@ func getNowDateInRFC3339Format() string {
|
|||||||
return time.Now().Format(fileTimeFormat)
|
return time.Now().Format(fileTimeFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func gzipFile(file string) error {
|
func gzipFile(file string, fsys fileSystem) (err error) {
|
||||||
in, err := os.Open(file)
|
in, err := fsys.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer in.Close()
|
defer func() {
|
||||||
|
if e := fsys.Close(in); e != nil {
|
||||||
|
Errorf("failed to close file: %s, error: %v", file, e)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
// only remove the original file when compression is successful
|
||||||
|
err = fsys.Remove(file)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
out, err := os.Create(fmt.Sprintf("%s%s", file, gzipExt))
|
out, err := fsys.Create(fmt.Sprintf("%s%s", file, gzipExt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer out.Close()
|
defer func() {
|
||||||
|
e := fsys.Close(out)
|
||||||
|
if err == nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
w := gzip.NewWriter(out)
|
w := gzip.NewWriter(out)
|
||||||
if _, err = io.Copy(w, in); err != nil {
|
if _, err = fsys.Copy(w, in); err != nil {
|
||||||
return err
|
// failed to copy, no need to close w
|
||||||
} else if err = w.Close(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.Remove(file)
|
return fsys.Close(w)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package logx
|
package logx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -203,6 +206,27 @@ func TestRotateLoggerClose(t *testing.T) {
|
|||||||
_, err := logger.Write([]byte("foo"))
|
_, err := logger.Write([]byte("foo"))
|
||||||
assert.ErrorIs(t, err, ErrLogFileClosed)
|
assert.ErrorIs(t, err, ErrLogFileClosed)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("close without losing logs", func(t *testing.T) {
|
||||||
|
text := "foo"
|
||||||
|
filename, err := fs.TempFilenameWithText(text)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
if len(filename) > 0 {
|
||||||
|
defer os.Remove(filename)
|
||||||
|
}
|
||||||
|
logger, err := NewLogger(filename, new(DailyRotateRule), false)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
msg := []byte("foo")
|
||||||
|
n := 100
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
_, err = logger.Write(msg)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
assert.Nil(t, logger.Close())
|
||||||
|
bs, err := os.ReadFile(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, len(msg)*n+len(text), len(bs))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRotateLoggerGetBackupFilename(t *testing.T) {
|
func TestRotateLoggerGetBackupFilename(t *testing.T) {
|
||||||
@@ -429,6 +453,85 @@ func TestRotateLoggerWithSizeLimitRotateRuleWrite(t *testing.T) {
|
|||||||
logger.write([]byte(`baz`))
|
logger.write([]byte(`baz`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGzipFile(t *testing.T) {
|
||||||
|
err := errors.New("any error")
|
||||||
|
|
||||||
|
t.Run("gzip file open failed", func(t *testing.T) {
|
||||||
|
fsys := &fakeFileSystem{
|
||||||
|
openFn: func(name string) (*os.File, error) {
|
||||||
|
return nil, err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.ErrorIs(t, err, gzipFile("any", fsys))
|
||||||
|
assert.False(t, fsys.Removed())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("gzip file create failed", func(t *testing.T) {
|
||||||
|
fsys := &fakeFileSystem{
|
||||||
|
createFn: func(name string) (*os.File, error) {
|
||||||
|
return nil, err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.ErrorIs(t, err, gzipFile("any", fsys))
|
||||||
|
assert.False(t, fsys.Removed())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("gzip file copy failed", func(t *testing.T) {
|
||||||
|
fsys := &fakeFileSystem{
|
||||||
|
copyFn: func(writer io.Writer, reader io.Reader) (int64, error) {
|
||||||
|
return 0, err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.ErrorIs(t, err, gzipFile("any", fsys))
|
||||||
|
assert.False(t, fsys.Removed())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("gzip file last close failed", func(t *testing.T) {
|
||||||
|
var called int32
|
||||||
|
fsys := &fakeFileSystem{
|
||||||
|
closeFn: func(closer io.Closer) error {
|
||||||
|
if atomic.AddInt32(&called, 1) > 2 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.NoError(t, gzipFile("any", fsys))
|
||||||
|
assert.True(t, fsys.Removed())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("gzip file remove failed", func(t *testing.T) {
|
||||||
|
fsys := &fakeFileSystem{
|
||||||
|
removeFn: func(name string) error {
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Error(t, err, gzipFile("any", fsys))
|
||||||
|
assert.True(t, fsys.Removed())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("gzip file everything ok", func(t *testing.T) {
|
||||||
|
fsys := &fakeFileSystem{}
|
||||||
|
assert.NoError(t, gzipFile("any", fsys))
|
||||||
|
assert.True(t, fsys.Removed())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRotateLogger_WithExistingFile(t *testing.T) {
|
||||||
|
const body = "foo"
|
||||||
|
filename, err := fs.TempFilenameWithText(body)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
if len(filename) > 0 {
|
||||||
|
defer os.Remove(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := NewSizeLimitRotateRule(filename, "-", 1, 100, 3, false)
|
||||||
|
logger, err := NewLogger(filename, rule, false)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(len(body)), logger.currentSize)
|
||||||
|
assert.Nil(t, logger.Close())
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkRotateLogger(b *testing.B) {
|
func BenchmarkRotateLogger(b *testing.B) {
|
||||||
filename := "./test.log"
|
filename := "./test.log"
|
||||||
filename2 := "./test2.log"
|
filename2 := "./test2.log"
|
||||||
@@ -480,3 +583,53 @@ func BenchmarkRotateLogger(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeFileSystem struct {
|
||||||
|
removed int32
|
||||||
|
closeFn func(closer io.Closer) error
|
||||||
|
copyFn func(writer io.Writer, reader io.Reader) (int64, error)
|
||||||
|
createFn func(name string) (*os.File, error)
|
||||||
|
openFn func(name string) (*os.File, error)
|
||||||
|
removeFn func(name string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeFileSystem) Close(closer io.Closer) error {
|
||||||
|
if f.closeFn != nil {
|
||||||
|
return f.closeFn(closer)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeFileSystem) Copy(writer io.Writer, reader io.Reader) (int64, error) {
|
||||||
|
if f.copyFn != nil {
|
||||||
|
return f.copyFn(writer, reader)
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeFileSystem) Create(name string) (*os.File, error) {
|
||||||
|
if f.createFn != nil {
|
||||||
|
return f.createFn(name)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeFileSystem) Open(name string) (*os.File, error) {
|
||||||
|
if f.openFn != nil {
|
||||||
|
return f.openFn(name)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeFileSystem) Remove(name string) error {
|
||||||
|
atomic.AddInt32(&f.removed, 1)
|
||||||
|
|
||||||
|
if f.removeFn != nil {
|
||||||
|
return f.removeFn(name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeFileSystem) Removed() bool {
|
||||||
|
return atomic.LoadInt32(&f.removed) > 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -20,6 +21,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
defaultKeyName = "key"
|
defaultKeyName = "key"
|
||||||
delimiter = '.'
|
delimiter = '.'
|
||||||
|
ignoreKey = "-"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -49,6 +51,7 @@ type (
|
|||||||
unmarshalOptions struct {
|
unmarshalOptions struct {
|
||||||
fillDefault bool
|
fillDefault bool
|
||||||
fromString bool
|
fromString bool
|
||||||
|
opaqueKeys bool
|
||||||
canonicalKey func(key string) string
|
canonicalKey func(key string) string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -72,7 +75,11 @@ func UnmarshalKey(m map[string]any, v any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal unmarshals m into v.
|
// Unmarshal unmarshals m into v.
|
||||||
func (u *Unmarshaler) Unmarshal(i any, v any) error {
|
func (u *Unmarshaler) Unmarshal(i, v any) error {
|
||||||
|
return u.unmarshal(i, v, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unmarshaler) unmarshal(i, v any, fullName string) error {
|
||||||
valueType := reflect.TypeOf(v)
|
valueType := reflect.TypeOf(v)
|
||||||
if valueType.Kind() != reflect.Ptr {
|
if valueType.Kind() != reflect.Ptr {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
@@ -85,13 +92,13 @@ func (u *Unmarshaler) Unmarshal(i any, v any) error {
|
|||||||
return errTypeMismatch
|
return errTypeMismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.UnmarshalValuer(mapValuer(iv), v)
|
return u.unmarshalValuer(mapValuer(iv), v, fullName)
|
||||||
case []any:
|
case []any:
|
||||||
if elemType.Kind() != reflect.Slice {
|
if elemType.Kind() != reflect.Slice {
|
||||||
return errTypeMismatch
|
return errTypeMismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.fillSlice(elemType, reflect.ValueOf(v).Elem(), iv)
|
return u.fillSlice(elemType, reflect.ValueOf(v).Elem(), iv, fullName)
|
||||||
default:
|
default:
|
||||||
return errUnsupportedType
|
return errUnsupportedType
|
||||||
}
|
}
|
||||||
@@ -99,17 +106,21 @@ func (u *Unmarshaler) Unmarshal(i any, v any) error {
|
|||||||
|
|
||||||
// UnmarshalValuer unmarshals m into v.
|
// UnmarshalValuer unmarshals m into v.
|
||||||
func (u *Unmarshaler) UnmarshalValuer(m Valuer, v any) error {
|
func (u *Unmarshaler) UnmarshalValuer(m Valuer, v any) error {
|
||||||
return u.unmarshalWithFullName(simpleValuer{current: m}, v, "")
|
return u.unmarshalValuer(simpleValuer{current: m}, v, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value, mapValue any) error {
|
func (u *Unmarshaler) unmarshalValuer(m Valuer, v any, fullName string) error {
|
||||||
|
return u.unmarshalWithFullName(simpleValuer{current: m}, v, fullName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value, mapValue any, fullName string) error {
|
||||||
if !value.CanSet() {
|
if !value.CanSet() {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldKeyType := fieldType.Key()
|
fieldKeyType := fieldType.Key()
|
||||||
fieldElemType := fieldType.Elem()
|
fieldElemType := fieldType.Elem()
|
||||||
targetValue, err := u.generateMap(fieldKeyType, fieldElemType, mapValue)
|
targetValue, err := u.generateMap(fieldKeyType, fieldElemType, mapValue, fullName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -143,14 +154,14 @@ func (u *Unmarshaler) fillMapFromString(value reflect.Value, mapValue any) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, mapValue any) error {
|
func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, mapValue any, fullName string) error {
|
||||||
if !value.CanSet() {
|
if !value.CanSet() {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
}
|
}
|
||||||
|
|
||||||
refValue := reflect.ValueOf(mapValue)
|
refValue := reflect.ValueOf(mapValue)
|
||||||
if refValue.Kind() != reflect.Slice {
|
if refValue.Kind() != reflect.Slice {
|
||||||
return errTypeMismatch
|
return newTypeMismatchErrorWithHint(fullName, reflect.Slice.String(), refValue.Type().String())
|
||||||
}
|
}
|
||||||
if refValue.IsNil() {
|
if refValue.IsNil() {
|
||||||
return nil
|
return nil
|
||||||
@@ -173,20 +184,27 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
|
|||||||
}
|
}
|
||||||
|
|
||||||
valid = true
|
valid = true
|
||||||
|
sliceFullName := fmt.Sprintf("%s[%d]", fullName, i)
|
||||||
|
|
||||||
switch dereffedBaseKind {
|
switch dereffedBaseKind {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
target := reflect.New(dereffedBaseType)
|
target := reflect.New(dereffedBaseType)
|
||||||
if err := u.Unmarshal(ithValue.(map[string]any), target.Interface()); err != nil {
|
val, ok := ithValue.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return errTypeMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.unmarshal(val, target.Interface(), sliceFullName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
SetValue(fieldType.Elem(), conv.Index(i), target.Elem())
|
SetValue(fieldType.Elem(), conv.Index(i), target.Elem())
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
if err := u.fillSlice(dereffedBaseType, conv.Index(i), ithValue); err != nil {
|
if err := u.fillSlice(dereffedBaseType, conv.Index(i), ithValue, sliceFullName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if err := u.fillSliceValue(conv, i, dereffedBaseKind, ithValue); err != nil {
|
if err := u.fillSliceValue(conv, i, dereffedBaseKind, ithValue, sliceFullName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,7 +218,7 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.Value,
|
func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.Value,
|
||||||
mapValue any) error {
|
mapValue any, fullName string) error {
|
||||||
var slice []any
|
var slice []any
|
||||||
switch v := mapValue.(type) {
|
switch v := mapValue.(type) {
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
@@ -220,7 +238,7 @@ func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.
|
|||||||
conv := reflect.MakeSlice(reflect.SliceOf(baseFieldType), len(slice), cap(slice))
|
conv := reflect.MakeSlice(reflect.SliceOf(baseFieldType), len(slice), cap(slice))
|
||||||
|
|
||||||
for i := 0; i < len(slice); i++ {
|
for i := 0; i < len(slice); i++ {
|
||||||
if err := u.fillSliceValue(conv, i, baseFieldKind, slice[i]); err != nil {
|
if err := u.fillSliceValue(conv, i, baseFieldKind, slice[i], fullName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +248,7 @@ func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int,
|
func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int,
|
||||||
baseKind reflect.Kind, value any) error {
|
baseKind reflect.Kind, value any, fullName string) error {
|
||||||
ithVal := slice.Index(index)
|
ithVal := slice.Index(index)
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
@@ -238,7 +256,7 @@ func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int,
|
|||||||
case string:
|
case string:
|
||||||
return setValueFromString(baseKind, ithVal, v)
|
return setValueFromString(baseKind, ithVal, v)
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return u.fillMap(ithVal.Type(), ithVal, value)
|
return u.fillMap(ithVal.Type(), ithVal, value, fullName)
|
||||||
default:
|
default:
|
||||||
// don't need to consider the difference between int, int8, int16, int32, int64,
|
// don't need to consider the difference between int, int8, int16, int32, int64,
|
||||||
// uint, uint8, uint16, uint32, uint64, because they're handled as json.Number.
|
// uint, uint8, uint16, uint32, uint64, because they're handled as json.Number.
|
||||||
@@ -264,7 +282,7 @@ func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value reflect.Value,
|
func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value reflect.Value,
|
||||||
defaultValue string) error {
|
defaultValue, fullName string) error {
|
||||||
baseFieldType := Deref(derefedType.Elem())
|
baseFieldType := Deref(derefedType.Elem())
|
||||||
baseFieldKind := baseFieldType.Kind()
|
baseFieldKind := baseFieldType.Kind()
|
||||||
defaultCacheLock.Lock()
|
defaultCacheLock.Lock()
|
||||||
@@ -282,10 +300,10 @@ func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value refle
|
|||||||
defaultCacheLock.Unlock()
|
defaultCacheLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.fillSlice(derefedType, value, slice)
|
return u.fillSlice(derefedType, value, slice, fullName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any) (reflect.Value, error) {
|
func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any, fullName string) (reflect.Value, error) {
|
||||||
mapType := reflect.MapOf(keyType, elemType)
|
mapType := reflect.MapOf(keyType, elemType)
|
||||||
valueType := reflect.TypeOf(mapValue)
|
valueType := reflect.TypeOf(mapValue)
|
||||||
if mapType == valueType {
|
if mapType == valueType {
|
||||||
@@ -304,11 +322,12 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any)
|
|||||||
for _, key := range refValue.MapKeys() {
|
for _, key := range refValue.MapKeys() {
|
||||||
keythValue := refValue.MapIndex(key)
|
keythValue := refValue.MapIndex(key)
|
||||||
keythData := keythValue.Interface()
|
keythData := keythValue.Interface()
|
||||||
|
mapFullName := fmt.Sprintf("%s[%s]", fullName, key.String())
|
||||||
|
|
||||||
switch dereffedElemKind {
|
switch dereffedElemKind {
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
target := reflect.New(dereffedElemType)
|
target := reflect.New(dereffedElemType)
|
||||||
if err := u.fillSlice(elemType, target.Elem(), keythData); err != nil {
|
if err := u.fillSlice(elemType, target.Elem(), keythData, mapFullName); err != nil {
|
||||||
return emptyValue, err
|
return emptyValue, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +339,7 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any)
|
|||||||
}
|
}
|
||||||
|
|
||||||
target := reflect.New(dereffedElemType)
|
target := reflect.New(dereffedElemType)
|
||||||
if err := u.Unmarshal(keythMap, target.Interface()); err != nil {
|
if err := u.unmarshal(keythMap, target.Interface(), mapFullName); err != nil {
|
||||||
return emptyValue, err
|
return emptyValue, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +350,7 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any)
|
|||||||
return emptyValue, errTypeMismatch
|
return emptyValue, errTypeMismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
innerValue, err := u.generateMap(elemType.Key(), elemType.Elem(), keythMap)
|
innerValue, err := u.generateMap(elemType.Key(), elemType.Elem(), keythMap, mapFullName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return emptyValue, err
|
return emptyValue, err
|
||||||
}
|
}
|
||||||
@@ -420,6 +439,10 @@ func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value ref
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key == ignoreKey {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if options.optional() {
|
if options.optional() {
|
||||||
return u.processAnonymousFieldOptional(field, value, key, m, fullName)
|
return u.processAnonymousFieldOptional(field, value, key, m, fullName)
|
||||||
}
|
}
|
||||||
@@ -478,7 +501,7 @@ func (u *Unmarshaler) processAnonymousStructFieldOptional(fieldType reflect.Type
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, hasValue := getValue(m, fieldKey)
|
_, hasValue := getValue(m, fieldKey, u.opts.opaqueKeys)
|
||||||
if hasValue {
|
if hasValue {
|
||||||
if !filled {
|
if !filled {
|
||||||
filled = true
|
filled = true
|
||||||
@@ -536,13 +559,13 @@ func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value re
|
|||||||
parent: vp.parent,
|
parent: vp.parent,
|
||||||
}, fullName)
|
}, fullName)
|
||||||
case typeKind == reflect.Slice && valueKind == reflect.Slice:
|
case typeKind == reflect.Slice && valueKind == reflect.Slice:
|
||||||
return u.fillSlice(fieldType, value, mapValue)
|
return u.fillSlice(fieldType, value, mapValue, fullName)
|
||||||
case valueKind == reflect.Map && typeKind == reflect.Map:
|
case valueKind == reflect.Map && typeKind == reflect.Map:
|
||||||
return u.fillMap(fieldType, value, mapValue)
|
return u.fillMap(fieldType, value, mapValue, fullName)
|
||||||
case valueKind == reflect.String && typeKind == reflect.Map:
|
case valueKind == reflect.String && typeKind == reflect.Map:
|
||||||
return u.fillMapFromString(value, mapValue)
|
return u.fillMapFromString(value, mapValue)
|
||||||
case valueKind == reflect.String && typeKind == reflect.Slice:
|
case valueKind == reflect.String && typeKind == reflect.Slice:
|
||||||
return u.fillSliceFromString(fieldType, value, mapValue)
|
return u.fillSliceFromString(fieldType, value, mapValue, fullName)
|
||||||
case valueKind == reflect.String && derefedFieldType == durationType:
|
case valueKind == reflect.String && derefedFieldType == durationType:
|
||||||
return fillDurationValue(fieldType, value, mapValue.(string))
|
return fillDurationValue(fieldType, value, mapValue.(string))
|
||||||
default:
|
default:
|
||||||
@@ -587,25 +610,23 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
|
|||||||
target := reflect.New(Deref(fieldType)).Elem()
|
target := reflect.New(Deref(fieldType)).Elem()
|
||||||
|
|
||||||
switch typeKind {
|
switch typeKind {
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
iValue, err := v.Int64()
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
if err := setValueFromString(typeKind, target, v.String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case reflect.Float32:
|
||||||
|
fValue, err := v.Float64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
target.SetInt(iValue)
|
if fValue > math.MaxFloat32 {
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
return float32OverflowError(v.String())
|
||||||
iValue, err := v.Int64()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if iValue < 0 {
|
target.SetFloat(fValue)
|
||||||
return fmt.Errorf("unmarshal %q with bad value %q", fullName, v.String())
|
case reflect.Float64:
|
||||||
}
|
|
||||||
|
|
||||||
target.SetUint(uint64(iValue))
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
fValue, err := v.Float64()
|
fValue, err := v.Float64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -613,7 +634,7 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
|
|||||||
|
|
||||||
target.SetFloat(fValue)
|
target.SetFloat(fValue)
|
||||||
default:
|
default:
|
||||||
return newTypeMismatchError(fullName)
|
return newTypeMismatchErrorWithHint(fullName, typeKind.String(), value.Type().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
SetValue(fieldType, value, target)
|
SetValue(fieldType, value, target)
|
||||||
@@ -707,6 +728,10 @@ func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key == ignoreKey {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
fullName = join(fullName, key)
|
fullName = join(fullName, key)
|
||||||
if opts != nil && len(opts.EnvVar) > 0 {
|
if opts != nil && len(opts.EnvVar) > 0 {
|
||||||
envVal := proc.Env(opts.EnvVar)
|
envVal := proc.Env(opts.EnvVar)
|
||||||
@@ -721,7 +746,7 @@ func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect
|
|||||||
}
|
}
|
||||||
|
|
||||||
valuer := createValuer(m, opts)
|
valuer := createValuer(m, opts)
|
||||||
mapValue, hasValue := getValue(valuer, canonicalKey)
|
mapValue, hasValue := getValue(valuer, canonicalKey, u.opts.opaqueKeys)
|
||||||
|
|
||||||
// When fillDefault is used, m is a null value, hasValue must be false, all priority judgments fillDefault.
|
// When fillDefault is used, m is a null value, hasValue must be false, all priority judgments fillDefault.
|
||||||
if u.opts.fillDefault {
|
if u.opts.fillDefault {
|
||||||
@@ -814,7 +839,7 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu
|
|||||||
|
|
||||||
switch fieldKind {
|
switch fieldKind {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
return u.fillSliceWithDefault(derefedType, value, defaultValue)
|
return u.fillSliceWithDefault(derefedType, value, defaultValue, fullName)
|
||||||
default:
|
default:
|
||||||
return setValueFromString(fieldKind, value, defaultValue)
|
return setValueFromString(fieldKind, value, defaultValue)
|
||||||
}
|
}
|
||||||
@@ -862,7 +887,7 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu
|
|||||||
|
|
||||||
func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v any, fullName string) error {
|
func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v any, fullName string) error {
|
||||||
rv := reflect.ValueOf(v)
|
rv := reflect.ValueOf(v)
|
||||||
if err := ValidatePtr(&rv); err != nil {
|
if err := ValidatePtr(rv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,11 +909,6 @@ func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v any, fullName
|
|||||||
typeField := baseType.Field(i)
|
typeField := baseType.Field(i)
|
||||||
valueField := valElem.Field(i)
|
valueField := valElem.Field(i)
|
||||||
if err := u.processField(typeField, valueField, m, fullName); err != nil {
|
if err := u.processField(typeField, valueField, m, fullName); err != nil {
|
||||||
if len(fullName) > 0 {
|
|
||||||
err = fmt.Errorf("%w, fullName: %s, field: %s, type: %s",
|
|
||||||
err, fullName, typeField.Name, valueField.Type().Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -917,6 +937,14 @@ func WithDefault() UnmarshalOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithOpaqueKeys customizes an Unmarshaler with opaque keys.
|
||||||
|
// Opaque keys are keys that are not processed by the unmarshaler.
|
||||||
|
func WithOpaqueKeys() UnmarshalOption {
|
||||||
|
return func(opt *unmarshalOptions) {
|
||||||
|
opt.opaqueKeys = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createValuer(v valuerWithParent, opts *fieldOptionsWithContext) valuerWithParent {
|
func createValuer(v valuerWithParent, opts *fieldOptionsWithContext) valuerWithParent {
|
||||||
if opts.inherit() {
|
if opts.inherit() {
|
||||||
return recursiveValuer{
|
return recursiveValuer{
|
||||||
@@ -994,8 +1022,8 @@ func fillWithSameType(fieldType reflect.Type, value reflect.Value, mapValue any,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getValue gets the value for the specific key, the key can be in the format of parentKey.childKey
|
// getValue gets the value for the specific key, the key can be in the format of parentKey.childKey
|
||||||
func getValue(m valuerWithParent, key string) (any, bool) {
|
func getValue(m valuerWithParent, key string, opaque bool) (any, bool) {
|
||||||
keys := readKeys(key)
|
keys := readKeys(key, opaque)
|
||||||
return getValueWithChainedKeys(m, keys)
|
return getValueWithChainedKeys(m, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1049,7 +1077,16 @@ func newTypeMismatchError(name string) error {
|
|||||||
return fmt.Errorf("type mismatch for field %q", name)
|
return fmt.Errorf("type mismatch for field %q", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readKeys(key string) []string {
|
func newTypeMismatchErrorWithHint(name, expectType, actualType string) error {
|
||||||
|
return fmt.Errorf("type mismatch for field %q, expect %q, actual %q",
|
||||||
|
name, expectType, actualType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readKeys(key string, opaque bool) []string {
|
||||||
|
if opaque {
|
||||||
|
return []string{key}
|
||||||
|
}
|
||||||
|
|
||||||
cacheKeysLock.Lock()
|
cacheKeysLock.Lock()
|
||||||
keys, ok := cacheKeys[key]
|
keys, ok := cacheKeys[key]
|
||||||
cacheKeysLock.Unlock()
|
cacheKeysLock.Unlock()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -42,6 +42,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
integer interface {
|
||||||
|
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
|
||||||
|
}
|
||||||
|
|
||||||
optionsCacheValue struct {
|
optionsCacheValue struct {
|
||||||
key string
|
key string
|
||||||
options *fieldOptions
|
options *fieldOptions
|
||||||
@@ -79,7 +83,7 @@ func SetMapIndexValue(tp reflect.Type, value, key, target reflect.Value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidatePtr validates v if it's a valid pointer.
|
// ValidatePtr validates v if it's a valid pointer.
|
||||||
func ValidatePtr(v *reflect.Value) error {
|
func ValidatePtr(v reflect.Value) error {
|
||||||
// sequence is very important, IsNil must be called after checking Kind() with reflect.Ptr,
|
// sequence is very important, IsNil must be called after checking Kind() with reflect.Ptr,
|
||||||
// panic otherwise
|
// panic otherwise
|
||||||
if !v.IsValid() || v.Kind() != reflect.Ptr || v.IsNil() {
|
if !v.IsValid() || v.Kind() != reflect.Ptr || v.IsNil() {
|
||||||
@@ -103,21 +107,32 @@ func convertTypeFromString(kind reflect.Kind, str string) (any, error) {
|
|||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
intValue, err := strconv.ParseInt(str, 10, 64)
|
intValue, err := strconv.ParseInt(str, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("the value %q cannot parsed as int", str)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return intValue, nil
|
return intValue, nil
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
uintValue, err := strconv.ParseUint(str, 10, 64)
|
uintValue, err := strconv.ParseUint(str, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("the value %q cannot parsed as uint", str)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return uintValue, nil
|
return uintValue, nil
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32:
|
||||||
floatValue, err := strconv.ParseFloat(str, 64)
|
floatValue, err := strconv.ParseFloat(str, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("the value %q cannot parsed as float", str)
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if floatValue > math.MaxFloat32 {
|
||||||
|
return 0, float32OverflowError(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return floatValue, nil
|
||||||
|
case reflect.Float64:
|
||||||
|
floatValue, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return floatValue, nil
|
return floatValue, nil
|
||||||
@@ -215,6 +230,10 @@ func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func intOverflowError[T integer](v T, kind reflect.Kind) error {
|
||||||
|
return fmt.Errorf("parsing \"%d\" as %s: value out of range", v, kind.String())
|
||||||
|
}
|
||||||
|
|
||||||
func isLeftInclude(b byte) (bool, error) {
|
func isLeftInclude(b byte) (bool, error) {
|
||||||
switch b {
|
switch b {
|
||||||
case '[':
|
case '[':
|
||||||
@@ -237,6 +256,10 @@ func isRightInclude(b byte) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func float32OverflowError(str string) error {
|
||||||
|
return fmt.Errorf("parsing %q as float32: value out of range", str)
|
||||||
|
}
|
||||||
|
|
||||||
func maybeNewValue(fieldType reflect.Type, value reflect.Value) {
|
func maybeNewValue(fieldType reflect.Type, value reflect.Value) {
|
||||||
if fieldType.Kind() == reflect.Ptr && value.IsNil() {
|
if fieldType.Kind() == reflect.Ptr && value.IsNil() {
|
||||||
value.Set(reflect.New(value.Type().Elem()))
|
value.Set(reflect.New(value.Type().Elem()))
|
||||||
@@ -372,8 +395,6 @@ func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
|
|||||||
default:
|
default:
|
||||||
return fmt.Errorf("field %q has wrong optional", fieldName)
|
return fmt.Errorf("field %q has wrong optional", fieldName)
|
||||||
}
|
}
|
||||||
case option == optionalOption:
|
|
||||||
fieldOpts.Optional = true
|
|
||||||
case strings.HasPrefix(option, optionsOption):
|
case strings.HasPrefix(option, optionsOption):
|
||||||
val, err := parseProperty(fieldName, optionsOption, option)
|
val, err := parseProperty(fieldName, optionsOption, option)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -484,22 +505,61 @@ func parseSegments(val string) []string {
|
|||||||
return segments
|
return segments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setIntValue(value reflect.Value, v any, min, max int64) error {
|
||||||
|
iv := v.(int64)
|
||||||
|
if iv < min || iv > max {
|
||||||
|
return intOverflowError(iv, value.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
value.SetInt(iv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v any) error {
|
func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v any) error {
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
value.SetBool(v.(bool))
|
value.SetBool(v.(bool))
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
return nil
|
||||||
|
case reflect.Int: // int depends on int size, 32 or 64
|
||||||
|
return setIntValue(value, v, math.MinInt, math.MaxInt)
|
||||||
|
case reflect.Int8:
|
||||||
|
return setIntValue(value, v, math.MinInt8, math.MaxInt8)
|
||||||
|
case reflect.Int16:
|
||||||
|
return setIntValue(value, v, math.MinInt16, math.MaxInt16)
|
||||||
|
case reflect.Int32:
|
||||||
|
return setIntValue(value, v, math.MinInt32, math.MaxInt32)
|
||||||
|
case reflect.Int64:
|
||||||
value.SetInt(v.(int64))
|
value.SetInt(v.(int64))
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
return nil
|
||||||
|
case reflect.Uint: // uint depends on int size, 32 or 64
|
||||||
|
return setUintValue(value, v, math.MaxUint)
|
||||||
|
case reflect.Uint8:
|
||||||
|
return setUintValue(value, v, math.MaxUint8)
|
||||||
|
case reflect.Uint16:
|
||||||
|
return setUintValue(value, v, math.MaxUint16)
|
||||||
|
case reflect.Uint32:
|
||||||
|
return setUintValue(value, v, math.MaxUint32)
|
||||||
|
case reflect.Uint64:
|
||||||
value.SetUint(v.(uint64))
|
value.SetUint(v.(uint64))
|
||||||
|
return nil
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
value.SetFloat(v.(float64))
|
value.SetFloat(v.(float64))
|
||||||
|
return nil
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
value.SetString(v.(string))
|
value.SetString(v.(string))
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
return errUnsupportedType
|
return errUnsupportedType
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUintValue(value reflect.Value, v any, boundary uint64) error {
|
||||||
|
iv := v.(uint64)
|
||||||
|
if iv > boundary {
|
||||||
|
return intOverflowError(iv, value.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
value.SetUint(iv)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,7 +637,8 @@ func usingDifferentKeys(key string, field reflect.StructField) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string, opts *fieldOptionsWithContext) error {
|
func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string,
|
||||||
|
opts *fieldOptionsWithContext) error {
|
||||||
if !value.CanSet() {
|
if !value.CanSet() {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,30 +218,31 @@ func TestParseSegments(t *testing.T) {
|
|||||||
func TestValidatePtrWithNonPtr(t *testing.T) {
|
func TestValidatePtrWithNonPtr(t *testing.T) {
|
||||||
var foo string
|
var foo string
|
||||||
rve := reflect.ValueOf(foo)
|
rve := reflect.ValueOf(foo)
|
||||||
assert.NotNil(t, ValidatePtr(&rve))
|
assert.NotNil(t, ValidatePtr(rve))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePtrWithPtr(t *testing.T) {
|
func TestValidatePtrWithPtr(t *testing.T) {
|
||||||
var foo string
|
var foo string
|
||||||
rve := reflect.ValueOf(&foo)
|
rve := reflect.ValueOf(&foo)
|
||||||
assert.Nil(t, ValidatePtr(&rve))
|
assert.Nil(t, ValidatePtr(rve))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePtrWithNilPtr(t *testing.T) {
|
func TestValidatePtrWithNilPtr(t *testing.T) {
|
||||||
var foo *string
|
var foo *string
|
||||||
rve := reflect.ValueOf(foo)
|
rve := reflect.ValueOf(foo)
|
||||||
assert.NotNil(t, ValidatePtr(&rve))
|
assert.NotNil(t, ValidatePtr(rve))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePtrWithZeroValue(t *testing.T) {
|
func TestValidatePtrWithZeroValue(t *testing.T) {
|
||||||
var s string
|
var s string
|
||||||
e := reflect.Zero(reflect.TypeOf(s))
|
e := reflect.Zero(reflect.TypeOf(s))
|
||||||
assert.NotNil(t, ValidatePtr(&e))
|
assert.NotNil(t, ValidatePtr(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetValueNotSettable(t *testing.T) {
|
func TestSetValueNotSettable(t *testing.T) {
|
||||||
var i int
|
var i int
|
||||||
assert.NotNil(t, setValueFromString(reflect.Int, reflect.ValueOf(i), "1"))
|
assert.Error(t, setValueFromString(reflect.Int, reflect.ValueOf(i), "1"))
|
||||||
|
assert.Error(t, validateAndSetValue(reflect.Int, reflect.ValueOf(i), "1", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeyAndOptionsErrors(t *testing.T) {
|
func TestParseKeyAndOptionsErrors(t *testing.T) {
|
||||||
@@ -300,3 +301,36 @@ func TestSetValueFormatErrors(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateValueRange(t *testing.T) {
|
||||||
|
t.Run("float", func(t *testing.T) {
|
||||||
|
assert.NoError(t, validateValueRange(1.2, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("float number range", func(t *testing.T) {
|
||||||
|
assert.NoError(t, validateNumberRange(1.2, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad float", func(t *testing.T) {
|
||||||
|
assert.Error(t, validateValueRange("a", &fieldOptionsWithContext{
|
||||||
|
Range: &numberRange{},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad float validate", func(t *testing.T) {
|
||||||
|
var v struct {
|
||||||
|
Foo float32
|
||||||
|
}
|
||||||
|
assert.Error(t, validateAndSetValue(reflect.Int, reflect.ValueOf(&v).Elem().Field(0),
|
||||||
|
"1", &fieldOptionsWithContext{
|
||||||
|
Range: &numberRange{
|
||||||
|
left: 2,
|
||||||
|
right: 3,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetMatchedPrimitiveValue(t *testing.T) {
|
||||||
|
assert.Error(t, setMatchedPrimitiveValue(reflect.Func, reflect.ValueOf(2), "1"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/proc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewHistogramVec(t *testing.T) {
|
func TestNewHistogramVec(t *testing.T) {
|
||||||
@@ -48,6 +47,4 @@ func TestHistogramObserve(t *testing.T) {
|
|||||||
|
|
||||||
err := testutil.CollectAndCompare(hv.histogram, strings.NewReader(metadata+val))
|
err := testutil.CollectAndCompare(hv.histogram, strings.NewReader(metadata+val))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
proc.Shutdown()
|
|
||||||
}
|
}
|
||||||
|
|||||||
65
core/metric/summary.go
Normal file
65
core/metric/summary.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
prom "github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/zeromicro/go-zero/core/proc"
|
||||||
|
"github.com/zeromicro/go-zero/core/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// A SummaryVecOpts is a summary vector options
|
||||||
|
SummaryVecOpts struct {
|
||||||
|
VecOpt VectorOpts
|
||||||
|
Objectives map[float64]float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SummaryVec interface represents a summary vector.
|
||||||
|
SummaryVec interface {
|
||||||
|
// Observe adds observation v to labels.
|
||||||
|
Observe(v float64, labels ...string)
|
||||||
|
close() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
promSummaryVec struct {
|
||||||
|
summary *prom.SummaryVec
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSummaryVec return a SummaryVec
|
||||||
|
func NewSummaryVec(cfg *SummaryVecOpts) SummaryVec {
|
||||||
|
if cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vec := prom.NewSummaryVec(
|
||||||
|
prom.SummaryOpts{
|
||||||
|
Namespace: cfg.VecOpt.Namespace,
|
||||||
|
Subsystem: cfg.VecOpt.Subsystem,
|
||||||
|
Name: cfg.VecOpt.Name,
|
||||||
|
Help: cfg.VecOpt.Help,
|
||||||
|
Objectives: cfg.Objectives,
|
||||||
|
},
|
||||||
|
cfg.VecOpt.Labels,
|
||||||
|
)
|
||||||
|
prom.MustRegister(vec)
|
||||||
|
sv := &promSummaryVec{
|
||||||
|
summary: vec,
|
||||||
|
}
|
||||||
|
proc.AddShutdownListener(func() {
|
||||||
|
sv.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
return sv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *promSummaryVec) Observe(v float64, labels ...string) {
|
||||||
|
if !prometheus.Enabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sv.summary.WithLabelValues(labels...).Observe(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *promSummaryVec) close() bool {
|
||||||
|
return prom.Unregister(sv.summary)
|
||||||
|
}
|
||||||
68
core/metric/summary_test.go
Normal file
68
core/metric/summary_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/proc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewSummaryVec(t *testing.T) {
|
||||||
|
summaryVec := NewSummaryVec(&SummaryVecOpts{
|
||||||
|
VecOpt: VectorOpts{
|
||||||
|
Namespace: "http_server",
|
||||||
|
Subsystem: "requests",
|
||||||
|
Name: "duration_quantiles",
|
||||||
|
Help: "rpc client requests duration(ms) φ quantiles ",
|
||||||
|
Labels: []string{"method"},
|
||||||
|
},
|
||||||
|
Objectives: map[float64]float64{
|
||||||
|
0.5: 0.01,
|
||||||
|
0.9: 0.01,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer summaryVec.close()
|
||||||
|
summaryVecNil := NewSummaryVec(nil)
|
||||||
|
assert.NotNil(t, summaryVec)
|
||||||
|
assert.Nil(t, summaryVecNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSummaryObserve(t *testing.T) {
|
||||||
|
startAgent()
|
||||||
|
summaryVec := NewSummaryVec(&SummaryVecOpts{
|
||||||
|
VecOpt: VectorOpts{
|
||||||
|
Namespace: "http_server",
|
||||||
|
Subsystem: "requests",
|
||||||
|
Name: "duration_quantiles",
|
||||||
|
Help: "rpc client requests duration(ms) φ quantiles ",
|
||||||
|
Labels: []string{"method"},
|
||||||
|
},
|
||||||
|
Objectives: map[float64]float64{
|
||||||
|
0.3: 0.01,
|
||||||
|
0.6: 0.01,
|
||||||
|
1: 0.01,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer summaryVec.close()
|
||||||
|
sv := summaryVec.(*promSummaryVec)
|
||||||
|
sv.Observe(100, "GET")
|
||||||
|
sv.Observe(200, "GET")
|
||||||
|
sv.Observe(300, "GET")
|
||||||
|
metadata := `
|
||||||
|
# HELP http_server_requests_duration_quantiles rpc client requests duration(ms) φ quantiles
|
||||||
|
# TYPE http_server_requests_duration_quantiles summary
|
||||||
|
`
|
||||||
|
val := `
|
||||||
|
http_server_requests_duration_quantiles{method="GET",quantile="0.3"} 100
|
||||||
|
http_server_requests_duration_quantiles{method="GET",quantile="0.6"} 200
|
||||||
|
http_server_requests_duration_quantiles{method="GET",quantile="1"} 300
|
||||||
|
http_server_requests_duration_quantiles_sum{method="GET"} 600
|
||||||
|
http_server_requests_duration_quantiles_count{method="GET"} 3
|
||||||
|
`
|
||||||
|
|
||||||
|
err := testutil.CollectAndCompare(sv.summary, strings.NewReader(metadata+val))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
proc.Shutdown()
|
||||||
|
}
|
||||||
@@ -574,6 +574,7 @@ func TestMapReduceWithContext(t *testing.T) {
|
|||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
writer.Write(i)
|
writer.Write(i)
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
}, func(pipe <-chan int, cancel func(error)) {
|
}, func(pipe <-chan int, cancel func(error)) {
|
||||||
for item := range pipe {
|
for item := range pipe {
|
||||||
i := item
|
i := item
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
|
|
||||||
package proc
|
|
||||||
|
|
||||||
func dumpGoroutines() {
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,11 @@ const (
|
|||||||
debugLevel = 2
|
debugLevel = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
func dumpGoroutines() {
|
type creator interface {
|
||||||
|
Create(name string) (file *os.File, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpGoroutines(ctor creator) {
|
||||||
command := path.Base(os.Args[0])
|
command := path.Base(os.Args[0])
|
||||||
pid := syscall.Getpid()
|
pid := syscall.Getpid()
|
||||||
dumpFile := path.Join(os.TempDir(), fmt.Sprintf("%s-%d-goroutines-%s.dump",
|
dumpFile := path.Join(os.TempDir(), fmt.Sprintf("%s-%d-goroutines-%s.dump",
|
||||||
@@ -26,10 +30,16 @@ func dumpGoroutines() {
|
|||||||
|
|
||||||
logx.Infof("Got dump goroutine signal, printing goroutine profile to %s", dumpFile)
|
logx.Infof("Got dump goroutine signal, printing goroutine profile to %s", dumpFile)
|
||||||
|
|
||||||
if f, err := os.Create(dumpFile); err != nil {
|
if f, err := ctor.Create(dumpFile); err != nil {
|
||||||
logx.Errorf("Failed to dump goroutine profile, error: %v", err)
|
logx.Errorf("Failed to dump goroutine profile, error: %v", err)
|
||||||
} else {
|
} else {
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
pprof.Lookup(goroutineProfile).WriteTo(f, debugLevel)
|
pprof.Lookup(goroutineProfile).WriteTo(f, debugLevel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fileCreator struct{}
|
||||||
|
|
||||||
|
func (fc fileCreator) Create(name string) (file *os.File, err error) {
|
||||||
|
return os.Create(name)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -9,7 +13,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDumpGoroutines(t *testing.T) {
|
func TestDumpGoroutines(t *testing.T) {
|
||||||
buf := logtest.NewCollector(t)
|
t.Run("real file", func(t *testing.T) {
|
||||||
dumpGoroutines()
|
buf := logtest.NewCollector(t)
|
||||||
assert.True(t, strings.Contains(buf.String(), ".dump"))
|
dumpGoroutines(fileCreator{})
|
||||||
|
assert.True(t, strings.Contains(buf.String(), ".dump"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fake file", func(t *testing.T) {
|
||||||
|
const msg = "any message"
|
||||||
|
buf := logtest.NewCollector(t)
|
||||||
|
err := errors.New(msg)
|
||||||
|
dumpGoroutines(fakeCreator{
|
||||||
|
file: &os.File{},
|
||||||
|
err: err,
|
||||||
|
})
|
||||||
|
assert.True(t, strings.Contains(buf.String(), msg))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeCreator struct {
|
||||||
|
file *os.File
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fc fakeCreator) Create(name string) (file *os.File, err error) {
|
||||||
|
return fc.file, fc.err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,4 +96,6 @@ func (lm *listenerManager) notifyListeners() {
|
|||||||
group.RunSafe(listener)
|
group.RunSafe(listener)
|
||||||
}
|
}
|
||||||
group.Wait()
|
group.Wait()
|
||||||
|
|
||||||
|
lm.listeners = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,3 +28,33 @@ func TestShutdown(t *testing.T) {
|
|||||||
called()
|
called()
|
||||||
assert.Equal(t, 3, val)
|
assert.Equal(t, 3, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNotifyMoreThanOnce(t *testing.T) {
|
||||||
|
ch := make(chan struct{}, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var val int
|
||||||
|
called := AddWrapUpListener(func() {
|
||||||
|
val++
|
||||||
|
})
|
||||||
|
WrapUp()
|
||||||
|
WrapUp()
|
||||||
|
called()
|
||||||
|
assert.Equal(t, 1, val)
|
||||||
|
|
||||||
|
called = AddShutdownListener(func() {
|
||||||
|
val += 2
|
||||||
|
})
|
||||||
|
Shutdown()
|
||||||
|
Shutdown()
|
||||||
|
called()
|
||||||
|
assert.Equal(t, 3, val)
|
||||||
|
ch <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("timeout, check error logs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func init() {
|
|||||||
v := <-signals
|
v := <-signals
|
||||||
switch v {
|
switch v {
|
||||||
case syscall.SIGUSR1:
|
case syscall.SIGUSR1:
|
||||||
dumpGoroutines()
|
dumpGoroutines(fileCreator{})
|
||||||
case syscall.SIGUSR2:
|
case syscall.SIGUSR2:
|
||||||
if profiler == nil {
|
if profiler == nil {
|
||||||
profiler = StartProfile()
|
profiler = StartProfile()
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package queue
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -39,7 +40,7 @@ func TestQueue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestQueue_Broadcast(t *testing.T) {
|
func TestQueue_Broadcast(t *testing.T) {
|
||||||
producer := newMockedProducer(rounds)
|
producer := newMockedProducer(math.MaxInt32)
|
||||||
consumer := newMockedConsumer()
|
consumer := newMockedConsumer()
|
||||||
consumer.wait.Add(consumers)
|
consumer.wait.Add(consumers)
|
||||||
q := NewQueue(func() (Producer, error) {
|
q := NewQueue(func() (Producer, error) {
|
||||||
@@ -51,14 +52,14 @@ func TestQueue_Broadcast(t *testing.T) {
|
|||||||
q.SetName("mockqueue")
|
q.SetName("mockqueue")
|
||||||
q.SetNumConsumer(consumers)
|
q.SetNumConsumer(consumers)
|
||||||
q.SetNumProducer(1)
|
q.SetNumProducer(1)
|
||||||
q.Broadcast("message")
|
|
||||||
go func() {
|
go func() {
|
||||||
producer.wait.Wait()
|
time.Sleep(time.Millisecond * 100)
|
||||||
q.Stop()
|
q.Stop()
|
||||||
}()
|
}()
|
||||||
q.Start()
|
go q.Start()
|
||||||
|
time.Sleep(time.Millisecond * 50)
|
||||||
|
q.Broadcast("message")
|
||||||
consumer.wait.Wait()
|
consumer.wait.Wait()
|
||||||
assert.Equal(t, int32(rounds), atomic.LoadInt32(&consumer.count))
|
|
||||||
assert.Equal(t, int32(consumers), atomic.LoadInt32(&consumer.events))
|
assert.Equal(t, int32(consumers), atomic.LoadInt32(&consumer.events))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,11 +171,11 @@ func add(nd *node, route string, item any) error {
|
|||||||
token := route[:i]
|
token := route[:i]
|
||||||
children := nd.getChildren(token)
|
children := nd.getChildren(token)
|
||||||
if child, ok := children[token]; ok {
|
if child, ok := children[token]; ok {
|
||||||
if child != nil {
|
if child == nil {
|
||||||
return add(child, route[i+1:], item)
|
return errInvalidState
|
||||||
}
|
}
|
||||||
|
|
||||||
return errInvalidState
|
return add(child, route[i+1:], item)
|
||||||
}
|
}
|
||||||
|
|
||||||
child := newNode(nil)
|
child := newNode(nil)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
type mockedRoute struct {
|
type mockedRoute struct {
|
||||||
route string
|
route string
|
||||||
value int
|
value any
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearch(t *testing.T) {
|
func TestSearch(t *testing.T) {
|
||||||
@@ -187,6 +187,12 @@ func TestSearchInvalidItem(t *testing.T) {
|
|||||||
assert.Equal(t, errEmptyItem, err)
|
assert.Equal(t, errEmptyItem, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchInvalidState(t *testing.T) {
|
||||||
|
nd := newNode("0")
|
||||||
|
nd.children[0]["1"] = nil
|
||||||
|
assert.Error(t, add(nd, "1/2", "2"))
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkSearchTree(b *testing.B) {
|
func BenchmarkSearchTree(b *testing.B) {
|
||||||
const (
|
const (
|
||||||
avgLen = 1000
|
avgLen = 1000
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func (sg *ServiceGroup) doStart() {
|
|||||||
|
|
||||||
for i := range sg.services {
|
for i := range sg.services {
|
||||||
service := sg.services[i]
|
service := sg.services[i]
|
||||||
routineGroup.RunSafe(func() {
|
routineGroup.Run(func() {
|
||||||
service.Start()
|
service.Start()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,30 +14,6 @@ var (
|
|||||||
done = make(chan struct{})
|
done = make(chan struct{})
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockedService struct {
|
|
||||||
quit chan struct{}
|
|
||||||
multiplier int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMockedService(multiplier int) *mockedService {
|
|
||||||
return &mockedService{
|
|
||||||
quit: make(chan struct{}),
|
|
||||||
multiplier: multiplier,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mockedService) Start() {
|
|
||||||
mutex.Lock()
|
|
||||||
number *= s.multiplier
|
|
||||||
mutex.Unlock()
|
|
||||||
done <- struct{}{}
|
|
||||||
<-s.quit
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mockedService) Stop() {
|
|
||||||
close(s.quit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServiceGroup(t *testing.T) {
|
func TestServiceGroup(t *testing.T) {
|
||||||
multipliers := []int{2, 3, 5, 7}
|
multipliers := []int{2, 3, 5, 7}
|
||||||
want := 1
|
want := 1
|
||||||
@@ -126,3 +102,27 @@ type mockedStarter struct {
|
|||||||
func (s mockedStarter) Start() {
|
func (s mockedStarter) Start() {
|
||||||
s.fn()
|
s.fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockedService struct {
|
||||||
|
quit chan struct{}
|
||||||
|
multiplier int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockedService(multiplier int) *mockedService {
|
||||||
|
return &mockedService{
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
multiplier: multiplier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockedService) Start() {
|
||||||
|
mutex.Lock()
|
||||||
|
number *= s.multiplier
|
||||||
|
mutex.Unlock()
|
||||||
|
done <- struct{}{}
|
||||||
|
<-s.quit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockedService) Stop() {
|
||||||
|
close(s.quit)
|
||||||
|
}
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ func parseUints(val string) ([]uint64, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sets []uint64
|
||||||
ints := make(map[uint64]lang.PlaceholderType)
|
ints := make(map[uint64]lang.PlaceholderType)
|
||||||
cols := strings.Split(val, ",")
|
cols := strings.Split(val, ",")
|
||||||
for _, r := range cols {
|
for _, r := range cols {
|
||||||
@@ -239,7 +240,10 @@ func parseUints(val string) ([]uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := min; i <= max; i++ {
|
for i := min; i <= max; i++ {
|
||||||
ints[i] = lang.Placeholder
|
if _, ok := ints[i]; !ok {
|
||||||
|
ints[i] = lang.Placeholder
|
||||||
|
sets = append(sets, i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
v, err := parseUint(r)
|
v, err := parseUint(r)
|
||||||
@@ -247,19 +251,17 @@ func parseUints(val string) ([]uint64, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ints[v] = lang.Placeholder
|
if _, ok := ints[v]; !ok {
|
||||||
|
ints[v] = lang.Placeholder
|
||||||
|
sets = append(sets, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sets []uint64
|
|
||||||
for k := range ints {
|
|
||||||
sets = append(sets, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sets, nil
|
return sets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// runningInUserNS detects whether we are currently running in an user namespace.
|
// runningInUserNS detects whether we are currently running in a user namespace.
|
||||||
func runningInUserNS() bool {
|
func runningInUserNS() bool {
|
||||||
nsOnce.Do(func() {
|
nsOnce.Do(func() {
|
||||||
file, err := os.Open("/proc/self/uid_map")
|
file, err := os.Open("/proc/self/uid_map")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -25,3 +26,46 @@ func TestCgroupV1(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseUint(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
want uint64
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{"0", 0, nil},
|
||||||
|
{"123", 123, nil},
|
||||||
|
{"-1", 0, nil},
|
||||||
|
{"-18446744073709551616", 0, nil},
|
||||||
|
{"foo", 0, fmt.Errorf("cgroup: bad int format: foo")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got, err := parseUint(tt.input)
|
||||||
|
assert.Equal(t, tt.err, err)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseUints(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
want []uint64
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{"", nil, nil},
|
||||||
|
{"1,2,3", []uint64{1, 2, 3}, nil},
|
||||||
|
{"1-3", []uint64{1, 2, 3}, nil},
|
||||||
|
{"1-3,5,7-9", []uint64{1, 2, 3, 5, 7, 8, 9}, nil},
|
||||||
|
{"foo", nil, fmt.Errorf("cgroup: bad int format: foo")},
|
||||||
|
{"1-bar", nil, fmt.Errorf("cgroup: bad int list format: 1-bar")},
|
||||||
|
{"bar-3", nil, fmt.Errorf("cgroup: bad int list format: bar-3")},
|
||||||
|
{"3-1", nil, fmt.Errorf("cgroup: bad int list format: 3-1")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got, err := parseUints(tt.input)
|
||||||
|
assert.Equal(t, tt.err, err)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alicebob/miniredis/v2"
|
||||||
|
red "github.com/go-redis/redis/v8"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,3 +43,17 @@ func TestSplitClusterAddrs(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetCluster(t *testing.T) {
|
||||||
|
r := miniredis.RunT(t)
|
||||||
|
defer r.Close()
|
||||||
|
c, err := getCluster(&Redis{
|
||||||
|
Addr: r.Addr(),
|
||||||
|
Type: ClusterType,
|
||||||
|
tls: true,
|
||||||
|
hooks: []red.Hook{durationHook},
|
||||||
|
})
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ func (cc CachedConn) Exec(exec ExecFn, keys ...string) (sql.Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ExecCtx runs given exec on given keys, and returns execution result.
|
// ExecCtx runs given exec on given keys, and returns execution result.
|
||||||
|
// If DB operation succeeds, it will delete cache with given keys,
|
||||||
|
// if DB operation fails, it will return nil result and non-nil error,
|
||||||
|
// if DB operation succeeds but cache deletion fails, it will return result and non-nil error.
|
||||||
func (cc CachedConn) ExecCtx(ctx context.Context, exec ExecCtxFn, keys ...string) (
|
func (cc CachedConn) ExecCtx(ctx context.Context, exec ExecCtxFn, keys ...string) (
|
||||||
sql.Result, error) {
|
sql.Result, error) {
|
||||||
res, err := exec(ctx, cc.db)
|
res, err := exec(ctx, cc.db)
|
||||||
@@ -104,11 +107,7 @@ func (cc CachedConn) ExecCtx(ctx context.Context, exec ExecCtxFn, keys ...string
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cc.DelCacheCtx(ctx, keys...); err != nil {
|
return res, cc.DelCacheCtx(ctx, keys...)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecNoCache runs exec with given sql statement, without affecting cache.
|
// ExecNoCache runs exec with given sql statement, without affecting cache.
|
||||||
|
|||||||
@@ -471,31 +471,33 @@ func TestCachedConnExec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnExecDropCache(t *testing.T) {
|
func TestCachedConnExecDropCache(t *testing.T) {
|
||||||
r, err := miniredis.Run()
|
t.Run("drop cache", func(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
r, err := miniredis.Run()
|
||||||
defer fx.DoWithTimeout(func() error {
|
assert.Nil(t, err)
|
||||||
r.Close()
|
defer fx.DoWithTimeout(func() error {
|
||||||
return nil
|
r.Close()
|
||||||
}, time.Second)
|
return nil
|
||||||
|
}, time.Second)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
value = "any"
|
value = "any"
|
||||||
)
|
)
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
c := NewNodeConn(&conn, redis.New(r.Addr()), cache.WithExpiry(time.Second*30))
|
c := NewNodeConn(&conn, redis.New(r.Addr()), cache.WithExpiry(time.Second*30))
|
||||||
assert.Nil(t, c.SetCache(key, value))
|
assert.Nil(t, c.SetCache(key, value))
|
||||||
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
||||||
return conn.Exec("delete from user_table where id='kevin'")
|
return conn.Exec("delete from user_table where id='kevin'")
|
||||||
}, key)
|
}, key)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, conn.execValue)
|
assert.True(t, conn.execValue)
|
||||||
_, err = r.Get(key)
|
_, err = r.Get(key)
|
||||||
assert.Exactly(t, miniredis.ErrKeyNotFound, err)
|
assert.Exactly(t, miniredis.ErrKeyNotFound, err)
|
||||||
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
||||||
return nil, errors.New("foo")
|
return nil, errors.New("foo")
|
||||||
}, key)
|
}, key)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConn_SetCacheWithExpire(t *testing.T) {
|
func TestCachedConn_SetCacheWithExpire(t *testing.T) {
|
||||||
|
|||||||
@@ -32,7 +32,5 @@ func mysqlAcceptable(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withMysqlAcceptable() SqlOption {
|
func withMysqlAcceptable() SqlOption {
|
||||||
return func(conn *commonSqlConn) {
|
return WithAcceptable(mysqlAcceptable)
|
||||||
conn.accept = mysqlAcceptable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ func unmarshalRow(v any, scanner rowsScanner, strict bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rv := reflect.ValueOf(v)
|
rv := reflect.ValueOf(v)
|
||||||
if err := mapping.ValidatePtr(&rv); err != nil {
|
if err := mapping.ValidatePtr(rv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ func unmarshalRow(v any, scanner rowsScanner, strict bool) error {
|
|||||||
|
|
||||||
func unmarshalRows(v any, scanner rowsScanner, strict bool) error {
|
func unmarshalRows(v any, scanner rowsScanner, strict bool) error {
|
||||||
rv := reflect.ValueOf(v)
|
rv := reflect.ValueOf(v)
|
||||||
if err := mapping.ValidatePtr(&rv); err != nil {
|
if err := mapping.ValidatePtr(rv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -291,12 +291,19 @@ func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (db *commonSqlConn) acceptable(err error) bool {
|
func (db *commonSqlConn) acceptable(err error) bool {
|
||||||
ok := err == nil || err == sql.ErrNoRows || err == sql.ErrTxDone || err == context.Canceled
|
if err == nil || err == sql.ErrNoRows || err == sql.ErrTxDone || err == context.Canceled {
|
||||||
if db.accept == nil {
|
return true
|
||||||
return ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok || db.accept(err)
|
if _, ok := err.(acceptableError); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if db.accept == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.accept(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error,
|
func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error,
|
||||||
@@ -399,3 +406,11 @@ func (s statement) QueryRowsPartialCtx(ctx context.Context, v any, args ...any)
|
|||||||
return unmarshalRows(v, rows, false)
|
return unmarshalRows(v, rows, false)
|
||||||
}, s.query, args...)
|
}, s.query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAcceptable returns a SqlOption that setting the acceptable function.
|
||||||
|
// acceptable is the func to check if the error can be accepted.
|
||||||
|
func WithAcceptable(acceptable func(err error) bool) SqlOption {
|
||||||
|
return func(conn *commonSqlConn) {
|
||||||
|
conn.accept = acceptable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -236,6 +236,33 @@ func TestStatement(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBreakerWithFormatError(t *testing.T) {
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
conn := NewSqlConnFromDB(db, withMysqlAcceptable())
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
var val string
|
||||||
|
if !assert.NotEqual(t, breaker.ErrServiceUnavailable,
|
||||||
|
conn.QueryRow(&val, "any ?, ?", "foo")) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBreakerWithScanError(t *testing.T) {
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
conn := NewSqlConnFromDB(db, withMysqlAcceptable())
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
rows := sqlmock.NewRows([]string{"foo"}).AddRow("bar")
|
||||||
|
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||||
|
var val int
|
||||||
|
if !assert.NotEqual(t, breaker.ErrServiceUnavailable, conn.QueryRow(&val, "any")) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func buildConn() (mock sqlmock.Sqlmock, err error) {
|
func buildConn() (mock sqlmock.Sqlmock, err error) {
|
||||||
_, err = connManager.GetResource(mockedDatasource, func() (io.Closer, error) {
|
_, err = connManager.GetResource(mockedDatasource, func() (io.Closer, error) {
|
||||||
var db *sql.DB
|
var db *sql.DB
|
||||||
|
|||||||
@@ -51,7 +51,13 @@ func escape(input string) string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func format(query string, args ...any) (string, error) {
|
func format(query string, args ...any) (val string, err error) {
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
err = newAcceptableError(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
numArgs := len(args)
|
numArgs := len(args)
|
||||||
if numArgs == 0 {
|
if numArgs == 0 {
|
||||||
return query, nil
|
return query, nil
|
||||||
@@ -66,7 +72,8 @@ func format(query string, args ...any) (string, error) {
|
|||||||
switch ch {
|
switch ch {
|
||||||
case '?':
|
case '?':
|
||||||
if argIndex >= numArgs {
|
if argIndex >= numArgs {
|
||||||
return "", fmt.Errorf("%d ? in sql, but less arguments provided", argIndex)
|
return "", fmt.Errorf("%d ? in sql, but only %d arguments provided",
|
||||||
|
argIndex+1, numArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
writeValue(&b, args[argIndex])
|
writeValue(&b, args[argIndex])
|
||||||
@@ -165,3 +172,17 @@ func writeValue(buf *strings.Builder, arg any) {
|
|||||||
buf.WriteString(mapping.Repr(v))
|
buf.WriteString(mapping.Repr(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type acceptableError struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAcceptableError(err error) error {
|
||||||
|
return acceptableError{
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e acceptableError) Error() string {
|
||||||
|
return e.err.Error()
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ func (manager *ResourceManager) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetResource returns the resource associated with given key.
|
// GetResource returns the resource associated with given key.
|
||||||
func (manager *ResourceManager) GetResource(key string, create func() (io.Closer, error)) (io.Closer, error) {
|
func (manager *ResourceManager) GetResource(key string, create func() (io.Closer, error)) (
|
||||||
|
io.Closer, error) {
|
||||||
val, err := manager.singleFlight.Do(key, func() (any, error) {
|
val, err := manager.singleFlight.Do(key, func() (any, error) {
|
||||||
manager.lock.RLock()
|
manager.lock.RLock()
|
||||||
resource, ok := manager.resources[key]
|
resource, ok := manager.resources[key]
|
||||||
|
|||||||
@@ -9,25 +9,44 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestTimeoutLimit(t *testing.T) {
|
func TestTimeoutLimit(t *testing.T) {
|
||||||
limit := NewTimeoutLimit(2)
|
tests := []struct {
|
||||||
assert.Nil(t, limit.Borrow(time.Millisecond*200))
|
name string
|
||||||
assert.Nil(t, limit.Borrow(time.Millisecond*200))
|
interval time.Duration
|
||||||
var wait1, wait2, wait3 sync.WaitGroup
|
}{
|
||||||
wait1.Add(1)
|
{
|
||||||
wait2.Add(1)
|
name: "no wait",
|
||||||
wait3.Add(1)
|
},
|
||||||
go func() {
|
{
|
||||||
wait1.Wait()
|
name: "wait",
|
||||||
wait2.Done()
|
interval: time.Millisecond * 100,
|
||||||
assert.Nil(t, limit.Return())
|
},
|
||||||
wait3.Done()
|
}
|
||||||
}()
|
|
||||||
wait1.Done()
|
for _, test := range tests {
|
||||||
wait2.Wait()
|
test := test
|
||||||
assert.Nil(t, limit.Borrow(time.Second))
|
t.Run(test.name, func(t *testing.T) {
|
||||||
wait3.Wait()
|
limit := NewTimeoutLimit(2)
|
||||||
assert.Equal(t, ErrTimeout, limit.Borrow(time.Millisecond*100))
|
assert.Nil(t, limit.Borrow(time.Millisecond*200))
|
||||||
assert.Nil(t, limit.Return())
|
assert.Nil(t, limit.Borrow(time.Millisecond*200))
|
||||||
assert.Nil(t, limit.Return())
|
var wait1, wait2, wait3 sync.WaitGroup
|
||||||
assert.Equal(t, ErrLimitReturn, limit.Return())
|
wait1.Add(1)
|
||||||
|
wait2.Add(1)
|
||||||
|
wait3.Add(1)
|
||||||
|
go func() {
|
||||||
|
wait1.Wait()
|
||||||
|
wait2.Done()
|
||||||
|
time.Sleep(test.interval)
|
||||||
|
assert.Nil(t, limit.Return())
|
||||||
|
wait3.Done()
|
||||||
|
}()
|
||||||
|
wait1.Done()
|
||||||
|
wait2.Wait()
|
||||||
|
assert.Nil(t, limit.Borrow(time.Second))
|
||||||
|
wait3.Wait()
|
||||||
|
assert.Equal(t, ErrTimeout, limit.Borrow(time.Millisecond*100))
|
||||||
|
assert.Nil(t, limit.Return())
|
||||||
|
assert.Nil(t, limit.Return())
|
||||||
|
assert.Equal(t, ErrLimitReturn, limit.Return())
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const (
|
|||||||
kindOtlpGrpc = "otlpgrpc"
|
kindOtlpGrpc = "otlpgrpc"
|
||||||
kindOtlpHttp = "otlphttp"
|
kindOtlpHttp = "otlphttp"
|
||||||
kindFile = "file"
|
kindFile = "file"
|
||||||
|
protocolUdp = "udp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -65,9 +66,10 @@ func createExporter(c Config) (sdktrace.SpanExporter, error) {
|
|||||||
// Just support jaeger and zipkin now, more for later
|
// Just support jaeger and zipkin now, more for later
|
||||||
switch c.Batcher {
|
switch c.Batcher {
|
||||||
case kindJaeger:
|
case kindJaeger:
|
||||||
u, _ := url.Parse(c.Endpoint)
|
u, err := url.Parse(c.Endpoint)
|
||||||
if u.Scheme == "udp" {
|
if err == nil && u.Scheme == protocolUdp {
|
||||||
return jaeger.New(jaeger.WithAgentEndpoint(jaeger.WithAgentHost(u.Hostname()), jaeger.WithAgentPort(u.Port())))
|
return jaeger.New(jaeger.WithAgentEndpoint(jaeger.WithAgentHost(u.Hostname()),
|
||||||
|
jaeger.WithAgentPort(u.Port())))
|
||||||
}
|
}
|
||||||
return jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(c.Endpoint)))
|
return jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(c.Endpoint)))
|
||||||
case kindZipkin:
|
case kindZipkin:
|
||||||
|
|||||||
49
go.mod
49
go.mod
@@ -4,25 +4,25 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||||
github.com/alicebob/miniredis/v2 v2.30.3
|
github.com/alicebob/miniredis/v2 v2.30.5
|
||||||
github.com/fatih/color v1.15.0
|
github.com/fatih/color v1.15.0
|
||||||
github.com/fullstorydev/grpcurl v1.8.7
|
github.com/fullstorydev/grpcurl v1.8.8
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
github.com/go-sql-driver/mysql v1.7.1
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/golang/protobuf v1.5.3
|
github.com/golang/protobuf v1.5.3
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.1
|
||||||
github.com/jackc/pgx/v5 v5.3.1
|
github.com/jackc/pgx/v5 v5.4.3
|
||||||
github.com/jhump/protoreflect v1.15.1
|
github.com/jhump/protoreflect v1.15.2
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8
|
github.com/pelletier/go-toml/v2 v2.1.0
|
||||||
github.com/prometheus/client_golang v1.15.1
|
github.com/prometheus/client_golang v1.16.0
|
||||||
github.com/spaolacci/murmur3 v1.1.0
|
github.com/spaolacci/murmur3 v1.1.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
go.etcd.io/etcd/api/v3 v3.5.9
|
go.etcd.io/etcd/api/v3 v3.5.9
|
||||||
go.etcd.io/etcd/client/v3 v3.5.9
|
go.etcd.io/etcd/client/v3 v3.5.9
|
||||||
go.mongodb.org/mongo-driver v1.11.6
|
go.mongodb.org/mongo-driver v1.12.1
|
||||||
go.opentelemetry.io/otel v1.14.0
|
go.opentelemetry.io/otel v1.14.0
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.14.0
|
go.opentelemetry.io/otel/exporters/jaeger v1.14.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0
|
||||||
@@ -31,14 +31,14 @@ require (
|
|||||||
go.opentelemetry.io/otel/exporters/zipkin v1.14.0
|
go.opentelemetry.io/otel/exporters/zipkin v1.14.0
|
||||||
go.opentelemetry.io/otel/sdk v1.14.0
|
go.opentelemetry.io/otel/sdk v1.14.0
|
||||||
go.opentelemetry.io/otel/trace v1.14.0
|
go.opentelemetry.io/otel/trace v1.14.0
|
||||||
go.uber.org/automaxprocs v1.5.2
|
go.uber.org/automaxprocs v1.5.3
|
||||||
go.uber.org/goleak v1.2.1
|
go.uber.org/goleak v1.2.1
|
||||||
golang.org/x/net v0.10.0
|
golang.org/x/net v0.15.0
|
||||||
golang.org/x/sys v0.8.0
|
golang.org/x/sys v0.12.0
|
||||||
golang.org/x/time v0.3.0
|
golang.org/x/time v0.3.0
|
||||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4
|
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e
|
||||||
google.golang.org/grpc v1.55.0
|
google.golang.org/grpc v1.58.2
|
||||||
google.golang.org/protobuf v1.30.0
|
google.golang.org/protobuf v1.31.0
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
||||||
gopkg.in/h2non/gock.v1 v1.1.2
|
gopkg.in/h2non/gock.v1 v1.1.2
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
@@ -51,7 +51,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bufbuild/protocompile v0.4.0 // indirect
|
github.com/bufbuild/protocompile v0.6.0 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
@@ -86,15 +86,14 @@ require (
|
|||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/openzipkin/zipkin-go v0.4.1 // indirect
|
github.com/openzipkin/zipkin-go v0.4.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.42.0 // indirect
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
github.com/prometheus/procfs v0.9.0 // indirect
|
github.com/prometheus/procfs v0.10.1 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.1 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||||
github.com/yuin/gopher-lua v1.1.0 // indirect
|
github.com/yuin/gopher-lua v1.1.0 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
|
||||||
@@ -104,12 +103,14 @@ require (
|
|||||||
go.uber.org/atomic v1.10.0 // indirect
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
go.uber.org/zap v1.24.0 // indirect
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
golang.org/x/crypto v0.6.0 // indirect
|
golang.org/x/crypto v0.13.0 // indirect
|
||||||
golang.org/x/oauth2 v0.6.0 // indirect
|
golang.org/x/oauth2 v0.10.0 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
golang.org/x/term v0.8.0 // indirect
|
golang.org/x/term v0.12.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/klog/v2 v2.90.1 // indirect
|
k8s.io/klog/v2 v2.90.1 // indirect
|
||||||
|
|||||||
@@ -9,36 +9,27 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TomlToJson converts TOML data into its JSON representation.
|
||||||
func TomlToJson(data []byte) ([]byte, error) {
|
func TomlToJson(data []byte) ([]byte, error) {
|
||||||
var val any
|
var val any
|
||||||
if err := toml.NewDecoder(bytes.NewReader(data)).Decode(&val); err != nil {
|
if err := toml.NewDecoder(bytes.NewReader(data)).Decode(&val); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
return encodeToJSON(val)
|
||||||
if err := json.NewEncoder(&buf).Encode(val); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// YamlToJson converts YAML data into its JSON representation.
|
||||||
func YamlToJson(data []byte) ([]byte, error) {
|
func YamlToJson(data []byte) ([]byte, error) {
|
||||||
var val any
|
var val any
|
||||||
if err := yaml.Unmarshal(data, &val); err != nil {
|
if err := yaml.Unmarshal(data, &val); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
val = toStringKeyMap(val)
|
return encodeToJSON(toStringKeyMap(val))
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := json.NewEncoder(&buf).Encode(val); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convertKeyToString ensures all keys of the map are of type string.
|
||||||
func convertKeyToString(in map[any]any) map[string]any {
|
func convertKeyToString(in map[any]any) map[string]any {
|
||||||
res := make(map[string]any)
|
res := make(map[string]any)
|
||||||
for k, v := range in {
|
for k, v := range in {
|
||||||
@@ -47,10 +38,12 @@ func convertKeyToString(in map[any]any) map[string]any {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convertNumberToJsonNumber converts numbers into json.Number type for compatibility.
|
||||||
func convertNumberToJsonNumber(in any) json.Number {
|
func convertNumberToJsonNumber(in any) json.Number {
|
||||||
return json.Number(lang.Repr(in))
|
return json.Number(lang.Repr(in))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convertSlice processes slice items to ensure key compatibility.
|
||||||
func convertSlice(in []any) []any {
|
func convertSlice(in []any) []any {
|
||||||
res := make([]any, len(in))
|
res := make([]any, len(in))
|
||||||
for i, v := range in {
|
for i, v := range in {
|
||||||
@@ -59,6 +52,17 @@ func convertSlice(in []any) []any {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encodeToJSON encodes the given value into its JSON representation.
|
||||||
|
func encodeToJSON(val any) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := json.NewEncoder(&buf).Encode(val); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toStringKeyMap processes the data to ensure that all map keys are of type string.
|
||||||
func toStringKeyMap(v any) any {
|
func toStringKeyMap(v any) any {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case []any:
|
case []any:
|
||||||
|
|||||||
@@ -53,6 +53,19 @@ func TestComboHealthManager(t *testing.T) {
|
|||||||
assert.True(t, chm.IsReady())
|
assert.True(t, chm.IsReady())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("is ready verbose", func(t *testing.T) {
|
||||||
|
chm := newComboHealthManager()
|
||||||
|
hm := NewHealthManager(probeName)
|
||||||
|
|
||||||
|
assert.True(t, chm.IsReady())
|
||||||
|
chm.addProbe(hm)
|
||||||
|
assert.False(t, chm.IsReady())
|
||||||
|
hm.MarkReady()
|
||||||
|
assert.True(t, chm.IsReady())
|
||||||
|
assert.Contains(t, chm.verboseInfo(), probeName)
|
||||||
|
assert.Contains(t, chm.verboseInfo(), "is ready")
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("concurrent add probes", func(t *testing.T) {
|
t.Run("concurrent add probes", func(t *testing.T) {
|
||||||
chm := newComboHealthManager()
|
chm := newComboHealthManager()
|
||||||
|
|
||||||
|
|||||||
10
readme-cn.md
10
readme-cn.md
@@ -17,14 +17,6 @@
|
|||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go-zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go-zero - A web & rpc framework written in Go. | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go-zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go-zero - A web & rpc framework written in Go. | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
|
||||||
> ***注意:***
|
|
||||||
>
|
|
||||||
> 从 v1.3.0 之前版本升级请执行以下命令:
|
|
||||||
>
|
|
||||||
> `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
|
|
||||||
>
|
|
||||||
> `goctl migrate —verbose —version v1.5.2`
|
|
||||||
|
|
||||||
## 0. go-zero 介绍
|
## 0. go-zero 介绍
|
||||||
|
|
||||||
go-zero(收录于 CNCF 云原生技术全景图:[https://landscape.cncf.io/?selected=go-zero](https://landscape.cncf.io/?selected=go-zero))是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。
|
go-zero(收录于 CNCF 云原生技术全景图:[https://landscape.cncf.io/?selected=go-zero](https://landscape.cncf.io/?selected=go-zero))是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。
|
||||||
@@ -302,6 +294,8 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
|
|||||||
>90. 元匠科技
|
>90. 元匠科技
|
||||||
>91. 宁波甬风信息科技有限公司
|
>91. 宁波甬风信息科技有限公司
|
||||||
>92. 深圳市万佳安物联科技股份有限公司
|
>92. 深圳市万佳安物联科技股份有限公司
|
||||||
|
>93. 武侯区编程之美软件开发工作室
|
||||||
|
>94. 西安交通大学智慧能源与碳中和研究中心
|
||||||
|
|
||||||
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
||||||
|
|
||||||
|
|||||||
11
readme.md
11
readme.md
@@ -102,17 +102,6 @@ Run the following command under your project:
|
|||||||
```shell
|
```shell
|
||||||
go get -u github.com/zeromicro/go-zero
|
go get -u github.com/zeromicro/go-zero
|
||||||
```
|
```
|
||||||
## Upgrade
|
|
||||||
|
|
||||||
To upgrade from versions eariler than v1.3.0, run the following commands.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go install github.com/zeromicro/go-zero/tools/goctl@latest
|
|
||||||
```
|
|
||||||
|
|
||||||
```shell
|
|
||||||
goctl migrate —verbose —version v1.5.2
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func BreakerHandler(method, path string, metrics *stat.Metrics) func(http.Handle
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cw := &response.WithCodeResponseWriter{Writer: w}
|
cw := response.NewWithCodeResponseWriter(w)
|
||||||
defer func() {
|
defer func() {
|
||||||
if cw.Code < http.StatusInternalServerError {
|
if cw.Code < http.StatusInternalServerError {
|
||||||
promise.Accept()
|
promise.Accept()
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ func (w *cryptionResponseWriter) flush(key []byte) {
|
|||||||
body := base64.StdEncoding.EncodeToString(content)
|
body := base64.StdEncoding.EncodeToString(content)
|
||||||
if n, err := io.WriteString(w.ResponseWriter, body); err != nil {
|
if n, err := io.WriteString(w.ResponseWriter, body); err != nil {
|
||||||
logx.Errorf("write response failed, error: %s", err)
|
logx.Errorf("write response failed, error: %s", err)
|
||||||
} else if n < len(content) {
|
} else if n < len(body) {
|
||||||
logx.Errorf("actual bytes: %d, written bytes: %d", len(content), n)
|
logx.Errorf("actual bytes: %d, written bytes: %d", len(body), n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/iotest"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/codec"
|
"github.com/zeromicro/go-zero/core/codec"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -37,6 +40,19 @@ func TestCryptionHandlerGet(t *testing.T) {
|
|||||||
assert.Equal(t, base64.StdEncoding.EncodeToString(expect), recorder.Body.String())
|
assert.Equal(t, base64.StdEncoding.EncodeToString(expect), recorder.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCryptionHandlerGet_badKey(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/any", http.NoBody)
|
||||||
|
handler := CryptionHandler(append(aesKey, aesKey...))(http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := w.Write([]byte(respText))
|
||||||
|
w.Header().Set("X-Test", "test")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}))
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(recorder, req)
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, recorder.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCryptionHandlerPost(t *testing.T) {
|
func TestCryptionHandlerPost(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
enc, err := codec.EcbEncrypt(aesKey, []byte(reqText))
|
enc, err := codec.EcbEncrypt(aesKey, []byte(reqText))
|
||||||
@@ -120,10 +136,110 @@ func TestCryptionHandler_ContentTooLong(t *testing.T) {
|
|||||||
defer svr.Close()
|
defer svr.Close()
|
||||||
|
|
||||||
body := make([]byte, maxBytes+1)
|
body := make([]byte, maxBytes+1)
|
||||||
rand.Read(body)
|
_, err := rand.Read(body)
|
||||||
|
assert.NoError(t, err)
|
||||||
req, err := http.NewRequest(http.MethodPost, svr.URL, bytes.NewReader(body))
|
req, err := http.NewRequest(http.MethodPost, svr.URL, bytes.NewReader(body))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCryptionHandler_BadBody(t *testing.T) {
|
||||||
|
req, err := http.NewRequest(http.MethodPost, "/foo", iotest.ErrReader(io.ErrUnexpectedEOF))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = decryptBody(maxBytes, aesKey, req)
|
||||||
|
assert.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptionHandler_BadKey(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc, err := codec.EcbEncrypt(aesKey, []byte(reqText))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
buf.WriteString(base64.StdEncoding.EncodeToString(enc))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/any", &buf)
|
||||||
|
err = decryptBody(maxBytes, append(aesKey, aesKey...), req)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptionResponseWriter_Flush(t *testing.T) {
|
||||||
|
body := []byte("hello, world!")
|
||||||
|
|
||||||
|
t.Run("half", func(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
f := flushableResponseWriter{
|
||||||
|
writer: &halfWriter{recorder},
|
||||||
|
}
|
||||||
|
w := newCryptionResponseWriter(f)
|
||||||
|
_, err := w.Write(body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
w.flush(aesKey)
|
||||||
|
b, err := io.ReadAll(recorder.Body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
expected, err := codec.EcbEncrypt(aesKey, body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, strings.HasPrefix(base64.StdEncoding.EncodeToString(expected), string(b)))
|
||||||
|
assert.True(t, len(string(b)) < len(base64.StdEncoding.EncodeToString(expected)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("full", func(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
f := flushableResponseWriter{
|
||||||
|
writer: recorder,
|
||||||
|
}
|
||||||
|
w := newCryptionResponseWriter(f)
|
||||||
|
_, err := w.Write(body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
w.flush(aesKey)
|
||||||
|
b, err := io.ReadAll(recorder.Body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
expected, err := codec.EcbEncrypt(aesKey, body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, base64.StdEncoding.EncodeToString(expected), string(b))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad writer", func(t *testing.T) {
|
||||||
|
buf := logtest.NewCollector(t)
|
||||||
|
f := flushableResponseWriter{
|
||||||
|
writer: new(badWriter),
|
||||||
|
}
|
||||||
|
w := newCryptionResponseWriter(f)
|
||||||
|
_, err := w.Write(body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
w.flush(aesKey)
|
||||||
|
assert.True(t, strings.Contains(buf.Content(), io.ErrClosedPipe.Error()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type flushableResponseWriter struct {
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m flushableResponseWriter) Header() http.Header {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m flushableResponseWriter) Write(p []byte) (int, error) {
|
||||||
|
return m.writer.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m flushableResponseWriter) WriteHeader(statusCode int) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
type halfWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *halfWriter) Write(p []byte) (n int, err error) {
|
||||||
|
n = len(p) >> 1
|
||||||
|
return t.w.Write(p[0:n])
|
||||||
|
}
|
||||||
|
|
||||||
|
type badWriter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *badWriter) Write(p []byte) (n int, err error) {
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -11,7 +10,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/color"
|
"github.com/zeromicro/go-zero/core/color"
|
||||||
@@ -37,14 +35,11 @@ func LogHandler(next http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
timer := utils.NewElapsedTimer()
|
timer := utils.NewElapsedTimer()
|
||||||
logs := new(internal.LogCollector)
|
logs := new(internal.LogCollector)
|
||||||
lrw := response.WithCodeResponseWriter{
|
lrw := response.NewWithCodeResponseWriter(w)
|
||||||
Writer: w,
|
|
||||||
Code: http.StatusOK,
|
|
||||||
}
|
|
||||||
|
|
||||||
var dup io.ReadCloser
|
var dup io.ReadCloser
|
||||||
r.Body, dup = iox.DupReadCloser(r.Body)
|
r.Body, dup = iox.LimitDupReadCloser(r.Body, limitBodyBytes)
|
||||||
next.ServeHTTP(&lrw, r.WithContext(context.WithValue(r.Context(), internal.LogContext, logs)))
|
next.ServeHTTP(lrw, r.WithContext(internal.WithLogCollector(r.Context(), logs)))
|
||||||
r.Body = dup
|
r.Body = dup
|
||||||
logBrief(r, lrw.Code, timer, logs)
|
logBrief(r, lrw.Code, timer, logs)
|
||||||
})
|
})
|
||||||
@@ -55,7 +50,8 @@ type detailLoggedResponseWriter struct {
|
|||||||
buf *bytes.Buffer
|
buf *bytes.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDetailLoggedResponseWriter(writer *response.WithCodeResponseWriter, buf *bytes.Buffer) *detailLoggedResponseWriter {
|
func newDetailLoggedResponseWriter(writer *response.WithCodeResponseWriter,
|
||||||
|
buf *bytes.Buffer) *detailLoggedResponseWriter {
|
||||||
return &detailLoggedResponseWriter{
|
return &detailLoggedResponseWriter{
|
||||||
writer: writer,
|
writer: writer,
|
||||||
buf: buf,
|
buf: buf,
|
||||||
@@ -94,15 +90,13 @@ func DetailedLogHandler(next http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
timer := utils.NewElapsedTimer()
|
timer := utils.NewElapsedTimer()
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
lrw := newDetailLoggedResponseWriter(&response.WithCodeResponseWriter{
|
rw := response.NewWithCodeResponseWriter(w)
|
||||||
Writer: w,
|
lrw := newDetailLoggedResponseWriter(rw, &buf)
|
||||||
Code: http.StatusOK,
|
|
||||||
}, &buf)
|
|
||||||
|
|
||||||
var dup io.ReadCloser
|
var dup io.ReadCloser
|
||||||
r.Body, dup = iox.DupReadCloser(r.Body)
|
r.Body, dup = iox.DupReadCloser(r.Body)
|
||||||
logs := new(internal.LogCollector)
|
logs := new(internal.LogCollector)
|
||||||
next.ServeHTTP(lrw, r.WithContext(context.WithValue(r.Context(), internal.LogContext, logs)))
|
next.ServeHTTP(lrw, r.WithContext(internal.WithLogCollector(r.Context(), logs)))
|
||||||
r.Body = dup
|
r.Body = dup
|
||||||
logDetails(r, lrw, timer, logs)
|
logDetails(r, lrw, timer, logs)
|
||||||
})
|
})
|
||||||
@@ -141,14 +135,7 @@ func logBrief(r *http.Request, code int, timer *utils.ElapsedTimer, logs *intern
|
|||||||
|
|
||||||
ok := isOkResponse(code)
|
ok := isOkResponse(code)
|
||||||
if !ok {
|
if !ok {
|
||||||
fullReq := dumpRequest(r)
|
buf.WriteString(fmt.Sprintf("\n%s", dumpRequest(r)))
|
||||||
limitReader := io.LimitReader(strings.NewReader(fullReq), limitBodyBytes)
|
|
||||||
body, err := io.ReadAll(limitReader)
|
|
||||||
if err != nil {
|
|
||||||
buf.WriteString(fmt.Sprintf("\n%s", fullReq))
|
|
||||||
} else {
|
|
||||||
buf.WriteString(fmt.Sprintf("\n%s", string(body)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body := logs.Flush()
|
body := logs.Flush()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -22,7 +23,7 @@ func TestLogHandler(t *testing.T) {
|
|||||||
for _, logHandler := range handlers {
|
for _, logHandler := range handlers {
|
||||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
|
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
|
||||||
handler := logHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := logHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
r.Context().Value(internal.LogContext).(*internal.LogCollector).Append("anything")
|
internal.LogCollectorFromContext(r.Context()).Append("anything")
|
||||||
w.Header().Set("X-Test", "test")
|
w.Header().Set("X-Test", "test")
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
_, err := w.Write([]byte("content"))
|
_, err := w.Write([]byte("content"))
|
||||||
@@ -49,7 +50,7 @@ func TestLogHandlerVeryLong(t *testing.T) {
|
|||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "http://localhost", &buf)
|
req := httptest.NewRequest(http.MethodPost, "http://localhost", &buf)
|
||||||
handler := LogHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := LogHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
r.Context().Value(internal.LogContext).(*internal.LogCollector).Append("anything")
|
internal.LogCollectorFromContext(r.Context()).Append("anything")
|
||||||
_, _ = io.Copy(io.Discard, r.Body)
|
_, _ = io.Copy(io.Discard, r.Body)
|
||||||
w.Header().Set("X-Test", "test")
|
w.Header().Set("X-Test", "test")
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
@@ -88,18 +89,23 @@ func TestLogHandlerSlow(t *testing.T) {
|
|||||||
func TestDetailedLogHandler_Hijack(t *testing.T) {
|
func TestDetailedLogHandler_Hijack(t *testing.T) {
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
writer := &detailLoggedResponseWriter{
|
writer := &detailLoggedResponseWriter{
|
||||||
writer: &response.WithCodeResponseWriter{
|
writer: response.NewWithCodeResponseWriter(resp),
|
||||||
Writer: resp,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
_, _, _ = writer.Hijack()
|
_, _, _ = writer.Hijack()
|
||||||
})
|
})
|
||||||
|
|
||||||
writer = &detailLoggedResponseWriter{
|
writer = &detailLoggedResponseWriter{
|
||||||
writer: &response.WithCodeResponseWriter{
|
writer: response.NewWithCodeResponseWriter(resp),
|
||||||
Writer: mockedHijackable{resp},
|
}
|
||||||
},
|
assert.NotPanics(t, func() {
|
||||||
|
_, _, _ = writer.Hijack()
|
||||||
|
})
|
||||||
|
|
||||||
|
writer = &detailLoggedResponseWriter{
|
||||||
|
writer: response.NewWithCodeResponseWriter(mockedHijackable{
|
||||||
|
ResponseRecorder: resp,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
_, _, _ = writer.Hijack()
|
_, _, _ = writer.Hijack()
|
||||||
@@ -133,6 +139,13 @@ func TestWrapStatusCodeWithColor(t *testing.T) {
|
|||||||
assert.Equal(t, "503", wrapStatusCode(http.StatusServiceUnavailable))
|
assert.Equal(t, "503", wrapStatusCode(http.StatusServiceUnavailable))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDumpRequest(t *testing.T) {
|
||||||
|
const errMsg = "error"
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
|
||||||
|
r.Body = mockedReadCloser{errMsg: errMsg}
|
||||||
|
assert.Equal(t, errMsg, dumpRequest(r))
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkLogHandler(b *testing.B) {
|
func BenchmarkLogHandler(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
@@ -146,3 +159,15 @@ func BenchmarkLogHandler(b *testing.B) {
|
|||||||
handler.ServeHTTP(resp, req)
|
handler.ServeHTTP(resp, req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockedReadCloser struct {
|
||||||
|
errMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedReadCloser) Read(p []byte) (n int, err error) {
|
||||||
|
return 0, errors.New(m.errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedReadCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func PrometheusHandler(path, method string) func(http.Handler) http.Handler {
|
|||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
startTime := timex.Now()
|
startTime := timex.Now()
|
||||||
cw := &response.WithCodeResponseWriter{Writer: w}
|
cw := response.NewWithCodeResponseWriter(w)
|
||||||
defer func() {
|
defer func() {
|
||||||
metricServerReqDur.Observe(timex.Since(startTime).Milliseconds(), path, method)
|
metricServerReqDur.Observe(timex.Since(startTime).Milliseconds(), path, method)
|
||||||
metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code), method)
|
metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code), method)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func SheddingHandler(shedder load.Shedder, metrics *stat.Metrics) func(http.Hand
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cw := &response.WithCodeResponseWriter{Writer: w}
|
cw := response.NewWithCodeResponseWriter(w)
|
||||||
defer func() {
|
defer func() {
|
||||||
if cw.Code == http.StatusServiceUnavailable {
|
if cw.Code == http.StatusServiceUnavailable {
|
||||||
promise.Fail()
|
promise.Fail()
|
||||||
|
|||||||
@@ -67,9 +67,10 @@ func (h *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
tw := &timeoutWriter{
|
tw := &timeoutWriter{
|
||||||
w: w,
|
w: w,
|
||||||
h: make(http.Header),
|
h: make(http.Header),
|
||||||
req: r,
|
req: r,
|
||||||
|
code: http.StatusOK,
|
||||||
}
|
}
|
||||||
panicChan := make(chan any, 1)
|
panicChan := make(chan any, 1)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -91,10 +92,12 @@ func (h *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
for k, vv := range tw.h {
|
for k, vv := range tw.h {
|
||||||
dst[k] = vv
|
dst[k] = vv
|
||||||
}
|
}
|
||||||
if !tw.wroteHeader {
|
|
||||||
tw.code = http.StatusOK
|
// We don't need to write header 200, because it's written by default.
|
||||||
|
// If we write it again, it will cause a warning: `http: superfluous response.WriteHeader call`.
|
||||||
|
if tw.code != http.StatusOK {
|
||||||
|
w.WriteHeader(tw.code)
|
||||||
}
|
}
|
||||||
w.WriteHeader(tw.code)
|
|
||||||
w.Write(tw.wbuf.Bytes())
|
w.Write(tw.wbuf.Bytes())
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
tw.mu.Lock()
|
tw.mu.Lock()
|
||||||
@@ -127,12 +130,29 @@ type timeoutWriter struct {
|
|||||||
|
|
||||||
var _ http.Pusher = (*timeoutWriter)(nil)
|
var _ http.Pusher = (*timeoutWriter)(nil)
|
||||||
|
|
||||||
|
// Flush implements the Flusher interface.
|
||||||
func (tw *timeoutWriter) Flush() {
|
func (tw *timeoutWriter) Flush() {
|
||||||
if flusher, ok := tw.w.(http.Flusher); ok {
|
flusher, ok := tw.w.(http.Flusher)
|
||||||
flusher.Flush()
|
if !ok {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header := tw.w.Header()
|
||||||
|
for k, v := range tw.h {
|
||||||
|
header[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
tw.w.Write(tw.wbuf.Bytes())
|
||||||
|
tw.wbuf.Reset()
|
||||||
|
flusher.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header returns the underline temporary http.Header.
|
||||||
|
func (tw *timeoutWriter) Header() http.Header {
|
||||||
|
return tw.h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hijack implements the Hijacker interface.
|
||||||
func (tw *timeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
func (tw *timeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
if hijacked, ok := tw.w.(http.Hijacker); ok {
|
if hijacked, ok := tw.w.(http.Hijacker); ok {
|
||||||
return hijacked.Hijack()
|
return hijacked.Hijack()
|
||||||
@@ -141,14 +161,12 @@ func (tw *timeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|||||||
return nil, nil, errors.New("server doesn't support hijacking")
|
return nil, nil, errors.New("server doesn't support hijacking")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header returns the underline temporary http.Header.
|
|
||||||
func (tw *timeoutWriter) Header() http.Header { return tw.h }
|
|
||||||
|
|
||||||
// Push implements the Pusher interface.
|
// Push implements the Pusher interface.
|
||||||
func (tw *timeoutWriter) Push(target string, opts *http.PushOptions) error {
|
func (tw *timeoutWriter) Push(target string, opts *http.PushOptions) error {
|
||||||
if pusher, ok := tw.w.(http.Pusher); ok {
|
if pusher, ok := tw.w.(http.Pusher); ok {
|
||||||
return pusher.Push(target, opts)
|
return pusher.Push(target, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.ErrNotSupported
|
return http.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +183,7 @@ func (tw *timeoutWriter) Write(p []byte) (int, error) {
|
|||||||
if !tw.wroteHeader {
|
if !tw.wroteHeader {
|
||||||
tw.writeHeaderLocked(http.StatusOK)
|
tw.writeHeaderLocked(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tw.wbuf.Write(p)
|
return tw.wbuf.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -12,6 +16,66 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/rest/internal/response"
|
"github.com/zeromicro/go-zero/rest/internal/response"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestTimeoutWriteFlushOutput(t *testing.T) {
|
||||||
|
t.Run("flusher", func(t *testing.T) {
|
||||||
|
timeoutHandler := TimeoutHandler(1000 * time.Millisecond)
|
||||||
|
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
|
||||||
|
flusher, ok := w.(http.Flusher)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "Flushing not supported", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
fmt.Fprint(w, strconv.Itoa(i)+" cats\n\n")
|
||||||
|
flusher.Flush()
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(resp, req)
|
||||||
|
scanner := bufio.NewScanner(resp.Body)
|
||||||
|
var cats int
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.Contains(line, "cats") {
|
||||||
|
cats++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
cats = 0
|
||||||
|
}
|
||||||
|
assert.Equal(t, 5, cats)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("writer", func(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
timeoutHandler := TimeoutHandler(1000 * time.Millisecond)
|
||||||
|
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
|
||||||
|
flusher, ok := w.(http.Flusher)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "Flushing not supported", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
fmt.Fprint(w, strconv.Itoa(i)+" cats\n\n")
|
||||||
|
flusher.Flush()
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
assert.Empty(t, recorder.Body.String())
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
|
||||||
|
resp := mockedResponseWriter{recorder}
|
||||||
|
handler.ServeHTTP(resp, req)
|
||||||
|
assert.Equal(t, "1 cats\n\n2 cats\n\n3 cats\n\n4 cats\n\n5 cats\n\n",
|
||||||
|
recorder.Body.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestTimeout(t *testing.T) {
|
func TestTimeout(t *testing.T) {
|
||||||
timeoutHandler := TimeoutHandler(time.Millisecond)
|
timeoutHandler := TimeoutHandler(time.Millisecond)
|
||||||
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -36,6 +100,18 @@ func TestWithinTimeout(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithinTimeoutBadCode(t *testing.T) {
|
||||||
|
timeoutHandler := TimeoutHandler(time.Second)
|
||||||
|
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(resp, req)
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, resp.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestWithTimeoutTimedout(t *testing.T) {
|
func TestWithTimeoutTimedout(t *testing.T) {
|
||||||
timeoutHandler := TimeoutHandler(time.Millisecond)
|
timeoutHandler := TimeoutHandler(time.Millisecond)
|
||||||
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -144,9 +220,7 @@ func TestTimeoutHijack(t *testing.T) {
|
|||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
|
|
||||||
writer := &timeoutWriter{
|
writer := &timeoutWriter{
|
||||||
w: &response.WithCodeResponseWriter{
|
w: response.NewWithCodeResponseWriter(resp),
|
||||||
Writer: resp,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
@@ -154,9 +228,7 @@ func TestTimeoutHijack(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
writer = &timeoutWriter{
|
writer = &timeoutWriter{
|
||||||
w: &response.WithCodeResponseWriter{
|
w: response.NewWithCodeResponseWriter(mockedHijackable{resp}),
|
||||||
Writer: mockedHijackable{resp},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
@@ -210,9 +282,7 @@ func TestTimeoutWriter_Hijack(t *testing.T) {
|
|||||||
func TestTimeoutWroteTwice(t *testing.T) {
|
func TestTimeoutWroteTwice(t *testing.T) {
|
||||||
c := logtest.NewCollector(t)
|
c := logtest.NewCollector(t)
|
||||||
writer := &timeoutWriter{
|
writer := &timeoutWriter{
|
||||||
w: &response.WithCodeResponseWriter{
|
w: response.NewWithCodeResponseWriter(httptest.NewRecorder()),
|
||||||
Writer: httptest.NewRecorder(),
|
|
||||||
},
|
|
||||||
h: make(http.Header),
|
h: make(http.Header),
|
||||||
req: httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody),
|
req: httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody),
|
||||||
}
|
}
|
||||||
@@ -238,3 +308,19 @@ func (m mockedPusher) WriteHeader(_ int) {
|
|||||||
func (m mockedPusher) Push(_ string, _ *http.PushOptions) error {
|
func (m mockedPusher) Push(_ string, _ *http.PushOptions) error {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockedResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedResponseWriter) Header() http.Header {
|
||||||
|
return m.ResponseWriter.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedResponseWriter) Write(bytes []byte) (int, error) {
|
||||||
|
return m.ResponseWriter.Write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedResponseWriter) WriteHeader(statusCode int) {
|
||||||
|
m.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func TraceHandler(serviceName, path string, opts ...TraceOption) func(http.Handl
|
|||||||
// convenient for tracking error messages
|
// convenient for tracking error messages
|
||||||
propagator.Inject(spanCtx, propagation.HeaderCarrier(w.Header()))
|
propagator.Inject(spanCtx, propagation.HeaderCarrier(w.Header()))
|
||||||
|
|
||||||
trw := &response.WithCodeResponseWriter{Writer: w, Code: http.StatusOK}
|
trw := response.NewWithCodeResponseWriter(w)
|
||||||
next.ServeHTTP(trw, r.WithContext(spanCtx))
|
next.ServeHTTP(trw, r.WithContext(spanCtx))
|
||||||
|
|
||||||
span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(trw.Code)...)
|
span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(trw.Code)...)
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues())
|
formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues(), mapping.WithOpaqueKeys())
|
||||||
pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues())
|
pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues(), mapping.WithOpaqueKeys())
|
||||||
validator atomic.Value
|
validator atomic.Value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -326,6 +326,8 @@ func TestParseHeaders_Error(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseWithValidator(t *testing.T) {
|
func TestParseWithValidator(t *testing.T) {
|
||||||
SetValidator(mockValidator{})
|
SetValidator(mockValidator{})
|
||||||
|
defer SetValidator(mockValidator{nop: true})
|
||||||
|
|
||||||
var v struct {
|
var v struct {
|
||||||
Name string `form:"name"`
|
Name string `form:"name"`
|
||||||
Age int `form:"age"`
|
Age int `form:"age"`
|
||||||
@@ -343,6 +345,8 @@ func TestParseWithValidator(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseWithValidatorWithError(t *testing.T) {
|
func TestParseWithValidatorWithError(t *testing.T) {
|
||||||
SetValidator(mockValidator{})
|
SetValidator(mockValidator{})
|
||||||
|
defer SetValidator(mockValidator{nop: true})
|
||||||
|
|
||||||
var v struct {
|
var v struct {
|
||||||
Name string `form:"name"`
|
Name string `form:"name"`
|
||||||
Age int `form:"age"`
|
Age int `form:"age"`
|
||||||
@@ -356,12 +360,41 @@ func TestParseWithValidatorWithError(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseWithValidatorRequest(t *testing.T) {
|
func TestParseWithValidatorRequest(t *testing.T) {
|
||||||
SetValidator(mockValidator{})
|
SetValidator(mockValidator{})
|
||||||
|
defer SetValidator(mockValidator{nop: true})
|
||||||
|
|
||||||
var v mockRequest
|
var v mockRequest
|
||||||
r, err := http.NewRequest(http.MethodGet, "/a?&age=18", http.NoBody)
|
r, err := http.NewRequest(http.MethodGet, "/a?&age=18", http.NoBody)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Error(t, Parse(r, &v))
|
assert.Error(t, Parse(r, &v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseFormWithDot(t *testing.T) {
|
||||||
|
var v struct {
|
||||||
|
Age int `form:"user.age"`
|
||||||
|
}
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "/a?user.age=18", http.NoBody)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NoError(t, Parse(r, &v))
|
||||||
|
assert.Equal(t, 18, v.Age)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePathWithDot(t *testing.T) {
|
||||||
|
var v struct {
|
||||||
|
Name string `path:"name.val"`
|
||||||
|
Age int `path:"age.val"`
|
||||||
|
}
|
||||||
|
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
|
||||||
|
r = pathvar.WithVars(r, map[string]string{
|
||||||
|
"name.val": "foo",
|
||||||
|
"age.val": "18",
|
||||||
|
})
|
||||||
|
err := Parse(r, &v)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "foo", v.Name)
|
||||||
|
assert.Equal(t, 18, v.Age)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkParseRaw(b *testing.B) {
|
func BenchmarkParseRaw(b *testing.B) {
|
||||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -406,9 +439,15 @@ func BenchmarkParseAuto(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockValidator struct{}
|
type mockValidator struct {
|
||||||
|
nop bool
|
||||||
|
}
|
||||||
|
|
||||||
func (m mockValidator) Validate(r *http.Request, data any) error {
|
func (m mockValidator) Validate(r *http.Request, data any) error {
|
||||||
|
if m.nop {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if r.URL.Path == "/a" {
|
if r.URL.Path == "/a" {
|
||||||
val := reflect.ValueOf(data).Elem().FieldByName("Name").String()
|
val := reflect.ValueOf(data).Elem().FieldByName("Name").String()
|
||||||
if val != "hello" {
|
if val != "hello" {
|
||||||
|
|||||||
@@ -13,37 +13,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errorHandler func(error) (int, any)
|
errorHandler func(context.Context, error) (int, any)
|
||||||
errorHandlerCtx func(context.Context, error) (int, any)
|
errorLock sync.RWMutex
|
||||||
lock sync.RWMutex
|
okHandler func(context.Context, any) any
|
||||||
|
okLock sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error writes err into w.
|
// Error writes err into w.
|
||||||
func Error(w http.ResponseWriter, err error, fns ...func(w http.ResponseWriter, err error)) {
|
func Error(w http.ResponseWriter, err error, fns ...func(w http.ResponseWriter, err error)) {
|
||||||
lock.RLock()
|
doHandleError(w, err, buildErrorHandler(context.Background()), WriteJson, fns...)
|
||||||
handler := errorHandler
|
|
||||||
lock.RUnlock()
|
|
||||||
|
|
||||||
doHandleError(w, err, handler, WriteJson, fns...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorCtx writes err into w.
|
// ErrorCtx writes err into w.
|
||||||
func ErrorCtx(ctx context.Context, w http.ResponseWriter, err error,
|
func ErrorCtx(ctx context.Context, w http.ResponseWriter, err error,
|
||||||
fns ...func(w http.ResponseWriter, err error)) {
|
fns ...func(w http.ResponseWriter, err error)) {
|
||||||
lock.RLock()
|
|
||||||
handlerCtx := errorHandlerCtx
|
|
||||||
lock.RUnlock()
|
|
||||||
|
|
||||||
var handler func(error) (int, any)
|
|
||||||
if handlerCtx != nil {
|
|
||||||
handler = func(err error) (int, any) {
|
|
||||||
return handlerCtx(ctx, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeJson := func(w http.ResponseWriter, code int, v any) {
|
writeJson := func(w http.ResponseWriter, code int, v any) {
|
||||||
WriteJsonCtx(ctx, w, code, v)
|
WriteJsonCtx(ctx, w, code, v)
|
||||||
}
|
}
|
||||||
doHandleError(w, err, handler, writeJson, fns...)
|
doHandleError(w, err, buildErrorHandler(ctx), writeJson, fns...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok writes HTTP 200 OK into w.
|
// Ok writes HTTP 200 OK into w.
|
||||||
@@ -53,26 +40,51 @@ func Ok(w http.ResponseWriter) {
|
|||||||
|
|
||||||
// OkJson writes v into w with 200 OK.
|
// OkJson writes v into w with 200 OK.
|
||||||
func OkJson(w http.ResponseWriter, v any) {
|
func OkJson(w http.ResponseWriter, v any) {
|
||||||
|
okLock.RLock()
|
||||||
|
handler := okHandler
|
||||||
|
okLock.RUnlock()
|
||||||
|
if handler != nil {
|
||||||
|
v = handler(context.Background(), v)
|
||||||
|
}
|
||||||
WriteJson(w, http.StatusOK, v)
|
WriteJson(w, http.StatusOK, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OkJsonCtx writes v into w with 200 OK.
|
// OkJsonCtx writes v into w with 200 OK.
|
||||||
func OkJsonCtx(ctx context.Context, w http.ResponseWriter, v any) {
|
func OkJsonCtx(ctx context.Context, w http.ResponseWriter, v any) {
|
||||||
|
okLock.RLock()
|
||||||
|
handlerCtx := okHandler
|
||||||
|
okLock.RUnlock()
|
||||||
|
if handlerCtx != nil {
|
||||||
|
v = handlerCtx(ctx, v)
|
||||||
|
}
|
||||||
WriteJsonCtx(ctx, w, http.StatusOK, v)
|
WriteJsonCtx(ctx, w, http.StatusOK, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetErrorHandler sets the error handler, which is called on calling Error.
|
// SetErrorHandler sets the error handler, which is called on calling Error.
|
||||||
|
// Notice: SetErrorHandler and SetErrorHandlerCtx set the same error handler.
|
||||||
|
// Keeping both SetErrorHandler and SetErrorHandlerCtx is for backward compatibility.
|
||||||
func SetErrorHandler(handler func(error) (int, any)) {
|
func SetErrorHandler(handler func(error) (int, any)) {
|
||||||
lock.Lock()
|
errorLock.Lock()
|
||||||
defer lock.Unlock()
|
defer errorLock.Unlock()
|
||||||
errorHandler = handler
|
errorHandler = func(_ context.Context, err error) (int, any) {
|
||||||
|
return handler(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetErrorHandlerCtx sets the error handler, which is called on calling Error.
|
// SetErrorHandlerCtx sets the error handler, which is called on calling Error.
|
||||||
|
// Notice: SetErrorHandler and SetErrorHandlerCtx set the same error handler.
|
||||||
|
// Keeping both SetErrorHandler and SetErrorHandlerCtx is for backward compatibility.
|
||||||
func SetErrorHandlerCtx(handlerCtx func(context.Context, error) (int, any)) {
|
func SetErrorHandlerCtx(handlerCtx func(context.Context, error) (int, any)) {
|
||||||
lock.Lock()
|
errorLock.Lock()
|
||||||
defer lock.Unlock()
|
defer errorLock.Unlock()
|
||||||
errorHandlerCtx = handlerCtx
|
errorHandler = handlerCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOkHandler sets the response handler, which is called on calling OkJson and OkJsonCtx.
|
||||||
|
func SetOkHandler(handler func(context.Context, any) any) {
|
||||||
|
okLock.Lock()
|
||||||
|
defer okLock.Unlock()
|
||||||
|
okHandler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteJson writes v as json string into w with code.
|
// WriteJson writes v as json string into w with code.
|
||||||
@@ -89,6 +101,21 @@ func WriteJsonCtx(ctx context.Context, w http.ResponseWriter, code int, v any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildErrorHandler(ctx context.Context) func(error) (int, any) {
|
||||||
|
errorLock.RLock()
|
||||||
|
handlerCtx := errorHandler
|
||||||
|
errorLock.RUnlock()
|
||||||
|
|
||||||
|
var handler func(error) (int, any)
|
||||||
|
if handlerCtx != nil {
|
||||||
|
handler = func(err error) (int, any) {
|
||||||
|
return handlerCtx(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
|
||||||
func doHandleError(w http.ResponseWriter, err error, handler func(error) (int, any),
|
func doHandleError(w http.ResponseWriter, err error, handler func(error) (int, any),
|
||||||
writeJson func(w http.ResponseWriter, code int, v any),
|
writeJson func(w http.ResponseWriter, code int, v any),
|
||||||
fns ...func(w http.ResponseWriter, err error)) {
|
fns ...func(w http.ResponseWriter, err error)) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package httpx
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -80,14 +81,14 @@ func TestError(t *testing.T) {
|
|||||||
headers: make(map[string][]string),
|
headers: make(map[string][]string),
|
||||||
}
|
}
|
||||||
if test.errorHandler != nil {
|
if test.errorHandler != nil {
|
||||||
lock.RLock()
|
errorLock.RLock()
|
||||||
prev := errorHandler
|
prev := errorHandler
|
||||||
lock.RUnlock()
|
errorLock.RUnlock()
|
||||||
SetErrorHandler(test.errorHandler)
|
SetErrorHandler(test.errorHandler)
|
||||||
defer func() {
|
defer func() {
|
||||||
lock.Lock()
|
errorLock.Lock()
|
||||||
errorHandler = prev
|
errorHandler = prev
|
||||||
lock.Unlock()
|
errorLock.Unlock()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
Error(&w, errors.New(test.input))
|
Error(&w, errors.New(test.input))
|
||||||
@@ -129,13 +130,71 @@ func TestOk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOkJson(t *testing.T) {
|
func TestOkJson(t *testing.T) {
|
||||||
w := tracedResponseWriter{
|
t.Run("no handler", func(t *testing.T) {
|
||||||
headers: make(map[string][]string),
|
w := tracedResponseWriter{
|
||||||
}
|
headers: make(map[string][]string),
|
||||||
msg := message{Name: "anyone"}
|
}
|
||||||
OkJson(&w, msg)
|
msg := message{Name: "anyone"}
|
||||||
assert.Equal(t, http.StatusOK, w.code)
|
OkJson(&w, msg)
|
||||||
assert.Equal(t, "{\"name\":\"anyone\"}", w.builder.String())
|
assert.Equal(t, http.StatusOK, w.code)
|
||||||
|
assert.Equal(t, "{\"name\":\"anyone\"}", w.builder.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with handler", func(t *testing.T) {
|
||||||
|
okLock.RLock()
|
||||||
|
prev := okHandler
|
||||||
|
okLock.RUnlock()
|
||||||
|
t.Cleanup(func() {
|
||||||
|
okLock.Lock()
|
||||||
|
okHandler = prev
|
||||||
|
okLock.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
SetOkHandler(func(_ context.Context, v interface{}) any {
|
||||||
|
return fmt.Sprintf("hello %s", v.(message).Name)
|
||||||
|
})
|
||||||
|
w := tracedResponseWriter{
|
||||||
|
headers: make(map[string][]string),
|
||||||
|
}
|
||||||
|
msg := message{Name: "anyone"}
|
||||||
|
OkJson(&w, msg)
|
||||||
|
assert.Equal(t, http.StatusOK, w.code)
|
||||||
|
assert.Equal(t, `"hello anyone"`, w.builder.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOkJsonCtx(t *testing.T) {
|
||||||
|
t.Run("no handler", func(t *testing.T) {
|
||||||
|
w := tracedResponseWriter{
|
||||||
|
headers: make(map[string][]string),
|
||||||
|
}
|
||||||
|
msg := message{Name: "anyone"}
|
||||||
|
OkJsonCtx(context.Background(), &w, msg)
|
||||||
|
assert.Equal(t, http.StatusOK, w.code)
|
||||||
|
assert.Equal(t, "{\"name\":\"anyone\"}", w.builder.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with handler", func(t *testing.T) {
|
||||||
|
okLock.RLock()
|
||||||
|
prev := okHandler
|
||||||
|
okLock.RUnlock()
|
||||||
|
t.Cleanup(func() {
|
||||||
|
okLock.Lock()
|
||||||
|
okHandler = prev
|
||||||
|
okLock.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
SetOkHandler(func(_ context.Context, v interface{}) any {
|
||||||
|
return fmt.Sprintf("hello %s", v.(message).Name)
|
||||||
|
})
|
||||||
|
w := tracedResponseWriter{
|
||||||
|
headers: make(map[string][]string),
|
||||||
|
}
|
||||||
|
msg := message{Name: "anyone"}
|
||||||
|
OkJsonCtx(context.Background(), &w, msg)
|
||||||
|
assert.Equal(t, http.StatusOK, w.code)
|
||||||
|
assert.Equal(t, `"hello anyone"`, w.builder.String())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteJsonTimeout(t *testing.T) {
|
func TestWriteJsonTimeout(t *testing.T) {
|
||||||
@@ -275,14 +334,14 @@ func TestErrorCtx(t *testing.T) {
|
|||||||
headers: make(map[string][]string),
|
headers: make(map[string][]string),
|
||||||
}
|
}
|
||||||
if test.errorHandlerCtx != nil {
|
if test.errorHandlerCtx != nil {
|
||||||
lock.RLock()
|
errorLock.RLock()
|
||||||
prev := errorHandlerCtx
|
prev := errorHandler
|
||||||
lock.RUnlock()
|
errorLock.RUnlock()
|
||||||
SetErrorHandlerCtx(test.errorHandlerCtx)
|
SetErrorHandlerCtx(test.errorHandlerCtx)
|
||||||
defer func() {
|
defer func() {
|
||||||
lock.Lock()
|
errorLock.Lock()
|
||||||
test.errorHandlerCtx = prev
|
test.errorHandlerCtx = prev
|
||||||
lock.Unlock()
|
errorLock.Unlock()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ErrorCtx(context.Background(), &w, errors.New(test.input))
|
ErrorCtx(context.Background(), &w, errors.New(test.input))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -10,13 +11,32 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogContext is a context key.
|
// logContextKey is a context key.
|
||||||
var LogContext = contextKey("request_logs")
|
var logContextKey = contextKey("request_logs")
|
||||||
|
|
||||||
// A LogCollector is used to collect logs.
|
type (
|
||||||
type LogCollector struct {
|
// LogCollector is used to collect logs.
|
||||||
Messages []string
|
LogCollector struct {
|
||||||
lock sync.Mutex
|
Messages []string
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
contextKey string
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithLogCollector returns a new context with LogCollector.
|
||||||
|
func WithLogCollector(ctx context.Context, lc *LogCollector) context.Context {
|
||||||
|
return context.WithValue(ctx, logContextKey, lc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogCollectorFromContext returns LogCollector from ctx.
|
||||||
|
func LogCollectorFromContext(ctx context.Context) *LogCollector {
|
||||||
|
val := ctx.Value(logContextKey)
|
||||||
|
if val == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return val.(*LogCollector)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append appends msg into log context.
|
// Append appends msg into log context.
|
||||||
@@ -73,9 +93,9 @@ func Infof(r *http.Request, format string, v ...any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func appendLog(r *http.Request, message string) {
|
func appendLog(r *http.Request, message string) {
|
||||||
logs := r.Context().Value(LogContext)
|
logs := LogCollectorFromContext(r.Context())
|
||||||
if logs != nil {
|
if logs != nil {
|
||||||
logs.(*LogCollector).Append(message)
|
logs.Append(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,9 +110,3 @@ func formatf(r *http.Request, format string, v ...any) string {
|
|||||||
func formatWithReq(r *http.Request, v string) string {
|
func formatWithReq(r *http.Request, v string) string {
|
||||||
return fmt.Sprintf("(%s - %s) %s", r.RequestURI, httpx.GetRemoteAddr(r), v)
|
return fmt.Sprintf("(%s - %s) %s", r.RequestURI, httpx.GetRemoteAddr(r), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
type contextKey string
|
|
||||||
|
|
||||||
func (c contextKey) String() string {
|
|
||||||
return "rest/internal context key " + string(c)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
func TestInfo(t *testing.T) {
|
func TestInfo(t *testing.T) {
|
||||||
collector := new(LogCollector)
|
collector := new(LogCollector)
|
||||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
|
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
|
||||||
req = req.WithContext(context.WithValue(req.Context(), LogContext, collector))
|
req = req.WithContext(WithLogCollector(req.Context(), collector))
|
||||||
Info(req, "first")
|
Info(req, "first")
|
||||||
Infof(req, "second %s", "third")
|
Infof(req, "second %s", "third")
|
||||||
val := collector.Flush()
|
val := collector.Flush()
|
||||||
@@ -35,7 +35,10 @@ func TestError(t *testing.T) {
|
|||||||
assert.True(t, strings.Contains(val, "third"))
|
assert.True(t, strings.Contains(val, "third"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextKey_String(t *testing.T) {
|
func TestLogCollectorContext(t *testing.T) {
|
||||||
val := contextKey("foo")
|
ctx := context.Background()
|
||||||
assert.True(t, strings.Contains(val.String(), "foo"))
|
assert.Nil(t, LogCollectorFromContext(ctx))
|
||||||
|
collector := new(LogCollector)
|
||||||
|
ctx = WithLogCollector(ctx, collector)
|
||||||
|
assert.Equal(t, collector, LogCollectorFromContext(ctx))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,20 @@ type WithCodeResponseWriter struct {
|
|||||||
Code int
|
Code int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWithCodeResponseWriter returns a WithCodeResponseWriter.
|
||||||
|
// If writer is already a WithCodeResponseWriter, it returns writer directly.
|
||||||
|
func NewWithCodeResponseWriter(writer http.ResponseWriter) *WithCodeResponseWriter {
|
||||||
|
switch w := writer.(type) {
|
||||||
|
case *WithCodeResponseWriter:
|
||||||
|
return w
|
||||||
|
default:
|
||||||
|
return &WithCodeResponseWriter{
|
||||||
|
Writer: writer,
|
||||||
|
Code: http.StatusOK,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Flush flushes the response writer.
|
// Flush flushes the response writer.
|
||||||
func (w *WithCodeResponseWriter) Flush() {
|
func (w *WithCodeResponseWriter) Flush() {
|
||||||
if flusher, ok := w.Writer.(http.Flusher); ok {
|
if flusher, ok := w.Writer.(http.Flusher); ok {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
func TestWithCodeResponseWriter(t *testing.T) {
|
func TestWithCodeResponseWriter(t *testing.T) {
|
||||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
|
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
|
||||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
cw := &WithCodeResponseWriter{Writer: w}
|
cw := NewWithCodeResponseWriter(w)
|
||||||
|
|
||||||
cw.Header().Set("X-Test", "test")
|
cw.Header().Set("X-Test", "test")
|
||||||
cw.WriteHeader(http.StatusServiceUnavailable)
|
cw.WriteHeader(http.StatusServiceUnavailable)
|
||||||
@@ -34,9 +34,7 @@ func TestWithCodeResponseWriter(t *testing.T) {
|
|||||||
|
|
||||||
func TestWithCodeResponseWriter_Hijack(t *testing.T) {
|
func TestWithCodeResponseWriter_Hijack(t *testing.T) {
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
writer := &WithCodeResponseWriter{
|
writer := NewWithCodeResponseWriter(NewWithCodeResponseWriter(resp))
|
||||||
Writer: resp,
|
|
||||||
}
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
writer.Hijack()
|
writer.Hijack()
|
||||||
})
|
})
|
||||||
|
|||||||
1
tools/goctl/.gitignore
vendored
1
tools/goctl/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
.vscode
|
|
||||||
@@ -58,7 +58,7 @@ func TestFormat(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, formattedStr, r)
|
assert.Equal(t, formattedStr, r)
|
||||||
_, err = apiFormat(notFormattedStr, false)
|
_, err = apiFormat(notFormattedStr, false)
|
||||||
assert.Errorf(t, err, " line 7:13 can not found declaration 'Student' in context")
|
assert.Errorf(t, err, " line 7:13 can not find declaration 'Student' in context")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_apiFormatReader_issue1721(t *testing.T) {
|
func Test_apiFormatReader_issue1721(t *testing.T) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
type Request {
|
type Request {
|
||||||
Name string `path:"name,options=you|me"`
|
Name string `path:"name,options=you|me"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ func (p *Parser) checkServices(apiItem *Api, types map[string]TypeExpr, linePref
|
|||||||
|
|
||||||
_, ok := types[structName]
|
_, ok := types[structName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
|
return fmt.Errorf("%s line %d:%d can not find declaration '%s' in context",
|
||||||
linePrefix, route.Reply.Name.Expr().Line(), route.Reply.Name.Expr().Column(), structName)
|
linePrefix, route.Reply.Name.Expr().Line(), route.Reply.Name.Expr().Column(), structName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,7 +433,7 @@ func (p *Parser) checkRequestBody(route *Route, types map[string]TypeExpr, lineP
|
|||||||
if route.Req != nil && route.Req.Name.IsNotNil() && route.Req.Name.Expr().IsNotNil() {
|
if route.Req != nil && route.Req.Name.IsNotNil() && route.Req.Name.Expr().IsNotNil() {
|
||||||
_, ok := types[route.Req.Name.Expr().Text()]
|
_, ok := types[route.Req.Name.Expr().Text()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
|
return fmt.Errorf("%s line %d:%d can not find declaration '%s' in context",
|
||||||
linePrefix, route.Req.Name.Expr().Line(), route.Req.Name.Expr().Column(), route.Req.Name.Expr().Text())
|
linePrefix, route.Req.Name.Expr().Line(), route.Req.Name.Expr().Column(), route.Req.Name.Expr().Text())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -470,7 +470,7 @@ func (p *Parser) checkType(linePrefix string, types map[string]TypeExpr, expr Da
|
|||||||
}
|
}
|
||||||
_, ok := types[name]
|
_, ok := types[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
|
return fmt.Errorf("%s line %d:%d can not find declaration '%s' in context",
|
||||||
linePrefix, v.Literal.Line(), v.Literal.Column(), name)
|
linePrefix, v.Literal.Line(), v.Literal.Column(), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +481,7 @@ func (p *Parser) checkType(linePrefix string, types map[string]TypeExpr, expr Da
|
|||||||
}
|
}
|
||||||
_, ok := types[name]
|
_, ok := types[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
|
return fmt.Errorf("%s line %d:%d can not find declaration '%s' in context",
|
||||||
linePrefix, v.Name.Line(), v.Name.Column(), name)
|
linePrefix, v.Name.Line(), v.Name.Column(), name)
|
||||||
}
|
}
|
||||||
case *Map:
|
case *Map:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package main
|
|||||||
|
|
||||||
import "github.com/zeromicro/go-zero/tools/goctl/compare/cmd"
|
import "github.com/zeromicro/go-zero/tools/goctl/compare/cmd"
|
||||||
|
|
||||||
// EXPRIMENTAL: compare goctl generated code results between old and new, it will be removed in the feature.
|
// EXPERIMENTAL: compare goctl generated code results between old and new, it will be removed in the feature.
|
||||||
// TODO: BEFORE RUNNING: export DSN=$datasource, the database must be gozero, and there has no limit for tables.
|
// TODO: BEFORE RUNNING: export DSN=$datasource, the database must be gozero, and there has no limit for tables.
|
||||||
// TODO: AFTER RUNNING: diff --recursive old_fs new_fs
|
// TODO: AFTER RUNNING: diff --recursive old_fs new_fs
|
||||||
|
|
||||||
|
|||||||
@@ -4,26 +4,26 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||||
github.com/emicklei/proto v1.11.2
|
github.com/emicklei/proto v1.12.1
|
||||||
github.com/fatih/structtag v1.2.0
|
github.com/fatih/structtag v1.2.0
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
github.com/go-sql-driver/mysql v1.7.1
|
||||||
github.com/gookit/color v1.5.3
|
github.com/gookit/color v1.5.4
|
||||||
github.com/iancoleman/strcase v0.2.0
|
github.com/iancoleman/strcase v0.3.0
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1
|
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1
|
||||||
github.com/zeromicro/antlr v0.0.1
|
github.com/zeromicro/antlr v0.0.1
|
||||||
github.com/zeromicro/ddl-parser v1.0.4
|
github.com/zeromicro/ddl-parser v1.0.5
|
||||||
github.com/zeromicro/go-zero v1.5.2
|
github.com/zeromicro/go-zero v1.5.5
|
||||||
golang.org/x/text v0.9.0
|
golang.org/x/text v0.13.0
|
||||||
google.golang.org/grpc v1.55.0
|
google.golang.org/grpc v1.58.2
|
||||||
google.golang.org/protobuf v1.30.0
|
google.golang.org/protobuf v1.31.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
||||||
github.com/alicebob/miniredis/v2 v2.30.2 // indirect
|
github.com/alicebob/miniredis/v2 v2.30.5 // indirect
|
||||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec // indirect
|
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
||||||
@@ -34,7 +34,6 @@ require (
|
|||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||||
github.com/fatih/color v1.15.0 // indirect
|
github.com/fatih/color v1.15.0 // indirect
|
||||||
github.com/felixge/fgprof v0.9.3 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/go-logr/logr v1.2.3 // indirect
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
@@ -48,12 +47,11 @@ require (
|
|||||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgx/v5 v5.3.1 // indirect
|
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||||
@@ -65,40 +63,43 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/openzipkin/zipkin-go v0.4.1 // indirect
|
github.com/openzipkin/zipkin-go v0.4.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.15.0 // indirect
|
github.com/prometheus/client_golang v1.16.0 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.42.0 // indirect
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
github.com/prometheus/procfs v0.9.0 // indirect
|
github.com/prometheus/procfs v0.10.1 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||||
github.com/yuin/gopher-lua v1.1.0 // indirect
|
github.com/yuin/gopher-lua v1.1.0 // indirect
|
||||||
go.etcd.io/etcd/api/v3 v3.5.8 // indirect
|
go.etcd.io/etcd/api/v3 v3.5.9 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.8 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
|
||||||
go.etcd.io/etcd/client/v3 v3.5.8 // indirect
|
go.etcd.io/etcd/client/v3 v3.5.9 // indirect
|
||||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/jaeger v1.14.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/zipkin v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/zipkin v1.14.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||||
go.uber.org/atomic v1.10.0 // indirect
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
go.uber.org/automaxprocs v1.5.2 // indirect
|
go.uber.org/automaxprocs v1.5.3 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
go.uber.org/zap v1.24.0 // indirect
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
golang.org/x/crypto v0.6.0 // indirect
|
golang.org/x/crypto v0.12.0 // indirect
|
||||||
golang.org/x/net v0.9.0 // indirect
|
golang.org/x/net v0.14.0 // indirect
|
||||||
golang.org/x/oauth2 v0.6.0 // indirect
|
golang.org/x/oauth2 v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.7.0 // indirect
|
golang.org/x/sys v0.11.0 // indirect
|
||||||
golang.org/x/term v0.7.0 // indirect
|
golang.org/x/term v0.11.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q
|
|||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
github.com/alicebob/miniredis/v2 v2.30.2 h1:lc1UAUT9ZA7h4srlfBmBt2aorm5Yftk9nBjxz7EyY9I=
|
github.com/alicebob/miniredis/v2 v2.30.5 h1:3r6kTHdKnuP4fkS8k2IrvSfxpxUTcW1SOL0wN7b7Dt0=
|
||||||
github.com/alicebob/miniredis/v2 v2.30.2/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
|
github.com/alicebob/miniredis/v2 v2.30.5/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec h1:EEyRvzmpEUZ+I8WmD5cw/vY8EqhambkOqy5iFr0908A=
|
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec h1:EEyRvzmpEUZ+I8WmD5cw/vY8EqhambkOqy5iFr0908A=
|
||||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||||
@@ -78,8 +78,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
|
|||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
|
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
|
||||||
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/emicklei/proto v1.11.2 h1:DiIeyTJ+gPSyJI+RIAqvuTeKb0tLUmaGXbYg6aFKsnE=
|
github.com/emicklei/proto v1.12.1 h1:6n/Z2pZAnBwuhU66Gs8160B8rrrYKo7h2F2sCOnNceE=
|
||||||
github.com/emicklei/proto v1.11.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
github.com/emicklei/proto v1.12.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
@@ -91,8 +91,6 @@ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
|||||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
|
||||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
@@ -178,15 +176,13 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
|||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE=
|
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||||
github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE=
|
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A=
|
||||||
@@ -194,18 +190,17 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0/go.mod h1:YDZoGHuwE+ov0c8smSH4
|
|||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
|
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
|
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||||
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
@@ -246,21 +241,21 @@ github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow=
|
|||||||
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
|
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
|
||||||
github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfPcEO5A=
|
github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfPcEO5A=
|
||||||
github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM=
|
github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
|
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM=
|
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||||
github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
|
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
@@ -299,16 +294,16 @@ github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE
|
|||||||
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk=
|
github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk=
|
||||||
github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M=
|
github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M=
|
||||||
github.com/zeromicro/ddl-parser v1.0.4 h1:fzU0ZNfV/a6T/WO8TvZZeJE9hmdt3qHvVUsW1X9SGJQ=
|
github.com/zeromicro/ddl-parser v1.0.5 h1:LaVqHdzMTjasua1yYpIYaksxKqRzFrEukj2Wi2EbWaQ=
|
||||||
github.com/zeromicro/ddl-parser v1.0.4/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
|
github.com/zeromicro/ddl-parser v1.0.5/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
|
||||||
github.com/zeromicro/go-zero v1.5.2 h1:vpMlZacCMtgdtYzKI3OMyhS6mZ9UQctiAh0J7gIq31I=
|
github.com/zeromicro/go-zero v1.5.5 h1:qEHnDuCBu/gDBmfWEZXYow6ZmWmzsrJTjtjSMVm4SiY=
|
||||||
github.com/zeromicro/go-zero v1.5.2/go.mod h1:ndCd1nMMAdEMZgPfdm1fpavHUdBW0ykB6ckCRaSG10w=
|
github.com/zeromicro/go-zero v1.5.5/go.mod h1:AGCspTFitHzYjl5ddAmYWLfdt341+BrhefqlwO45UbU=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.8 h1:Zf44zJszoU7zRV0X/nStPenegNXoFDWcB/MwrJbA+L4=
|
go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.8/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
|
go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.8 h1:tPp9YRn/UBFAHdhOQUII9eUs7aOK35eulpMhX4YBd+M=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.8/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.8 h1:B6ngTKZSWWowHEoaucOKHQR/AtZKaoHLiUpWxOLG4l4=
|
go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.8/go.mod h1:idZYIPVkttBJBiRigkB5EM0MmEyx8jcl18zCV3F5noc=
|
go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
@@ -326,6 +321,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8
|
|||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0 h1:3jAYbRHQAqzLjd9I4tzxwJ8Pk/N6AqBcF6m1ZHrxG94=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0 h1:3jAYbRHQAqzLjd9I4tzxwJ8Pk/N6AqBcF6m1ZHrxG94=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0/go.mod h1:+N7zNjIJv4K+DeX67XXET0P+eIciESgaFDBqh+ZJFS4=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0/go.mod h1:+N7zNjIJv4K+DeX67XXET0P+eIciESgaFDBqh+ZJFS4=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 h1:sEL90JjOO/4yhquXl5zTAkLLsZ5+MycAgX99SDsxGc8=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0/go.mod h1:oCslUcizYdpKYyS9e8srZEqM6BB8fq41VJBjLAE6z1w=
|
||||||
go.opentelemetry.io/otel/exporters/zipkin v1.14.0 h1:reEVE1upBF9tcujgvSqLJS0SrI7JQPaTKP4s4rymnSs=
|
go.opentelemetry.io/otel/exporters/zipkin v1.14.0 h1:reEVE1upBF9tcujgvSqLJS0SrI7JQPaTKP4s4rymnSs=
|
||||||
go.opentelemetry.io/otel/exporters/zipkin v1.14.0/go.mod h1:RcjvOAcvhzcufQP8aHmzRw1gE9g/VEZufDdo2w+s4sk=
|
go.opentelemetry.io/otel/exporters/zipkin v1.14.0/go.mod h1:RcjvOAcvhzcufQP8aHmzRw1gE9g/VEZufDdo2w+s4sk=
|
||||||
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
|
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
|
||||||
@@ -337,8 +334,8 @@ go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJP
|
|||||||
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
@@ -349,8 +346,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@@ -410,16 +407,16 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
|||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
|
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||||
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -460,22 +457,21 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -584,8 +580,12 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
|
|||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
|
||||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
@@ -602,8 +602,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
|
|||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I=
|
||||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -617,8 +617,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
|||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -226,7 +226,8 @@
|
|||||||
"home": "{{.global.home}}",
|
"home": "{{.global.home}}",
|
||||||
"remote": "{{.global.remote}}",
|
"remote": "{{.global.remote}}",
|
||||||
"branch": "{{.global.branch}}",
|
"branch": "{{.global.branch}}",
|
||||||
"verbose": "Enable log output"
|
"verbose": "Enable log output",
|
||||||
|
"client": "Whether to generate rpc client"
|
||||||
},
|
},
|
||||||
"template": {
|
"template": {
|
||||||
"short": "Generate proto template",
|
"short": "Generate proto template",
|
||||||
@@ -243,7 +244,8 @@
|
|||||||
"home": "{{.global.home}}",
|
"home": "{{.global.home}}",
|
||||||
"remote": "{{.global.remote}}",
|
"remote": "{{.global.remote}}",
|
||||||
"branch": "{{.global.branch}}",
|
"branch": "{{.global.branch}}",
|
||||||
"verbose": "Enable log output"
|
"verbose": "Enable log output",
|
||||||
|
"client": "Whether to generate rpc client"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"template": {
|
"template": {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// BuildVersion is the version of goctl.
|
// BuildVersion is the version of goctl.
|
||||||
const BuildVersion = "1.5.3"
|
const BuildVersion = "1.5.5"
|
||||||
|
|
||||||
var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5}
|
var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5}
|
||||||
|
|
||||||
|
|||||||
@@ -30,5 +30,6 @@ CREATE TABLE `student`
|
|||||||
) DEFAULT NULL,
|
) DEFAULT NULL,
|
||||||
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`update_time` timestamp NULL DEFAULT NULL,
|
`update_time` timestamp NULL DEFAULT NULL,
|
||||||
|
`delete_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (`type`) USING BTREE
|
PRIMARY KEY (`type`) USING BTREE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ from (
|
|||||||
t.typname AS type,
|
t.typname AS type,
|
||||||
a.atttypmod AS lengthvar,
|
a.atttypmod AS lengthvar,
|
||||||
a.attnotnull AS not_null,
|
a.attnotnull AS not_null,
|
||||||
b.description AS comment
|
b.description AS comment,
|
||||||
|
(c.relnamespace::regnamespace)::varchar AS schema_name
|
||||||
FROM pg_class c,
|
FROM pg_class c,
|
||||||
pg_attribute a
|
pg_attribute a
|
||||||
LEFT OUTER JOIN pg_description b ON a.attrelid = b.objoid AND a.attnum = b.objsubid,
|
LEFT OUTER JOIN pg_description b ON a.attrelid = b.objoid AND a.attnum = b.objsubid,
|
||||||
@@ -81,10 +82,11 @@ from (
|
|||||||
and a.attnum > 0
|
and a.attnum > 0
|
||||||
and a.attrelid = c.oid
|
and a.attrelid = c.oid
|
||||||
and a.atttypid = t.oid
|
and a.atttypid = t.oid
|
||||||
GROUP BY a.attnum, c.relname, a.attname, t.typname, a.atttypmod, a.attnotnull, b.description
|
GROUP BY a.attnum, c.relname, a.attname, t.typname, a.atttypmod, a.attnotnull, b.description, c.relnamespace::regnamespace
|
||||||
ORDER BY a.attnum) AS t
|
ORDER BY a.attnum) AS t
|
||||||
left join information_schema.columns AS c on t.relname = c.table_name
|
left join information_schema.columns AS c on t.relname = c.table_name and t.schema_name = c.table_schema
|
||||||
and t.field = c.column_name and c.table_schema = $2`
|
and t.field = c.column_name
|
||||||
|
where c.table_schema = $2`
|
||||||
|
|
||||||
var reply []*PostgreColumn
|
var reply []*PostgreColumn
|
||||||
err := m.conn.QueryRowsPartial(&reply, querySql, table, schema)
|
err := m.conn.QueryRowsPartial(&reply, querySql, table, schema)
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ func ConvertDataType(table *model.Table, strict bool) (*Table, error) {
|
|||||||
if len(each) == 1 {
|
if len(each) == 1 {
|
||||||
one := each[0]
|
one := each[0]
|
||||||
if one.Name == table.PrimaryKey.Name {
|
if one.Name == table.PrimaryKey.Name {
|
||||||
log.Warning("[ConvertDataType]: table q%, duplicate unique index with primary key: %q", table.Table, one.Name)
|
log.Warning("[ConvertDataType]: table %q, duplicate unique index with primary key: %q", table.Table, one.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ CREATE TABLE `test_user`
|
|||||||
名',
|
名',
|
||||||
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP comment '创建\r时间',
|
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP comment '创建\r时间',
|
||||||
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
`delete_time` timestamp NULL DEFAULT NULL,
|
||||||
|
`delete_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `mobile_unique` (`mobile`),
|
UNIQUE KEY `mobile_unique` (`mobile`),
|
||||||
UNIQUE KEY `class_name_unique` (`class`,`name`),
|
UNIQUE KEY `class_name_unique` (`class`,`name`),
|
||||||
|
|||||||
@@ -219,9 +219,7 @@ func transferTokenNode(node *TokenNode, opt ...tokenNodeOption) *TokenNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !option.ignoreLeadingComment {
|
if !option.ignoreLeadingComment {
|
||||||
for _, v := range node.LeadingCommentGroup {
|
result.LeadingCommentGroup = append(result.LeadingCommentGroup, node.LeadingCommentGroup...)
|
||||||
result.LeadingCommentGroup = append(result.LeadingCommentGroup, v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ var (
|
|||||||
VarBoolVerbose bool
|
VarBoolVerbose bool
|
||||||
// VarBoolMultiple describes whether support generating multiple rpc services or not.
|
// VarBoolMultiple describes whether support generating multiple rpc services or not.
|
||||||
VarBoolMultiple bool
|
VarBoolMultiple bool
|
||||||
|
// VarBoolClient describes whether to generate rpc client
|
||||||
|
VarBoolClient bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// RPCNew is to generate rpc greet service, this greet service can speed
|
// RPCNew is to generate rpc greet service, this greet service can speed
|
||||||
@@ -88,6 +90,7 @@ func RPCNew(_ *cobra.Command, args []string) error {
|
|||||||
ctx.IsGooglePlugin = true
|
ctx.IsGooglePlugin = true
|
||||||
ctx.Output = filepath.Dir(src)
|
ctx.Output = filepath.Dir(src)
|
||||||
ctx.ProtocCmd = fmt.Sprintf("protoc -I=%s %s --go_out=%s --go-grpc_out=%s", filepath.Dir(src), filepath.Base(src), filepath.Dir(src), filepath.Dir(src))
|
ctx.ProtocCmd = fmt.Sprintf("protoc -I=%s %s --go_out=%s --go-grpc_out=%s", filepath.Dir(src), filepath.Base(src), filepath.Dir(src), filepath.Dir(src))
|
||||||
|
ctx.IsGenClient = VarBoolClient
|
||||||
|
|
||||||
grpcOptList := VarStringSliceGoGRPCOpt
|
grpcOptList := VarStringSliceGoGRPCOpt
|
||||||
if len(grpcOptList) > 0 {
|
if len(grpcOptList) > 0 {
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ func ZRPC(_ *cobra.Command, args []string) error {
|
|||||||
ctx.IsGooglePlugin = isGooglePlugin
|
ctx.IsGooglePlugin = isGooglePlugin
|
||||||
ctx.Output = zrpcOut
|
ctx.Output = zrpcOut
|
||||||
ctx.ProtocCmd = strings.Join(protocArgs, " ")
|
ctx.ProtocCmd = strings.Join(protocArgs, " ")
|
||||||
|
ctx.IsGenClient = VarBoolClient
|
||||||
g := generator.NewGenerator(style, verbose)
|
g := generator.NewGenerator(style, verbose)
|
||||||
return g.Generate(&ctx)
|
return g.Generate(&ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ func init() {
|
|||||||
newCmdFlags.BoolVarP(&cli.VarBoolVerbose, "verbose", "v")
|
newCmdFlags.BoolVarP(&cli.VarBoolVerbose, "verbose", "v")
|
||||||
newCmdFlags.MarkHidden("go_opt")
|
newCmdFlags.MarkHidden("go_opt")
|
||||||
newCmdFlags.MarkHidden("go-grpc_opt")
|
newCmdFlags.MarkHidden("go-grpc_opt")
|
||||||
|
newCmdFlags.BoolVarPWithDefaultValue(&cli.VarBoolClient, "client", "c", true)
|
||||||
|
|
||||||
protocCmdFlags.BoolVarP(&cli.VarBoolMultiple, "multiple", "m")
|
protocCmdFlags.BoolVarP(&cli.VarBoolMultiple, "multiple", "m")
|
||||||
protocCmdFlags.StringSliceVar(&cli.VarStringSliceGoOut, "go_out")
|
protocCmdFlags.StringSliceVar(&cli.VarStringSliceGoOut, "go_out")
|
||||||
@@ -63,6 +64,7 @@ func init() {
|
|||||||
protocCmdFlags.MarkHidden("go-grpc_opt")
|
protocCmdFlags.MarkHidden("go-grpc_opt")
|
||||||
protocCmdFlags.MarkHidden("plugin")
|
protocCmdFlags.MarkHidden("plugin")
|
||||||
protocCmdFlags.MarkHidden("proto_path")
|
protocCmdFlags.MarkHidden("proto_path")
|
||||||
|
protocCmdFlags.BoolVarPWithDefaultValue(&cli.VarBoolClient, "client", "c", true)
|
||||||
|
|
||||||
templateCmdFlags.StringVar(&cli.VarStringOutput, "o")
|
templateCmdFlags.StringVar(&cli.VarStringOutput, "o")
|
||||||
templateCmdFlags.StringVar(&cli.VarStringHome, "home")
|
templateCmdFlags.StringVar(&cli.VarStringHome, "home")
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ type ZRpcContext struct {
|
|||||||
Output string
|
Output string
|
||||||
// Multiple is the flag to indicate whether the proto file is generated in multiple mode.
|
// Multiple is the flag to indicate whether the proto file is generated in multiple mode.
|
||||||
Multiple bool
|
Multiple bool
|
||||||
|
// Whether to generate rpc client
|
||||||
|
IsGenClient bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate generates a rpc service, through the proto file,
|
// Generate generates a rpc service, through the proto file,
|
||||||
@@ -100,7 +102,9 @@ func (g *Generator) Generate(zctx *ZRpcContext) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = g.GenCall(dirCtx, proto, g.cfg, zctx)
|
if zctx.IsGenClient {
|
||||||
|
err = g.GenCall(dirCtx, proto, g.cfg, zctx)
|
||||||
|
}
|
||||||
|
|
||||||
console.NewColorConsole().MarkDone()
|
console.NewColorConsole().MarkDone()
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ func mkdir(ctx *ctx.ProjectContext, proto parser.Proto, conf *conf.Config, c *ZR
|
|||||||
return filepath.ToSlash(pkg), nil
|
return filepath.ToSlash(pkg), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var callClientDir string
|
||||||
if !c.Multiple {
|
if !c.Multiple {
|
||||||
callDir := filepath.Join(ctx.WorkDir,
|
callDir := filepath.Join(ctx.WorkDir,
|
||||||
strings.ToLower(stringx.From(proto.Service[0].Name).ToCamel()))
|
strings.ToLower(stringx.From(proto.Service[0].Name).ToCamel()))
|
||||||
@@ -98,23 +99,18 @@ func mkdir(ctx *ctx.ProjectContext, proto parser.Proto, conf *conf.Config, c *ZR
|
|||||||
}
|
}
|
||||||
callDir = filepath.Join(ctx.WorkDir, clientDir)
|
callDir = filepath.Join(ctx.WorkDir, clientDir)
|
||||||
}
|
}
|
||||||
inner[call] = Dir{
|
callClientDir = callDir
|
||||||
Filename: callDir,
|
|
||||||
Package: filepath.ToSlash(filepath.Join(ctx.Path,
|
|
||||||
strings.TrimPrefix(callDir, ctx.Dir))),
|
|
||||||
Base: filepath.Base(callDir),
|
|
||||||
GetChildPackage: func(childPath string) (string, error) {
|
|
||||||
return getChildPackage(callDir, childPath)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
callClientDir = clientDir
|
||||||
|
}
|
||||||
|
if c.IsGenClient {
|
||||||
inner[call] = Dir{
|
inner[call] = Dir{
|
||||||
Filename: clientDir,
|
Filename: callClientDir,
|
||||||
Package: filepath.ToSlash(filepath.Join(ctx.Path,
|
Package: filepath.ToSlash(filepath.Join(ctx.Path,
|
||||||
strings.TrimPrefix(clientDir, ctx.Dir))),
|
strings.TrimPrefix(callClientDir, ctx.Dir))),
|
||||||
Base: filepath.Base(clientDir),
|
Base: filepath.Base(callClientDir),
|
||||||
GetChildPackage: func(childPath string) (string, error) {
|
GetChildPackage: func(childPath string) (string, error) {
|
||||||
return getChildPackage(clientDir, childPath)
|
return getChildPackage(callClientDir, childPath)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ctx
|
package ctx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -76,26 +77,44 @@ func getRealModule(workDir string, execRun execx.RunFunc) (*Module, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
modules, err := decodePackages(strings.NewReader(data))
|
modules, err := decodePackages(strings.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, m := range modules {
|
for _, m := range modules {
|
||||||
if strings.HasPrefix(workDir, m.Dir) {
|
realDir, err := pathx.ReadLink(m.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read go.mod, dir: %s, error: %w", m.Dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(workDir, realDir) {
|
||||||
return &m, nil
|
return &m, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("no matched module")
|
return nil, errors.New("no matched module")
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodePackages(rc io.Reader) ([]Module, error) {
|
func decodePackages(reader io.Reader) ([]Module, error) {
|
||||||
|
br := bufio.NewReader(reader)
|
||||||
|
if _, err := br.ReadSlice('{'); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := br.UnreadByte(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var modules []Module
|
var modules []Module
|
||||||
decoder := json.NewDecoder(rc)
|
decoder := json.NewDecoder(br)
|
||||||
for decoder.More() {
|
for decoder.More() {
|
||||||
var m Module
|
var m Module
|
||||||
if err := decoder.Decode(&m); err != nil {
|
if err := decoder.Decode(&m); err != nil {
|
||||||
return nil, fmt.Errorf("invalid module: %v", err)
|
return nil, fmt.Errorf("invalid module: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
modules = append(modules, m)
|
modules = append(modules, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package ctx
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -110,3 +112,90 @@ func Test_getRealModule(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecodePackages(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data io.Reader
|
||||||
|
want []Module
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single module",
|
||||||
|
data: strings.NewReader(`{
|
||||||
|
"Path":"foo",
|
||||||
|
"Dir":"/home/foo",
|
||||||
|
"GoMod":"/home/foo/go.mod",
|
||||||
|
"GoVersion":"go1.16"}`),
|
||||||
|
want: []Module{
|
||||||
|
{
|
||||||
|
Path: "foo",
|
||||||
|
Dir: "/home/foo",
|
||||||
|
GoMod: "/home/foo/go.mod",
|
||||||
|
GoVersion: "go1.16",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "go work multiple modules",
|
||||||
|
data: strings.NewReader(`
|
||||||
|
{
|
||||||
|
"Path":"foo",
|
||||||
|
"Dir":"/home/foo",
|
||||||
|
"GoMod":"/home/foo/go.mod",
|
||||||
|
"GoVersion":"go1.18"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"Path":"bar",
|
||||||
|
"Dir":"/home/bar",
|
||||||
|
"GoMod":"/home/bar/go.mod",
|
||||||
|
"GoVersion":"go1.18"
|
||||||
|
}`),
|
||||||
|
want: []Module{
|
||||||
|
{
|
||||||
|
Path: "foo",
|
||||||
|
Dir: "/home/foo",
|
||||||
|
GoMod: "/home/foo/go.mod",
|
||||||
|
GoVersion: "go1.18",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "bar",
|
||||||
|
Dir: "/home/bar",
|
||||||
|
GoMod: "/home/bar/go.mod",
|
||||||
|
GoVersion: "go1.18",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "There are extra characters at the beginning",
|
||||||
|
data: strings.NewReader(`Active code page: 65001
|
||||||
|
{
|
||||||
|
"Path":"foo",
|
||||||
|
"Dir":"/home/foo",
|
||||||
|
"GoMod":"/home/foo/go.mod",
|
||||||
|
"GoVersion":"go1.18"
|
||||||
|
}`),
|
||||||
|
want: []Module{
|
||||||
|
{
|
||||||
|
Path: "foo",
|
||||||
|
Dir: "/home/foo",
|
||||||
|
GoMod: "/home/foo/go.mod",
|
||||||
|
GoVersion: "go1.18",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := decodePackages(tt.data)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decodePackages() error %v,wantErr = %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, tt.want) {
|
||||||
|
t.Errorf("decodePackages() = %v,want %v", result, tt.want)
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -145,15 +145,12 @@ func GetTemplateDir(category string) (string, error) {
|
|||||||
// backward compatible, it will be removed in the feature
|
// backward compatible, it will be removed in the feature
|
||||||
// backward compatible start.
|
// backward compatible start.
|
||||||
beforeTemplateDir := filepath.Join(home, version.GetGoctlVersion(), category)
|
beforeTemplateDir := filepath.Join(home, version.GetGoctlVersion(), category)
|
||||||
entries, err := os.ReadDir(beforeTemplateDir)
|
entries, _ := os.ReadDir(beforeTemplateDir)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
infos := make([]fs.FileInfo, 0, len(entries))
|
infos := make([]fs.FileInfo, 0, len(entries))
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
info, err := entry.Info()
|
info, err := entry.Info()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
continue
|
||||||
}
|
}
|
||||||
infos = append(infos, info)
|
infos = append(infos, info)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package zrpc
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"github.com/zeromicro/go-zero/zrpc/internal"
|
"github.com/zeromicro/go-zero/zrpc/internal"
|
||||||
"github.com/zeromicro/go-zero/zrpc/internal/auth"
|
"github.com/zeromicro/go-zero/zrpc/internal/auth"
|
||||||
@@ -85,15 +86,14 @@ func NewClient(c RpcClientConf, options ...ClientOption) (Client, error) {
|
|||||||
|
|
||||||
// NewClientWithTarget returns a Client with connecting to given target.
|
// NewClientWithTarget returns a Client with connecting to given target.
|
||||||
func NewClientWithTarget(target string, opts ...ClientOption) (Client, error) {
|
func NewClientWithTarget(target string, opts ...ClientOption) (Client, error) {
|
||||||
middlewares := ClientMiddlewaresConf{
|
var config RpcClientConf
|
||||||
Trace: true,
|
if err := conf.FillDefault(&config); err != nil {
|
||||||
Duration: true,
|
return nil, err
|
||||||
Prometheus: true,
|
|
||||||
Breaker: true,
|
|
||||||
Timeout: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return internal.NewClient(target, middlewares, opts...)
|
config.Target = target
|
||||||
|
|
||||||
|
return NewClient(config, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conn returns the underlying grpc.ClientConn.
|
// Conn returns the underlying grpc.ClientConn.
|
||||||
|
|||||||
@@ -215,3 +215,15 @@ func TestNewClientWithError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewClientWithTarget(t *testing.T) {
|
||||||
|
_, err := NewClientWithTarget("",
|
||||||
|
WithDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
|
||||||
|
WithDialOption(grpc.WithContextDialer(dialer())),
|
||||||
|
WithUnaryClientInterceptor(func(ctx context.Context, method string, req, reply any,
|
||||||
|
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||||
|
return invoker(ctx, method, req, reply, cc, opts...)
|
||||||
|
}))
|
||||||
|
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -147,3 +147,6 @@ func (m mockClientConn) UpdateAddresses(addresses []resolver.Address) {
|
|||||||
|
|
||||||
func (m mockClientConn) Connect() {
|
func (m mockClientConn) Connect() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m mockClientConn) Shutdown() {
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user