mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-06-14 01:41:57 +08:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07e3e14c0e | ||
|
|
34c5f6616c | ||
|
|
32600f2619 | ||
|
|
b07df1c344 | ||
|
|
a1fca3a1da | ||
|
|
9394e59597 | ||
|
|
f8adc71529 | ||
|
|
c05e03bb5a | ||
|
|
199e86050e | ||
|
|
1e2a12b3d6 | ||
|
|
922efbfc2d | ||
|
|
842c4d81cc | ||
|
|
2a335c7608 | ||
|
|
35edd6b19d | ||
|
|
36bbc6a2e2 | ||
|
|
e20ccdd011 | ||
|
|
c2ff00883a | ||
|
|
00db97fcc1 | ||
|
|
117c3a9069 | ||
|
|
172ff407f3 | ||
|
|
a242fec5e1 | ||
|
|
6286941ebf | ||
|
|
42e0a6f90c | ||
|
|
81ae7d36b5 | ||
|
|
944e76edb9 | ||
|
|
151768ef82 | ||
|
|
50581c7f5c | ||
|
|
54041ef9e4 | ||
|
|
5a9ae5ef02 | ||
|
|
19de13bb04 | ||
|
|
3ab4e82168 | ||
|
|
619e838513 | ||
|
|
423597a01c | ||
|
|
d84dfe1b20 | ||
|
|
87b7a1120d | ||
|
|
528af8a99d | ||
|
|
17fc68ac5a | ||
|
|
804a56bd14 | ||
|
|
88f60d7736 | ||
|
|
95b7a3d3ce | ||
|
|
d71c0da7b7 | ||
|
|
fd070fec91 | ||
|
|
4f22034342 | ||
|
|
b731aa38af | ||
|
|
bf996a1812 | ||
|
|
af7ce65244 | ||
|
|
952db71835 | ||
|
|
abd1fa96a9 | ||
|
|
5aedd9c076 | ||
|
|
ff230c4b1d | ||
|
|
02c95108b9 | ||
|
|
1ff541afe4 | ||
|
|
11a8cbc1e5 | ||
|
|
c063976822 | ||
|
|
cb707034ce | ||
|
|
f10db27efd | ||
|
|
4878f90546 |
@@ -1,3 +1,7 @@
|
|||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
patch: true
|
||||||
|
project: false # disabled because project coverage is not stable
|
||||||
comment:
|
comment:
|
||||||
layout: "flags, files"
|
layout: "flags, files"
|
||||||
behavior: once
|
behavior: once
|
||||||
|
|||||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|||||||
14
.github/workflows/go.yml
vendored
14
.github/workflows/go.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: 1.19
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: true
|
cache: true
|
||||||
id: go
|
id: go
|
||||||
@@ -47,13 +47,13 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
# use 1.18 to guarantee Go 1.18 compatibility
|
# use 1.19 to guarantee Go 1.19 compatibility
|
||||||
go-version: 1.18
|
go-version: 1.19
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/issues.yml
vendored
2
.github/workflows/issues.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
close-issues:
|
close-issues:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v6
|
- uses: actions/stale@v8
|
||||||
with:
|
with:
|
||||||
days-before-issue-stale: 365
|
days-before-issue-stale: 365
|
||||||
days-before-issue-close: 90
|
days-before-issue-close: 90
|
||||||
|
|||||||
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -16,13 +16,13 @@ jobs:
|
|||||||
- goarch: "386"
|
- goarch: "386"
|
||||||
goos: darwin
|
goos: darwin
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: zeromicro/go-zero-release-action@master
|
- uses: zeromicro/go-zero-release-action@master
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
goos: ${{ matrix.goos }}
|
goos: ${{ matrix.goos }}
|
||||||
goarch: ${{ matrix.goarch }}
|
goarch: ${{ matrix.goarch }}
|
||||||
goversion: "https://dl.google.com/go/go1.18.10.linux-amd64.tar.gz"
|
goversion: "https://dl.google.com/go/go1.19.13.linux-amd64.tar.gz"
|
||||||
project_path: "tools/goctl"
|
project_path: "tools/goctl"
|
||||||
binary_name: "goctl"
|
binary_name: "goctl"
|
||||||
extra_files: tools/goctl/readme.md tools/goctl/readme-cn.md
|
extra_files: tools/goctl/readme.md tools/goctl/readme-cn.md
|
||||||
2
.github/workflows/reviewdog.yml
vendored
2
.github/workflows/reviewdog.yml
vendored
@@ -5,7 +5,7 @@ jobs:
|
|||||||
name: runner / staticcheck
|
name: runner / staticcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: reviewdog/action-staticcheck@v1
|
- uses: reviewdog/action-staticcheck@v1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.github_token }}
|
github_token: ${{ secrets.github_token }}
|
||||||
|
|||||||
110
CONTRIBUTING.md
110
CONTRIBUTING.md
@@ -1,102 +1,76 @@
|
|||||||
# Contributing
|
# 🚀 Contributing to go-zero
|
||||||
|
|
||||||
Welcome to go-zero!
|
Welcome to the go-zero community! We're thrilled to have you here. Contributing to our project is a fantastic way to be a part of the go-zero journey. Let's make this guide exciting and fun!
|
||||||
|
|
||||||
- [Before you get started](#before-you-get-started)
|
## 📜 Before You Dive In
|
||||||
- [Code of Conduct](#code-of-conduct)
|
|
||||||
- [Community Expectations](#community-expectations)
|
|
||||||
- [Getting started](#getting-started)
|
|
||||||
- [Your First Contribution](#your-first-contribution)
|
|
||||||
- [Find something to work on](#find-something-to-work-on)
|
|
||||||
- [Find a good first topic](#find-a-good-first-topic)
|
|
||||||
- [Work on an Issue](#work-on-an-issue)
|
|
||||||
- [File an Issue](#file-an-issue)
|
|
||||||
- [Contributor Workflow](#contributor-workflow)
|
|
||||||
- [Creating Pull Requests](#creating-pull-requests)
|
|
||||||
- [Code Review](#code-review)
|
|
||||||
- [Testing](#testing)
|
|
||||||
|
|
||||||
# Before you get started
|
### 🤝 Code of Conduct
|
||||||
|
|
||||||
## Code of Conduct
|
Let's start on the right foot. Please take a moment to read and embrace our [Code of Conduct](/code-of-conduct.md). We're all about creating a welcoming and respectful environment.
|
||||||
|
|
||||||
Please make sure to read and observe our [Code of Conduct](/code-of-conduct.md).
|
### 🌟 Community Expectations
|
||||||
|
|
||||||
## Community Expectations
|
At go-zero, we're like a close-knit family, and we believe in creating a healthy, friendly, and productive atmosphere. It's all about sharing knowledge and building amazing things together.
|
||||||
|
|
||||||
go-zero is a community project driven by its community which strives to promote a healthy, friendly and productive environment.
|
## 🚀 Getting Started
|
||||||
go-zero is a web and rpc framework written in Go. It's born to ensure the stability of the busy sites with resilient design. Builtin goctl greatly improves the development productivity.
|
|
||||||
|
|
||||||
# Getting started
|
Get your adventure rolling! Here's how to begin:
|
||||||
|
|
||||||
- Fork the repository on GitHub.
|
1. 🍴 **Fork the Repository**: Head over to the GitHub repository and fork it to your own space.
|
||||||
- Make your changes on your fork repository.
|
|
||||||
- Submit a PR.
|
|
||||||
|
|
||||||
|
2. 🛠️ **Make Your Magic**: Work your magic in your forked repository. Create new features, squash bugs, or improve documentation - it's your world to conquer!
|
||||||
|
|
||||||
# Your First Contribution
|
3. 🚀 **Submit a PR (Pull Request)**: When you're ready to unveil your creation, submit a Pull Request. We can't wait to see your awesome work!
|
||||||
|
|
||||||
We will help you to contribute in different areas like filing issues, developing features, fixing critical bugs and
|
## 🌟 Your First Contribution
|
||||||
getting your work reviewed and merged.
|
|
||||||
|
|
||||||
If you have questions about the development process,
|
We're here to guide you on your quest to become a go-zero contributor. Whether you want to file issues, develop features, or tame some critical bugs, we've got you covered.
|
||||||
feel free to [file an issue](https://github.com/zeromicro/go-zero/issues/new/choose).
|
|
||||||
|
|
||||||
## Find something to work on
|
If you have questions or need guidance at any stage, don't hesitate to [open an issue](https://github.com/zeromicro/go-zero/issues/new/choose).
|
||||||
|
|
||||||
We are always in need of help, be it fixing documentation, reporting bugs or writing some code.
|
## 🔍 Find Something to Work On
|
||||||
Look at places where you feel best coding practices aren't followed, code refactoring is needed or tests are missing.
|
|
||||||
Here is how you get started.
|
|
||||||
|
|
||||||
### Find a good first topic
|
Ready to dive into the action? There are several ways to contribute:
|
||||||
|
|
||||||
[go-zero](https://github.com/zeromicro/go-zero) has beginner-friendly issues that provide a good first issue.
|
### 💼 Find a Good First Topic
|
||||||
For example, [go-zero](https://github.com/zeromicro/go-zero) has
|
|
||||||
[help wanted](https://github.com/zeromicro/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) and
|
|
||||||
[good first issue](https://github.com/zeromicro/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
|
||||||
labels for issues that should not need deep knowledge of the system.
|
|
||||||
We can help new contributors who wish to work on such issues.
|
|
||||||
|
|
||||||
Another good way to contribute is to find a documentation improvement, such as a missing/broken link.
|
Discover easy-entry issues labeled as [help wanted](https://github.com/zeromicro/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) or [good first issue](https://github.com/zeromicro/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). These issues are perfect for newcomers and don't require deep knowledge of the system. We're here to assist you with these tasks.
|
||||||
Please see [Contributing](#contributing) below for the workflow.
|
|
||||||
|
|
||||||
#### Work on an issue
|
### 🪄 Work on an Issue
|
||||||
|
|
||||||
When you are willing to take on an issue, just reply on the issue. The maintainer will assign it to you.
|
Once you've picked an issue that excites you, let us know by commenting on it. Our maintainers will assign it to you, and you can embark on your mission!
|
||||||
|
|
||||||
### File an Issue
|
### 📢 File an Issue
|
||||||
|
|
||||||
While we encourage everyone to contribute code, it is also appreciated when someone reports an issue.
|
Reporting an issue is just as valuable as code contributions. If you discover a problem, don't hesitate to [open an issue](https://github.com/zeromicro/go-zero/issues/new/choose). Be sure to follow our guidelines when submitting an issue.
|
||||||
|
|
||||||
Please follow the prompted submission guidelines while opening an issue.
|
## 🎯 Contributor Workflow
|
||||||
|
|
||||||
# Contributor Workflow
|
Here's a rough guide to your contributor journey:
|
||||||
|
|
||||||
Please do not ever hesitate to ask a question or send a pull request.
|
1. 🌱 Create a New Branch: Start by creating a topic branch, usually based on the 'master' branch. This is where your contribution will grow.
|
||||||
|
|
||||||
This is a rough outline of what a contributor's workflow looks like:
|
2. 💡 Make Commits: Commit your work in logical units. Each commit should tell a story.
|
||||||
|
|
||||||
- Create a topic branch from where to base the contribution. This is usually master.
|
3. 🚀 Push Changes: Push the changes in your topic branch to your personal fork of the repository.
|
||||||
- Make commits of logical units.
|
|
||||||
- Push changes in a topic branch to a personal fork of the repository.
|
|
||||||
- Submit a pull request to [go-zero](https://github.com/zeromicro/go-zero).
|
|
||||||
|
|
||||||
## Creating Pull Requests
|
4. 📦 Submit a Pull Request: When your creation is complete, submit a Pull Request to the [go-zero repository](https://github.com/zeromicro/go-zero).
|
||||||
|
|
||||||
Pull requests are often called simply "PR".
|
## 🌠 Creating Pull Requests
|
||||||
go-zero generally follows the standard [github pull request](https://help.github.com/articles/about-pull-requests/) process.
|
|
||||||
To submit a proposed change, please develop the code/fix and add new test cases.
|
|
||||||
After that, run these local verifications before submitting pull request to predict the pass or
|
|
||||||
fail of continuous integration.
|
|
||||||
|
|
||||||
* Format the code with `gofmt`
|
Pull Requests (PRs) are your way of making a grand entrance with your contribution. Here's how to do it:
|
||||||
* Run the test with data race enabled `go test -race ./...`
|
|
||||||
|
|
||||||
## Code Review
|
- 💼 Format Your Code: Ensure your code is beautifully formatted with `gofmt`.
|
||||||
|
- 🏃 Run Tests: Verify that your changes pass all the tests, including data race tests. Run `go test -race ./...` for the ultimate validation.
|
||||||
|
|
||||||
To make it easier for your PR to receive reviews, consider the reviewers will need you to:
|
## 👁️🗨️ Code Review
|
||||||
|
|
||||||
* follow [good coding guidelines](https://github.com/golang/go/wiki/CodeReviewComments).
|
Getting your PR reviewed is the final step before your contribution becomes part of go-zero's magical world. To make the process smooth, keep these things in mind:
|
||||||
* write [good commit messages](https://chris.beams.io/posts/git-commit/).
|
|
||||||
* break large changes into a logical series of smaller patches which individually make easily understandable changes, and in aggregate solve a broader issue.
|
|
||||||
|
|
||||||
|
- 🧙♀️ Follow Good Coding Practices: Stick to [good coding guidelines](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||||
|
- 📝 Write Awesome Commit Messages: Craft [impressive commit messages](https://chris.beams.io/posts/git-commit/) - they're like spells in the wizard's book!
|
||||||
|
- 🔍 Break It Down: For larger changes, consider breaking them into a series of smaller, logical patches. Each patch should make an understandable and meaningful improvement.
|
||||||
|
|
||||||
|
Congratulations on your contribution journey! We're thrilled to have you as part of our go-zero community. Let's make amazing things together! 🌟
|
||||||
|
|
||||||
|
Now, go out there and start your adventure! If you have any more magical ideas to enhance this guide, please share them. 🔥
|
||||||
@@ -46,7 +46,7 @@ type (
|
|||||||
// DoWithAcceptable returns an error instantly if the Breaker rejects the request.
|
// DoWithAcceptable returns an error instantly if the Breaker rejects the request.
|
||||||
// If a panic occurs in the request, the Breaker handles it as an error
|
// If a panic occurs in the request, the Breaker handles it as an error
|
||||||
// and causes the same panic again.
|
// and causes the same panic again.
|
||||||
// acceptable checks if it's a successful call, even if the err is not nil.
|
// acceptable checks if it's a successful call, even if the error is not nil.
|
||||||
DoWithAcceptable(req func() error, acceptable Acceptable) error
|
DoWithAcceptable(req func() error, acceptable Acceptable) error
|
||||||
|
|
||||||
// DoWithFallback runs the given request if the Breaker accepts it.
|
// DoWithFallback runs the given request if the Breaker accepts it.
|
||||||
@@ -59,7 +59,7 @@ type (
|
|||||||
// DoWithFallbackAcceptable runs the fallback if the Breaker rejects the request.
|
// DoWithFallbackAcceptable runs the fallback if the Breaker rejects the request.
|
||||||
// If a panic occurs in the request, the Breaker handles it as an error
|
// If a panic occurs in the request, the Breaker handles it as an error
|
||||||
// and causes the same panic again.
|
// and causes the same panic again.
|
||||||
// acceptable checks if it's a successful call, even if the err is not nil.
|
// acceptable checks if it's a successful call, even if the error is not nil.
|
||||||
DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error
|
DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (lt loggedThrottle) logError(err error) error {
|
func (lt loggedThrottle) logError(err error) error {
|
||||||
if err == ErrServiceUnavailable {
|
if errors.Is(err, ErrServiceUnavailable) {
|
||||||
// if circuit open, not possible to have empty error window
|
// if circuit open, not possible to have empty error window
|
||||||
stat.Report(fmt.Sprintf(
|
stat.Report(fmt.Sprintf(
|
||||||
"proc(%s/%d), callee: %s, breaker is open and requests dropped\nlast errors:\n%s",
|
"proc(%s/%d), callee: %s, breaker is open and requests dropped\nlast errors:\n%s",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func GetBreaker(name string) Breaker {
|
|||||||
// NoBreakerFor disables the circuit breaker for the given name.
|
// NoBreakerFor disables the circuit breaker for the given name.
|
||||||
func NoBreakerFor(name string) {
|
func NoBreakerFor(name string) {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
breakers[name] = newNoOpBreaker()
|
breakers[name] = newNopBreaker()
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func TestBreakersDoWithAcceptable(t *testing.T) {
|
|||||||
assert.Equal(t, errDummy, GetBreaker("anyone").DoWithAcceptable(func() error {
|
assert.Equal(t, errDummy, GetBreaker("anyone").DoWithAcceptable(func() error {
|
||||||
return errDummy
|
return errDummy
|
||||||
}, func(err error) bool {
|
}, func(err error) bool {
|
||||||
return err == nil || err == errDummy
|
return err == nil || errors.Is(err, errDummy)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
verify(t, func() bool {
|
verify(t, func() bool {
|
||||||
@@ -45,12 +45,12 @@ func TestBreakersDoWithAcceptable(t *testing.T) {
|
|||||||
}, func(err error) bool {
|
}, func(err error) bool {
|
||||||
return err == nil
|
return err == nil
|
||||||
})
|
})
|
||||||
assert.True(t, err == errDummy || err == ErrServiceUnavailable)
|
assert.True(t, errors.Is(err, errDummy) || errors.Is(err, ErrServiceUnavailable))
|
||||||
}
|
}
|
||||||
verify(t, func() bool {
|
verify(t, func() bool {
|
||||||
return ErrServiceUnavailable == Do("another", func() error {
|
return errors.Is(Do("another", func() error {
|
||||||
return nil
|
return nil
|
||||||
})
|
}), ErrServiceUnavailable)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,12 +75,12 @@ func TestBreakersFallback(t *testing.T) {
|
|||||||
}, func(err error) error {
|
}, func(err error) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
assert.True(t, err == nil || err == errDummy)
|
assert.True(t, err == nil || errors.Is(err, errDummy))
|
||||||
}
|
}
|
||||||
verify(t, func() bool {
|
verify(t, func() bool {
|
||||||
return ErrServiceUnavailable == Do("fallback", func() error {
|
return errors.Is(Do("fallback", func() error {
|
||||||
return nil
|
return nil
|
||||||
})
|
}), ErrServiceUnavailable)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,12 +94,12 @@ func TestBreakersAcceptableFallback(t *testing.T) {
|
|||||||
}, func(err error) bool {
|
}, func(err error) bool {
|
||||||
return err == nil
|
return err == nil
|
||||||
})
|
})
|
||||||
assert.True(t, err == nil || err == errDummy)
|
assert.True(t, err == nil || errors.Is(err, errDummy))
|
||||||
}
|
}
|
||||||
verify(t, func() bool {
|
verify(t, func() bool {
|
||||||
return ErrServiceUnavailable == Do("acceptablefallback", func() error {
|
return errors.Is(Do("acceptablefallback", func() error {
|
||||||
return nil
|
return nil
|
||||||
})
|
}), ErrServiceUnavailable)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func TestGoogleBreakerAcceptable(t *testing.T) {
|
|||||||
assert.Equal(t, errAcceptable, b.doReq(func() error {
|
assert.Equal(t, errAcceptable, b.doReq(func() error {
|
||||||
return errAcceptable
|
return errAcceptable
|
||||||
}, nil, func(err error) bool {
|
}, nil, func(err error) bool {
|
||||||
return err == errAcceptable
|
return errors.Is(err, errAcceptable)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ func TestGoogleBreakerNotAcceptable(t *testing.T) {
|
|||||||
assert.Equal(t, errAcceptable, b.doReq(func() error {
|
assert.Equal(t, errAcceptable, b.doReq(func() error {
|
||||||
return errAcceptable
|
return errAcceptable
|
||||||
}, nil, func(err error) bool {
|
}, nil, func(err error) bool {
|
||||||
return err != errAcceptable
|
return !errors.Is(err, errAcceptable)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
package breaker
|
package breaker
|
||||||
|
|
||||||
const noOpBreakerName = "nopBreaker"
|
const nopBreakerName = "nopBreaker"
|
||||||
|
|
||||||
type noOpBreaker struct{}
|
type nopBreaker struct{}
|
||||||
|
|
||||||
func newNoOpBreaker() Breaker {
|
func newNopBreaker() Breaker {
|
||||||
return noOpBreaker{}
|
return nopBreaker{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b noOpBreaker) Name() string {
|
func (b nopBreaker) Name() string {
|
||||||
return noOpBreakerName
|
return nopBreakerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b noOpBreaker) Allow() (Promise, error) {
|
func (b nopBreaker) Allow() (Promise, error) {
|
||||||
return nopPromise{}, nil
|
return nopPromise{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b noOpBreaker) Do(req func() error) error {
|
func (b nopBreaker) Do(req func() error) error {
|
||||||
return req()
|
return req()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b noOpBreaker) DoWithAcceptable(req func() error, _ Acceptable) error {
|
func (b nopBreaker) DoWithAcceptable(req func() error, _ Acceptable) error {
|
||||||
return req()
|
return req()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b noOpBreaker) DoWithFallback(req func() error, _ func(err error) error) error {
|
func (b nopBreaker) DoWithFallback(req func() error, _ func(err error) error) error {
|
||||||
return req()
|
return req()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b noOpBreaker) DoWithFallbackAcceptable(req func() error, _ func(err error) error,
|
func (b nopBreaker) DoWithFallbackAcceptable(req func() error, _ func(err error) error,
|
||||||
_ Acceptable) error {
|
_ Acceptable) error {
|
||||||
return req()
|
return req()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNopBreaker(t *testing.T) {
|
func TestNopBreaker(t *testing.T) {
|
||||||
b := newNoOpBreaker()
|
b := newNopBreaker()
|
||||||
assert.Equal(t, noOpBreakerName, b.Name())
|
assert.Equal(t, nopBreakerName, b.Name())
|
||||||
p, err := b.Allow()
|
p, err := b.Allow()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
p.Accept()
|
p.Accept()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package fx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/errorx"
|
"github.com/zeromicro/go-zero/core/errorx"
|
||||||
@@ -10,8 +9,6 @@ import (
|
|||||||
|
|
||||||
const defaultRetryTimes = 3
|
const defaultRetryTimes = 3
|
||||||
|
|
||||||
var errTimeout = errors.New("retry timeout")
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// RetryOption defines the method to customize DoWithRetry.
|
// RetryOption defines the method to customize DoWithRetry.
|
||||||
RetryOption func(*retryOptions)
|
RetryOption func(*retryOptions)
|
||||||
@@ -28,7 +25,7 @@ type (
|
|||||||
// and performs modification operations, it is best to lock them,
|
// and performs modification operations, it is best to lock them,
|
||||||
// otherwise there may be data race issues
|
// otherwise there may be data race issues
|
||||||
func DoWithRetry(fn func() error, opts ...RetryOption) error {
|
func DoWithRetry(fn func() error, opts ...RetryOption) error {
|
||||||
return retry(func(errChan chan error, retryCount int) {
|
return retry(context.Background(), func(errChan chan error, retryCount int) {
|
||||||
errChan <- fn()
|
errChan <- fn()
|
||||||
}, opts...)
|
}, opts...)
|
||||||
}
|
}
|
||||||
@@ -40,12 +37,12 @@ func DoWithRetry(fn func() error, opts ...RetryOption) error {
|
|||||||
// otherwise there may be data race issues
|
// otherwise there may be data race issues
|
||||||
func DoWithRetryCtx(ctx context.Context, fn func(ctx context.Context, retryCount int) error,
|
func DoWithRetryCtx(ctx context.Context, fn func(ctx context.Context, retryCount int) error,
|
||||||
opts ...RetryOption) error {
|
opts ...RetryOption) error {
|
||||||
return retry(func(errChan chan error, retryCount int) {
|
return retry(ctx, func(errChan chan error, retryCount int) {
|
||||||
errChan <- fn(ctx, retryCount)
|
errChan <- fn(ctx, retryCount)
|
||||||
}, opts...)
|
}, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func retry(fn func(errChan chan error, retryCount int), opts ...RetryOption) error {
|
func retry(ctx context.Context, fn func(errChan chan error, retryCount int), opts ...RetryOption) error {
|
||||||
options := newRetryOptions()
|
options := newRetryOptions()
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(options)
|
opt(options)
|
||||||
@@ -53,7 +50,6 @@ func retry(fn func(errChan chan error, retryCount int), opts ...RetryOption) err
|
|||||||
|
|
||||||
var berr errorx.BatchError
|
var berr errorx.BatchError
|
||||||
var cancelFunc context.CancelFunc
|
var cancelFunc context.CancelFunc
|
||||||
ctx := context.Background()
|
|
||||||
if options.timeout > 0 {
|
if options.timeout > 0 {
|
||||||
ctx, cancelFunc = context.WithTimeout(ctx, options.timeout)
|
ctx, cancelFunc = context.WithTimeout(ctx, options.timeout)
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
@@ -71,14 +67,14 @@ func retry(fn func(errChan chan error, retryCount int), opts ...RetryOption) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
berr.Add(errTimeout)
|
berr.Add(ctx.Err())
|
||||||
return berr.Err()
|
return berr.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.interval > 0 {
|
if options.interval > 0 {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
berr.Add(errTimeout)
|
berr.Add(ctx.Err())
|
||||||
return berr.Err()
|
return berr.Err()
|
||||||
case <-time.After(options.interval):
|
case <-time.After(options.interval):
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,19 +98,51 @@ func TestRetryWithInterval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRetryCtx(t *testing.T) {
|
func TestRetryCtx(t *testing.T) {
|
||||||
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
|
t.Run("with timeout", func(t *testing.T) {
|
||||||
if retryCount == 0 {
|
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
|
||||||
return errors.New("any")
|
if retryCount == 0 {
|
||||||
}
|
return errors.New("any")
|
||||||
time.Sleep(time.Millisecond * 150)
|
}
|
||||||
return nil
|
time.Sleep(time.Millisecond * 150)
|
||||||
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
|
||||||
|
|
||||||
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
|
|
||||||
if retryCount == 1 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
||||||
time.Sleep(time.Millisecond * 150)
|
|
||||||
return errors.New("any ")
|
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
|
||||||
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
if retryCount == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 150)
|
||||||
|
return errors.New("any ")
|
||||||
|
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with deadline exceeded", func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond*250))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var times int
|
||||||
|
assert.Error(t, DoWithRetryCtx(ctx, func(ctx context.Context, retryCount int) error {
|
||||||
|
times++
|
||||||
|
time.Sleep(time.Millisecond * 150)
|
||||||
|
return errors.New("any")
|
||||||
|
}, WithInterval(time.Millisecond*150)))
|
||||||
|
assert.Equal(t, 1, times)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with deadline not exceeded", func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond*250))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var times int
|
||||||
|
assert.NoError(t, DoWithRetryCtx(ctx, func(ctx context.Context, retryCount int) error {
|
||||||
|
times++
|
||||||
|
if times == defaultRetryTimes {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 50)
|
||||||
|
return errors.New("any")
|
||||||
|
}))
|
||||||
|
assert.Equal(t, defaultRetryTimes, times)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type LogConf struct {
|
|||||||
- `Compress`: whether or not to compress log files, only works with `file` mode.
|
- `Compress`: whether or not to compress log files, only works with `file` mode.
|
||||||
- `KeepDays`: how many days that the log files are kept, after the given days, the outdated files will be deleted automatically. It has no effect on `console` mode.
|
- `KeepDays`: how many days that the log files are kept, after the given days, the outdated files will be deleted automatically. It has no effect on `console` mode.
|
||||||
- `StackCooldownMillis`: how many milliseconds to rewrite stacktrace again. It’s used to avoid stacktrace flooding.
|
- `StackCooldownMillis`: how many milliseconds to rewrite stacktrace again. It’s used to avoid stacktrace flooding.
|
||||||
- `MaxBackups`: represents how many backup log files will be kept. 0 means all files will be kept forever. Only take effect when `Rotation` is `size`. NOTE: the level of option `KeepDays` will be higher. Even thougth `MaxBackups` sets 0, log files will still be removed if the `KeepDays` limitation is reached.
|
- `MaxBackups`: represents how many backup log files will be kept. 0 means all files will be kept forever. Only take effect when `Rotation` is `size`. NOTE: the level of option `KeepDays` will be higher. Even though `MaxBackups` sets 0, log files will still be removed if the `KeepDays` limitation is reached.
|
||||||
- `MaxSize`: represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`. Only take effect when `Rotation` is `size`.
|
- `MaxSize`: represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`. Only take effect when `Rotation` is `size`.
|
||||||
- `Rotation`: represents the type of log rotation rule. Default is `daily`.
|
- `Rotation`: represents the type of log rotation rule. Default is `daily`.
|
||||||
- `daily` rotate the logs by day.
|
- `daily` rotate the logs by day.
|
||||||
|
|||||||
@@ -41,67 +41,99 @@ type richLogger struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Debug(v ...any) {
|
func (l *richLogger) Debug(v ...any) {
|
||||||
l.debug(fmt.Sprint(v...))
|
if shallLog(DebugLevel) {
|
||||||
|
l.debug(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Debugf(format string, v ...any) {
|
func (l *richLogger) Debugf(format string, v ...any) {
|
||||||
l.debug(fmt.Sprintf(format, v...))
|
if shallLog(DebugLevel) {
|
||||||
|
l.debug(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Debugv(v any) {
|
func (l *richLogger) Debugv(v any) {
|
||||||
l.debug(v)
|
if shallLog(DebugLevel) {
|
||||||
|
l.debug(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Debugw(msg string, fields ...LogField) {
|
func (l *richLogger) Debugw(msg string, fields ...LogField) {
|
||||||
l.debug(msg, fields...)
|
if shallLog(DebugLevel) {
|
||||||
|
l.debug(msg, fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Error(v ...any) {
|
func (l *richLogger) Error(v ...any) {
|
||||||
l.err(fmt.Sprint(v...))
|
if shallLog(ErrorLevel) {
|
||||||
|
l.err(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Errorf(format string, v ...any) {
|
func (l *richLogger) Errorf(format string, v ...any) {
|
||||||
l.err(fmt.Sprintf(format, v...))
|
if shallLog(ErrorLevel) {
|
||||||
|
l.err(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Errorv(v any) {
|
func (l *richLogger) Errorv(v any) {
|
||||||
l.err(v)
|
if shallLog(ErrorLevel) {
|
||||||
|
l.err(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Errorw(msg string, fields ...LogField) {
|
func (l *richLogger) Errorw(msg string, fields ...LogField) {
|
||||||
l.err(msg, fields...)
|
if shallLog(ErrorLevel) {
|
||||||
|
l.err(msg, fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Info(v ...any) {
|
func (l *richLogger) Info(v ...any) {
|
||||||
l.info(fmt.Sprint(v...))
|
if shallLog(InfoLevel) {
|
||||||
|
l.info(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Infof(format string, v ...any) {
|
func (l *richLogger) Infof(format string, v ...any) {
|
||||||
l.info(fmt.Sprintf(format, v...))
|
if shallLog(InfoLevel) {
|
||||||
|
l.info(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Infov(v any) {
|
func (l *richLogger) Infov(v any) {
|
||||||
l.info(v)
|
if shallLog(InfoLevel) {
|
||||||
|
l.info(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Infow(msg string, fields ...LogField) {
|
func (l *richLogger) Infow(msg string, fields ...LogField) {
|
||||||
l.info(msg, fields...)
|
if shallLog(InfoLevel) {
|
||||||
|
l.info(msg, fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Slow(v ...any) {
|
func (l *richLogger) Slow(v ...any) {
|
||||||
l.slow(fmt.Sprint(v...))
|
if shallLog(ErrorLevel) {
|
||||||
|
l.slow(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Slowf(format string, v ...any) {
|
func (l *richLogger) Slowf(format string, v ...any) {
|
||||||
l.slow(fmt.Sprintf(format, v...))
|
if shallLog(ErrorLevel) {
|
||||||
|
l.slow(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Slowv(v any) {
|
func (l *richLogger) Slowv(v any) {
|
||||||
l.slow(v)
|
if shallLog(ErrorLevel) {
|
||||||
|
l.slow(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Sloww(msg string, fields ...LogField) {
|
func (l *richLogger) Sloww(msg string, fields ...LogField) {
|
||||||
l.slow(msg, fields...)
|
if shallLog(ErrorLevel) {
|
||||||
|
l.slow(msg, fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) WithCallerSkip(skip int) Logger {
|
func (l *richLogger) WithCallerSkip(skip int) Logger {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"path"
|
"path"
|
||||||
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
@@ -332,11 +333,13 @@ func wrapLevelWithColor(level string) string {
|
|||||||
|
|
||||||
func writeJson(writer io.Writer, info any) {
|
func writeJson(writer io.Writer, info any) {
|
||||||
if content, err := json.Marshal(info); err != nil {
|
if content, err := json.Marshal(info); err != nil {
|
||||||
log.Println(err.Error())
|
log.Printf("err: %s\n\n%s", err.Error(), debug.Stack())
|
||||||
} else if writer == nil {
|
} else if writer == nil {
|
||||||
log.Println(string(content))
|
log.Println(string(content))
|
||||||
} else {
|
} else {
|
||||||
writer.Write(append(content, '\n'))
|
if _, err := writer.Write(append(content, '\n')); err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +387,7 @@ func writePlainValue(writer io.Writer, level string, val any, fields ...string)
|
|||||||
buf.WriteString(level)
|
buf.WriteString(level)
|
||||||
buf.WriteByte(plainEncodingSep)
|
buf.WriteByte(plainEncodingSep)
|
||||||
if err := json.NewEncoder(&buf).Encode(val); err != nil {
|
if err := json.NewEncoder(&buf).Encode(val); err != nil {
|
||||||
log.Println(err.Error())
|
log.Printf("err: %s\n\n%s", err.Error(), debug.Stack())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,9 +126,23 @@ func TestWriteJson(t *testing.T) {
|
|||||||
log.SetOutput(&buf)
|
log.SetOutput(&buf)
|
||||||
writeJson(nil, "foo")
|
writeJson(nil, "foo")
|
||||||
assert.Contains(t, buf.String(), "foo")
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
writeJson(hardToWriteWriter{}, "foo")
|
||||||
|
assert.Contains(t, buf.String(), "write error")
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
writeJson(nil, make(chan int))
|
writeJson(nil, make(chan int))
|
||||||
assert.Contains(t, buf.String(), "unsupported type")
|
assert.Contains(t, buf.String(), "unsupported type")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
type C struct {
|
||||||
|
RC func()
|
||||||
|
}
|
||||||
|
writeJson(nil, C{
|
||||||
|
RC: func() {},
|
||||||
|
})
|
||||||
|
assert.Contains(t, buf.String(), "runtime/debug.Stack")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWritePlainAny(t *testing.T) {
|
func TestWritePlainAny(t *testing.T) {
|
||||||
@@ -165,6 +179,14 @@ func TestWritePlainAny(t *testing.T) {
|
|||||||
writePlainAny(hardToWriteWriter{}, levelFatal, "foo")
|
writePlainAny(hardToWriteWriter{}, levelFatal, "foo")
|
||||||
assert.Contains(t, buf.String(), "write error")
|
assert.Contains(t, buf.String(), "write error")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
type C struct {
|
||||||
|
RC func()
|
||||||
|
}
|
||||||
|
writePlainAny(nil, levelError, C{
|
||||||
|
RC: func() {},
|
||||||
|
})
|
||||||
|
assert.Contains(t, buf.String(), "runtime/debug.Stack")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogWithLimitContentLength(t *testing.T) {
|
func TestLogWithLimitContentLength(t *testing.T) {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultKeyName = "key"
|
defaultKeyName = "key"
|
||||||
delimiter = '.'
|
delimiter = '.'
|
||||||
ignoreKey = "-"
|
ignoreKey = "-"
|
||||||
|
numberTypeString = "number"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -622,7 +623,7 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fValue > math.MaxFloat32 {
|
if fValue > math.MaxFloat32 {
|
||||||
return float32OverflowError(v.String())
|
return fmt.Errorf("parsing %q as float32: value out of range", v.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
target.SetFloat(fValue)
|
target.SetFloat(fValue)
|
||||||
@@ -634,7 +635,7 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
|
|||||||
|
|
||||||
target.SetFloat(fValue)
|
target.SetFloat(fValue)
|
||||||
default:
|
default:
|
||||||
return newTypeMismatchErrorWithHint(fullName, typeKind.String(), value.Type().String())
|
return newTypeMismatchErrorWithHint(fullName, typeKind.String(), numberTypeString)
|
||||||
}
|
}
|
||||||
|
|
||||||
SetValue(fieldType, value, target)
|
SetValue(fieldType, value, target)
|
||||||
|
|||||||
@@ -5490,7 +5490,7 @@ func TestUnmarshalerProcessFieldPrimitiveWithJSONNumber(t *testing.T) {
|
|||||||
err := m.processFieldPrimitiveWithJSONNumber(fieldType, value.Elem(), v,
|
err := m.processFieldPrimitiveWithJSONNumber(fieldType, value.Elem(), v,
|
||||||
&fieldOptionsWithContext{}, "field")
|
&fieldOptionsWithContext{}, "field")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, `type mismatch for field "field", expect "string", actual "int"`, err.Error())
|
assert.Equal(t, `type mismatch for field "field", expect "string", actual "number"`, err.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("right type", func(t *testing.T) {
|
t.Run("right type", func(t *testing.T) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const (
|
|||||||
leftSquareBracket = '['
|
leftSquareBracket = '['
|
||||||
rightSquareBracket = ']'
|
rightSquareBracket = ']'
|
||||||
segmentSeparator = ','
|
segmentSeparator = ','
|
||||||
|
intSize = 32 << (^uint(0) >> 63) // 32 or 64
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -42,10 +43,6 @@ 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
|
||||||
@@ -104,38 +101,30 @@ func convertTypeFromString(kind reflect.Kind, str string) (any, error) {
|
|||||||
default:
|
default:
|
||||||
return false, errTypeMismatch
|
return false, errTypeMismatch
|
||||||
}
|
}
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int:
|
||||||
intValue, err := strconv.ParseInt(str, 10, 64)
|
return strconv.ParseInt(str, 10, intSize)
|
||||||
if err != nil {
|
case reflect.Int8:
|
||||||
return 0, err
|
return strconv.ParseInt(str, 10, 8)
|
||||||
}
|
case reflect.Int16:
|
||||||
|
return strconv.ParseInt(str, 10, 16)
|
||||||
return intValue, nil
|
case reflect.Int32:
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
return strconv.ParseInt(str, 10, 32)
|
||||||
uintValue, err := strconv.ParseUint(str, 10, 64)
|
case reflect.Int64:
|
||||||
if err != nil {
|
return strconv.ParseInt(str, 10, 64)
|
||||||
return 0, err
|
case reflect.Uint:
|
||||||
}
|
return strconv.ParseUint(str, 10, intSize)
|
||||||
|
case reflect.Uint8:
|
||||||
return uintValue, nil
|
return strconv.ParseUint(str, 10, 8)
|
||||||
|
case reflect.Uint16:
|
||||||
|
return strconv.ParseUint(str, 10, 16)
|
||||||
|
case reflect.Uint32:
|
||||||
|
return strconv.ParseUint(str, 10, 32)
|
||||||
|
case reflect.Uint64:
|
||||||
|
return strconv.ParseUint(str, 10, 64)
|
||||||
case reflect.Float32:
|
case reflect.Float32:
|
||||||
floatValue, err := strconv.ParseFloat(str, 64)
|
return strconv.ParseFloat(str, 32)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if floatValue > math.MaxFloat32 {
|
|
||||||
return 0, float32OverflowError(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
return floatValue, nil
|
|
||||||
case reflect.Float64:
|
case reflect.Float64:
|
||||||
floatValue, err := strconv.ParseFloat(str, 64)
|
return strconv.ParseFloat(str, 64)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return floatValue, nil
|
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return str, nil
|
return str, nil
|
||||||
default:
|
default:
|
||||||
@@ -230,10 +219,6 @@ 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 '[':
|
||||||
@@ -256,10 +241,6 @@ 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()))
|
||||||
@@ -505,41 +486,15 @@ 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))
|
||||||
return nil
|
return nil
|
||||||
case reflect.Int: // int depends on int size, 32 or 64
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
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))
|
||||||
return nil
|
return nil
|
||||||
case reflect.Uint: // uint depends on int size, 32 or 64
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
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
|
return nil
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
@@ -553,16 +508,6 @@ func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v any) err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func setValueFromString(kind reflect.Kind, value reflect.Value, str string) error {
|
func setValueFromString(kind reflect.Kind, value reflect.Value, str string) error {
|
||||||
if !value.CanSet() {
|
if !value.CanSet() {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
|
|||||||
@@ -1011,6 +1011,15 @@ func TestUnmarshalYamlMapRune(t *testing.T) {
|
|||||||
assert.Equal(t, rune(3), v.Machine["node3"])
|
assert.Equal(t, rune(3), v.Machine["node3"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalYamlStringOfInt(t *testing.T) {
|
||||||
|
text := `password: 123456`
|
||||||
|
var v struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
reader := strings.NewReader(text)
|
||||||
|
assert.Error(t, UnmarshalYamlReader(reader, &v))
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalYamlBadInput(t *testing.T) {
|
func TestUnmarshalYamlBadInput(t *testing.T) {
|
||||||
var v struct {
|
var v struct {
|
||||||
Any string
|
Any string
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package metric
|
|||||||
import (
|
import (
|
||||||
prom "github.com/prometheus/client_golang/prometheus"
|
prom "github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/zeromicro/go-zero/core/proc"
|
"github.com/zeromicro/go-zero/core/proc"
|
||||||
"github.com/zeromicro/go-zero/core/prometheus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -47,20 +46,16 @@ func NewCounterVec(cfg *CounterVecOpts) CounterVec {
|
|||||||
return cv
|
return cv
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *promCounterVec) Inc(labels ...string) {
|
func (cv *promCounterVec) Add(v float64, labels ...string) {
|
||||||
if !prometheus.Enabled() {
|
update(func() {
|
||||||
return
|
cv.counter.WithLabelValues(labels...).Add(v)
|
||||||
}
|
})
|
||||||
|
|
||||||
cv.counter.WithLabelValues(labels...).Inc()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *promCounterVec) Add(v float64, labels ...string) {
|
func (cv *promCounterVec) Inc(labels ...string) {
|
||||||
if !prometheus.Enabled() {
|
update(func() {
|
||||||
return
|
cv.counter.WithLabelValues(labels...).Inc()
|
||||||
}
|
})
|
||||||
|
|
||||||
cv.counter.WithLabelValues(labels...).Add(v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *promCounterVec) close() bool {
|
func (cv *promCounterVec) close() bool {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package metric
|
|||||||
import (
|
import (
|
||||||
prom "github.com/prometheus/client_golang/prometheus"
|
prom "github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/zeromicro/go-zero/core/proc"
|
"github.com/zeromicro/go-zero/core/proc"
|
||||||
"github.com/zeromicro/go-zero/core/prometheus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -16,8 +15,12 @@ type (
|
|||||||
Set(v float64, labels ...string)
|
Set(v float64, labels ...string)
|
||||||
// Inc increments labels.
|
// Inc increments labels.
|
||||||
Inc(labels ...string)
|
Inc(labels ...string)
|
||||||
|
// Dec decrements labels.
|
||||||
|
Dec(labels ...string)
|
||||||
// Add adds v to labels.
|
// Add adds v to labels.
|
||||||
Add(v float64, labels ...string)
|
Add(v float64, labels ...string)
|
||||||
|
// Sub subtracts v to labels.
|
||||||
|
Sub(v float64, labels ...string)
|
||||||
close() bool
|
close() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,13 +35,12 @@ func NewGaugeVec(cfg *GaugeVecOpts) GaugeVec {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
vec := prom.NewGaugeVec(
|
vec := prom.NewGaugeVec(prom.GaugeOpts{
|
||||||
prom.GaugeOpts{
|
Namespace: cfg.Namespace,
|
||||||
Namespace: cfg.Namespace,
|
Subsystem: cfg.Subsystem,
|
||||||
Subsystem: cfg.Subsystem,
|
Name: cfg.Name,
|
||||||
Name: cfg.Name,
|
Help: cfg.Help,
|
||||||
Help: cfg.Help,
|
}, cfg.Labels)
|
||||||
}, cfg.Labels)
|
|
||||||
prom.MustRegister(vec)
|
prom.MustRegister(vec)
|
||||||
gv := &promGaugeVec{
|
gv := &promGaugeVec{
|
||||||
gauge: vec,
|
gauge: vec,
|
||||||
@@ -50,28 +52,34 @@ func NewGaugeVec(cfg *GaugeVecOpts) GaugeVec {
|
|||||||
return gv
|
return gv
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gv *promGaugeVec) Inc(labels ...string) {
|
func (gv *promGaugeVec) Add(v float64, labels ...string) {
|
||||||
if !prometheus.Enabled() {
|
update(func() {
|
||||||
return
|
gv.gauge.WithLabelValues(labels...).Add(v)
|
||||||
}
|
})
|
||||||
|
|
||||||
gv.gauge.WithLabelValues(labels...).Inc()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gv *promGaugeVec) Add(v float64, labels ...string) {
|
func (gv *promGaugeVec) Dec(labels ...string) {
|
||||||
if !prometheus.Enabled() {
|
update(func() {
|
||||||
return
|
gv.gauge.WithLabelValues(labels...).Dec()
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
gv.gauge.WithLabelValues(labels...).Add(v)
|
func (gv *promGaugeVec) Inc(labels ...string) {
|
||||||
|
update(func() {
|
||||||
|
gv.gauge.WithLabelValues(labels...).Inc()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gv *promGaugeVec) Set(v float64, labels ...string) {
|
func (gv *promGaugeVec) Set(v float64, labels ...string) {
|
||||||
if !prometheus.Enabled() {
|
update(func() {
|
||||||
return
|
gv.gauge.WithLabelValues(labels...).Set(v)
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
gv.gauge.WithLabelValues(labels...).Set(v)
|
func (gv *promGaugeVec) Sub(v float64, labels ...string) {
|
||||||
|
update(func() {
|
||||||
|
gv.gauge.WithLabelValues(labels...).Sub(v)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gv *promGaugeVec) close() bool {
|
func (gv *promGaugeVec) close() bool {
|
||||||
|
|||||||
@@ -40,6 +40,23 @@ func TestGaugeInc(t *testing.T) {
|
|||||||
assert.Equal(t, float64(2), r)
|
assert.Equal(t, float64(2), r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGaugeDec(t *testing.T) {
|
||||||
|
startAgent()
|
||||||
|
gaugeVec := NewGaugeVec(&GaugeVecOpts{
|
||||||
|
Namespace: "rpc_client",
|
||||||
|
Subsystem: "requests",
|
||||||
|
Name: "duration_ms",
|
||||||
|
Help: "rpc server requests duration(ms).",
|
||||||
|
Labels: []string{"path"},
|
||||||
|
})
|
||||||
|
defer gaugeVec.close()
|
||||||
|
gv, _ := gaugeVec.(*promGaugeVec)
|
||||||
|
gv.Dec("/users")
|
||||||
|
gv.Dec("/users")
|
||||||
|
r := testutil.ToFloat64(gv.gauge)
|
||||||
|
assert.Equal(t, float64(-2), r)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGaugeAdd(t *testing.T) {
|
func TestGaugeAdd(t *testing.T) {
|
||||||
startAgent()
|
startAgent()
|
||||||
gaugeVec := NewGaugeVec(&GaugeVecOpts{
|
gaugeVec := NewGaugeVec(&GaugeVecOpts{
|
||||||
@@ -57,6 +74,23 @@ func TestGaugeAdd(t *testing.T) {
|
|||||||
assert.Equal(t, float64(20), r)
|
assert.Equal(t, float64(20), r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGaugeSub(t *testing.T) {
|
||||||
|
startAgent()
|
||||||
|
gaugeVec := NewGaugeVec(&GaugeVecOpts{
|
||||||
|
Namespace: "rpc_client",
|
||||||
|
Subsystem: "request",
|
||||||
|
Name: "duration_ms",
|
||||||
|
Help: "rpc server requests duration(ms).",
|
||||||
|
Labels: []string{"path"},
|
||||||
|
})
|
||||||
|
defer gaugeVec.close()
|
||||||
|
gv, _ := gaugeVec.(*promGaugeVec)
|
||||||
|
gv.Sub(-100, "/classroom")
|
||||||
|
gv.Sub(30, "/classroom")
|
||||||
|
r := testutil.ToFloat64(gv.gauge)
|
||||||
|
assert.Equal(t, float64(70), r)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGaugeSet(t *testing.T) {
|
func TestGaugeSet(t *testing.T) {
|
||||||
startAgent()
|
startAgent()
|
||||||
gaugeVec := NewGaugeVec(&GaugeVecOpts{
|
gaugeVec := NewGaugeVec(&GaugeVecOpts{
|
||||||
|
|||||||
@@ -3,24 +3,26 @@ package metric
|
|||||||
import (
|
import (
|
||||||
prom "github.com/prometheus/client_golang/prometheus"
|
prom "github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/zeromicro/go-zero/core/proc"
|
"github.com/zeromicro/go-zero/core/proc"
|
||||||
"github.com/zeromicro/go-zero/core/prometheus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// A HistogramVecOpts is a histogram vector options.
|
// A HistogramVecOpts is a histogram vector options.
|
||||||
HistogramVecOpts struct {
|
HistogramVecOpts struct {
|
||||||
Namespace string
|
Namespace string
|
||||||
Subsystem string
|
Subsystem string
|
||||||
Name string
|
Name string
|
||||||
Help string
|
Help string
|
||||||
Labels []string
|
Labels []string
|
||||||
Buckets []float64
|
Buckets []float64
|
||||||
|
ConstLabels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// A HistogramVec interface represents a histogram vector.
|
// A HistogramVec interface represents a histogram vector.
|
||||||
HistogramVec interface {
|
HistogramVec interface {
|
||||||
// Observe adds observation v to labels.
|
// Observe adds observation v to labels.
|
||||||
Observe(v int64, labels ...string)
|
Observe(v int64, labels ...string)
|
||||||
|
// ObserveFloat allow to observe float64 values.
|
||||||
|
ObserveFloat(v float64, labels ...string)
|
||||||
close() bool
|
close() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,11 +38,12 @@ func NewHistogramVec(cfg *HistogramVecOpts) HistogramVec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vec := prom.NewHistogramVec(prom.HistogramOpts{
|
vec := prom.NewHistogramVec(prom.HistogramOpts{
|
||||||
Namespace: cfg.Namespace,
|
Namespace: cfg.Namespace,
|
||||||
Subsystem: cfg.Subsystem,
|
Subsystem: cfg.Subsystem,
|
||||||
Name: cfg.Name,
|
Name: cfg.Name,
|
||||||
Help: cfg.Help,
|
Help: cfg.Help,
|
||||||
Buckets: cfg.Buckets,
|
Buckets: cfg.Buckets,
|
||||||
|
ConstLabels: cfg.ConstLabels,
|
||||||
}, cfg.Labels)
|
}, cfg.Labels)
|
||||||
prom.MustRegister(vec)
|
prom.MustRegister(vec)
|
||||||
hv := &promHistogramVec{
|
hv := &promHistogramVec{
|
||||||
@@ -54,11 +57,15 @@ func NewHistogramVec(cfg *HistogramVecOpts) HistogramVec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hv *promHistogramVec) Observe(v int64, labels ...string) {
|
func (hv *promHistogramVec) Observe(v int64, labels ...string) {
|
||||||
if !prometheus.Enabled() {
|
update(func() {
|
||||||
return
|
hv.histogram.WithLabelValues(labels...).Observe(float64(v))
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
hv.histogram.WithLabelValues(labels...).Observe(float64(v))
|
func (hv *promHistogramVec) ObserveFloat(v float64, labels ...string) {
|
||||||
|
update(func() {
|
||||||
|
hv.histogram.WithLabelValues(labels...).Observe(v)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hv *promHistogramVec) close() bool {
|
func (hv *promHistogramVec) close() bool {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func TestNewHistogramVec(t *testing.T) {
|
|||||||
Help: "rpc server requests duration(ms).",
|
Help: "rpc server requests duration(ms).",
|
||||||
Buckets: []float64{1, 2, 3},
|
Buckets: []float64{1, 2, 3},
|
||||||
})
|
})
|
||||||
defer histogramVec.close()
|
defer histogramVec.(*promHistogramVec).close()
|
||||||
histogramVecNil := NewHistogramVec(nil)
|
histogramVecNil := NewHistogramVec(nil)
|
||||||
assert.NotNil(t, histogramVec)
|
assert.NotNil(t, histogramVec)
|
||||||
assert.Nil(t, histogramVecNil)
|
assert.Nil(t, histogramVecNil)
|
||||||
@@ -28,9 +28,10 @@ func TestHistogramObserve(t *testing.T) {
|
|||||||
Buckets: []float64{1, 2, 3},
|
Buckets: []float64{1, 2, 3},
|
||||||
Labels: []string{"method"},
|
Labels: []string{"method"},
|
||||||
})
|
})
|
||||||
defer histogramVec.close()
|
defer histogramVec.(*promHistogramVec).close()
|
||||||
hv, _ := histogramVec.(*promHistogramVec)
|
hv, _ := histogramVec.(*promHistogramVec)
|
||||||
hv.Observe(2, "/Users")
|
hv.Observe(2, "/Users")
|
||||||
|
hv.ObserveFloat(1.1, "/Users")
|
||||||
|
|
||||||
metadata := `
|
metadata := `
|
||||||
# HELP counts rpc server requests duration(ms).
|
# HELP counts rpc server requests duration(ms).
|
||||||
@@ -38,11 +39,11 @@ func TestHistogramObserve(t *testing.T) {
|
|||||||
`
|
`
|
||||||
val := `
|
val := `
|
||||||
counts_bucket{method="/Users",le="1"} 0
|
counts_bucket{method="/Users",le="1"} 0
|
||||||
counts_bucket{method="/Users",le="2"} 1
|
counts_bucket{method="/Users",le="2"} 2
|
||||||
counts_bucket{method="/Users",le="3"} 1
|
counts_bucket{method="/Users",le="3"} 2
|
||||||
counts_bucket{method="/Users",le="+Inf"} 1
|
counts_bucket{method="/Users",le="+Inf"} 2
|
||||||
counts_sum{method="/Users"} 2
|
counts_sum{method="/Users"} 3.1
|
||||||
counts_count{method="/Users"} 1
|
counts_count{method="/Users"} 2
|
||||||
`
|
`
|
||||||
|
|
||||||
err := testutil.CollectAndCompare(hv.histogram, strings.NewReader(metadata+val))
|
err := testutil.CollectAndCompare(hv.histogram, strings.NewReader(metadata+val))
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package metric
|
package metric
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/core/prometheus"
|
||||||
|
|
||||||
// A VectorOpts is a general configuration.
|
// A VectorOpts is a general configuration.
|
||||||
type VectorOpts struct {
|
type VectorOpts struct {
|
||||||
Namespace string
|
Namespace string
|
||||||
@@ -8,3 +10,11 @@ type VectorOpts struct {
|
|||||||
Help string
|
Help string
|
||||||
Labels []string
|
Labels []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func update(fn func()) {
|
||||||
|
if !prometheus.Enabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package metric
|
|||||||
import (
|
import (
|
||||||
prom "github.com/prometheus/client_golang/prometheus"
|
prom "github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/zeromicro/go-zero/core/proc"
|
"github.com/zeromicro/go-zero/core/proc"
|
||||||
"github.com/zeromicro/go-zero/core/prometheus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -53,11 +52,9 @@ func NewSummaryVec(cfg *SummaryVecOpts) SummaryVec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sv *promSummaryVec) Observe(v float64, labels ...string) {
|
func (sv *promSummaryVec) Observe(v float64, labels ...string) {
|
||||||
if !prometheus.Enabled() {
|
update(func() {
|
||||||
return
|
sv.summary.WithLabelValues(labels...).Observe(v)
|
||||||
}
|
})
|
||||||
|
|
||||||
sv.summary.WithLabelValues(labels...).Observe(v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *promSummaryVec) close() bool {
|
func (sv *promSummaryVec) close() bool {
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ func WrapUp() {
|
|||||||
wrapUpListeners.notifyListeners()
|
wrapUpListeners.notifyListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
func gracefulStop(signals chan os.Signal) {
|
func gracefulStop(signals chan os.Signal, sig syscall.Signal) {
|
||||||
signal.Stop(signals)
|
signal.Stop(signals)
|
||||||
|
|
||||||
logx.Info("Got signal SIGTERM, shutting down...")
|
logx.Infof("Got signal %d, shutting down...", sig)
|
||||||
go wrapUpListeners.notifyListeners()
|
go wrapUpListeners.notifyListeners()
|
||||||
|
|
||||||
time.Sleep(wrapUpTime)
|
time.Sleep(wrapUpTime)
|
||||||
@@ -63,7 +63,7 @@ func gracefulStop(signals chan os.Signal) {
|
|||||||
|
|
||||||
time.Sleep(delayTimeBeforeForceQuit - wrapUpTime)
|
time.Sleep(delayTimeBeforeForceQuit - wrapUpTime)
|
||||||
logx.Infof("Still alive after %v, going to force kill the process...", delayTimeBeforeForceQuit)
|
logx.Infof("Still alive after %v, going to force kill the process...", delayTimeBeforeForceQuit)
|
||||||
syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
|
_ = syscall.Kill(syscall.Getpid(), sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
type listenerManager struct {
|
type listenerManager struct {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func init() {
|
|||||||
|
|
||||||
// https://golang.org/pkg/os/signal/#Notify
|
// https://golang.org/pkg/os/signal/#Notify
|
||||||
signals := make(chan os.Signal, 1)
|
signals := make(chan os.Signal, 1)
|
||||||
signal.Notify(signals, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTERM)
|
signal.Notify(signals, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTERM, syscall.SIGINT)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
v := <-signals
|
v := <-signals
|
||||||
@@ -35,14 +35,11 @@ func init() {
|
|||||||
profiler = nil
|
profiler = nil
|
||||||
}
|
}
|
||||||
case syscall.SIGTERM:
|
case syscall.SIGTERM:
|
||||||
select {
|
stopOnSignal()
|
||||||
case <-done:
|
gracefulStop(signals, syscall.SIGTERM)
|
||||||
// already closed
|
case syscall.SIGINT:
|
||||||
default:
|
stopOnSignal()
|
||||||
close(done)
|
gracefulStop(signals, syscall.SIGINT)
|
||||||
}
|
|
||||||
|
|
||||||
gracefulStop(signals)
|
|
||||||
default:
|
default:
|
||||||
logx.Error("Got unregistered signal:", v)
|
logx.Error("Got unregistered signal:", v)
|
||||||
}
|
}
|
||||||
@@ -54,3 +51,12 @@ func init() {
|
|||||||
func Done() <-chan struct{} {
|
func Done() <-chan struct{} {
|
||||||
return done
|
return done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stopOnSignal() {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// already closed
|
||||||
|
default:
|
||||||
|
close(done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ func (t *Tree) Add(route string, item any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := add(t.root, route[1:], item)
|
err := add(t.root, route[1:], item)
|
||||||
switch err {
|
switch {
|
||||||
case errDupItem:
|
case errors.Is(err, errDupItem):
|
||||||
return duplicatedItem(route)
|
return duplicatedItem(route)
|
||||||
case errDupSlash:
|
case errors.Is(err, errDupSlash):
|
||||||
return duplicatedSlash(route)
|
return duplicatedSlash(route)
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,17 +23,22 @@ const (
|
|||||||
ProMode = "pro"
|
ProMode = "pro"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A ServiceConf is a service config.
|
type (
|
||||||
type ServiceConf struct {
|
// DevServerConfig is type alias for devserver.Config
|
||||||
Name string
|
DevServerConfig = devserver.Config
|
||||||
Log logx.LogConf
|
|
||||||
Mode string `json:",default=pro,options=dev|test|rt|pre|pro"`
|
// A ServiceConf is a service config.
|
||||||
MetricsUrl string `json:",optional"`
|
ServiceConf struct {
|
||||||
// Deprecated: please use DevServer
|
Name string
|
||||||
Prometheus prometheus.Config `json:",optional"`
|
Log logx.LogConf
|
||||||
Telemetry trace.Config `json:",optional"`
|
Mode string `json:",default=pro,options=dev|test|rt|pre|pro"`
|
||||||
DevServer devserver.Config `json:",optional"`
|
MetricsUrl string `json:",optional"`
|
||||||
}
|
// Deprecated: please use DevServer
|
||||||
|
Prometheus prometheus.Config `json:",optional"`
|
||||||
|
Telemetry trace.Config `json:",optional"`
|
||||||
|
DevServer DevServerConfig `json:",optional"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// MustSetUp sets up the service, exits on error.
|
// MustSetUp sets up the service, exits on error.
|
||||||
func (sc ServiceConf) MustSetUp() {
|
func (sc ServiceConf) MustSetUp() {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/internal/devserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServiceConf(t *testing.T) {
|
func TestServiceConf(t *testing.T) {
|
||||||
@@ -14,6 +15,10 @@ func TestServiceConf(t *testing.T) {
|
|||||||
Mode: "console",
|
Mode: "console",
|
||||||
},
|
},
|
||||||
Mode: "dev",
|
Mode: "dev",
|
||||||
|
DevServer: devserver.Config{
|
||||||
|
Port: 6470,
|
||||||
|
HealthPath: "/healthz",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
c.MustSetUp()
|
c.MustSetUp()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
const dbTag = "db"
|
const dbTag = "db"
|
||||||
|
|
||||||
// RawFieldNames converts golang struct field into slice string.
|
// RawFieldNames converts golang struct field into slice string.
|
||||||
func RawFieldNames(in any, postgresSql ...bool) []string {
|
func RawFieldNames(in any, postgreSql ...bool) []string {
|
||||||
out := make([]string, 0)
|
out := make([]string, 0)
|
||||||
v := reflect.ValueOf(in)
|
v := reflect.ValueOf(in)
|
||||||
if v.Kind() == reflect.Ptr {
|
if v.Kind() == reflect.Ptr {
|
||||||
@@ -17,8 +17,8 @@ func RawFieldNames(in any, postgresSql ...bool) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pg bool
|
var pg bool
|
||||||
if len(postgresSql) > 0 {
|
if len(postgreSql) > 0 {
|
||||||
pg = postgresSql[0]
|
pg = postgreSql[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only accept structs
|
// we only accept structs
|
||||||
|
|||||||
8
core/stores/cache/cachenode.go
vendored
8
core/stores/cache/cachenode.go
vendored
@@ -96,7 +96,7 @@ func (c cacheNode) Get(key string, val any) error {
|
|||||||
// GetCtx gets the cache with key and fills into v.
|
// GetCtx gets the cache with key and fills into v.
|
||||||
func (c cacheNode) GetCtx(ctx context.Context, key string, val any) error {
|
func (c cacheNode) GetCtx(ctx context.Context, key string, val any) error {
|
||||||
err := c.doGetCache(ctx, key, val)
|
err := c.doGetCache(ctx, key, val)
|
||||||
if err == errPlaceholder {
|
if errors.Is(err, errPlaceholder) {
|
||||||
return c.errNotFound
|
return c.errNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,16 +210,16 @@ func (c cacheNode) doTake(ctx context.Context, v any, key string,
|
|||||||
logger := logx.WithContext(ctx)
|
logger := logx.WithContext(ctx)
|
||||||
val, fresh, err := c.barrier.DoEx(key, func() (any, error) {
|
val, fresh, err := c.barrier.DoEx(key, func() (any, error) {
|
||||||
if err := c.doGetCache(ctx, key, v); err != nil {
|
if err := c.doGetCache(ctx, key, v); err != nil {
|
||||||
if err == errPlaceholder {
|
if errors.Is(err, errPlaceholder) {
|
||||||
return nil, c.errNotFound
|
return nil, c.errNotFound
|
||||||
} else if err != c.errNotFound {
|
} else if !errors.Is(err, c.errNotFound) {
|
||||||
// why we just return the error instead of query from db,
|
// why we just return the error instead of query from db,
|
||||||
// because we don't allow the disaster pass to the dbs.
|
// because we don't allow the disaster pass to the dbs.
|
||||||
// fail fast, in case we bring down the dbs.
|
// fail fast, in case we bring down the dbs.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = query(v); err == c.errNotFound {
|
if err = query(v); errors.Is(err, c.errNotFound) {
|
||||||
if err = c.setCacheWithNotFound(ctx, key); err != nil {
|
if err = c.setCacheWithNotFound(ctx, key); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package mon
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/breaker"
|
"github.com/zeromicro/go-zero/core/breaker"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
"github.com/zeromicro/go-zero/core/timex"
|
"github.com/zeromicro/go-zero/core/timex"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
||||||
@@ -502,45 +501,11 @@ func (c *decoratedCollection) UpdateOne(ctx context.Context, filter, update any,
|
|||||||
|
|
||||||
func (c *decoratedCollection) logDuration(ctx context.Context, method string,
|
func (c *decoratedCollection) logDuration(ctx context.Context, method string,
|
||||||
startTime time.Duration, err error, docs ...any) {
|
startTime time.Duration, err error, docs ...any) {
|
||||||
duration := timex.Since(startTime)
|
logDurationWithDocs(ctx, c.name, method, startTime, err, docs...)
|
||||||
logger := logx.WithContext(ctx).WithDuration(duration)
|
|
||||||
|
|
||||||
content, jerr := json.Marshal(docs)
|
|
||||||
// jerr should not be non-nil, but we don't care much on this,
|
|
||||||
// if non-nil, we just log without docs.
|
|
||||||
if jerr != nil {
|
|
||||||
if err != nil {
|
|
||||||
if duration > slowThreshold.Load() {
|
|
||||||
logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s)", c.name, method, err.Error())
|
|
||||||
} else {
|
|
||||||
logger.Infof("mongo(%s) - %s - fail(%s)", c.name, method, err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if duration > slowThreshold.Load() {
|
|
||||||
logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - ok", c.name, method)
|
|
||||||
} else {
|
|
||||||
logger.Infof("mongo(%s) - %s - ok", c.name, method)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
if duration > slowThreshold.Load() {
|
|
||||||
logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s",
|
|
||||||
c.name, method, err.Error(), string(content))
|
|
||||||
} else {
|
|
||||||
logger.Infof("mongo(%s) - %s - fail(%s) - %s",
|
|
||||||
c.name, method, err.Error(), string(content))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if duration > slowThreshold.Load() {
|
|
||||||
logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
|
|
||||||
c.name, method, string(content))
|
|
||||||
} else {
|
|
||||||
logger.Infof("mongo(%s) - %s - ok - %s", c.name, method, string(content))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *decoratedCollection) logDurationSimple(ctx context.Context, method string, startTime time.Duration, err error) {
|
func (c *decoratedCollection) logDurationSimple(ctx context.Context, method string,
|
||||||
|
startTime time.Duration, err error) {
|
||||||
logDuration(ctx, c.name, method, startTime, err)
|
logDuration(ctx, c.name, method, startTime, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,11 +527,19 @@ func (p keepablePromise) keep(err error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func acceptable(err error) bool {
|
func acceptable(err error) bool {
|
||||||
return err == nil || err == mongo.ErrNoDocuments || err == mongo.ErrNilValue ||
|
return err == nil ||
|
||||||
err == mongo.ErrNilDocument || err == mongo.ErrNilCursor || err == mongo.ErrEmptySlice ||
|
errors.Is(err, mongo.ErrNoDocuments) ||
|
||||||
|
errors.Is(err, mongo.ErrNilValue) ||
|
||||||
|
errors.Is(err, mongo.ErrNilDocument) ||
|
||||||
|
errors.Is(err, mongo.ErrNilCursor) ||
|
||||||
|
errors.Is(err, mongo.ErrEmptySlice) ||
|
||||||
// session errors
|
// session errors
|
||||||
err == session.ErrSessionEnded || err == session.ErrNoTransactStarted ||
|
errors.Is(err, session.ErrSessionEnded) ||
|
||||||
err == session.ErrTransactInProgress || err == session.ErrAbortAfterCommit ||
|
errors.Is(err, session.ErrNoTransactStarted) ||
|
||||||
err == session.ErrAbortTwice || err == session.ErrCommitAfterAbort ||
|
errors.Is(err, session.ErrTransactInProgress) ||
|
||||||
err == session.ErrUnackWCUnsupported || err == session.ErrSnapshotTransaction
|
errors.Is(err, session.ErrAbortAfterCommit) ||
|
||||||
|
errors.Is(err, session.ErrAbortTwice) ||
|
||||||
|
errors.Is(err, session.ErrCommitAfterAbort) ||
|
||||||
|
errors.Is(err, session.ErrUnackWCUnsupported) ||
|
||||||
|
errors.Is(err, session.ErrSnapshotTransaction)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -599,13 +599,11 @@ func TestDecoratedCollection_LogDuration(t *testing.T) {
|
|||||||
errors.New("bar"), make(chan int))
|
errors.New("bar"), make(chan int))
|
||||||
assert.Contains(t, buf.String(), "foo")
|
assert.Contains(t, buf.String(), "foo")
|
||||||
assert.Contains(t, buf.String(), "bar")
|
assert.Contains(t, buf.String(), "bar")
|
||||||
assert.Contains(t, buf.String(), "slowcall")
|
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
c.logDuration(context.Background(), "foo", timex.Now()-slowThreshold.Load()*2,
|
c.logDuration(context.Background(), "foo", timex.Now()-slowThreshold.Load()*2,
|
||||||
errors.New("bar"))
|
errors.New("bar"))
|
||||||
assert.Contains(t, buf.String(), "foo")
|
assert.Contains(t, buf.String(), "foo")
|
||||||
assert.Contains(t, buf.String(), "slowcall")
|
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
c.logDuration(context.Background(), "foo", timex.Now()-slowThreshold.Load()*2, nil)
|
c.logDuration(context.Background(), "foo", timex.Now()-slowThreshold.Load()*2, nil)
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import (
|
|||||||
|
|
||||||
const defaultTimeout = time.Second * 3
|
const defaultTimeout = time.Second * 3
|
||||||
|
|
||||||
var slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
|
var (
|
||||||
|
slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
|
||||||
|
logMon = syncx.ForAtomicBool(true)
|
||||||
|
logSlowMon = syncx.ForAtomicBool(true)
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
options = mopt.ClientOptions
|
options = mopt.ClientOptions
|
||||||
@@ -18,6 +22,17 @@ type (
|
|||||||
Option func(opts *options)
|
Option func(opts *options)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DisableLog disables logging of mongo commands, includes info and slow logs.
|
||||||
|
func DisableLog() {
|
||||||
|
logMon.Set(false)
|
||||||
|
logSlowMon.Set(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableInfoLog disables info logging of mongo commands, but keeps slow logs.
|
||||||
|
func DisableInfoLog() {
|
||||||
|
logMon.Set(false)
|
||||||
|
}
|
||||||
|
|
||||||
// SetSlowThreshold sets the slow threshold.
|
// SetSlowThreshold sets the slow threshold.
|
||||||
func SetSlowThreshold(threshold time.Duration) {
|
func SetSlowThreshold(threshold time.Duration) {
|
||||||
slowThreshold.Set(threshold)
|
slowThreshold.Set(threshold)
|
||||||
|
|||||||
@@ -25,3 +25,29 @@ func TestWithTimeout(t *testing.T) {
|
|||||||
WithTimeout(time.Second)(opts)
|
WithTimeout(time.Second)(opts)
|
||||||
assert.Equal(t, time.Second, *opts.Timeout)
|
assert.Equal(t, time.Second, *opts.Timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisableLog(t *testing.T) {
|
||||||
|
assert.True(t, logMon.True())
|
||||||
|
assert.True(t, logSlowMon.True())
|
||||||
|
defer func() {
|
||||||
|
logMon.Set(true)
|
||||||
|
logSlowMon.Set(true)
|
||||||
|
}()
|
||||||
|
|
||||||
|
DisableLog()
|
||||||
|
assert.False(t, logMon.True())
|
||||||
|
assert.False(t, logSlowMon.True())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisableInfoLog(t *testing.T) {
|
||||||
|
assert.True(t, logMon.True())
|
||||||
|
assert.True(t, logSlowMon.True())
|
||||||
|
defer func() {
|
||||||
|
logMon.Set(true)
|
||||||
|
logSlowMon.Set(true)
|
||||||
|
}()
|
||||||
|
|
||||||
|
DisableInfoLog()
|
||||||
|
assert.False(t, logMon.True())
|
||||||
|
assert.True(t, logSlowMon.True())
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package mon
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/trace"
|
"github.com/zeromicro/go-zero/core/trace"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
@@ -23,8 +24,8 @@ func startSpan(ctx context.Context, cmd string) (context.Context, oteltrace.Span
|
|||||||
func endSpan(span oteltrace.Span, err error) {
|
func endSpan(span oteltrace.Span, err error) {
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
if err == nil || err == mongo.ErrNoDocuments ||
|
if err == nil || errors.Is(err, mongo.ErrNoDocuments) ||
|
||||||
err == mongo.ErrNilValue || err == mongo.ErrNilDocument {
|
errors.Is(err, mongo.ErrNilValue) || errors.Is(err, mongo.ErrNilDocument) {
|
||||||
span.SetStatus(codes.Ok, "")
|
span.SetStatus(codes.Ok, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package mon
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -20,8 +21,41 @@ func logDuration(ctx context.Context, name, method string, startTime time.Durati
|
|||||||
duration := timex.Since(startTime)
|
duration := timex.Since(startTime)
|
||||||
logger := logx.WithContext(ctx).WithDuration(duration)
|
logger := logx.WithContext(ctx).WithDuration(duration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("mongo(%s) - %s - fail(%s)", name, method, err.Error())
|
logger.Errorf("mongo(%s) - %s - fail(%s)", name, method, err.Error())
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if logSlowMon.True() && duration > slowThreshold.Load() {
|
||||||
|
logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - ok", name, method)
|
||||||
|
} else if logMon.True() {
|
||||||
logger.Infof("mongo(%s) - %s - ok", name, method)
|
logger.Infof("mongo(%s) - %s - ok", name, method)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logDurationWithDocs(ctx context.Context, name, method string, startTime time.Duration,
|
||||||
|
err error, docs ...any) {
|
||||||
|
duration := timex.Since(startTime)
|
||||||
|
logger := logx.WithContext(ctx).WithDuration(duration)
|
||||||
|
|
||||||
|
content, jerr := json.Marshal(docs)
|
||||||
|
// jerr should not be non-nil, but we don't care much on this,
|
||||||
|
// if non-nil, we just log without docs.
|
||||||
|
if jerr != nil {
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("mongo(%s) - %s - fail(%s)", name, method, err.Error())
|
||||||
|
} else if logSlowMon.True() && duration > slowThreshold.Load() {
|
||||||
|
logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - ok", name, method)
|
||||||
|
} else if logMon.True() {
|
||||||
|
logger.Infof("mongo(%s) - %s - ok", name, method)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("mongo(%s) - %s - fail(%s) - %s", name, method, err.Error(), string(content))
|
||||||
|
} else if logSlowMon.True() && duration > slowThreshold.Load() {
|
||||||
|
logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s", name, method, string(content))
|
||||||
|
} else if logMon.True() {
|
||||||
|
logger.Infof("mongo(%s) - %s - ok - %s", name, method, string(content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/logx/logtest"
|
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||||
|
"github.com/zeromicro/go-zero/core/timex"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFormatAddrs(t *testing.T) {
|
func TestFormatAddrs(t *testing.T) {
|
||||||
@@ -42,13 +42,148 @@ func Test_logDuration(t *testing.T) {
|
|||||||
buf := logtest.NewCollector(t)
|
buf := logtest.NewCollector(t)
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
logDuration(context.Background(), "foo", "bar", time.Millisecond, nil)
|
logDuration(context.Background(), "foo", "bar", timex.Now()-slowThreshold.Load()*2, nil)
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "slow")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDuration(context.Background(), "foo", "bar", timex.Now(), nil)
|
||||||
assert.Contains(t, buf.String(), "foo")
|
assert.Contains(t, buf.String(), "foo")
|
||||||
assert.Contains(t, buf.String(), "bar")
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
logDuration(context.Background(), "foo", "bar", time.Millisecond, errors.New("bar"))
|
logDuration(context.Background(), "foo", "bar", timex.Now(), errors.New("bar"))
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "fail")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
logMon.Set(true)
|
||||||
|
logSlowMon.Set(true)
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
DisableInfoLog()
|
||||||
|
logDuration(context.Background(), "foo", "bar", timex.Now(), nil)
|
||||||
|
assert.Empty(t, buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDuration(context.Background(), "foo", "bar", timex.Now()-slowThreshold.Load()*2, nil)
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "slow")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
DisableLog()
|
||||||
|
logDuration(context.Background(), "foo", "bar", timex.Now(), nil)
|
||||||
|
assert.Empty(t, buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDuration(context.Background(), "foo", "bar", timex.Now()-slowThreshold.Load()*2, nil)
|
||||||
|
assert.Empty(t, buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDuration(context.Background(), "foo", "bar", timex.Now(), errors.New("bar"))
|
||||||
assert.Contains(t, buf.String(), "foo")
|
assert.Contains(t, buf.String(), "foo")
|
||||||
assert.Contains(t, buf.String(), "bar")
|
assert.Contains(t, buf.String(), "bar")
|
||||||
assert.Contains(t, buf.String(), "fail")
|
assert.Contains(t, buf.String(), "fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_logDurationWithDoc(t *testing.T) {
|
||||||
|
buf := logtest.NewCollector(t)
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now()-slowThreshold.Load()*2, nil, make(chan int))
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "slow")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now()-slowThreshold.Load()*2, nil, "{'json': ''}")
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "slow")
|
||||||
|
assert.Contains(t, buf.String(), "json")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now(), nil, make(chan int))
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now(), nil, "{'json': ''}")
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "json")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now(), errors.New("bar"), make(chan int))
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "fail")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now(), errors.New("bar"), "{'json': ''}")
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "fail")
|
||||||
|
assert.Contains(t, buf.String(), "json")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
logMon.Set(true)
|
||||||
|
logSlowMon.Set(true)
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
DisableInfoLog()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now(), nil, make(chan int))
|
||||||
|
assert.Empty(t, buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now(), nil, "{'json': ''}")
|
||||||
|
assert.Empty(t, buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now()-slowThreshold.Load()*2, nil, make(chan int))
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "slow")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now()-slowThreshold.Load()*2, nil, "{'json': ''}")
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "slow")
|
||||||
|
assert.Contains(t, buf.String(), "json")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
DisableLog()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now(), nil, make(chan int))
|
||||||
|
assert.Empty(t, buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now(), nil, "{'json': ''}")
|
||||||
|
assert.Empty(t, buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now()-slowThreshold.Load()*2, nil, make(chan int))
|
||||||
|
assert.Empty(t, buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now()-slowThreshold.Load()*2, nil, "{'json': ''}")
|
||||||
|
assert.Empty(t, buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now(), errors.New("bar"), make(chan int))
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "fail")
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
logDurationWithDocs(context.Background(), "foo", "bar", timex.Now(), errors.New("bar"), "{'json': ''}")
|
||||||
|
assert.Contains(t, buf.String(), "foo")
|
||||||
|
assert.Contains(t, buf.String(), "bar")
|
||||||
|
assert.Contains(t, buf.String(), "fail")
|
||||||
|
assert.Contains(t, buf.String(), "json")
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,9 +54,10 @@ func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
|
|||||||
duration := timex.Since(start)
|
duration := timex.Since(start)
|
||||||
if duration > slowThreshold.Load() {
|
if duration > slowThreshold.Load() {
|
||||||
logDuration(ctx, []red.Cmder{cmd}, duration)
|
logDuration(ctx, []red.Cmder{cmd}, duration)
|
||||||
|
metricSlowCount.Inc(cmd.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
metricReqDur.Observe(duration.Milliseconds(), cmd.Name())
|
metricReqDur.ObserveFloat(float64(duration)/float64(time.Millisecond), cmd.Name())
|
||||||
if msg := formatError(err); len(msg) > 0 {
|
if msg := formatError(err); len(msg) > 0 {
|
||||||
metricReqErr.Inc(cmd.Name(), msg)
|
metricReqErr.Inc(cmd.Name(), msg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,19 +52,15 @@ func TestHookProcessCase2(t *testing.T) {
|
|||||||
defer ztrace.StopAgent()
|
defer ztrace.StopAgent()
|
||||||
|
|
||||||
w := logtest.NewCollector(t)
|
w := logtest.NewCollector(t)
|
||||||
|
|
||||||
ctx, err := durationHook.BeforeProcess(context.Background(), red.NewCmd(context.Background()))
|
ctx, err := durationHook.BeforeProcess(context.Background(), red.NewCmd(context.Background()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
|
|
||||||
|
|
||||||
time.Sleep(slowThreshold.Load() + time.Millisecond)
|
time.Sleep(slowThreshold.Load() + time.Millisecond)
|
||||||
|
|
||||||
assert.Nil(t, durationHook.AfterProcess(ctx, red.NewCmd(context.Background(), "foo", "bar")))
|
assert.Nil(t, durationHook.AfterProcess(ctx, red.NewCmd(context.Background(), "foo", "bar")))
|
||||||
assert.True(t, strings.Contains(w.String(), "slow"))
|
assert.True(t, strings.Contains(w.String(), "slow"))
|
||||||
assert.True(t, strings.Contains(w.String(), "trace"))
|
|
||||||
assert.True(t, strings.Contains(w.String(), "span"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHookProcessCase3(t *testing.T) {
|
func TestHookProcessCase3(t *testing.T) {
|
||||||
@@ -89,6 +85,14 @@ func TestHookProcessCase4(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHookProcessPipelineCase1(t *testing.T) {
|
func TestHookProcessPipelineCase1(t *testing.T) {
|
||||||
|
ztrace.StartAgent(ztrace.Config{
|
||||||
|
Name: "go-zero-test",
|
||||||
|
Endpoint: "http://localhost:14268/api/traces",
|
||||||
|
Batcher: "jaeger",
|
||||||
|
Sampler: 1.0,
|
||||||
|
})
|
||||||
|
defer ztrace.StopAgent()
|
||||||
|
|
||||||
writer := log.Writer()
|
writer := log.Writer()
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
log.SetOutput(&buf)
|
log.SetOutput(&buf)
|
||||||
@@ -100,7 +104,6 @@ func TestHookProcessPipelineCase1(t *testing.T) {
|
|||||||
red.NewCmd(context.Background()),
|
red.NewCmd(context.Background()),
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
|
|
||||||
|
|
||||||
assert.NoError(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{}))
|
assert.NoError(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{}))
|
||||||
assert.NoError(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
|
assert.NoError(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
|
||||||
@@ -119,12 +122,10 @@ func TestHookProcessPipelineCase2(t *testing.T) {
|
|||||||
defer ztrace.StopAgent()
|
defer ztrace.StopAgent()
|
||||||
|
|
||||||
w := logtest.NewCollector(t)
|
w := logtest.NewCollector(t)
|
||||||
|
|
||||||
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{
|
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{
|
||||||
red.NewCmd(context.Background()),
|
red.NewCmd(context.Background()),
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
|
|
||||||
|
|
||||||
time.Sleep(slowThreshold.Load() + time.Millisecond)
|
time.Sleep(slowThreshold.Load() + time.Millisecond)
|
||||||
|
|
||||||
@@ -132,8 +133,6 @@ func TestHookProcessPipelineCase2(t *testing.T) {
|
|||||||
red.NewCmd(context.Background(), "foo", "bar"),
|
red.NewCmd(context.Background(), "foo", "bar"),
|
||||||
}))
|
}))
|
||||||
assert.True(t, strings.Contains(w.String(), "slow"))
|
assert.True(t, strings.Contains(w.String(), "slow"))
|
||||||
assert.True(t, strings.Contains(w.String(), "trace"))
|
|
||||||
assert.True(t, strings.Contains(w.String(), "span"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHookProcessPipelineCase3(t *testing.T) {
|
func TestHookProcessPipelineCase3(t *testing.T) {
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package redis
|
package redis
|
||||||
|
|
||||||
import "github.com/zeromicro/go-zero/core/metric"
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
red "github.com/go-redis/redis/v8"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/zeromicro/go-zero/core/metric"
|
||||||
|
)
|
||||||
|
|
||||||
const namespace = "redis_client"
|
const namespace = "redis_client"
|
||||||
|
|
||||||
@@ -11,7 +17,7 @@ var (
|
|||||||
Name: "duration_ms",
|
Name: "duration_ms",
|
||||||
Help: "redis client requests duration(ms).",
|
Help: "redis client requests duration(ms).",
|
||||||
Labels: []string{"command"},
|
Labels: []string{"command"},
|
||||||
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000, 2500},
|
Buckets: []float64{0.25, 0.5, 1, 1.5, 2, 3, 5, 10, 25, 50, 100, 250, 500, 1000, 2000, 5000, 10000, 15000},
|
||||||
})
|
})
|
||||||
metricReqErr = metric.NewCounterVec(&metric.CounterVecOpts{
|
metricReqErr = metric.NewCounterVec(&metric.CounterVecOpts{
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
@@ -20,4 +26,162 @@ var (
|
|||||||
Help: "redis client requests error count.",
|
Help: "redis client requests error count.",
|
||||||
Labels: []string{"command", "error"},
|
Labels: []string{"command", "error"},
|
||||||
})
|
})
|
||||||
|
metricSlowCount = metric.NewCounterVec(&metric.CounterVecOpts{
|
||||||
|
Namespace: namespace,
|
||||||
|
Subsystem: "requests",
|
||||||
|
Name: "slow_total",
|
||||||
|
Help: "redis client requests slow count.",
|
||||||
|
Labels: []string{"command"},
|
||||||
|
})
|
||||||
|
|
||||||
|
connLabels = []string{"key", "client_type"}
|
||||||
|
connCollector = newCollector()
|
||||||
|
_ prometheus.Collector = (*collector)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
statGetter struct {
|
||||||
|
clientType string
|
||||||
|
key string
|
||||||
|
poolSize int
|
||||||
|
poolStats func() *red.PoolStats
|
||||||
|
}
|
||||||
|
|
||||||
|
// collector collects statistics from a redis client.
|
||||||
|
// It implements the prometheus.Collector interface.
|
||||||
|
collector struct {
|
||||||
|
hitDesc *prometheus.Desc
|
||||||
|
missDesc *prometheus.Desc
|
||||||
|
timeoutDesc *prometheus.Desc
|
||||||
|
totalDesc *prometheus.Desc
|
||||||
|
idleDesc *prometheus.Desc
|
||||||
|
staleDesc *prometheus.Desc
|
||||||
|
maxDesc *prometheus.Desc
|
||||||
|
|
||||||
|
clients []*statGetter
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCollector() *collector {
|
||||||
|
c := &collector{
|
||||||
|
hitDesc: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "pool_hit_total"),
|
||||||
|
"Number of times a connection was found in the pool",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
missDesc: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "pool_miss_total"),
|
||||||
|
"Number of times a connection was not found in the pool",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
timeoutDesc: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "pool_timeout_total"),
|
||||||
|
"Number of times a timeout occurred when looking for a connection in the pool",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
totalDesc: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "pool_conn_total_current"),
|
||||||
|
"Current number of connections in the pool",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
idleDesc: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "pool_conn_idle_current"),
|
||||||
|
"Current number of idle connections in the pool",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
staleDesc: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "pool_conn_stale_total"),
|
||||||
|
"Number of times a connection was removed from the pool because it was stale",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
maxDesc: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "pool_conn_max"),
|
||||||
|
"Max number of connections in the pool",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
prometheus.MustRegister(c)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe implements the prometheus.Collector interface.
|
||||||
|
func (s *collector) Describe(descs chan<- *prometheus.Desc) {
|
||||||
|
descs <- s.hitDesc
|
||||||
|
descs <- s.missDesc
|
||||||
|
descs <- s.timeoutDesc
|
||||||
|
descs <- s.totalDesc
|
||||||
|
descs <- s.idleDesc
|
||||||
|
descs <- s.staleDesc
|
||||||
|
descs <- s.maxDesc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect implements the prometheus.Collector interface.
|
||||||
|
func (s *collector) Collect(metrics chan<- prometheus.Metric) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
for _, client := range s.clients {
|
||||||
|
key, clientType := client.key, client.clientType
|
||||||
|
stats := client.poolStats()
|
||||||
|
|
||||||
|
metrics <- prometheus.MustNewConstMetric(
|
||||||
|
s.hitDesc,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
float64(stats.Hits),
|
||||||
|
key,
|
||||||
|
clientType,
|
||||||
|
)
|
||||||
|
metrics <- prometheus.MustNewConstMetric(
|
||||||
|
s.missDesc,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
float64(stats.Misses),
|
||||||
|
key,
|
||||||
|
clientType,
|
||||||
|
)
|
||||||
|
metrics <- prometheus.MustNewConstMetric(
|
||||||
|
s.timeoutDesc,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
float64(stats.Timeouts),
|
||||||
|
key,
|
||||||
|
clientType,
|
||||||
|
)
|
||||||
|
metrics <- prometheus.MustNewConstMetric(
|
||||||
|
s.totalDesc,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(stats.TotalConns),
|
||||||
|
key,
|
||||||
|
clientType,
|
||||||
|
)
|
||||||
|
metrics <- prometheus.MustNewConstMetric(
|
||||||
|
s.idleDesc,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(stats.IdleConns),
|
||||||
|
key,
|
||||||
|
clientType,
|
||||||
|
)
|
||||||
|
metrics <- prometheus.MustNewConstMetric(
|
||||||
|
s.staleDesc,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
float64(stats.StaleConns),
|
||||||
|
key,
|
||||||
|
clientType,
|
||||||
|
)
|
||||||
|
metrics <- prometheus.MustNewConstMetric(
|
||||||
|
s.maxDesc,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
float64(client.poolSize),
|
||||||
|
key,
|
||||||
|
clientType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *collector) registerClient(client *statGetter) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.clients = append(s.clients, client)
|
||||||
|
}
|
||||||
|
|||||||
130
core/stores/redis/metrics_test.go
Normal file
130
core/stores/redis/metrics_test.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
red "github.com/go-redis/redis/v8"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
|
"github.com/zeromicro/go-zero/internal/devserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRedisMetric(t *testing.T) {
|
||||||
|
cfg := devserver.Config{}
|
||||||
|
_ = conf.FillDefault(&cfg)
|
||||||
|
server := devserver.NewServer(cfg)
|
||||||
|
server.StartAsync()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
metricReqDur.Observe(8, "test-cmd")
|
||||||
|
metricReqErr.Inc("test-cmd", "internal-error")
|
||||||
|
metricSlowCount.Inc("test-cmd")
|
||||||
|
|
||||||
|
url := "http://127.0.0.1:6060/metrics"
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
s, err := io.ReadAll(resp.Body)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
content := string(s)
|
||||||
|
assert.Contains(t, content, "redis_client_requests_duration_ms_sum{command=\"test-cmd\"} 8\n")
|
||||||
|
assert.Contains(t, content, "redis_client_requests_duration_ms_count{command=\"test-cmd\"} 1\n")
|
||||||
|
assert.Contains(t, content, "redis_client_requests_error_total{command=\"test-cmd\",error=\"internal-error\"} 1\n")
|
||||||
|
assert.Contains(t, content, "redis_client_requests_slow_total{command=\"test-cmd\"} 1\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_newCollector(t *testing.T) {
|
||||||
|
prometheus.Unregister(connCollector)
|
||||||
|
c := newCollector()
|
||||||
|
c.registerClient(&statGetter{
|
||||||
|
clientType: "node",
|
||||||
|
key: "test1",
|
||||||
|
poolSize: 10,
|
||||||
|
poolStats: func() *red.PoolStats {
|
||||||
|
return &red.PoolStats{
|
||||||
|
Hits: 10000,
|
||||||
|
Misses: 10,
|
||||||
|
Timeouts: 5,
|
||||||
|
TotalConns: 100,
|
||||||
|
IdleConns: 20,
|
||||||
|
StaleConns: 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
c.registerClient(&statGetter{
|
||||||
|
clientType: "node",
|
||||||
|
key: "test2",
|
||||||
|
poolSize: 11,
|
||||||
|
poolStats: func() *red.PoolStats {
|
||||||
|
return &red.PoolStats{
|
||||||
|
Hits: 10001,
|
||||||
|
Misses: 11,
|
||||||
|
Timeouts: 6,
|
||||||
|
TotalConns: 101,
|
||||||
|
IdleConns: 21,
|
||||||
|
StaleConns: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
c.registerClient(&statGetter{
|
||||||
|
clientType: "cluster",
|
||||||
|
key: "test3",
|
||||||
|
poolSize: 5,
|
||||||
|
poolStats: func() *red.PoolStats {
|
||||||
|
return &red.PoolStats{
|
||||||
|
Hits: 20000,
|
||||||
|
Misses: 20,
|
||||||
|
Timeouts: 10,
|
||||||
|
TotalConns: 200,
|
||||||
|
IdleConns: 40,
|
||||||
|
StaleConns: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
val := `
|
||||||
|
# HELP redis_client_pool_conn_idle_current Current number of idle connections in the pool
|
||||||
|
# TYPE redis_client_pool_conn_idle_current gauge
|
||||||
|
redis_client_pool_conn_idle_current{client_type="cluster",key="test3"} 40
|
||||||
|
redis_client_pool_conn_idle_current{client_type="node",key="test1"} 20
|
||||||
|
redis_client_pool_conn_idle_current{client_type="node",key="test2"} 21
|
||||||
|
# HELP redis_client_pool_conn_max Max number of connections in the pool
|
||||||
|
# TYPE redis_client_pool_conn_max counter
|
||||||
|
redis_client_pool_conn_max{client_type="cluster",key="test3"} 5
|
||||||
|
redis_client_pool_conn_max{client_type="node",key="test1"} 10
|
||||||
|
redis_client_pool_conn_max{client_type="node",key="test2"} 11
|
||||||
|
# HELP redis_client_pool_conn_stale_total Number of times a connection was removed from the pool because it was stale
|
||||||
|
# TYPE redis_client_pool_conn_stale_total counter
|
||||||
|
redis_client_pool_conn_stale_total{client_type="cluster",key="test3"} 2
|
||||||
|
redis_client_pool_conn_stale_total{client_type="node",key="test1"} 1
|
||||||
|
redis_client_pool_conn_stale_total{client_type="node",key="test2"} 2
|
||||||
|
# HELP redis_client_pool_conn_total_current Current number of connections in the pool
|
||||||
|
# TYPE redis_client_pool_conn_total_current gauge
|
||||||
|
redis_client_pool_conn_total_current{client_type="cluster",key="test3"} 200
|
||||||
|
redis_client_pool_conn_total_current{client_type="node",key="test1"} 100
|
||||||
|
redis_client_pool_conn_total_current{client_type="node",key="test2"} 101
|
||||||
|
# HELP redis_client_pool_hit_total Number of times a connection was found in the pool
|
||||||
|
# TYPE redis_client_pool_hit_total counter
|
||||||
|
redis_client_pool_hit_total{client_type="cluster",key="test3"} 20000
|
||||||
|
redis_client_pool_hit_total{client_type="node",key="test1"} 10000
|
||||||
|
redis_client_pool_hit_total{client_type="node",key="test2"} 10001
|
||||||
|
# HELP redis_client_pool_miss_total Number of times a connection was not found in the pool
|
||||||
|
# TYPE redis_client_pool_miss_total counter
|
||||||
|
redis_client_pool_miss_total{client_type="cluster",key="test3"} 20
|
||||||
|
redis_client_pool_miss_total{client_type="node",key="test1"} 10
|
||||||
|
redis_client_pool_miss_total{client_type="node",key="test2"} 11
|
||||||
|
# HELP redis_client_pool_timeout_total Number of times a timeout occurred when looking for a connection in the pool
|
||||||
|
# TYPE redis_client_pool_timeout_total counter
|
||||||
|
redis_client_pool_timeout_total{client_type="cluster",key="test3"} 10
|
||||||
|
redis_client_pool_timeout_total{client_type="node",key="test1"} 5
|
||||||
|
redis_client_pool_timeout_total{client_type="node",key="test2"} 6
|
||||||
|
`
|
||||||
|
|
||||||
|
err := testutil.CollectAndCompare(c, strings.NewReader(val))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
@@ -2849,7 +2849,7 @@ func withHook(hook red.Hook) Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func acceptable(err error) bool {
|
func acceptable(err error) bool {
|
||||||
return err == nil || err == red.Nil || err == context.Canceled
|
return err == nil || err == red.Nil || errors.Is(err, context.Canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRedis(r *Redis) (RedisNode, error) {
|
func getRedis(r *Redis) (RedisNode, error) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
"io"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
red "github.com/go-redis/redis/v8"
|
red "github.com/go-redis/redis/v8"
|
||||||
"github.com/zeromicro/go-zero/core/syncx"
|
"github.com/zeromicro/go-zero/core/syncx"
|
||||||
@@ -14,7 +15,11 @@ const (
|
|||||||
idleConns = 8
|
idleConns = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
var clientManager = syncx.NewResourceManager()
|
var (
|
||||||
|
clientManager = syncx.NewResourceManager()
|
||||||
|
// nodePoolSize is default pool size for node type of redis.
|
||||||
|
nodePoolSize = 10 * runtime.GOMAXPROCS(0)
|
||||||
|
)
|
||||||
|
|
||||||
func getClient(r *Redis) (*red.Client, error) {
|
func getClient(r *Redis) (*red.Client, error) {
|
||||||
val, err := clientManager.GetResource(r.Addr, func() (io.Closer, error) {
|
val, err := clientManager.GetResource(r.Addr, func() (io.Closer, error) {
|
||||||
@@ -37,6 +42,15 @@ func getClient(r *Redis) (*red.Client, error) {
|
|||||||
store.AddHook(hook)
|
store.AddHook(hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connCollector.registerClient(&statGetter{
|
||||||
|
clientType: NodeType,
|
||||||
|
key: r.Addr,
|
||||||
|
poolSize: nodePoolSize,
|
||||||
|
poolStats: func() *red.PoolStats {
|
||||||
|
return store.PoolStats()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return store, nil
|
return store, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
"io"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
red "github.com/go-redis/redis/v8"
|
red "github.com/go-redis/redis/v8"
|
||||||
@@ -11,7 +12,11 @@ import (
|
|||||||
|
|
||||||
const addrSep = ","
|
const addrSep = ","
|
||||||
|
|
||||||
var clusterManager = syncx.NewResourceManager()
|
var (
|
||||||
|
clusterManager = syncx.NewResourceManager()
|
||||||
|
// clusterPoolSize is default pool size for cluster type of redis.
|
||||||
|
clusterPoolSize = 5 * runtime.GOMAXPROCS(0)
|
||||||
|
)
|
||||||
|
|
||||||
func getCluster(r *Redis) (*red.ClusterClient, error) {
|
func getCluster(r *Redis) (*red.ClusterClient, error) {
|
||||||
val, err := clusterManager.GetResource(r.Addr, func() (io.Closer, error) {
|
val, err := clusterManager.GetResource(r.Addr, func() (io.Closer, error) {
|
||||||
@@ -33,6 +38,15 @@ func getCluster(r *Redis) (*red.ClusterClient, error) {
|
|||||||
store.AddHook(hook)
|
store.AddHook(hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connCollector.registerClient(&statGetter{
|
||||||
|
clientType: ClusterType,
|
||||||
|
key: r.Addr,
|
||||||
|
poolSize: clusterPoolSize,
|
||||||
|
poolStats: func() *red.PoolStats {
|
||||||
|
return store.PoolStats()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return store, nil
|
return store, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/executors"
|
"github.com/zeromicro/go-zero/core/executors"
|
||||||
@@ -30,6 +31,7 @@ type (
|
|||||||
executor *executors.PeriodicalExecutor
|
executor *executors.PeriodicalExecutor
|
||||||
inserter *dbInserter
|
inserter *dbInserter
|
||||||
stmt bulkStmt
|
stmt bulkStmt
|
||||||
|
lock sync.RWMutex // guards stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
bulkStmt struct {
|
bulkStmt struct {
|
||||||
@@ -65,6 +67,9 @@ func (bi *BulkInserter) Flush() {
|
|||||||
|
|
||||||
// Insert inserts given args.
|
// Insert inserts given args.
|
||||||
func (bi *BulkInserter) Insert(args ...any) error {
|
func (bi *BulkInserter) Insert(args ...any) error {
|
||||||
|
bi.lock.RLock()
|
||||||
|
defer bi.lock.RUnlock()
|
||||||
|
|
||||||
value, err := format(bi.stmt.valueFormat, args...)
|
value, err := format(bi.stmt.valueFormat, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -95,6 +100,11 @@ func (bi *BulkInserter) UpdateStmt(stmt string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bi.lock.Lock()
|
||||||
|
defer bi.lock.Unlock()
|
||||||
|
|
||||||
|
// with write lock, it doesn't matter what's the order of setting bi.stmt and calling flush.
|
||||||
|
bi.stmt = bkStmt
|
||||||
bi.executor.Flush()
|
bi.executor.Flush()
|
||||||
bi.executor.Sync(func() {
|
bi.executor.Sync(func() {
|
||||||
bi.inserter.stmt = bkStmt
|
bi.inserter.stmt = bkStmt
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
@@ -13,14 +16,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type mockedConn struct {
|
type mockedConn struct {
|
||||||
query string
|
query string
|
||||||
args []any
|
args []any
|
||||||
execErr error
|
execErr error
|
||||||
|
updateCallback func(query string, args []any)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockedConn) ExecCtx(_ context.Context, query string, args ...any) (sql.Result, error) {
|
func (c *mockedConn) ExecCtx(_ context.Context, query string, args ...any) (sql.Result, error) {
|
||||||
c.query = query
|
c.query = query
|
||||||
c.args = args
|
c.args = args
|
||||||
|
if c.updateCallback != nil {
|
||||||
|
c.updateCallback(query, args)
|
||||||
|
}
|
||||||
|
|
||||||
return nil, c.execErr
|
return nil, c.execErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,3 +152,50 @@ func TestBulkInserter_Update(t *testing.T) {
|
|||||||
assert.NotNil(t, inserter.UpdateStmt("foo"))
|
assert.NotNil(t, inserter.UpdateStmt("foo"))
|
||||||
assert.NotNil(t, inserter.Insert("foo", "bar"))
|
assert.NotNil(t, inserter.Insert("foo", "bar"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBulkInserter_UpdateStmt(t *testing.T) {
|
||||||
|
var updated int32
|
||||||
|
conn := mockedConn{
|
||||||
|
execErr: errors.New("foo"),
|
||||||
|
updateCallback: func(query string, args []any) {
|
||||||
|
count := atomic.AddInt32(&updated, 1)
|
||||||
|
assert.Empty(t, args)
|
||||||
|
assert.Equal(t, 100, strings.Count(query, "foo"))
|
||||||
|
if count == 1 {
|
||||||
|
assert.Equal(t, 0, strings.Count(query, "bar"))
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, 100, strings.Count(query, "bar"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
inserter, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom) VALUES(?)`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var wg1 sync.WaitGroup
|
||||||
|
wg1.Add(2)
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg1.Done()
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
assert.NoError(t, inserter.Insert("foo"))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg1.Wait()
|
||||||
|
|
||||||
|
assert.NoError(t, inserter.UpdateStmt(`INSERT INTO classroom_dau(classroom, user) VALUES(?, ?)`))
|
||||||
|
|
||||||
|
var wg2 sync.WaitGroup
|
||||||
|
wg2.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg2.Done()
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
assert.NoError(t, inserter.Insert("foo", "bar"))
|
||||||
|
}
|
||||||
|
inserter.Flush()
|
||||||
|
}()
|
||||||
|
wg2.Wait()
|
||||||
|
|
||||||
|
assert.Equal(t, int32(2), atomic.LoadInt32(&updated))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
package sqlx
|
package sqlx
|
||||||
|
|
||||||
import "github.com/zeromicro/go-zero/core/metric"
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"sync"
|
||||||
|
|
||||||
const namespace = "sql_client"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/zeromicro/go-zero/core/metric"
|
||||||
|
)
|
||||||
|
|
||||||
|
const namespace = "mysql_client"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
metricReqDur = metric.NewHistogramVec(&metric.HistogramVecOpts{
|
metricReqDur = metric.NewHistogramVec(&metric.HistogramVecOpts{
|
||||||
@@ -11,7 +17,7 @@ var (
|
|||||||
Name: "duration_ms",
|
Name: "duration_ms",
|
||||||
Help: "mysql client requests duration(ms).",
|
Help: "mysql client requests duration(ms).",
|
||||||
Labels: []string{"command"},
|
Labels: []string{"command"},
|
||||||
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000, 2500},
|
Buckets: []float64{0.25, 0.5, 1, 1.5, 2, 3, 5, 10, 25, 50, 100, 250, 500, 1000, 2000, 5000, 10000, 15000},
|
||||||
})
|
})
|
||||||
metricReqErr = metric.NewCounterVec(&metric.CounterVecOpts{
|
metricReqErr = metric.NewCounterVec(&metric.CounterVecOpts{
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
@@ -20,4 +26,145 @@ var (
|
|||||||
Help: "mysql client requests error count.",
|
Help: "mysql client requests error count.",
|
||||||
Labels: []string{"command", "error"},
|
Labels: []string{"command", "error"},
|
||||||
})
|
})
|
||||||
|
metricSlowCount = metric.NewCounterVec(&metric.CounterVecOpts{
|
||||||
|
Namespace: namespace,
|
||||||
|
Subsystem: "requests",
|
||||||
|
Name: "slow_total",
|
||||||
|
Help: "mysql client requests slow count.",
|
||||||
|
Labels: []string{"command"},
|
||||||
|
})
|
||||||
|
|
||||||
|
connLabels = []string{"db_name", "hash"}
|
||||||
|
connCollector = newCollector()
|
||||||
|
_ prometheus.Collector = (*collector)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
statGetter struct {
|
||||||
|
dbName string
|
||||||
|
hash string
|
||||||
|
poolStats func() sql.DBStats
|
||||||
|
}
|
||||||
|
|
||||||
|
// collector collects statistics from a redis client.
|
||||||
|
// It implements the prometheus.Collector interface.
|
||||||
|
collector struct {
|
||||||
|
maxOpenConnections *prometheus.Desc
|
||||||
|
|
||||||
|
openConnections *prometheus.Desc
|
||||||
|
inUseConnections *prometheus.Desc
|
||||||
|
idleConnections *prometheus.Desc
|
||||||
|
|
||||||
|
waitCount *prometheus.Desc
|
||||||
|
waitDuration *prometheus.Desc
|
||||||
|
maxIdleClosed *prometheus.Desc
|
||||||
|
maxIdleTimeClosed *prometheus.Desc
|
||||||
|
maxLifetimeClosed *prometheus.Desc
|
||||||
|
|
||||||
|
clients []*statGetter
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCollector() *collector {
|
||||||
|
c := &collector{
|
||||||
|
maxOpenConnections: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "max_open_connections"),
|
||||||
|
"Maximum number of open connections to the database.",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
openConnections: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "open_connections"),
|
||||||
|
"The number of established connections both in use and idle.",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
inUseConnections: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "in_use_connections"),
|
||||||
|
"The number of connections currently in use.",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
idleConnections: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "idle_connections"),
|
||||||
|
"The number of idle connections.",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
waitCount: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "wait_count_total"),
|
||||||
|
"The total number of connections waited for.",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
waitDuration: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "wait_duration_seconds_total"),
|
||||||
|
"The total time blocked waiting for a new connection.",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
maxIdleClosed: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "max_idle_closed_total"),
|
||||||
|
"The total number of connections closed due to SetMaxIdleConns.",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
maxIdleTimeClosed: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "max_idle_time_closed_total"),
|
||||||
|
"The total number of connections closed due to SetConnMaxIdleTime.",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
maxLifetimeClosed: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "max_lifetime_closed_total"),
|
||||||
|
"The total number of connections closed due to SetConnMaxLifetime.",
|
||||||
|
connLabels, nil,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
prometheus.MustRegister(c)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe implements the prometheus.Collector interface.
|
||||||
|
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
|
||||||
|
ch <- c.maxOpenConnections
|
||||||
|
ch <- c.openConnections
|
||||||
|
ch <- c.inUseConnections
|
||||||
|
ch <- c.idleConnections
|
||||||
|
ch <- c.waitCount
|
||||||
|
ch <- c.waitDuration
|
||||||
|
ch <- c.maxIdleClosed
|
||||||
|
ch <- c.maxLifetimeClosed
|
||||||
|
ch <- c.maxIdleTimeClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect implements the prometheus.Collector interface.
|
||||||
|
func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
for _, client := range c.clients {
|
||||||
|
dbName, hash := client.dbName, client.hash
|
||||||
|
stats := client.poolStats()
|
||||||
|
ch <- prometheus.MustNewConstMetric(c.maxOpenConnections, prometheus.GaugeValue,
|
||||||
|
float64(stats.MaxOpenConnections), dbName, hash)
|
||||||
|
ch <- prometheus.MustNewConstMetric(c.openConnections, prometheus.GaugeValue,
|
||||||
|
float64(stats.OpenConnections), dbName, hash)
|
||||||
|
ch <- prometheus.MustNewConstMetric(c.inUseConnections, prometheus.GaugeValue,
|
||||||
|
float64(stats.InUse), dbName, hash)
|
||||||
|
ch <- prometheus.MustNewConstMetric(c.idleConnections, prometheus.GaugeValue,
|
||||||
|
float64(stats.Idle), dbName, hash)
|
||||||
|
ch <- prometheus.MustNewConstMetric(c.waitCount, prometheus.CounterValue,
|
||||||
|
float64(stats.WaitCount), dbName, hash)
|
||||||
|
ch <- prometheus.MustNewConstMetric(c.waitDuration, prometheus.CounterValue,
|
||||||
|
stats.WaitDuration.Seconds(), dbName, hash)
|
||||||
|
ch <- prometheus.MustNewConstMetric(c.maxIdleClosed, prometheus.CounterValue,
|
||||||
|
float64(stats.MaxIdleClosed), dbName, hash)
|
||||||
|
ch <- prometheus.MustNewConstMetric(c.maxLifetimeClosed, prometheus.CounterValue,
|
||||||
|
float64(stats.MaxLifetimeClosed), dbName, hash)
|
||||||
|
ch <- prometheus.MustNewConstMetric(c.maxIdleTimeClosed, prometheus.CounterValue,
|
||||||
|
float64(stats.MaxIdleTimeClosed), dbName, hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *collector) registerClient(client *statGetter) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
c.clients = append(c.clients, client)
|
||||||
|
}
|
||||||
|
|||||||
147
core/stores/sqlx/metrics_test.go
Normal file
147
core/stores/sqlx/metrics_test.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package sqlx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
|
"github.com/zeromicro/go-zero/internal/devserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSqlxMetric(t *testing.T) {
|
||||||
|
cfg := devserver.Config{}
|
||||||
|
_ = conf.FillDefault(&cfg)
|
||||||
|
cfg.Port = 6480
|
||||||
|
server := devserver.NewServer(cfg)
|
||||||
|
server.StartAsync()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
metricReqDur.Observe(8, "test-cmd")
|
||||||
|
metricReqErr.Inc("test-cmd", "internal-error")
|
||||||
|
metricSlowCount.Inc("test-cmd")
|
||||||
|
|
||||||
|
url := "http://127.0.0.1:6480/metrics"
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
s, err := io.ReadAll(resp.Body)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
content := string(s)
|
||||||
|
assert.Contains(t, content, "mysql_client_requests_duration_ms_sum{command=\"test-cmd\"} 8\n")
|
||||||
|
assert.Contains(t, content, "mysql_client_requests_duration_ms_count{command=\"test-cmd\"} 1\n")
|
||||||
|
assert.Contains(t, content, "mysql_client_requests_error_total{command=\"test-cmd\",error=\"internal-error\"} 1\n")
|
||||||
|
assert.Contains(t, content, "mysql_client_requests_slow_total{command=\"test-cmd\"} 1\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetricCollector(t *testing.T) {
|
||||||
|
prometheus.Unregister(connCollector)
|
||||||
|
c := newCollector()
|
||||||
|
c.registerClient(&statGetter{
|
||||||
|
dbName: "db-1",
|
||||||
|
hash: "hash-1",
|
||||||
|
poolStats: func() sql.DBStats {
|
||||||
|
return sql.DBStats{
|
||||||
|
MaxOpenConnections: 1,
|
||||||
|
OpenConnections: 2,
|
||||||
|
InUse: 3,
|
||||||
|
Idle: 4,
|
||||||
|
WaitCount: 5,
|
||||||
|
WaitDuration: 6 * time.Second,
|
||||||
|
MaxIdleClosed: 7,
|
||||||
|
MaxIdleTimeClosed: 8,
|
||||||
|
MaxLifetimeClosed: 9,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
c.registerClient(&statGetter{
|
||||||
|
dbName: "db-1",
|
||||||
|
hash: "hash-2",
|
||||||
|
poolStats: func() sql.DBStats {
|
||||||
|
return sql.DBStats{
|
||||||
|
MaxOpenConnections: 10,
|
||||||
|
OpenConnections: 20,
|
||||||
|
InUse: 30,
|
||||||
|
Idle: 40,
|
||||||
|
WaitCount: 50,
|
||||||
|
WaitDuration: 60 * time.Second,
|
||||||
|
MaxIdleClosed: 70,
|
||||||
|
MaxIdleTimeClosed: 80,
|
||||||
|
MaxLifetimeClosed: 90,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
c.registerClient(&statGetter{
|
||||||
|
dbName: "db-2",
|
||||||
|
hash: "hash-2",
|
||||||
|
poolStats: func() sql.DBStats {
|
||||||
|
return sql.DBStats{
|
||||||
|
MaxOpenConnections: 100,
|
||||||
|
OpenConnections: 200,
|
||||||
|
InUse: 300,
|
||||||
|
Idle: 400,
|
||||||
|
WaitCount: 500,
|
||||||
|
WaitDuration: 600 * time.Second,
|
||||||
|
MaxIdleClosed: 700,
|
||||||
|
MaxIdleTimeClosed: 800,
|
||||||
|
MaxLifetimeClosed: 900,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
val := `
|
||||||
|
# HELP mysql_client_idle_connections The number of idle connections.
|
||||||
|
# TYPE mysql_client_idle_connections gauge
|
||||||
|
mysql_client_idle_connections{db_name="db-1",hash="hash-1"} 4
|
||||||
|
mysql_client_idle_connections{db_name="db-1",hash="hash-2"} 40
|
||||||
|
mysql_client_idle_connections{db_name="db-2",hash="hash-2"} 400
|
||||||
|
# HELP mysql_client_in_use_connections The number of connections currently in use.
|
||||||
|
# TYPE mysql_client_in_use_connections gauge
|
||||||
|
mysql_client_in_use_connections{db_name="db-1",hash="hash-1"} 3
|
||||||
|
mysql_client_in_use_connections{db_name="db-1",hash="hash-2"} 30
|
||||||
|
mysql_client_in_use_connections{db_name="db-2",hash="hash-2"} 300
|
||||||
|
# HELP mysql_client_max_idle_closed_total The total number of connections closed due to SetMaxIdleConns.
|
||||||
|
# TYPE mysql_client_max_idle_closed_total counter
|
||||||
|
mysql_client_max_idle_closed_total{db_name="db-1",hash="hash-1"} 7
|
||||||
|
mysql_client_max_idle_closed_total{db_name="db-1",hash="hash-2"} 70
|
||||||
|
mysql_client_max_idle_closed_total{db_name="db-2",hash="hash-2"} 700
|
||||||
|
# HELP mysql_client_max_idle_time_closed_total The total number of connections closed due to SetConnMaxIdleTime.
|
||||||
|
# TYPE mysql_client_max_idle_time_closed_total counter
|
||||||
|
mysql_client_max_idle_time_closed_total{db_name="db-1",hash="hash-1"} 8
|
||||||
|
mysql_client_max_idle_time_closed_total{db_name="db-1",hash="hash-2"} 80
|
||||||
|
mysql_client_max_idle_time_closed_total{db_name="db-2",hash="hash-2"} 800
|
||||||
|
# HELP mysql_client_max_lifetime_closed_total The total number of connections closed due to SetConnMaxLifetime.
|
||||||
|
# TYPE mysql_client_max_lifetime_closed_total counter
|
||||||
|
mysql_client_max_lifetime_closed_total{db_name="db-1",hash="hash-1"} 9
|
||||||
|
mysql_client_max_lifetime_closed_total{db_name="db-1",hash="hash-2"} 90
|
||||||
|
mysql_client_max_lifetime_closed_total{db_name="db-2",hash="hash-2"} 900
|
||||||
|
# HELP mysql_client_max_open_connections Maximum number of open connections to the database.
|
||||||
|
# TYPE mysql_client_max_open_connections gauge
|
||||||
|
mysql_client_max_open_connections{db_name="db-1",hash="hash-1"} 1
|
||||||
|
mysql_client_max_open_connections{db_name="db-1",hash="hash-2"} 10
|
||||||
|
mysql_client_max_open_connections{db_name="db-2",hash="hash-2"} 100
|
||||||
|
# HELP mysql_client_open_connections The number of established connections both in use and idle.
|
||||||
|
# TYPE mysql_client_open_connections gauge
|
||||||
|
mysql_client_open_connections{db_name="db-1",hash="hash-1"} 2
|
||||||
|
mysql_client_open_connections{db_name="db-1",hash="hash-2"} 20
|
||||||
|
mysql_client_open_connections{db_name="db-2",hash="hash-2"} 200
|
||||||
|
# HELP mysql_client_wait_count_total The total number of connections waited for.
|
||||||
|
# TYPE mysql_client_wait_count_total counter
|
||||||
|
mysql_client_wait_count_total{db_name="db-1",hash="hash-1"} 5
|
||||||
|
mysql_client_wait_count_total{db_name="db-1",hash="hash-2"} 50
|
||||||
|
mysql_client_wait_count_total{db_name="db-2",hash="hash-2"} 500
|
||||||
|
# HELP mysql_client_wait_duration_seconds_total The total time blocked waiting for a new connection.
|
||||||
|
# TYPE mysql_client_wait_duration_seconds_total counter
|
||||||
|
mysql_client_wait_duration_seconds_total{db_name="db-1",hash="hash-1"} 6
|
||||||
|
mysql_client_wait_duration_seconds_total{db_name="db-1",hash="hash-2"} 60
|
||||||
|
mysql_client_wait_duration_seconds_total{db_name="db-2",hash="hash-2"} 600
|
||||||
|
`
|
||||||
|
|
||||||
|
err := testutil.CollectAndCompare(c, strings.NewReader(val))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package sqlx
|
package sqlx
|
||||||
|
|
||||||
import "github.com/go-sql-driver/mysql"
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
mysqlDriverName = "mysql"
|
mysqlDriverName = "mysql"
|
||||||
@@ -18,7 +22,8 @@ func mysqlAcceptable(err error) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
myerr, ok := err.(*mysql.MySQLError)
|
var myerr *mysql.MySQLError
|
||||||
|
ok := errors.As(err, &myerr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func TestBreakerOnNotHandlingDuplicateEntry(t *testing.T) {
|
|||||||
|
|
||||||
var found bool
|
var found bool
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
if tryOnDuplicateEntryError(t, nil) == breaker.ErrServiceUnavailable {
|
if errors.Is(tryOnDuplicateEntryError(t, nil), breaker.ErrServiceUnavailable) {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package sqlx
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/breaker"
|
"github.com/zeromicro/go-zero/core/breaker"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
@@ -157,7 +158,7 @@ func (db *commonSqlConn) ExecCtx(ctx context.Context, q string, args ...any) (
|
|||||||
result, err = exec(ctx, conn, q, args...)
|
result, err = exec(ctx, conn, q, args...)
|
||||||
return err
|
return err
|
||||||
}, db.acceptable)
|
}, db.acceptable)
|
||||||
if err == breaker.ErrServiceUnavailable {
|
if errors.Is(err, breaker.ErrServiceUnavailable) {
|
||||||
metricReqErr.Inc("Exec", "breaker")
|
metricReqErr.Inc("Exec", "breaker")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +194,7 @@ func (db *commonSqlConn) PrepareCtx(ctx context.Context, query string) (stmt Stm
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, db.acceptable)
|
}, db.acceptable)
|
||||||
if err == breaker.ErrServiceUnavailable {
|
if errors.Is(err, breaker.ErrServiceUnavailable) {
|
||||||
metricReqErr.Inc("Prepare", "breaker")
|
metricReqErr.Inc("Prepare", "breaker")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +284,7 @@ func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Contex
|
|||||||
err = db.brk.DoWithAcceptable(func() error {
|
err = db.brk.DoWithAcceptable(func() error {
|
||||||
return transact(ctx, db, db.beginTx, fn)
|
return transact(ctx, db, db.beginTx, fn)
|
||||||
}, db.acceptable)
|
}, db.acceptable)
|
||||||
if err == breaker.ErrServiceUnavailable {
|
if errors.Is(err, breaker.ErrServiceUnavailable) {
|
||||||
metricReqErr.Inc("Transact", "breaker")
|
metricReqErr.Inc("Transact", "breaker")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,11 +292,13 @@ 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 {
|
||||||
if err == nil || err == sql.ErrNoRows || err == sql.ErrTxDone || err == context.Canceled {
|
if err == nil || errors.Is(err, sql.ErrNoRows) || errors.Is(err, sql.ErrTxDone) ||
|
||||||
|
errors.Is(err, context.Canceled) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := err.(acceptableError); ok {
|
var e acceptableError
|
||||||
|
if errors.As(err, &e) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,9 +324,9 @@ func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows)
|
|||||||
return qerr
|
return qerr
|
||||||
}, q, args...)
|
}, q, args...)
|
||||||
}, func(err error) bool {
|
}, func(err error) bool {
|
||||||
return qerr == err || db.acceptable(err)
|
return errors.Is(err, qerr) || db.acceptable(err)
|
||||||
})
|
})
|
||||||
if err == breaker.ErrServiceUnavailable {
|
if errors.Is(err, breaker.ErrServiceUnavailable) {
|
||||||
metricReqErr.Inc("queryRows", "breaker")
|
metricReqErr.Inc("queryRows", "breaker")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ func (e *realSqlGuard) finish(ctx context.Context, err error) {
|
|||||||
duration := timex.Since(e.startTime)
|
duration := timex.Since(e.startTime)
|
||||||
if duration > slowThreshold.Load() {
|
if duration > slowThreshold.Load() {
|
||||||
logx.WithContext(ctx).WithDuration(duration).Slowf("[SQL] %s: slowcall - %s", e.command, e.stmt)
|
logx.WithContext(ctx).WithDuration(duration).Slowf("[SQL] %s: slowcall - %s", e.command, e.stmt)
|
||||||
|
metricSlowCount.Inc(e.command)
|
||||||
} else if logSql.True() {
|
} else if logSql.True() {
|
||||||
logx.WithContext(ctx).WithDuration(duration).Infof("sql %s: %s", e.command, e.stmt)
|
logx.WithContext(ctx).WithDuration(duration).Infof("sql %s: %s", e.command, e.stmt)
|
||||||
}
|
}
|
||||||
@@ -136,7 +137,7 @@ func (e *realSqlGuard) finish(ctx context.Context, err error) {
|
|||||||
logSqlError(ctx, e.stmt, err)
|
logSqlError(ctx, e.stmt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
metricReqDur.Observe(duration.Milliseconds(), e.command)
|
metricReqDur.ObserveFloat(float64(duration)/float64(time.Millisecond), e.command)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *realSqlGuard) start(q string, args ...any) error {
|
func (e *realSqlGuard) start(q string, args ...any) error {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package sqlx
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/trace"
|
"github.com/zeromicro/go-zero/core/trace"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
@@ -23,7 +24,7 @@ func startSpan(ctx context.Context, method string) (context.Context, oteltrace.S
|
|||||||
func endSpan(span oteltrace.Span, err error) {
|
func endSpan(span oteltrace.Span, err error) {
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
if err == nil || err == sql.ErrNoRows {
|
if err == nil || errors.Is(err, sql.ErrNoRows) {
|
||||||
span.SetStatus(codes.Ok, "")
|
span.SetStatus(codes.Ok, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ func logInstanceError(ctx context.Context, datasource string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func logSqlError(ctx context.Context, stmt string, err error) {
|
func logSqlError(ctx context.Context, stmt string, err error) {
|
||||||
if err != nil && err != ErrNotFound {
|
if err != nil && !errors.Is(err, ErrNotFound) {
|
||||||
logx.WithContext(ctx).Errorf("stmt: %s, error: %s", stmt, err.Error())
|
logx.WithContext(ctx).Errorf("stmt: %s, error: %s", stmt, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build go1.18
|
|
||||||
|
|
||||||
package stringx
|
package stringx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func TestLockedCallDoErr(t *testing.T) {
|
|||||||
v, err := g.Do("key", func() (any, error) {
|
v, err := g.Do("key", func() (any, error) {
|
||||||
return nil, someErr
|
return nil, someErr
|
||||||
})
|
})
|
||||||
if err != someErr {
|
if !errors.Is(err, someErr) {
|
||||||
t.Errorf("Do error = %v; want someErr", err)
|
t.Errorf("Do error = %v; want someErr", err)
|
||||||
}
|
}
|
||||||
if v != nil {
|
if v != nil {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func TestExclusiveCallDoErr(t *testing.T) {
|
|||||||
v, err := g.Do("key", func() (any, error) {
|
v, err := g.Do("key", func() (any, error) {
|
||||||
return nil, someErr
|
return nil, someErr
|
||||||
})
|
})
|
||||||
if err != someErr {
|
if !errors.Is(err, someErr) {
|
||||||
t.Errorf("Do error = %v; want someErr", err)
|
t.Errorf("Do error = %v; want someErr", err)
|
||||||
}
|
}
|
||||||
if v != nil {
|
if v != nil {
|
||||||
|
|||||||
@@ -113,11 +113,13 @@ func createExporter(c Config) (sdktrace.SpanExporter, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startAgent(c Config) error {
|
func startAgent(c Config) error {
|
||||||
|
AddResources(semconv.ServiceNameKey.String(c.Name))
|
||||||
|
|
||||||
opts := []sdktrace.TracerProviderOption{
|
opts := []sdktrace.TracerProviderOption{
|
||||||
// Set the sampling rate based on the parent span to 100%
|
// Set the sampling rate based on the parent span to 100%
|
||||||
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(c.Sampler))),
|
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(c.Sampler))),
|
||||||
// Record information about this application in a Resource.
|
// Record information about this application in a Resource.
|
||||||
sdktrace.WithResource(resource.NewSchemaless(semconv.ServiceNameKey.String(c.Name))),
|
sdktrace.WithResource(resource.NewSchemaless(attrResources...)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Endpoint) > 0 {
|
if len(c.Endpoint) > 0 {
|
||||||
|
|||||||
10
core/trace/resource.go
Normal file
10
core/trace/resource.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package trace
|
||||||
|
|
||||||
|
import "go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
|
var attrResources = make([]attribute.KeyValue, 0)
|
||||||
|
|
||||||
|
// AddResources add more resources in addition to configured trace name.
|
||||||
|
func AddResources(attrs ...attribute.KeyValue) {
|
||||||
|
attrResources = append(attrResources, attrs...)
|
||||||
|
}
|
||||||
102
go.mod
102
go.mod
@@ -1,120 +1,120 @@
|
|||||||
module github.com/zeromicro/go-zero
|
module github.com/zeromicro/go-zero
|
||||||
|
|
||||||
go 1.18
|
go 1.19
|
||||||
|
|
||||||
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.5
|
github.com/alicebob/miniredis/v2 v2.31.0
|
||||||
github.com/fatih/color v1.15.0
|
github.com/fatih/color v1.15.0
|
||||||
github.com/fullstorydev/grpcurl v1.8.8
|
github.com/fullstorydev/grpcurl v1.8.9
|
||||||
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.1
|
github.com/google/uuid v1.4.0
|
||||||
github.com/jackc/pgx/v5 v5.4.3
|
github.com/jackc/pgx/v5 v5.4.3
|
||||||
github.com/jhump/protoreflect v1.15.2
|
github.com/jhump/protoreflect v1.15.3
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0
|
github.com/pelletier/go-toml/v2 v2.1.0
|
||||||
github.com/prometheus/client_golang v1.16.0
|
github.com/prometheus/client_golang v1.17.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.10
|
||||||
go.etcd.io/etcd/client/v3 v3.5.9
|
go.etcd.io/etcd/client/v3 v3.5.10
|
||||||
go.mongodb.org/mongo-driver v1.12.1
|
go.mongodb.org/mongo-driver v1.12.1
|
||||||
go.opentelemetry.io/otel v1.14.0
|
go.opentelemetry.io/otel v1.19.0
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.14.0
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0
|
||||||
go.opentelemetry.io/otel/exporters/zipkin v1.14.0
|
go.opentelemetry.io/otel/exporters/zipkin v1.19.0
|
||||||
go.opentelemetry.io/otel/sdk v1.14.0
|
go.opentelemetry.io/otel/sdk v1.19.0
|
||||||
go.opentelemetry.io/otel/trace v1.14.0
|
go.opentelemetry.io/otel/trace v1.19.0
|
||||||
go.uber.org/automaxprocs v1.5.3
|
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.15.0
|
golang.org/x/net v0.17.0
|
||||||
golang.org/x/sys v0.12.0
|
golang.org/x/sys v0.13.0
|
||||||
golang.org/x/time v0.3.0
|
golang.org/x/time v0.3.0
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e
|
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b
|
||||||
google.golang.org/grpc v1.58.2
|
google.golang.org/grpc v1.59.0
|
||||||
google.golang.org/protobuf v1.31.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
|
||||||
k8s.io/api v0.26.3
|
k8s.io/api v0.28.3
|
||||||
k8s.io/apimachinery v0.27.0-alpha.3
|
k8s.io/apimachinery v0.28.3
|
||||||
k8s.io/client-go v0.26.3
|
k8s.io/client-go v0.28.3
|
||||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491
|
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bufbuild/protocompile v0.6.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.1 // 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
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
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/go-logr/logr v1.2.3 // indirect
|
github.com/go-logr/logr v1.2.4 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.1 // indirect
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
github.com/go-openapi/swag v0.22.3 // indirect
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
github.com/google/gnostic-models v0.6.8 // 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/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // 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/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/klauspost/compress v1.15.15 // indirect
|
github.com/klauspost/compress v1.16.7 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
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.2 // 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.4.1-0.20230718164431-9a2bf3000d16 // indirect
|
||||||
github.com/prometheus/common v0.42.0 // indirect
|
github.com/prometheus/common v0.44.0 // indirect
|
||||||
github.com/prometheus/procfs v0.10.1 // indirect
|
github.com/prometheus/procfs v0.11.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.2 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // 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-20201027041543-1326539a0a0a // 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.10 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect
|
go.opentelemetry.io/otel/metric v1.19.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||||
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.13.0 // indirect
|
golang.org/x/crypto v0.14.0 // indirect
|
||||||
golang.org/x/oauth2 v0.10.0 // indirect
|
golang.org/x/oauth2 v0.12.0 // indirect
|
||||||
golang.org/x/sync v0.3.0 // indirect
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
golang.org/x/term v0.12.0 // indirect
|
golang.org/x/term v0.13.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
|
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // 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.100.1 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20230307230338-69ee2d25a840 // indirect
|
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
|
|||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
|
||||||
|
|
||||||
# docker for arm64 (M1) architecture
|
# docker for arm64(Mac) architecture
|
||||||
docker pull kevinwan/goctl:latest-arm64
|
docker pull kevinwan/goctl:latest-arm64
|
||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
|
||||||
@@ -296,6 +296,7 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
|
|||||||
>92. 深圳市万佳安物联科技股份有限公司
|
>92. 深圳市万佳安物联科技股份有限公司
|
||||||
>93. 武侯区编程之美软件开发工作室
|
>93. 武侯区编程之美软件开发工作室
|
||||||
>94. 西安交通大学智慧能源与碳中和研究中心
|
>94. 西安交通大学智慧能源与碳中和研究中心
|
||||||
|
>95. 成都创软科技有限责任公司
|
||||||
|
|
||||||
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
||||||
|
|
||||||
|
|||||||
86
readme.md
86
readme.md
@@ -31,41 +31,43 @@ go-zero contains simple API description syntax and code generation tool called `
|
|||||||
|
|
||||||
#### Advantages of go-zero:
|
#### Advantages of go-zero:
|
||||||
|
|
||||||
* improve the stability of the services with tens of millions of daily active users
|
* Improves the stability of the services with tens of millions of daily active users
|
||||||
* builtin chained timeout control, concurrency control, rate limit, adaptive circuit breaker, adaptive load shedding, even no configuration needed
|
* Builtin chained timeout control, concurrency control, rate limit, adaptive circuit breaker, adaptive load shedding, even no configuration needed
|
||||||
* builtin middlewares also can be integrated into your frameworks
|
* Builtin middlewares also can be integrated into your frameworks
|
||||||
* simple API syntax, one command to generate a couple of different languages
|
* Simple API syntax, one command to generate a couple of different languages
|
||||||
* auto validate the request parameters from clients
|
* Auto validate the request parameters from clients
|
||||||
* plenty of builtin microservice management and concurrent toolkits
|
* Plenty of builtin microservice management and concurrent toolkits
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/architecture-en.png" alt="Architecture" width="1500" />
|
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/architecture-en.png" alt="Architecture" width="1500" />
|
||||||
|
|
||||||
## Backgrounds of go-zero
|
## Backgrounds of go-zero
|
||||||
|
|
||||||
At the beginning of 2018, we decided to re-design our system, from monolithic architecture with Java+MongoDB to microservice architecture. After research and comparison, we chose to:
|
At the beginning of 2018, we decided to re-design our system, from monolithic architecture with Java+MongoDB to microservice architecture. After research and comparison, we chose to:
|
||||||
|
In early 2018, we embarked on a transformative journey to redesign our system, transitioning from a monolithic architecture built with Java and MongoDB to a microservices architecture. After careful research and comparison, we made a deliberate choice to:
|
||||||
|
|
||||||
* Golang based
|
* Go Beyond with Golang
|
||||||
* great performance
|
* Great performance
|
||||||
* simple syntax
|
* Simple syntax
|
||||||
* proven engineering efficiency
|
* Proven engineering efficiency
|
||||||
* extreme deployment experience
|
* Extreme deployment experience
|
||||||
* less server resource consumption
|
* Less server resource consumption
|
||||||
* Self-designed microservice architecture
|
|
||||||
* I have rich experience in designing microservice architectures
|
* Self-Design Our Microservice Architecture
|
||||||
* easy to locate the problems
|
* Microservice architecture facilitates the creation of scalable, flexible, and maintainable software systems with independent, reusable components.
|
||||||
* easy to extend the features
|
* Easy to locate the problems within microservices.
|
||||||
|
* Easy to extend the features by adding or modifying specific microservices without impacting the entire system.
|
||||||
|
|
||||||
## Design considerations on go-zero
|
## Design considerations on go-zero
|
||||||
|
|
||||||
By designing the microservice architecture, we expected to ensure stability, as well as productivity. And from just the beginning, we have the following design principles:
|
By designing the microservice architecture, we expected to ensure stability, as well as productivity. And from just the beginning, we have the following design principles:
|
||||||
|
|
||||||
* keep it simple
|
* Keep it simple
|
||||||
* high availability
|
* High availability
|
||||||
* stable on high concurrency
|
* Stable on high concurrency
|
||||||
* easy to extend
|
* Easy to extend
|
||||||
* resilience design, failure-oriented programming
|
* Resilience design, failure-oriented programming
|
||||||
* try best to be friendly to the business logic development, encapsulate the complexity
|
* Try best to be friendly to the business logic development, encapsulate the complexity
|
||||||
* one thing, one way
|
* One thing, one way
|
||||||
|
|
||||||
After almost half a year, we finished the transfer from a monolithic system to microservice system and deployed on August 2018. The new system guaranteed business growth and system stability.
|
After almost half a year, we finished the transfer from a monolithic system to microservice system and deployed on August 2018. The new system guaranteed business growth and system stability.
|
||||||
|
|
||||||
@@ -73,19 +75,19 @@ After almost half a year, we finished the transfer from a monolithic system to m
|
|||||||
|
|
||||||
go-zero is a web and rpc framework that integrates lots of engineering practices. The features are mainly listed below:
|
go-zero is a web and rpc framework that integrates lots of engineering practices. The features are mainly listed below:
|
||||||
|
|
||||||
* powerful tool included, less code to write
|
* Powerful tool included, less code to write
|
||||||
* simple interfaces
|
* Simple interfaces
|
||||||
* fully compatible with net/http
|
* Fully compatible with net/http
|
||||||
* middlewares are supported, easy to extend
|
* Middlewares are supported, easy to extend
|
||||||
* high performance
|
* High performance
|
||||||
* failure-oriented programming, resilience design
|
* Failure-oriented programming, resilience design
|
||||||
* builtin service discovery, load balancing
|
* Builtin service discovery, load balancing
|
||||||
* builtin concurrency control, adaptive circuit breaker, adaptive load shedding, auto-trigger, auto recover
|
* Builtin concurrency control, adaptive circuit breaker, adaptive load shedding, auto-trigger, auto recover
|
||||||
* auto validation of API request parameters
|
* Auto validation of API request parameters
|
||||||
* chained timeout control
|
* Chained timeout control
|
||||||
* auto management of data caching
|
* Auto management of data caching
|
||||||
* call tracing, metrics, and monitoring
|
* Call tracing, metrics, and monitoring
|
||||||
* high concurrency protected
|
* High concurrency protected
|
||||||
|
|
||||||
As below, go-zero protects the system with a couple of layers and mechanisms:
|
As below, go-zero protects the system with a couple of layers and mechanisms:
|
||||||
|
|
||||||
@@ -105,13 +107,13 @@ go get -u github.com/zeromicro/go-zero
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
1. full examples can be checked out from below:
|
1. Full examples can be checked out from below:
|
||||||
|
|
||||||
[Rapid development of microservice systems](https://github.com/zeromicro/zero-doc/blob/main/doc/shorturl-en.md)
|
[Rapid development of microservice systems](https://github.com/zeromicro/zero-doc/blob/main/doc/shorturl-en.md)
|
||||||
|
|
||||||
[Rapid development of microservice systems - multiple RPCs](https://github.com/zeromicro/zero-doc/blob/main/docs/zero/bookstore-en.md)
|
[Rapid development of microservice systems - multiple RPCs](https://github.com/zeromicro/zero-doc/blob/main/docs/zero/bookstore-en.md)
|
||||||
|
|
||||||
2. install goctl
|
2. Install goctl
|
||||||
|
|
||||||
`goctl`can be read as `go control`. `goctl` means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve productivity, and make our lives easier.
|
`goctl`can be read as `go control`. `goctl` means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve productivity, and make our lives easier.
|
||||||
|
|
||||||
@@ -127,7 +129,7 @@ go get -u github.com/zeromicro/go-zero
|
|||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
|
||||||
|
|
||||||
# docker for arm64 (M1) architecture
|
# docker for arm64(Mac) architecture
|
||||||
docker pull kevinwan/goctl:latest-arm64
|
docker pull kevinwan/goctl:latest-arm64
|
||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
|
||||||
@@ -135,7 +137,7 @@ go get -u github.com/zeromicro/go-zero
|
|||||||
|
|
||||||
make sure goctl is executable.
|
make sure goctl is executable.
|
||||||
|
|
||||||
3. create the API file, like greet.api, you can install the plugin of goctl in vs code, api syntax is supported.
|
3. Create the API file, like greet.api, you can install the plugin of goctl in vs code, api syntax is supported.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type (
|
type (
|
||||||
@@ -160,7 +162,7 @@ go get -u github.com/zeromicro/go-zero
|
|||||||
goctl api -o greet.api
|
goctl api -o greet.api
|
||||||
```
|
```
|
||||||
|
|
||||||
4. generate the go server-side code
|
4. Generate the go server-side code
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
goctl api go -api greet.api -dir greet
|
goctl api go -api greet.api -dir greet
|
||||||
@@ -215,7 +217,7 @@ go get -u github.com/zeromicro/go-zero
|
|||||||
|
|
||||||
5. Write the business logic code
|
5. Write the business logic code
|
||||||
|
|
||||||
* the dependencies can be passed into the logic within servicecontext.go, like mysql, reds, etc.
|
* the dependencies can be passed into the logic within servicecontext.go, like mysql, redis, etc.
|
||||||
* add the logic code in a logic package according to .api file
|
* add the logic code in a logic package according to .api file
|
||||||
|
|
||||||
6. Generate code like Java, TypeScript, Dart, JavaScript, etc. just from the api file
|
6. Generate code like Java, TypeScript, Dart, JavaScript, etc. just from the api file
|
||||||
|
|||||||
69
rest/httpc/internal/metricsinterceptor.go
Normal file
69
rest/httpc/internal/metricsinterceptor.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/metric"
|
||||||
|
"github.com/zeromicro/go-zero/core/timex"
|
||||||
|
)
|
||||||
|
|
||||||
|
const clientNamespace = "httpc_client"
|
||||||
|
|
||||||
|
var (
|
||||||
|
MetricClientReqDur = metric.NewHistogramVec(&metric.HistogramVecOpts{
|
||||||
|
Namespace: clientNamespace,
|
||||||
|
Subsystem: "requests",
|
||||||
|
Name: "duration_ms",
|
||||||
|
Help: "http client requests duration(ms).",
|
||||||
|
Labels: []string{"name", "method", "url"},
|
||||||
|
Buckets: []float64{0.25, 0.5, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2000, 5000, 10000, 15000},
|
||||||
|
})
|
||||||
|
|
||||||
|
MetricClientReqCodeTotal = metric.NewCounterVec(&metric.CounterVecOpts{
|
||||||
|
Namespace: clientNamespace,
|
||||||
|
Subsystem: "requests",
|
||||||
|
Name: "code_total",
|
||||||
|
Help: "http client requests code count.",
|
||||||
|
Labels: []string{"name", "method", "url", "code"},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
type MetricsURLRewriter func(u url.URL) string
|
||||||
|
|
||||||
|
func MetricsInterceptor(name string, pr MetricsURLRewriter) Interceptor {
|
||||||
|
return func(r *http.Request) (*http.Request, ResponseHandler) {
|
||||||
|
startTime := timex.Now()
|
||||||
|
return r, func(resp *http.Response, err error) {
|
||||||
|
var code int
|
||||||
|
var path string
|
||||||
|
|
||||||
|
// error or resp is nil, set code=500
|
||||||
|
if err != nil || resp == nil {
|
||||||
|
code = http.StatusInternalServerError
|
||||||
|
} else {
|
||||||
|
code = resp.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
u := cleanURL(*r.URL)
|
||||||
|
method := r.Method
|
||||||
|
if pr != nil {
|
||||||
|
path = pr(u)
|
||||||
|
} else {
|
||||||
|
path = u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricClientReqDur.ObserveFloat(float64(timex.Since(startTime))/float64(time.Millisecond), name, method, path)
|
||||||
|
MetricClientReqCodeTotal.Inc(name, method, path, strconv.Itoa(code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanURL(r url.URL) url.URL {
|
||||||
|
r.RawQuery = ""
|
||||||
|
r.RawFragment = ""
|
||||||
|
r.User = nil
|
||||||
|
return r
|
||||||
|
}
|
||||||
35
rest/httpc/internal/metricsinterceptor_test.go
Normal file
35
rest/httpc/internal/metricsinterceptor_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMetricsInterceptor(t *testing.T) {
|
||||||
|
c := gomock.NewController(t)
|
||||||
|
defer c.Finish()
|
||||||
|
|
||||||
|
logx.Disable()
|
||||||
|
|
||||||
|
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}))
|
||||||
|
defer svr.Close()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
|
||||||
|
assert.NotNil(t, req)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
interceptor := MetricsInterceptor("test", nil)
|
||||||
|
req, handler := interceptor(req)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
assert.NotNil(t, resp)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
handler(resp, err)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package httpx
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -141,10 +142,10 @@ func doHandleError(w http.ResponseWriter, err error, handler func(error) (int, a
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e, ok := body.(error)
|
switch v := body.(type) {
|
||||||
if ok {
|
case error:
|
||||||
http.Error(w, e.Error(), code)
|
http.Error(w, v.Error(), code)
|
||||||
} else {
|
default:
|
||||||
writeJson(w, code, body)
|
writeJson(w, code, body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,7 +163,7 @@ func doWriteJson(w http.ResponseWriter, code int, v any) error {
|
|||||||
if n, err := w.Write(bs); err != nil {
|
if n, err := w.Write(bs); err != nil {
|
||||||
// http.ErrHandlerTimeout has been handled by http.TimeoutHandler,
|
// http.ErrHandlerTimeout has been handled by http.TimeoutHandler,
|
||||||
// so it's ignored here.
|
// so it's ignored here.
|
||||||
if err != http.ErrHandlerTimeout {
|
if !errors.Is(err, http.ErrHandlerTimeout) {
|
||||||
return fmt.Errorf("write response failed, error: %w", err)
|
return fmt.Errorf("write response failed, error: %w", err)
|
||||||
}
|
}
|
||||||
} else if n < len(bs) {
|
} else if n < len(bs) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -42,14 +43,14 @@ func start(host string, port int, handler http.Handler, run func(svr *http.Serve
|
|||||||
}
|
}
|
||||||
healthManager := health.NewHealthManager(fmt.Sprintf("%s-%s:%d", probeNamePrefix, host, port))
|
healthManager := health.NewHealthManager(fmt.Sprintf("%s-%s:%d", probeNamePrefix, host, port))
|
||||||
|
|
||||||
waitForCalled := proc.AddWrapUpListener(func() {
|
waitForCalled := proc.AddShutdownListener(func() {
|
||||||
healthManager.MarkNotReady()
|
healthManager.MarkNotReady()
|
||||||
if e := server.Shutdown(context.Background()); e != nil {
|
if e := server.Shutdown(context.Background()); e != nil {
|
||||||
logx.Error(e)
|
logx.Error(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == http.ErrServerClosed {
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
waitForCalled()
|
waitForCalled()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package rest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
@@ -307,7 +308,7 @@ func WithUnsignedCallback(callback handler.UnsignedCallback) RunOption {
|
|||||||
|
|
||||||
func handleError(err error) {
|
func handleError(err error) {
|
||||||
// ErrServerClosed means the server is closed manually
|
// ErrServerClosed means the server is closed manually
|
||||||
if err == nil || err == http.ErrServerClosed {
|
if err == nil || errors.Is(err, http.ErrServerClosed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/parser"
|
"github.com/zeromicro/go-zero/tools/goctl/api/parser"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
|
"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
|
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
|
||||||
)
|
)
|
||||||
@@ -53,7 +54,10 @@ func TestParser(t *testing.T) {
|
|||||||
filename := "greet.api"
|
filename := "greet.api"
|
||||||
err := os.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
|
err := os.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer os.Remove(filename)
|
env.Set(t, env.GoctlExperimental, "off")
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Remove(filename)
|
||||||
|
})
|
||||||
|
|
||||||
api, err := parser.Parse(filename)
|
api, err := parser.Parse(filename)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ func (p *Parser) invokeImportedApi(filename string, imports []*ImportExpr) ([]*A
|
|||||||
}
|
}
|
||||||
// ignore already imported file
|
// ignore already imported file
|
||||||
if p.alreadyImported(impPath) {
|
if p.alreadyImported(impPath) {
|
||||||
|
p.importStatck.pop()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p.fileMap[impPath] = PlaceHolder{}
|
p.fileMap[impPath] = PlaceHolder{}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFileSplitor(t *testing.T) {
|
func TestFileSplitor(t *testing.T) {
|
||||||
|
t.Skip("skip this test because it is used to split the apiparser_parser.go file by developer.")
|
||||||
dir := "."
|
dir := "."
|
||||||
data, err := os.ReadFile(filepath.Join(dir, "apiparser_parser.go"))
|
data, err := os.ReadFile(filepath.Join(dir, "apiparser_parser.go"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -174,17 +174,15 @@ func (p parser) findDefinedType(name string) (*spec.Type, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p parser) fieldToMember(field *ast.TypeField) spec.Member {
|
func (p parser) fieldToMember(field *ast.TypeField) spec.Member {
|
||||||
name := ""
|
var name string
|
||||||
tag := ""
|
var tag string
|
||||||
if !field.IsAnonymous {
|
if !field.IsAnonymous {
|
||||||
name = field.Name.Text()
|
name = field.Name.Text()
|
||||||
if field.Tag == nil {
|
if field.Tag != nil {
|
||||||
panic(fmt.Sprintf("error: line %d:%d field %s has no tag",
|
tag = field.Tag.Text()
|
||||||
field.Name.Line(), field.Name.Column(), field.Name.Text()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tag = field.Tag.Text()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec.Member{
|
return spec.Member{
|
||||||
Name: name,
|
Name: name,
|
||||||
Type: p.astTypeToSpec(field.DataType),
|
Type: p.astTypeToSpec(field.DataType),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/zeromicro/go-zero/tools/goctl
|
module github.com/zeromicro/go-zero/tools/goctl
|
||||||
|
|
||||||
go 1.18
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||||
@@ -15,9 +15,9 @@ require (
|
|||||||
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.5
|
github.com/zeromicro/ddl-parser v1.0.5
|
||||||
github.com/zeromicro/go-zero v1.5.5
|
github.com/zeromicro/go-zero v1.5.6
|
||||||
golang.org/x/text v0.13.0
|
golang.org/x/text v0.13.0
|
||||||
google.golang.org/grpc v1.58.2
|
google.golang.org/grpc v1.59.0
|
||||||
google.golang.org/protobuf v1.31.0
|
google.golang.org/protobuf v1.31.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ 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.9 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.0 // 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.16.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
|
||||||
@@ -90,16 +90,16 @@ require (
|
|||||||
go.uber.org/automaxprocs v1.5.3 // 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.12.0 // indirect
|
golang.org/x/crypto v0.14.0 // indirect
|
||||||
golang.org/x/net v0.14.0 // indirect
|
golang.org/x/net v0.17.0 // indirect
|
||||||
golang.org/x/oauth2 v0.10.0 // indirect
|
golang.org/x/oauth2 v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.11.0 // indirect
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
golang.org/x/term v0.11.0 // indirect
|
golang.org/x/term v0.13.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-20230711160842-782d3b101e98 // indirect
|
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // 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.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
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||||
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
|
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
@@ -178,7 +178,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
|
|||||||
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/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.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||||
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.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||||
@@ -241,8 +241,8 @@ 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.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.0/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=
|
||||||
@@ -296,8 +296,8 @@ 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.5 h1:LaVqHdzMTjasua1yYpIYaksxKqRzFrEukj2Wi2EbWaQ=
|
github.com/zeromicro/ddl-parser v1.0.5 h1:LaVqHdzMTjasua1yYpIYaksxKqRzFrEukj2Wi2EbWaQ=
|
||||||
github.com/zeromicro/ddl-parser v1.0.5/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.5 h1:qEHnDuCBu/gDBmfWEZXYow6ZmWmzsrJTjtjSMVm4SiY=
|
github.com/zeromicro/go-zero v1.5.6 h1:vBzrLaj+xQySBAeMBA6vhPxNgasz0T3WE60s98H0Bb4=
|
||||||
github.com/zeromicro/go-zero v1.5.5/go.mod h1:AGCspTFitHzYjl5ddAmYWLfdt341+BrhefqlwO45UbU=
|
github.com/zeromicro/go-zero v1.5.6/go.mod h1:FX2a2MQd5EvAYO7neJBm2GAmPU5XfFnj3JMM/qj+kpY=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs=
|
go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.9/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.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE=
|
||||||
@@ -346,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.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
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=
|
||||||
@@ -407,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.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
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.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
|
||||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
|
||||||
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=
|
||||||
@@ -459,11 +459,11 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-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.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.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.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
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=
|
||||||
@@ -580,12 +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-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
|
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
|
||||||
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0=
|
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb h1:Isk1sSH7bovx8Rti2wZK0UZF6oraBDK74uoyLEEVFN0=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||||
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.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I=
|
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||||
google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||||
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=
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// BuildVersion is the version of goctl.
|
// BuildVersion is the version of goctl.
|
||||||
const BuildVersion = "1.5.5"
|
const BuildVersion = "1.6.0"
|
||||||
|
|
||||||
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}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ func init() {
|
|||||||
pgDatasourceCmdFlags.StringVar(&command.VarStringHome, "home")
|
pgDatasourceCmdFlags.StringVar(&command.VarStringHome, "home")
|
||||||
pgDatasourceCmdFlags.StringVar(&command.VarStringRemote, "remote")
|
pgDatasourceCmdFlags.StringVar(&command.VarStringRemote, "remote")
|
||||||
pgDatasourceCmdFlags.StringVar(&command.VarStringBranch, "branch")
|
pgDatasourceCmdFlags.StringVar(&command.VarStringBranch, "branch")
|
||||||
|
pgCmd.PersistentFlags().StringSliceVarPWithDefaultValue(&command.VarStringSliceIgnoreColumns,
|
||||||
|
"ignore-columns", "i", []string{"create_at", "created_at", "create_time", "update_at", "updated_at", "update_time"})
|
||||||
|
|
||||||
mongoCmdFlags.StringSliceVarP(&mongo.VarStringSliceType, "type", "t")
|
mongoCmdFlags.StringSliceVarP(&mongo.VarStringSliceType, "type", "t")
|
||||||
mongoCmdFlags.BoolVarP(&mongo.VarBoolCache, "cache", "c")
|
mongoCmdFlags.BoolVarP(&mongo.VarBoolCache, "cache", "c")
|
||||||
@@ -67,7 +69,8 @@ func init() {
|
|||||||
mongoCmdFlags.StringVar(&mongo.VarStringBranch, "branch")
|
mongoCmdFlags.StringVar(&mongo.VarStringBranch, "branch")
|
||||||
|
|
||||||
mysqlCmd.PersistentFlags().BoolVar(&command.VarBoolStrict, "strict")
|
mysqlCmd.PersistentFlags().BoolVar(&command.VarBoolStrict, "strict")
|
||||||
mysqlCmd.PersistentFlags().StringSliceVarPWithDefaultValue(&command.VarStringSliceIgnoreColumns, "ignore-columns", "i", []string{"create_at", "created_at", "create_time", "update_at", "updated_at", "update_time"})
|
mysqlCmd.PersistentFlags().StringSliceVarPWithDefaultValue(&command.VarStringSliceIgnoreColumns,
|
||||||
|
"ignore-columns", "i", []string{"create_at", "created_at", "create_time", "update_at", "updated_at", "update_time"})
|
||||||
|
|
||||||
mysqlCmd.AddCommand(datasourceCmd, ddlCmd)
|
mysqlCmd.AddCommand(datasourceCmd, ddlCmd)
|
||||||
pgCmd.AddCommand(pgDatasourceCmd)
|
pgCmd.AddCommand(pgDatasourceCmd)
|
||||||
|
|||||||
@@ -216,8 +216,9 @@ func PostgreSqlDataSource(_ *cobra.Command, _ []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ignoreColumns := mergeColumns(VarStringSliceIgnoreColumns)
|
||||||
|
|
||||||
return fromPostgreSqlDataSource(url, pattern, dir, schema, cfg, cache, idea, VarBoolStrict)
|
return fromPostgreSqlDataSource(url, pattern, dir, schema, cfg, cache, idea, VarBoolStrict, ignoreColumns)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ddlArg struct {
|
type ddlArg struct {
|
||||||
@@ -329,7 +330,7 @@ func fromMysqlDataSource(arg dataSourceArg) error {
|
|||||||
return generator.StartFromInformationSchema(matchTables, arg.cache, arg.strict)
|
return generator.StartFromInformationSchema(matchTables, arg.cache, arg.strict)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromPostgreSqlDataSource(url, pattern, dir, schema string, cfg *config.Config, cache, idea, strict bool) error {
|
func fromPostgreSqlDataSource(url, pattern, dir, schema string, cfg *config.Config, cache, idea, strict bool, ignoreColumns []string) error {
|
||||||
log := console.NewConsole(idea)
|
log := console.NewConsole(idea)
|
||||||
if len(url) == 0 {
|
if len(url) == 0 {
|
||||||
log.Error("%v", "expected data source of postgresql, but nothing found")
|
log.Error("%v", "expected data source of postgresql, but nothing found")
|
||||||
@@ -376,7 +377,7 @@ func fromPostgreSqlDataSource(url, pattern, dir, schema string, cfg *config.Conf
|
|||||||
return errors.New("no tables matched")
|
return errors.New("no tables matched")
|
||||||
}
|
}
|
||||||
|
|
||||||
generator, err := gen.NewDefaultGenerator(dir, cfg, gen.WithConsoleOption(log), gen.WithPostgreSql())
|
generator, err := gen.NewDefaultGenerator(dir, cfg, gen.WithConsoleOption(log), gen.WithPostgreSql(), gen.WithIgnoreColumns(ignoreColumns))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,9 +76,9 @@ func TestGenCacheKeys(t *testing.T) {
|
|||||||
VarExpression: `cacheGoZeroUserIdPrefix = "cache:goZero:user:id:"`,
|
VarExpression: `cacheGoZeroUserIdPrefix = "cache:goZero:user:id:"`,
|
||||||
KeyLeft: "goZeroUserIdKey",
|
KeyLeft: "goZeroUserIdKey",
|
||||||
KeyRight: `fmt.Sprintf("%s%v", cacheGoZeroUserIdPrefix, id)`,
|
KeyRight: `fmt.Sprintf("%s%v", cacheGoZeroUserIdPrefix, id)`,
|
||||||
DataKeyRight: `fmt.Sprintf("%s%v", cacheGoZeroUserIdPrefix, data.ID)`,
|
DataKeyRight: `fmt.Sprintf("%s%v", cacheGoZeroUserIdPrefix, data.Id)`,
|
||||||
KeyExpression: `goZeroUserIdKey := fmt.Sprintf("%s%v", cacheGoZeroUserIdPrefix, id)`,
|
KeyExpression: `goZeroUserIdKey := fmt.Sprintf("%s%v", cacheGoZeroUserIdPrefix, id)`,
|
||||||
DataKeyExpression: `goZeroUserIdKey := fmt.Sprintf("%s%v", cacheGoZeroUserIdPrefix, data.ID)`,
|
DataKeyExpression: `goZeroUserIdKey := fmt.Sprintf("%s%v", cacheGoZeroUserIdPrefix, data.Id)`,
|
||||||
FieldNameJoin: []string{"id"},
|
FieldNameJoin: []string{"id"},
|
||||||
})
|
})
|
||||||
}())
|
}())
|
||||||
@@ -170,9 +170,9 @@ func TestGenCacheKeys(t *testing.T) {
|
|||||||
VarExpression: `cacheUserIdPrefix = "cache:user:id:"`,
|
VarExpression: `cacheUserIdPrefix = "cache:user:id:"`,
|
||||||
KeyLeft: "userIdKey",
|
KeyLeft: "userIdKey",
|
||||||
KeyRight: `fmt.Sprintf("%s%v", cacheUserIdPrefix, id)`,
|
KeyRight: `fmt.Sprintf("%s%v", cacheUserIdPrefix, id)`,
|
||||||
DataKeyRight: `fmt.Sprintf("%s%v", cacheUserIdPrefix, data.ID)`,
|
DataKeyRight: `fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)`,
|
||||||
KeyExpression: `userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)`,
|
KeyExpression: `userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)`,
|
||||||
DataKeyExpression: `userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.ID)`,
|
DataKeyExpression: `userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)`,
|
||||||
FieldNameJoin: []string{"id"},
|
FieldNameJoin: []string{"id"},
|
||||||
})
|
})
|
||||||
}())
|
}())
|
||||||
|
|||||||
@@ -5,9 +5,3 @@ func new{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c ca
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *default{{.upperStartCamelObject}}Model) withSession(session sqlx.Session) *default{{.upperStartCamelObject}}Model {
|
|
||||||
return &default{{.upperStartCamelObject}}Model{
|
|
||||||
{{if .withCache}}CachedConn:m.CachedConn.WithSession(session){{else}}conn:sqlx.NewSqlConnFromSession(session){{end}},
|
|
||||||
table: {{.table}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type (
|
|||||||
// and implement the added methods in custom{{.upperStartCamelObject}}Model.
|
// and implement the added methods in custom{{.upperStartCamelObject}}Model.
|
||||||
{{.upperStartCamelObject}}Model interface {
|
{{.upperStartCamelObject}}Model interface {
|
||||||
{{.lowerStartCamelObject}}Model
|
{{.lowerStartCamelObject}}Model
|
||||||
|
{{if not .withCache}}withSession(session sqlx.Session) {{.upperStartCamelObject}}Model{{end}}
|
||||||
}
|
}
|
||||||
|
|
||||||
custom{{.upperStartCamelObject}}Model struct {
|
custom{{.upperStartCamelObject}}Model struct {
|
||||||
@@ -28,3 +29,10 @@ func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c ca
|
|||||||
default{{.upperStartCamelObject}}Model: new{{.upperStartCamelObject}}Model(conn{{if .withCache}}, c, opts...{{end}}),
|
default{{.upperStartCamelObject}}Model: new{{.upperStartCamelObject}}Model(conn{{if .withCache}}, c, opts...{{end}}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{{if not .withCache}}
|
||||||
|
func (m *custom{{.upperStartCamelObject}}Model) withSession(session sqlx.Session) {{.upperStartCamelObject}}Model {
|
||||||
|
return New{{.upperStartCamelObject}}Model(sqlx.NewSqlConnFromSession(session))
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func TestStudentModel(t *testing.T) {
|
|||||||
Valid: true,
|
Valid: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := mockStudent(func(mock sqlmock.Sqlmock) {
|
err := mockStudent(t, func(mock sqlmock.Sqlmock) {
|
||||||
mock.ExpectExec(fmt.Sprintf("insert into %s", testTable)).
|
mock.ExpectExec(fmt.Sprintf("insert into %s", testTable)).
|
||||||
WithArgs(data.Class, data.Name, data.Age, data.Score).
|
WithArgs(data.Class, data.Name, data.Age, data.Score).
|
||||||
WillReturnResult(sqlmock.NewResult(testInsertId, testRowsAffected))
|
WillReturnResult(sqlmock.NewResult(testInsertId, testRowsAffected))
|
||||||
@@ -61,7 +61,7 @@ func TestStudentModel(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
err = mockStudent(func(mock sqlmock.Sqlmock) {
|
err = mockStudent(t, func(mock sqlmock.Sqlmock) {
|
||||||
mock.ExpectQuery(fmt.Sprintf("select (.+) from %s", testTable)).
|
mock.ExpectQuery(fmt.Sprintf("select (.+) from %s", testTable)).
|
||||||
WithArgs(testInsertId).
|
WithArgs(testInsertId).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "class", "name", "age", "score", "create_time", "update_time"}).AddRow(testInsertId, data.Class, data.Name, data.Age, data.Score, testTimeValue, testTimeValue))
|
WillReturnRows(sqlmock.NewRows([]string{"id", "class", "name", "age", "score", "create_time", "update_time"}).AddRow(testInsertId, data.Class, data.Name, data.Age, data.Score, testTimeValue, testTimeValue))
|
||||||
@@ -79,7 +79,7 @@ func TestStudentModel(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
err = mockStudent(func(mock sqlmock.Sqlmock) {
|
err = mockStudent(t, func(mock sqlmock.Sqlmock) {
|
||||||
mock.ExpectExec(fmt.Sprintf("update %s", testTable)).WithArgs(data.Class, testUpdateName, data.Age, data.Score, testInsertId).WillReturnResult(sqlmock.NewResult(testInsertId, testRowsAffected))
|
mock.ExpectExec(fmt.Sprintf("update %s", testTable)).WithArgs(data.Class, testUpdateName, data.Age, data.Score, testInsertId).WillReturnResult(sqlmock.NewResult(testInsertId, testRowsAffected))
|
||||||
}, func(m StudentModel, redis *redis.Redis) {
|
}, func(m StudentModel, redis *redis.Redis) {
|
||||||
data.Name = testUpdateName
|
data.Name = testUpdateName
|
||||||
@@ -93,7 +93,7 @@ func TestStudentModel(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
data.Name = testUpdateName
|
data.Name = testUpdateName
|
||||||
err = mockStudent(func(mock sqlmock.Sqlmock) {
|
err = mockStudent(t, func(mock sqlmock.Sqlmock) {
|
||||||
mock.ExpectQuery(fmt.Sprintf("select (.+) from %s ", testTable)).
|
mock.ExpectQuery(fmt.Sprintf("select (.+) from %s ", testTable)).
|
||||||
WithArgs(testInsertId).
|
WithArgs(testInsertId).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "class", "name", "age", "score", "create_time", "update_time"}).AddRow(testInsertId, data.Class, data.Name, data.Age, data.Score, testTimeValue, testTimeValue))
|
WillReturnRows(sqlmock.NewRows([]string{"id", "class", "name", "age", "score", "create_time", "update_time"}).AddRow(testInsertId, data.Class, data.Name, data.Age, data.Score, testTimeValue, testTimeValue))
|
||||||
@@ -111,7 +111,7 @@ func TestStudentModel(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
err = mockStudent(func(mock sqlmock.Sqlmock) {
|
err = mockStudent(t, func(mock sqlmock.Sqlmock) {
|
||||||
mock.ExpectQuery(fmt.Sprintf("select (.+) from %s ", testTable)).
|
mock.ExpectQuery(fmt.Sprintf("select (.+) from %s ", testTable)).
|
||||||
WithArgs(class, testUpdateName).
|
WithArgs(class, testUpdateName).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "class", "name", "age", "score", "create_time", "update_time"}).AddRow(testInsertId, data.Class, data.Name, data.Age, data.Score, testTimeValue, testTimeValue))
|
WillReturnRows(sqlmock.NewRows([]string{"id", "class", "name", "age", "score", "create_time", "update_time"}).AddRow(testInsertId, data.Class, data.Name, data.Age, data.Score, testTimeValue, testTimeValue))
|
||||||
@@ -126,7 +126,7 @@ func TestStudentModel(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
err = mockStudent(func(mock sqlmock.Sqlmock) {
|
err = mockStudent(t, func(mock sqlmock.Sqlmock) {
|
||||||
mock.ExpectExec(fmt.Sprintf("delete from %s where `id` = ?", testTable)).WithArgs(testInsertId).WillReturnResult(sqlmock.NewResult(testInsertId, testRowsAffected))
|
mock.ExpectExec(fmt.Sprintf("delete from %s where `id` = ?", testTable)).WithArgs(testInsertId).WillReturnResult(sqlmock.NewResult(testInsertId, testRowsAffected))
|
||||||
}, func(m StudentModel, redis *redis.Redis) {
|
}, func(m StudentModel, redis *redis.Redis) {
|
||||||
err = m.Delete(testInsertId, class, testUpdateName)
|
err = m.Delete(testInsertId, class, testUpdateName)
|
||||||
@@ -228,7 +228,7 @@ func TestUserModel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// with cache
|
// with cache
|
||||||
func mockStudent(mockFn func(mock sqlmock.Sqlmock), fn func(m StudentModel, r *redis.Redis)) error {
|
func mockStudent(t *testing.T, mockFn func(mock sqlmock.Sqlmock), fn func(m StudentModel, r *redis.Redis)) error {
|
||||||
db, mock, err := sqlmock.New()
|
db, mock, err := sqlmock.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -241,13 +241,7 @@ func mockStudent(mockFn func(mock sqlmock.Sqlmock), fn func(m StudentModel, r *r
|
|||||||
mock.ExpectCommit()
|
mock.ExpectCommit()
|
||||||
|
|
||||||
conn := mocksql.NewMockConn(db)
|
conn := mocksql.NewMockConn(db)
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
m := NewStudentModel(conn, cache.CacheConf{
|
m := NewStudentModel(conn, cache.CacheConf{
|
||||||
{
|
{
|
||||||
RedisConf: redis.RedisConf{
|
RedisConf: redis.RedisConf{
|
||||||
|
|||||||
@@ -128,7 +128,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +163,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
tools/goctl/pkg/env/env.go
vendored
9
tools/goctl/pkg/env/env.go
vendored
@@ -7,6 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
|
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
|
||||||
sortedmap "github.com/zeromicro/go-zero/tools/goctl/pkg/collection"
|
sortedmap "github.com/zeromicro/go-zero/tools/goctl/pkg/collection"
|
||||||
@@ -111,6 +112,14 @@ func Get(key string) string {
|
|||||||
return GetOr(key, "")
|
return GetOr(key, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set sets the environment variable for testing
|
||||||
|
func Set(t *testing.T, key, value string) {
|
||||||
|
goctlEnv.SetKV(key, value)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
goctlEnv.Remove(key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func GetOr(key, def string) string {
|
func GetOr(key, def string) string {
|
||||||
return goctlEnv.GetStringOr(key, def)
|
return goctlEnv.GetStringOr(key, def)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package parser
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||||
@@ -18,13 +19,14 @@ type Analyzer struct {
|
|||||||
|
|
||||||
func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
|
func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
|
||||||
isLiteralType := func(dt ast.DataType) bool {
|
isLiteralType := func(dt ast.DataType) bool {
|
||||||
_, ok := dt.(*ast.BaseDataType)
|
if _, ok := dt.(*ast.BaseDataType); ok {
|
||||||
if ok {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
_, ok = dt.(*ast.AnyDataType)
|
|
||||||
|
_, ok := dt.(*ast.AnyDataType)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := (in).(type) {
|
switch v := (in).(type) {
|
||||||
case *ast.BaseDataType:
|
case *ast.BaseDataType:
|
||||||
raw := v.RawText()
|
raw := v.RawText()
|
||||||
@@ -33,6 +35,7 @@ func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
|
|||||||
RawName: raw,
|
RawName: raw,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec.DefineStruct{RawName: raw}, nil
|
return spec.DefineStruct{RawName: raw}, nil
|
||||||
case *ast.AnyDataType:
|
case *ast.AnyDataType:
|
||||||
return nil, ast.SyntaxError(v.Pos(), "unsupported any type")
|
return nil, ast.SyntaxError(v.Pos(), "unsupported any type")
|
||||||
@@ -47,10 +50,12 @@ func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
|
|||||||
if !v.Key.CanEqual() {
|
if !v.Key.CanEqual() {
|
||||||
return nil, ast.SyntaxError(v.Pos(), "map key <%T> must be equal data type", v)
|
return nil, ast.SyntaxError(v.Pos(), "map key <%T> must be equal data type", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := a.astTypeToSpec(v.Value)
|
value, err := a.astTypeToSpec(v.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec.MapType{
|
return spec.MapType{
|
||||||
RawName: v.RawText(),
|
RawName: v.RawText(),
|
||||||
Key: v.RawText(),
|
Key: v.RawText(),
|
||||||
@@ -66,6 +71,7 @@ func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec.PointerType{
|
return spec.PointerType{
|
||||||
RawName: v.RawText(),
|
RawName: v.RawText(),
|
||||||
Type: value,
|
Type: value,
|
||||||
@@ -74,10 +80,12 @@ func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
|
|||||||
if v.Length.Token.Type == token.ELLIPSIS {
|
if v.Length.Token.Type == token.ELLIPSIS {
|
||||||
return nil, ast.SyntaxError(v.Pos(), "Array: unsupported dynamic length")
|
return nil, ast.SyntaxError(v.Pos(), "Array: unsupported dynamic length")
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := a.astTypeToSpec(v.DataType)
|
value, err := a.astTypeToSpec(v.DataType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec.ArrayType{
|
return spec.ArrayType{
|
||||||
RawName: v.RawText(),
|
RawName: v.RawText(),
|
||||||
Value: value,
|
Value: value,
|
||||||
@@ -87,6 +95,7 @@ func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec.ArrayType{
|
return spec.ArrayType{
|
||||||
RawName: v.RawText(),
|
RawName: v.RawText(),
|
||||||
Value: value,
|
Value: value,
|
||||||
@@ -101,7 +110,27 @@ func (a *Analyzer) convert2Spec() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.fillService()
|
if err := a.fillService(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(a.spec.Types, func(i, j int) bool {
|
||||||
|
return a.spec.Types[i].Name() < a.spec.Types[j].Name()
|
||||||
|
})
|
||||||
|
|
||||||
|
groups := make([]spec.Group, 0, len(a.spec.Service.Groups))
|
||||||
|
for _, v := range a.spec.Service.Groups {
|
||||||
|
sort.SliceStable(v.Routes, func(i, j int) bool {
|
||||||
|
return v.Routes[i].Path < v.Routes[j].Path
|
||||||
|
})
|
||||||
|
groups = append(groups, v)
|
||||||
|
}
|
||||||
|
sort.SliceStable(groups, func(i, j int) bool {
|
||||||
|
return groups[i].Annotation.Properties["group"] < groups[j].Annotation.Properties["group"]
|
||||||
|
})
|
||||||
|
a.spec.Service.Groups = groups
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Analyzer) convertAtDoc(atDoc ast.AtDocStmt) spec.AtDoc {
|
func (a *Analyzer) convertAtDoc(atDoc ast.AtDocStmt) spec.AtDoc {
|
||||||
@@ -146,6 +175,7 @@ func (a *Analyzer) fieldToMember(field *ast.ElemExpr) (spec.Member, error) {
|
|||||||
if field.Tag != nil {
|
if field.Tag != nil {
|
||||||
m.Tag = field.Tag.Token.Text
|
m.Tag = field.Tag.Token.Text
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,8 +272,7 @@ func (a *Analyzer) fillTypes() error {
|
|||||||
for _, item := range a.api.TypeStmt {
|
for _, item := range a.api.TypeStmt {
|
||||||
switch v := (item).(type) {
|
switch v := (item).(type) {
|
||||||
case *ast.TypeLiteralStmt:
|
case *ast.TypeLiteralStmt:
|
||||||
err := a.fillTypeExpr(v.Expr)
|
if err := a.fillTypeExpr(v.Expr); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case *ast.TypeGroupStmt:
|
case *ast.TypeGroupStmt:
|
||||||
|
|||||||
@@ -31,18 +31,40 @@ func convert2API(a *ast.AST, importManager map[string]placeholder.Type) (*API, e
|
|||||||
one := a.Stmts[0]
|
one := a.Stmts[0]
|
||||||
syntax, ok := one.(*ast.SyntaxStmt)
|
syntax, ok := one.(*ast.SyntaxStmt)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ast.SyntaxError(one.Pos(), "expected syntax statement, got <%T>", one)
|
syntax = &ast.SyntaxStmt{
|
||||||
|
Syntax: ast.NewTokenNode(token.Token{
|
||||||
|
Type: token.IDENT,
|
||||||
|
Text: token.Syntax,
|
||||||
|
}),
|
||||||
|
Assign: ast.NewTokenNode(token.Token{
|
||||||
|
Type: token.ASSIGN,
|
||||||
|
Text: "=",
|
||||||
|
}),
|
||||||
|
Value: ast.NewTokenNode(token.Token{
|
||||||
|
Type: token.STRING,
|
||||||
|
Text: `"v1"`,
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
api.Syntax = syntax
|
|
||||||
|
|
||||||
for i := 1; i < len(a.Stmts); i++ {
|
api.Syntax = syntax
|
||||||
|
var hasSyntax, hasInfo bool
|
||||||
|
for i := 0; i < len(a.Stmts); i++ {
|
||||||
one := a.Stmts[i]
|
one := a.Stmts[i]
|
||||||
switch val := one.(type) {
|
switch val := one.(type) {
|
||||||
case *ast.SyntaxStmt:
|
case *ast.SyntaxStmt:
|
||||||
return nil, ast.DuplicateStmtError(val.Pos(), "duplicate syntax statement")
|
if hasSyntax {
|
||||||
|
return nil, ast.DuplicateStmtError(val.Pos(), "duplicate syntax statement")
|
||||||
|
} else {
|
||||||
|
hasSyntax = true
|
||||||
|
}
|
||||||
case *ast.InfoStmt:
|
case *ast.InfoStmt:
|
||||||
if api.info != nil {
|
if api.info != nil {
|
||||||
return nil, ast.DuplicateStmtError(val.Pos(), "duplicate info statement")
|
if hasInfo {
|
||||||
|
return nil, ast.DuplicateStmtError(val.Pos(), "duplicate info statement")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasInfo = true
|
||||||
}
|
}
|
||||||
api.info = val
|
api.info = val
|
||||||
case ast.ImportStmt:
|
case ast.ImportStmt:
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// test case: expected syntax statement
|
|
||||||
info ()
|
|
||||||
|
|
||||||
-----
|
|
||||||
// test case: duplicate syntax statement
|
// test case: duplicate syntax statement
|
||||||
syntax = "v1"
|
syntax = "v1"
|
||||||
syntax = "v1"
|
syntax = "v1"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.18
|
FROM golang:1.19
|
||||||
|
|
||||||
ENV TZ Asia/Shanghai
|
ENV TZ Asia/Shanghai
|
||||||
ENV GOPROXY https://goproxy.cn,direct
|
ENV GOPROXY https://goproxy.cn,direct
|
||||||
|
|||||||
@@ -81,13 +81,13 @@ func Test_getRealModule(t *testing.T) {
|
|||||||
"Path":"foo",
|
"Path":"foo",
|
||||||
"Dir":"/home/foo",
|
"Dir":"/home/foo",
|
||||||
"GoMod":"/home/foo/go.mod",
|
"GoMod":"/home/foo/go.mod",
|
||||||
"GoVersion":"go1.18"
|
"GoVersion":"go1.19"
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
"Path":"bar",
|
"Path":"bar",
|
||||||
"Dir":"/home/bar",
|
"Dir":"/home/bar",
|
||||||
"GoMod":"/home/bar/go.mod",
|
"GoMod":"/home/bar/go.mod",
|
||||||
"GoVersion":"go1.18"
|
"GoVersion":"go1.19"
|
||||||
}`, nil
|
}`, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -95,7 +95,7 @@ func Test_getRealModule(t *testing.T) {
|
|||||||
Path: "bar",
|
Path: "bar",
|
||||||
Dir: "/home/bar",
|
Dir: "/home/bar",
|
||||||
GoMod: "/home/bar/go.mod",
|
GoMod: "/home/bar/go.mod",
|
||||||
GoVersion: "go1.18",
|
GoVersion: "go1.19",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -143,26 +143,26 @@ func TestDecodePackages(t *testing.T) {
|
|||||||
"Path":"foo",
|
"Path":"foo",
|
||||||
"Dir":"/home/foo",
|
"Dir":"/home/foo",
|
||||||
"GoMod":"/home/foo/go.mod",
|
"GoMod":"/home/foo/go.mod",
|
||||||
"GoVersion":"go1.18"
|
"GoVersion":"go1.19"
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
"Path":"bar",
|
"Path":"bar",
|
||||||
"Dir":"/home/bar",
|
"Dir":"/home/bar",
|
||||||
"GoMod":"/home/bar/go.mod",
|
"GoMod":"/home/bar/go.mod",
|
||||||
"GoVersion":"go1.18"
|
"GoVersion":"go1.19"
|
||||||
}`),
|
}`),
|
||||||
want: []Module{
|
want: []Module{
|
||||||
{
|
{
|
||||||
Path: "foo",
|
Path: "foo",
|
||||||
Dir: "/home/foo",
|
Dir: "/home/foo",
|
||||||
GoMod: "/home/foo/go.mod",
|
GoMod: "/home/foo/go.mod",
|
||||||
GoVersion: "go1.18",
|
GoVersion: "go1.19",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Path: "bar",
|
Path: "bar",
|
||||||
Dir: "/home/bar",
|
Dir: "/home/bar",
|
||||||
GoMod: "/home/bar/go.mod",
|
GoMod: "/home/bar/go.mod",
|
||||||
GoVersion: "go1.18",
|
GoVersion: "go1.19",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -173,14 +173,14 @@ func TestDecodePackages(t *testing.T) {
|
|||||||
"Path":"foo",
|
"Path":"foo",
|
||||||
"Dir":"/home/foo",
|
"Dir":"/home/foo",
|
||||||
"GoMod":"/home/foo/go.mod",
|
"GoMod":"/home/foo/go.mod",
|
||||||
"GoVersion":"go1.18"
|
"GoVersion":"go1.19"
|
||||||
}`),
|
}`),
|
||||||
want: []Module{
|
want: []Module{
|
||||||
{
|
{
|
||||||
Path: "foo",
|
Path: "foo",
|
||||||
Dir: "/home/foo",
|
Dir: "/home/foo",
|
||||||
GoMod: "/home/foo/go.mod",
|
GoMod: "/home/foo/go.mod",
|
||||||
GoVersion: "go1.18",
|
GoVersion: "go1.19",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -110,3 +110,8 @@ func DontLogClientContentForMethod(method string) {
|
|||||||
func SetClientSlowThreshold(threshold time.Duration) {
|
func SetClientSlowThreshold(threshold time.Duration) {
|
||||||
clientinterceptors.SetSlowThreshold(threshold)
|
clientinterceptors.SetSlowThreshold(threshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCallTimeout return a call option with given timeout to make a method call.
|
||||||
|
func WithCallTimeout(timeout time.Duration) grpc.CallOption {
|
||||||
|
return clientinterceptors.WithCallTimeout(timeout)
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,30 +43,35 @@ func TestDepositServer_Deposit(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
amount float32
|
amount float32
|
||||||
|
timeout time.Duration
|
||||||
res *mock.DepositResponse
|
res *mock.DepositResponse
|
||||||
errCode codes.Code
|
errCode codes.Code
|
||||||
errMsg string
|
errMsg string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"invalid request with negative amount",
|
name: "invalid request with negative amount",
|
||||||
-1.11,
|
amount: -1.11,
|
||||||
nil,
|
errCode: codes.InvalidArgument,
|
||||||
codes.InvalidArgument,
|
errMsg: fmt.Sprintf("cannot deposit %v", -1.11),
|
||||||
fmt.Sprintf("cannot deposit %v", -1.11),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid request with non negative amount",
|
name: "valid request with non negative amount",
|
||||||
0.00,
|
res: &mock.DepositResponse{Ok: true},
|
||||||
&mock.DepositResponse{Ok: true},
|
errCode: codes.OK,
|
||||||
codes.OK,
|
|
||||||
"",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid request with long handling time",
|
name: "valid request with long handling time",
|
||||||
2000.00,
|
amount: 2000.00,
|
||||||
nil,
|
errCode: codes.DeadlineExceeded,
|
||||||
codes.DeadlineExceeded,
|
errMsg: "context deadline exceeded",
|
||||||
"context deadline exceeded",
|
},
|
||||||
|
{
|
||||||
|
name: "valid request with timeout call option",
|
||||||
|
amount: 2000.00,
|
||||||
|
timeout: time.Second * 3,
|
||||||
|
res: &mock.DepositResponse{Ok: true},
|
||||||
|
errCode: codes.OK,
|
||||||
|
errMsg: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,9 +161,22 @@ func TestDepositServer_Deposit(t *testing.T) {
|
|||||||
client := client
|
client := client
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
cli := mock.NewDepositServiceClient(client.Conn())
|
cli := mock.NewDepositServiceClient(client.Conn())
|
||||||
request := &mock.DepositRequest{Amount: tt.amount}
|
request := &mock.DepositRequest{Amount: tt.amount}
|
||||||
response, err := cli.Deposit(context.Background(), request)
|
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
response *mock.DepositResponse
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if tt.timeout > 0 {
|
||||||
|
response, err = cli.Deposit(ctx, request, WithCallTimeout(tt.timeout))
|
||||||
|
} else {
|
||||||
|
response, err = cli.Deposit(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
if response != nil {
|
if response != nil {
|
||||||
assert.True(t, len(response.String()) > 0)
|
assert.True(t, len(response.String()) > 0)
|
||||||
if response.GetOk() != tt.res.GetOk() {
|
if response.GetOk() != tt.res.GetOk() {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ type (
|
|||||||
ServerMiddlewaresConf = internal.ServerMiddlewaresConf
|
ServerMiddlewaresConf = internal.ServerMiddlewaresConf
|
||||||
// StatConf defines the stat config.
|
// StatConf defines the stat config.
|
||||||
StatConf = internal.StatConf
|
StatConf = internal.StatConf
|
||||||
|
// MethodTimeoutConf defines specified timeout for gRPC method.
|
||||||
|
MethodTimeoutConf = internal.MethodTimeoutConf
|
||||||
|
|
||||||
// A RpcClientConf is a rpc client config.
|
// A RpcClientConf is a rpc client config.
|
||||||
RpcClientConf struct {
|
RpcClientConf struct {
|
||||||
@@ -45,6 +47,8 @@ type (
|
|||||||
// grpc health check switch
|
// grpc health check switch
|
||||||
Health bool `json:",default=true"`
|
Health bool `json:",default=true"`
|
||||||
Middlewares ServerMiddlewaresConf
|
Middlewares ServerMiddlewaresConf
|
||||||
|
// setting specified timeout for gRPC method
|
||||||
|
MethodTimeouts []MethodTimeoutConf `json:",optional"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ var (
|
|||||||
Name: "duration_ms",
|
Name: "duration_ms",
|
||||||
Help: "rpc client requests duration(ms).",
|
Help: "rpc client requests duration(ms).",
|
||||||
Labels: []string{"method"},
|
Labels: []string{"method"},
|
||||||
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000},
|
Buckets: []float64{1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2000, 5000},
|
||||||
})
|
})
|
||||||
|
|
||||||
metricClientReqCodeTotal = metric.NewCounterVec(&metric.CounterVecOpts{
|
metricClientReqCodeTotal = metric.NewCounterVec(&metric.CounterVecOpts{
|
||||||
|
|||||||
@@ -7,17 +7,41 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TimeoutCallOption is a call option that controls timeout.
|
||||||
|
type TimeoutCallOption struct {
|
||||||
|
grpc.EmptyCallOption
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
// TimeoutInterceptor is an interceptor that controls timeout.
|
// TimeoutInterceptor is an interceptor that controls timeout.
|
||||||
func TimeoutInterceptor(timeout time.Duration) grpc.UnaryClientInterceptor {
|
func TimeoutInterceptor(timeout time.Duration) grpc.UnaryClientInterceptor {
|
||||||
return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn,
|
return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn,
|
||||||
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||||
if timeout <= 0 {
|
t := getTimeoutFromCallOptions(opts, timeout)
|
||||||
|
if t <= 0 {
|
||||||
return invoker(ctx, method, req, reply, cc, opts...)
|
return invoker(ctx, method, req, reply, cc, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
ctx, cancel := context.WithTimeout(ctx, t)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
return invoker(ctx, method, req, reply, cc, opts...)
|
return invoker(ctx, method, req, reply, cc, opts...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCallTimeout returns a call option that controls method call timeout.
|
||||||
|
func WithCallTimeout(timeout time.Duration) grpc.CallOption {
|
||||||
|
return TimeoutCallOption{
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeoutFromCallOptions(opts []grpc.CallOption, defaultTimeout time.Duration) time.Duration {
|
||||||
|
for _, opt := range opts {
|
||||||
|
if o, ok := opt.(TimeoutCallOption); ok {
|
||||||
|
return o.timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultTimeout
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user