mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-12 01:10:00 +08:00
Compare commits
218 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a8638fc85 | ||
|
|
837a9ffa03 | ||
|
|
d28cfe5f20 | ||
|
|
022c100dc9 | ||
|
|
426b09c356 | ||
|
|
40dc21e4cf | ||
|
|
9b114e3251 | ||
|
|
4c6234f108 | ||
|
|
3cdfcb05f1 | ||
|
|
9f5bfa0088 | ||
|
|
2d42c8fa00 | ||
|
|
10e7922597 | ||
|
|
6e34b55ba7 | ||
|
|
ed15ca04f4 | ||
|
|
295ec27e1b | ||
|
|
d1e702e8a3 | ||
|
|
d1bfb5ef61 | ||
|
|
e43357164c | ||
|
|
cd21c9fa74 | ||
|
|
cdd2fcbbc9 | ||
|
|
8d2db09d45 | ||
|
|
65905b914d | ||
|
|
80e3407be1 | ||
|
|
657d27213a | ||
|
|
8ac18a9422 | ||
|
|
d3ae9cfd49 | ||
|
|
d7f42161fd | ||
|
|
e03229cabe | ||
|
|
8403ed16ae | ||
|
|
d87d203c3b | ||
|
|
3ae6a882a7 | ||
|
|
41c980f00c | ||
|
|
f34d81ca2c | ||
|
|
004ee488a6 | ||
|
|
2e12cd2c99 | ||
|
|
2695c30886 | ||
|
|
c74fb988e0 | ||
|
|
e8a340c1c0 | ||
|
|
06e114e5a3 | ||
|
|
74ad681a66 | ||
|
|
e7bbc09093 | ||
|
|
1eb1450c43 | ||
|
|
9a724fe907 | ||
|
|
30e49f2939 | ||
|
|
a5407479a6 | ||
|
|
7fb5bab26b | ||
|
|
27249e021f | ||
|
|
d809795fec | ||
|
|
c9db9588b7 | ||
|
|
872c50b71a | ||
|
|
7c83155e4f | ||
|
|
358d86b8ae | ||
|
|
f4bb9f5635 | ||
|
|
5c6a3132eb | ||
|
|
2bd95aa007 | ||
|
|
e8376936d5 | ||
|
|
71c0288023 | ||
|
|
9e2f07a842 | ||
|
|
24fd34413f | ||
|
|
3f47251892 | ||
|
|
0b6bc69afa | ||
|
|
5b9bdc8d02 | ||
|
|
ded22e296e | ||
|
|
f0ed2370a3 | ||
|
|
6bf6cfdd01 | ||
|
|
5cc9eb0de4 | ||
|
|
f070d447ef | ||
|
|
f6d9e19ecb | ||
|
|
56807aabf6 | ||
|
|
861dcf2f36 | ||
|
|
c837dc21bb | ||
|
|
96a35ecf1a | ||
|
|
bdec5f2349 | ||
|
|
bc92b57bdb | ||
|
|
d8905b9e9e | ||
|
|
dec6309c55 | ||
|
|
10805577f5 | ||
|
|
a4d8286e36 | ||
|
|
84d2b64e7c | ||
|
|
6476da4a18 | ||
|
|
79eab0ea2f | ||
|
|
3b683fd498 | ||
|
|
d179b342b2 | ||
|
|
58874779e7 | ||
|
|
8829c31c0d | ||
|
|
b42f3fa047 | ||
|
|
9bdadf2381 | ||
|
|
20f665ede8 | ||
|
|
0325d8e92d | ||
|
|
2125977281 | ||
|
|
c26c187e11 | ||
|
|
4ef1859f0b | ||
|
|
407a6cbf9c | ||
|
|
76fc1ef460 | ||
|
|
423955c55f | ||
|
|
db95b3f0e3 | ||
|
|
4bee60eb7f | ||
|
|
7618139dad | ||
|
|
6fd08027ff | ||
|
|
b9e268aae8 | ||
|
|
4c1bb1148b | ||
|
|
50a6bbe6b9 | ||
|
|
dfb3cb510a | ||
|
|
519db812b4 | ||
|
|
3203f8e06b | ||
|
|
b71ac2042a | ||
|
|
d0f9e57022 | ||
|
|
aa68210cde | ||
|
|
280e837c9e | ||
|
|
f669e1226c | ||
|
|
cd15c19250 | ||
|
|
5b35fa17de | ||
|
|
9672298fa8 | ||
|
|
bf3ce16823 | ||
|
|
189721da16 | ||
|
|
a523ab1f93 | ||
|
|
7ea8b636d9 | ||
|
|
b2fea65faa | ||
|
|
a1fe8bf6cd | ||
|
|
67ee9e4391 | ||
|
|
9c1ee50497 | ||
|
|
7c842f22d0 | ||
|
|
14ec29991c | ||
|
|
c7f5aad83a | ||
|
|
e77747cff8 | ||
|
|
f2612db4b1 | ||
|
|
a21ff71373 | ||
|
|
fc04ad7854 | ||
|
|
fbf2eebc42 | ||
|
|
dc43430812 | ||
|
|
c6642bc2e6 | ||
|
|
bdca24dd3b | ||
|
|
00c5734021 | ||
|
|
33f87cf1f0 | ||
|
|
69935c1ba3 | ||
|
|
1fb356f328 | ||
|
|
0b0406f41a | ||
|
|
cc264dcf55 | ||
|
|
e024aebb66 | ||
|
|
f204729482 | ||
|
|
d20cf56a69 | ||
|
|
54d57c7d4b | ||
|
|
28a7c9d38f | ||
|
|
872e75e10d | ||
|
|
af1730079e | ||
|
|
04521e2d24 | ||
|
|
02adcccbf4 | ||
|
|
a74aaf1823 | ||
|
|
1eb2089c69 | ||
|
|
f7f3730e1a | ||
|
|
0ee7654407 | ||
|
|
16cc990fdd | ||
|
|
00061c2e5b | ||
|
|
6793f7a1de | ||
|
|
c8428a7f65 | ||
|
|
a5e1d0d0dc | ||
|
|
8270c7deed | ||
|
|
9f4a882a1b | ||
|
|
cb7b7cb72e | ||
|
|
603c93aa4a | ||
|
|
cb8d9d413a | ||
|
|
ff7443c6a7 | ||
|
|
b812e74d6f | ||
|
|
089cdaa75f | ||
|
|
476026e393 | ||
|
|
75952308f9 | ||
|
|
df0550d6dc | ||
|
|
e481b63b21 | ||
|
|
e47079f0f4 | ||
|
|
9b2a279948 | ||
|
|
db87fd3239 | ||
|
|
598fda0c97 | ||
|
|
b0e335e7b0 | ||
|
|
efdf475da4 | ||
|
|
22a1315136 | ||
|
|
5b22823018 | ||
|
|
9ccb997ed8 | ||
|
|
01c92a6bc5 | ||
|
|
c9a2a60e28 | ||
|
|
b0739d63c0 | ||
|
|
c22f84cb5f | ||
|
|
60450bab02 | ||
|
|
3e8cec5c78 | ||
|
|
74ee163761 | ||
|
|
ea4f680052 | ||
|
|
58cdba2c5d | ||
|
|
a2fbc14c70 | ||
|
|
158df8c270 | ||
|
|
30ec236a87 | ||
|
|
ac3653b3f9 | ||
|
|
8520db4fd9 | ||
|
|
14141fed62 | ||
|
|
5d86cc2f20 | ||
|
|
8a6e4b7580 | ||
|
|
453f949638 | ||
|
|
75a330184d | ||
|
|
546fcd8bab | ||
|
|
3022f93b6d | ||
|
|
8ffc392c66 | ||
|
|
ae7d85dadf | ||
|
|
e89268ac37 | ||
|
|
aaa3623404 | ||
|
|
8998f16054 | ||
|
|
94417be018 | ||
|
|
f300408fc0 | ||
|
|
aaa39e17a3 | ||
|
|
73906f996d | ||
|
|
73417f54db | ||
|
|
491213afb8 | ||
|
|
edf743cd72 | ||
|
|
78a88be787 | ||
|
|
9f6a574f97 | ||
|
|
ea01cc78f0 | ||
|
|
a87978568a | ||
|
|
14cecb9b31 | ||
|
|
0ce54100a4 | ||
|
|
d28ac35ff7 | ||
|
|
a5962f677f |
@@ -1,4 +1,3 @@
|
|||||||
|
comment: false
|
||||||
ignore:
|
ignore:
|
||||||
- "doc"
|
- "tools"
|
||||||
- "example"
|
|
||||||
- "tools"
|
|
||||||
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior, if applicable:
|
||||||
|
|
||||||
|
1. The code is
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
2. The error is
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Environments (please complete the following information):**
|
||||||
|
- OS: [e.g. Linux]
|
||||||
|
- go-zero version [e.g. 1.2.1]
|
||||||
|
- goctl version [e.g. 1.2.1, optional]
|
||||||
|
|
||||||
|
**More description**
|
||||||
|
Add any other context about the problem here.
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
10
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: Ask a question on using go-zero or goctl
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
12
.github/workflows/go.yml
vendored
12
.github/workflows/go.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.13
|
go-version: ^1.14
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
@@ -25,10 +25,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
go get -v -t -d ./...
|
go get -v -t -d ./...
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: |
|
||||||
|
go vet -stdmethods=false $(go list ./...)
|
||||||
|
go install mvdan.cc/gofumpt@latest
|
||||||
|
test -z "$(gofumpt -s -l -extra .)" || echo "Please run 'gofumpt -l -w -extra .'"
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
|
||||||
- name: Codecov
|
- name: Codecov
|
||||||
uses: codecov/codecov-action@v1.0.6
|
uses: codecov/codecov-action@v2
|
||||||
with:
|
|
||||||
token: ${{secrets.CODECOV_TOKEN}}
|
|
||||||
|
|||||||
19
.github/workflows/issues.yml
vendored
Normal file
19
.github/workflows/issues.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Close inactive issues
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
close-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
days-before-issue-stale: 30
|
||||||
|
days-before-issue-close: 14
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||||
|
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||||
|
days-before-pr-stale: -1
|
||||||
|
days-before-pr-close: -1
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
19
.github/workflows/reviewdog.yml
vendored
Normal file
19
.github/workflows/reviewdog.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: reviewdog
|
||||||
|
on: [pull_request]
|
||||||
|
jobs:
|
||||||
|
staticcheck:
|
||||||
|
name: runner / staticcheck
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: reviewdog/action-staticcheck@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.github_token }}
|
||||||
|
# Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review].
|
||||||
|
reporter: github-pr-review
|
||||||
|
# Report all results.
|
||||||
|
filter_mode: nofilter
|
||||||
|
# Exit with 1 when it find at least one finding.
|
||||||
|
fail_on_error: true
|
||||||
|
# Set staticcheck flags
|
||||||
|
staticcheck_flags: -checks=inherit,-SA1019,-SA1029,-SA5008
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@
|
|||||||
!*/
|
!*/
|
||||||
!api
|
!api
|
||||||
|
|
||||||
|
# ignore
|
||||||
.idea
|
.idea
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/logs
|
**/logs
|
||||||
|
|||||||
102
CONTRIBUTING.md
Normal file
102
CONTRIBUTING.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Welcome to go-zero!
|
||||||
|
|
||||||
|
- [Before you get started](#before-you-get-started)
|
||||||
|
- [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
|
||||||
|
|
||||||
|
Please make sure to read and observe our [Code of Conduct](/code-of-conduct.md).
|
||||||
|
|
||||||
|
## Community Expectations
|
||||||
|
|
||||||
|
go-zero is a community project driven by its community which strives to promote a healthy, friendly and productive environment.
|
||||||
|
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
|
||||||
|
|
||||||
|
- Fork the repository on GitHub.
|
||||||
|
- Make your changes on your fork repository.
|
||||||
|
- Submit a PR.
|
||||||
|
|
||||||
|
|
||||||
|
# Your First Contribution
|
||||||
|
|
||||||
|
We will help you to contribute in different areas like filing issues, developing features, fixing critical bugs and
|
||||||
|
getting your work reviewed and merged.
|
||||||
|
|
||||||
|
If you have questions about the development process,
|
||||||
|
feel free to [file an issue](https://github.com/tal-tech/go-zero/issues/new/choose).
|
||||||
|
|
||||||
|
## Find something to work on
|
||||||
|
|
||||||
|
We are always in need of help, be it fixing documentation, reporting bugs or writing some code.
|
||||||
|
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
|
||||||
|
|
||||||
|
[go-zero](https://github.com/tal-tech/go-zero) has beginner-friendly issues that provide a good first issue.
|
||||||
|
For example, [go-zero](https://github.com/tal-tech/go-zero) has
|
||||||
|
[help wanted](https://github.com/tal-tech/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) and
|
||||||
|
[good first issue](https://github.com/tal-tech/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||||
|
labels for issues that should not need deep knowledge of the system.
|
||||||
|
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.
|
||||||
|
Please see [Contributing](#contributing) below for the workflow.
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
|
||||||
|
### File an Issue
|
||||||
|
|
||||||
|
While we encourage everyone to contribute code, it is also appreciated when someone reports an issue.
|
||||||
|
|
||||||
|
Please follow the prompted submission guidelines while opening an issue.
|
||||||
|
|
||||||
|
# Contributor Workflow
|
||||||
|
|
||||||
|
Please do not ever hesitate to ask a question or send a pull request.
|
||||||
|
|
||||||
|
This is a rough outline of what a contributor's workflow looks like:
|
||||||
|
|
||||||
|
- Create a topic branch from where to base the contribution. This is usually master.
|
||||||
|
- 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/tal-tech/go-zero).
|
||||||
|
|
||||||
|
## Creating Pull Requests
|
||||||
|
|
||||||
|
Pull requests are often called simply "PR".
|
||||||
|
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`
|
||||||
|
* Run the test with data race enabled `go test -race ./...`
|
||||||
|
|
||||||
|
## Code Review
|
||||||
|
|
||||||
|
To make it easier for your PR to receive reviews, consider the reviewers will need you to:
|
||||||
|
|
||||||
|
* follow [good coding guidelines](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||||
|
* 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.
|
||||||
|
|
||||||
22
ROADMAP.md
Normal file
22
ROADMAP.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# go-zero Roadmap
|
||||||
|
|
||||||
|
This document defines a high level roadmap for go-zero development and upcoming releases.
|
||||||
|
Community and contributor involvement is vital for successfully implementing all desired items for each release.
|
||||||
|
We hope that the items listed below will inspire further engagement from the community to keep go-zero progressing and shipping exciting and valuable features.
|
||||||
|
|
||||||
|
## 2021 Q2
|
||||||
|
- [x] Support service discovery through K8S client api
|
||||||
|
- [x] Log full sql statements for easier sql problem solving
|
||||||
|
|
||||||
|
## 2021 Q3
|
||||||
|
- [x] Support `goctl model pg` to support PostgreSQL code generation
|
||||||
|
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file
|
||||||
|
- [ ] Adapt builtin tracing mechanism to opentracing solutions
|
||||||
|
|
||||||
|
## 2021 Q4
|
||||||
|
- [ ] Add `httpx.Client` with governance, like circuit breaker etc.
|
||||||
|
- [ ] Support `goctl doctor` command to report potential issues for given service
|
||||||
|
- [ ] Support `context` in redis related methods for timeout and tracing
|
||||||
|
- [ ] Support `context` in sql related methods for timeout and tracing
|
||||||
|
- [ ] Support `context` in mongodb related methods for timeout and tracing
|
||||||
|
- [ ] Support TLS in redis connections
|
||||||
76
code-of-conduct.md
Normal file
76
code-of-conduct.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to make participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||||
|
level of experience, education, socio-economic status, nationality, personal
|
||||||
|
appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all project spaces, and it also applies when
|
||||||
|
an individual is representing the project or its community in public spaces.
|
||||||
|
Examples of representing a project or community include using an official
|
||||||
|
project e-mail address, posting via an official social media account, or acting
|
||||||
|
as an appointed representative at an online or offline event. Representation of
|
||||||
|
a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see
|
||||||
|
https://www.contributor-covenant.org/faq
|
||||||
@@ -94,7 +94,7 @@ func (b *googleBreaker) markFailure() {
|
|||||||
b.stat.Add(0)
|
b.stat.Add(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *googleBreaker) history() (accepts int64, total int64) {
|
func (b *googleBreaker) history() (accepts, total int64) {
|
||||||
b.stat.Reduce(func(b *collection.Bucket) {
|
b.stat.Reduce(func(b *collection.Bucket) {
|
||||||
accepts += int64(b.Sum)
|
accepts += int64(b.Sum)
|
||||||
total += b.Count
|
total += b.Count
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ type (
|
|||||||
expire time.Duration
|
expire time.Duration
|
||||||
timingWheel *TimingWheel
|
timingWheel *TimingWheel
|
||||||
lruCache lru
|
lruCache lru
|
||||||
barrier syncx.SharedCalls
|
barrier syncx.SingleFlight
|
||||||
unstableExpiry mathx.Unstable
|
unstableExpiry mathx.Unstable
|
||||||
stats *cacheStat
|
stats *cacheStat
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ func NewCache(expire time.Duration, opts ...CacheOption) (*Cache, error) {
|
|||||||
data: make(map[string]interface{}),
|
data: make(map[string]interface{}),
|
||||||
expire: expire,
|
expire: expire,
|
||||||
lruCache: emptyLruCache,
|
lruCache: emptyLruCache,
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSingleFlight(),
|
||||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,9 +106,7 @@ func (s *Set) KeysInt() []int {
|
|||||||
var keys []int
|
var keys []int
|
||||||
|
|
||||||
for key := range s.data {
|
for key := range s.data {
|
||||||
if intKey, ok := key.(int); !ok {
|
if intKey, ok := key.(int); ok {
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
keys = append(keys, intKey)
|
keys = append(keys, intKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,9 +119,7 @@ func (s *Set) KeysInt64() []int64 {
|
|||||||
var keys []int64
|
var keys []int64
|
||||||
|
|
||||||
for key := range s.data {
|
for key := range s.data {
|
||||||
if intKey, ok := key.(int64); !ok {
|
if intKey, ok := key.(int64); ok {
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
keys = append(keys, intKey)
|
keys = append(keys, intKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,9 +132,7 @@ func (s *Set) KeysUint() []uint {
|
|||||||
var keys []uint
|
var keys []uint
|
||||||
|
|
||||||
for key := range s.data {
|
for key := range s.data {
|
||||||
if intKey, ok := key.(uint); !ok {
|
if intKey, ok := key.(uint); ok {
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
keys = append(keys, intKey)
|
keys = append(keys, intKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,9 +145,7 @@ func (s *Set) KeysUint64() []uint64 {
|
|||||||
var keys []uint64
|
var keys []uint64
|
||||||
|
|
||||||
for key := range s.data {
|
for key := range s.data {
|
||||||
if intKey, ok := key.(uint64); !ok {
|
if intKey, ok := key.(uint64); ok {
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
keys = append(keys, intKey)
|
keys = append(keys, intKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,9 +158,7 @@ func (s *Set) KeysStr() []string {
|
|||||||
var keys []string
|
var keys []string
|
||||||
|
|
||||||
for key := range s.data {
|
for key := range s.data {
|
||||||
if strKey, ok := key.(string); !ok {
|
if strKey, ok := key.(string); ok {
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
keys = append(keys, strKey)
|
keys = append(keys, strKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ func (tw *TimingWheel) drainAll(fn func(key, value interface{})) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tw *TimingWheel) getPositionAndCircle(d time.Duration) (pos int, circle int) {
|
func (tw *TimingWheel) getPositionAndCircle(d time.Duration) (pos, circle int) {
|
||||||
steps := int(d / tw.interval)
|
steps := int(d / tw.interval)
|
||||||
pos = (tw.tickedPos + steps) % tw.numSlots
|
pos = (tw.tickedPos + steps) % tw.numSlots
|
||||||
circle = (steps - 1) / tw.numSlots
|
circle = (steps - 1) / tw.numSlots
|
||||||
|
|||||||
@@ -47,9 +47,11 @@ func TestUnmarshalContextWithMissing(t *testing.T) {
|
|||||||
Name string `ctx:"name"`
|
Name string `ctx:"name"`
|
||||||
Age int `ctx:"age"`
|
Age int `ctx:"age"`
|
||||||
}
|
}
|
||||||
|
type name string
|
||||||
|
const PersonNameKey name = "name"
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx = context.WithValue(ctx, "name", "kevin")
|
ctx = context.WithValue(ctx, PersonNameKey, "kevin")
|
||||||
|
|
||||||
var person Person
|
var person Person
|
||||||
err := For(ctx, &person)
|
err := For(ctx, &person)
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestContextCancel(t *testing.T) {
|
func TestContextCancel(t *testing.T) {
|
||||||
c := context.WithValue(context.Background(), "key", "value")
|
type key string
|
||||||
|
var nameKey key = "name"
|
||||||
|
c := context.WithValue(context.Background(), nameKey, "value")
|
||||||
c1, cancel := context.WithCancel(c)
|
c1, cancel := context.WithCancel(c)
|
||||||
o := ValueOnlyFrom(c1)
|
o := ValueOnlyFrom(c1)
|
||||||
c2, cancel2 := context.WithCancel(o)
|
c2, cancel2 := context.WithCancel(o)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"go.etcd.io/etcd/clientv3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
clientv3 "go.etcd.io/etcd/clientv3"
|
|
||||||
grpc "google.golang.org/grpc"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockEtcdClient is a mock of EtcdClient interface
|
// MockEtcdClient is a mock of EtcdClient interface
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ package internal
|
|||||||
|
|
||||||
// Listener interface wraps the OnUpdate method.
|
// Listener interface wraps the OnUpdate method.
|
||||||
type Listener interface {
|
type Listener interface {
|
||||||
OnUpdate(keys []string, values []string, newKey string)
|
OnUpdate(keys, values []string, newKey string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/syncx"
|
"github.com/tal-tech/go-zero/core/syncx"
|
||||||
"github.com/tal-tech/go-zero/core/threading"
|
"github.com/tal-tech/go-zero/core/threading"
|
||||||
"go.etcd.io/etcd/clientv3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -260,26 +260,34 @@ func (c *cluster) reload(cli EtcdClient) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) watch(cli EtcdClient, key string) {
|
func (c *cluster) watch(cli EtcdClient, key string) {
|
||||||
|
for {
|
||||||
|
if c.watchStream(cli, key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cluster) watchStream(cli EtcdClient, key string) bool {
|
||||||
rch := cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix())
|
rch := cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix())
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case wresp, ok := <-rch:
|
case wresp, ok := <-rch:
|
||||||
if !ok {
|
if !ok {
|
||||||
logx.Error("etcd monitor chan has been closed")
|
logx.Error("etcd monitor chan has been closed")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
if wresp.Canceled {
|
if wresp.Canceled {
|
||||||
logx.Error("etcd monitor chan has been canceled")
|
logx.Errorf("etcd monitor chan has been canceled, error: %v", wresp.Err())
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
if wresp.Err() != nil {
|
if wresp.Err() != nil {
|
||||||
logx.Error(fmt.Sprintf("etcd monitor chan error: %v", wresp.Err()))
|
logx.Error(fmt.Sprintf("etcd monitor chan error: %v", wresp.Err()))
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
c.handleWatchEvents(key, wresp.Events)
|
c.handleWatchEvents(key, wresp.Events)
|
||||||
case <-c.done:
|
case <-c.done:
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import (
|
|||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/contextx"
|
"github.com/tal-tech/go-zero/core/contextx"
|
||||||
|
"github.com/tal-tech/go-zero/core/lang"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/stringx"
|
"github.com/tal-tech/go-zero/core/stringx"
|
||||||
"go.etcd.io/etcd/clientv3"
|
"go.etcd.io/etcd/api/v3/mvccpb"
|
||||||
"go.etcd.io/etcd/mvcc/mvccpb"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mockLock sync.Mutex
|
var mockLock sync.Mutex
|
||||||
@@ -202,11 +203,13 @@ func TestClusterWatch_RespFailures(t *testing.T) {
|
|||||||
restore := setMockClient(cli)
|
restore := setMockClient(cli)
|
||||||
defer restore()
|
defer restore()
|
||||||
ch := make(chan clientv3.WatchResponse)
|
ch := make(chan clientv3.WatchResponse)
|
||||||
cli.EXPECT().Watch(gomock.Any(), "any/", gomock.Any()).Return(ch)
|
cli.EXPECT().Watch(gomock.Any(), "any/", gomock.Any()).Return(ch).AnyTimes()
|
||||||
cli.EXPECT().Ctx().Return(context.Background()).AnyTimes()
|
cli.EXPECT().Ctx().Return(context.Background()).AnyTimes()
|
||||||
c := new(cluster)
|
c := new(cluster)
|
||||||
|
c.done = make(chan lang.PlaceholderType)
|
||||||
go func() {
|
go func() {
|
||||||
ch <- resp
|
ch <- resp
|
||||||
|
close(c.done)
|
||||||
}()
|
}()
|
||||||
c.watch(cli, "any")
|
c.watch(cli, "any")
|
||||||
})
|
})
|
||||||
@@ -220,11 +223,13 @@ func TestClusterWatch_CloseChan(t *testing.T) {
|
|||||||
restore := setMockClient(cli)
|
restore := setMockClient(cli)
|
||||||
defer restore()
|
defer restore()
|
||||||
ch := make(chan clientv3.WatchResponse)
|
ch := make(chan clientv3.WatchResponse)
|
||||||
cli.EXPECT().Watch(gomock.Any(), "any/", gomock.Any()).Return(ch)
|
cli.EXPECT().Watch(gomock.Any(), "any/", gomock.Any()).Return(ch).AnyTimes()
|
||||||
cli.EXPECT().Ctx().Return(context.Background()).AnyTimes()
|
cli.EXPECT().Ctx().Return(context.Background()).AnyTimes()
|
||||||
c := new(cluster)
|
c := new(cluster)
|
||||||
|
c.done = make(chan lang.PlaceholderType)
|
||||||
go func() {
|
go func() {
|
||||||
close(ch)
|
close(ch)
|
||||||
|
close(c.done)
|
||||||
}()
|
}()
|
||||||
c.watch(cli, "any")
|
c.watch(cli, "any")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
connectivity "google.golang.org/grpc/connectivity"
|
connectivity "google.golang.org/grpc/connectivity"
|
||||||
reflect "reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MocketcdConn is a mock of etcdConn interface
|
// MocketcdConn is a mock of etcdConn interface
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockUpdateListener is a mock of UpdateListener interface
|
// MockUpdateListener is a mock of UpdateListener interface
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/proc"
|
"github.com/tal-tech/go-zero/core/proc"
|
||||||
"github.com/tal-tech/go-zero/core/syncx"
|
"github.com/tal-tech/go-zero/core/syncx"
|
||||||
"github.com/tal-tech/go-zero/core/threading"
|
"github.com/tal-tech/go-zero/core/threading"
|
||||||
"go.etcd.io/etcd/clientv3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/discov/internal"
|
"github.com/tal-tech/go-zero/core/discov/internal"
|
||||||
"github.com/tal-tech/go-zero/core/lang"
|
"github.com/tal-tech/go-zero/core/lang"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"go.etcd.io/etcd/clientv3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ type AtomicError struct {
|
|||||||
|
|
||||||
// Set sets the error.
|
// Set sets the error.
|
||||||
func (ae *AtomicError) Set(err error) {
|
func (ae *AtomicError) Set(err error) {
|
||||||
ae.err.Store(err)
|
if err != nil {
|
||||||
|
ae.err.Store(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load returns the error.
|
// Load returns the error.
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ func TestAtomicError(t *testing.T) {
|
|||||||
assert.Equal(t, errDummy, err.Load())
|
assert.Equal(t, errDummy, err.Load())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAtomicErrorSetNil(t *testing.T) {
|
||||||
|
var (
|
||||||
|
errNil error
|
||||||
|
err AtomicError
|
||||||
|
)
|
||||||
|
err.Set(errNil)
|
||||||
|
assert.Equal(t, errNil, err.Load())
|
||||||
|
}
|
||||||
|
|
||||||
func TestAtomicErrorNil(t *testing.T) {
|
func TestAtomicErrorNil(t *testing.T) {
|
||||||
var err AtomicError
|
var err AtomicError
|
||||||
assert.Nil(t, err.Load())
|
assert.Nil(t, err.Load())
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package fs
|
package fs
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
// +build linux darwin
|
||||||
|
|
||||||
package fs
|
package fs
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Concat returns a concatenated Stream.
|
||||||
|
func Concat(s Stream, others ...Stream) Stream {
|
||||||
|
return s.Concat(others...)
|
||||||
|
}
|
||||||
|
|
||||||
// From constructs a Stream from the given GenerateFunc.
|
// From constructs a Stream from the given GenerateFunc.
|
||||||
func From(generate GenerateFunc) Stream {
|
func From(generate GenerateFunc) Stream {
|
||||||
source := make(chan interface{})
|
source := make(chan interface{})
|
||||||
@@ -79,16 +84,42 @@ func Range(source <-chan interface{}) Stream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllMach returns whether all elements of this stream match the provided predicate.
|
||||||
|
// May not evaluate the predicate on all elements if not necessary for determining the result.
|
||||||
|
// If the stream is empty then true is returned and the predicate is not evaluated.
|
||||||
|
func (s Stream) AllMach(predicate func(item interface{}) bool) bool {
|
||||||
|
for item := range s.source {
|
||||||
|
if !predicate(item) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyMach returns whether any elements of this stream match the provided predicate.
|
||||||
|
// May not evaluate the predicate on all elements if not necessary for determining the result.
|
||||||
|
// If the stream is empty then false is returned and the predicate is not evaluated.
|
||||||
|
func (s Stream) AnyMach(predicate func(item interface{}) bool) bool {
|
||||||
|
for item := range s.source {
|
||||||
|
if predicate(item) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Buffer buffers the items into a queue with size n.
|
// Buffer buffers the items into a queue with size n.
|
||||||
// It can balance the producer and the consumer if their processing throughput don't match.
|
// It can balance the producer and the consumer if their processing throughput don't match.
|
||||||
func (p Stream) Buffer(n int) Stream {
|
func (s Stream) Buffer(n int) Stream {
|
||||||
if n < 0 {
|
if n < 0 {
|
||||||
n = 0
|
n = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
source := make(chan interface{}, n)
|
source := make(chan interface{}, n)
|
||||||
go func() {
|
go func() {
|
||||||
for item := range p.source {
|
for item := range s.source {
|
||||||
source <- item
|
source <- item
|
||||||
}
|
}
|
||||||
close(source)
|
close(source)
|
||||||
@@ -97,23 +128,51 @@ func (p Stream) Buffer(n int) Stream {
|
|||||||
return Range(source)
|
return Range(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Concat returns a Stream that concatenated other streams
|
||||||
|
func (s Stream) Concat(others ...Stream) Stream {
|
||||||
|
source := make(chan interface{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
group := threading.NewRoutineGroup()
|
||||||
|
group.Run(func() {
|
||||||
|
for item := range s.source {
|
||||||
|
source <- item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, each := range others {
|
||||||
|
each := each
|
||||||
|
group.Run(func() {
|
||||||
|
for item := range each.source {
|
||||||
|
source <- item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Wait()
|
||||||
|
close(source)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return Range(source)
|
||||||
|
}
|
||||||
|
|
||||||
// Count counts the number of elements in the result.
|
// Count counts the number of elements in the result.
|
||||||
func (p Stream) Count() (count int) {
|
func (s Stream) Count() (count int) {
|
||||||
for range p.source {
|
for range s.source {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Distinct removes the duplicated items base on the given KeyFunc.
|
// Distinct removes the duplicated items base on the given KeyFunc.
|
||||||
func (p Stream) Distinct(fn KeyFunc) Stream {
|
func (s Stream) Distinct(fn KeyFunc) Stream {
|
||||||
source := make(chan interface{})
|
source := make(chan interface{})
|
||||||
|
|
||||||
threading.GoSafe(func() {
|
threading.GoSafe(func() {
|
||||||
defer close(source)
|
defer close(source)
|
||||||
|
|
||||||
keys := make(map[interface{}]lang.PlaceholderType)
|
keys := make(map[interface{}]lang.PlaceholderType)
|
||||||
for item := range p.source {
|
for item := range s.source {
|
||||||
key := fn(item)
|
key := fn(item)
|
||||||
if _, ok := keys[key]; !ok {
|
if _, ok := keys[key]; !ok {
|
||||||
source <- item
|
source <- item
|
||||||
@@ -126,14 +185,14 @@ func (p Stream) Distinct(fn KeyFunc) Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Done waits all upstreaming operations to be done.
|
// Done waits all upstreaming operations to be done.
|
||||||
func (p Stream) Done() {
|
func (s Stream) Done() {
|
||||||
for range p.source {
|
for range s.source {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter filters the items by the given FilterFunc.
|
// Filter filters the items by the given FilterFunc.
|
||||||
func (p Stream) Filter(fn FilterFunc, opts ...Option) Stream {
|
func (s Stream) Filter(fn FilterFunc, opts ...Option) Stream {
|
||||||
return p.Walk(func(item interface{}, pipe chan<- interface{}) {
|
return s.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||||
if fn(item) {
|
if fn(item) {
|
||||||
pipe <- item
|
pipe <- item
|
||||||
}
|
}
|
||||||
@@ -141,21 +200,21 @@ func (p Stream) Filter(fn FilterFunc, opts ...Option) Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ForAll handles the streaming elements from the source and no later streams.
|
// ForAll handles the streaming elements from the source and no later streams.
|
||||||
func (p Stream) ForAll(fn ForAllFunc) {
|
func (s Stream) ForAll(fn ForAllFunc) {
|
||||||
fn(p.source)
|
fn(s.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEach seals the Stream with the ForEachFunc on each item, no successive operations.
|
// ForEach seals the Stream with the ForEachFunc on each item, no successive operations.
|
||||||
func (p Stream) ForEach(fn ForEachFunc) {
|
func (s Stream) ForEach(fn ForEachFunc) {
|
||||||
for item := range p.source {
|
for item := range s.source {
|
||||||
fn(item)
|
fn(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group groups the elements into different groups based on their keys.
|
// Group groups the elements into different groups based on their keys.
|
||||||
func (p Stream) Group(fn KeyFunc) Stream {
|
func (s Stream) Group(fn KeyFunc) Stream {
|
||||||
groups := make(map[interface{}][]interface{})
|
groups := make(map[interface{}][]interface{})
|
||||||
for item := range p.source {
|
for item := range s.source {
|
||||||
key := fn(item)
|
key := fn(item)
|
||||||
groups[key] = append(groups[key], item)
|
groups[key] = append(groups[key], item)
|
||||||
}
|
}
|
||||||
@@ -172,7 +231,7 @@ func (p Stream) Group(fn KeyFunc) Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Head returns the first n elements in p.
|
// Head returns the first n elements in p.
|
||||||
func (p Stream) Head(n int64) Stream {
|
func (s Stream) Head(n int64) Stream {
|
||||||
if n < 1 {
|
if n < 1 {
|
||||||
panic("n must be greater than 0")
|
panic("n must be greater than 0")
|
||||||
}
|
}
|
||||||
@@ -180,7 +239,7 @@ func (p Stream) Head(n int64) Stream {
|
|||||||
source := make(chan interface{})
|
source := make(chan interface{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for item := range p.source {
|
for item := range s.source {
|
||||||
n--
|
n--
|
||||||
if n >= 0 {
|
if n >= 0 {
|
||||||
source <- item
|
source <- item
|
||||||
@@ -201,16 +260,16 @@ func (p Stream) Head(n int64) Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Map converts each item to another corresponding item, which means it's a 1:1 model.
|
// Map converts each item to another corresponding item, which means it's a 1:1 model.
|
||||||
func (p Stream) Map(fn MapFunc, opts ...Option) Stream {
|
func (s Stream) Map(fn MapFunc, opts ...Option) Stream {
|
||||||
return p.Walk(func(item interface{}, pipe chan<- interface{}) {
|
return s.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||||
pipe <- fn(item)
|
pipe <- fn(item)
|
||||||
}, opts...)
|
}, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge merges all the items into a slice and generates a new stream.
|
// Merge merges all the items into a slice and generates a new stream.
|
||||||
func (p Stream) Merge() Stream {
|
func (s Stream) Merge() Stream {
|
||||||
var items []interface{}
|
var items []interface{}
|
||||||
for item := range p.source {
|
for item := range s.source {
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,21 +281,21 @@ func (p Stream) Merge() Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parallel applies the given ParallelFunc to each item concurrently with given number of workers.
|
// Parallel applies the given ParallelFunc to each item concurrently with given number of workers.
|
||||||
func (p Stream) Parallel(fn ParallelFunc, opts ...Option) {
|
func (s Stream) Parallel(fn ParallelFunc, opts ...Option) {
|
||||||
p.Walk(func(item interface{}, pipe chan<- interface{}) {
|
s.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||||
fn(item)
|
fn(item)
|
||||||
}, opts...).Done()
|
}, opts...).Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reduce is a utility method to let the caller deal with the underlying channel.
|
// Reduce is a utility method to let the caller deal with the underlying channel.
|
||||||
func (p Stream) Reduce(fn ReduceFunc) (interface{}, error) {
|
func (s Stream) Reduce(fn ReduceFunc) (interface{}, error) {
|
||||||
return fn(p.source)
|
return fn(s.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse reverses the elements in the stream.
|
// Reverse reverses the elements in the stream.
|
||||||
func (p Stream) Reverse() Stream {
|
func (s Stream) Reverse() Stream {
|
||||||
var items []interface{}
|
var items []interface{}
|
||||||
for item := range p.source {
|
for item := range s.source {
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
// reverse, official method
|
// reverse, official method
|
||||||
@@ -248,10 +307,36 @@ func (p Stream) Reverse() Stream {
|
|||||||
return Just(items...)
|
return Just(items...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip returns a Stream that skips size elements.
|
||||||
|
func (s Stream) Skip(n int64) Stream {
|
||||||
|
if n < 0 {
|
||||||
|
panic("n must not be negative")
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
source := make(chan interface{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for item := range s.source {
|
||||||
|
n--
|
||||||
|
if n >= 0 {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
source <- item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(source)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return Range(source)
|
||||||
|
}
|
||||||
|
|
||||||
// Sort sorts the items from the underlying source.
|
// Sort sorts the items from the underlying source.
|
||||||
func (p Stream) Sort(less LessFunc) Stream {
|
func (s Stream) Sort(less LessFunc) Stream {
|
||||||
var items []interface{}
|
var items []interface{}
|
||||||
for item := range p.source {
|
for item := range s.source {
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
sort.Slice(items, func(i, j int) bool {
|
sort.Slice(items, func(i, j int) bool {
|
||||||
@@ -263,7 +348,7 @@ func (p Stream) Sort(less LessFunc) Stream {
|
|||||||
|
|
||||||
// Split splits the elements into chunk with size up to n,
|
// Split splits the elements into chunk with size up to n,
|
||||||
// might be less than n on tailing elements.
|
// might be less than n on tailing elements.
|
||||||
func (p Stream) Split(n int) Stream {
|
func (s Stream) Split(n int) Stream {
|
||||||
if n < 1 {
|
if n < 1 {
|
||||||
panic("n should be greater than 0")
|
panic("n should be greater than 0")
|
||||||
}
|
}
|
||||||
@@ -271,7 +356,7 @@ func (p Stream) Split(n int) Stream {
|
|||||||
source := make(chan interface{})
|
source := make(chan interface{})
|
||||||
go func() {
|
go func() {
|
||||||
var chunk []interface{}
|
var chunk []interface{}
|
||||||
for item := range p.source {
|
for item := range s.source {
|
||||||
chunk = append(chunk, item)
|
chunk = append(chunk, item)
|
||||||
if len(chunk) == n {
|
if len(chunk) == n {
|
||||||
source <- chunk
|
source <- chunk
|
||||||
@@ -288,7 +373,7 @@ func (p Stream) Split(n int) Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tail returns the last n elements in p.
|
// Tail returns the last n elements in p.
|
||||||
func (p Stream) Tail(n int64) Stream {
|
func (s Stream) Tail(n int64) Stream {
|
||||||
if n < 1 {
|
if n < 1 {
|
||||||
panic("n should be greater than 0")
|
panic("n should be greater than 0")
|
||||||
}
|
}
|
||||||
@@ -297,7 +382,7 @@ func (p Stream) Tail(n int64) Stream {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
ring := collection.NewRing(int(n))
|
ring := collection.NewRing(int(n))
|
||||||
for item := range p.source {
|
for item := range s.source {
|
||||||
ring.Add(item)
|
ring.Add(item)
|
||||||
}
|
}
|
||||||
for _, item := range ring.Take() {
|
for _, item := range ring.Take() {
|
||||||
@@ -310,16 +395,16 @@ func (p Stream) Tail(n int64) Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Walk lets the callers handle each item, the caller may write zero, one or more items base on the given item.
|
// Walk lets the callers handle each item, the caller may write zero, one or more items base on the given item.
|
||||||
func (p Stream) Walk(fn WalkFunc, opts ...Option) Stream {
|
func (s Stream) Walk(fn WalkFunc, opts ...Option) Stream {
|
||||||
option := buildOptions(opts...)
|
option := buildOptions(opts...)
|
||||||
if option.unlimitedWorkers {
|
if option.unlimitedWorkers {
|
||||||
return p.walkUnlimited(fn, option)
|
return s.walkUnlimited(fn, option)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.walkLimited(fn, option)
|
return s.walkLimited(fn, option)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
||||||
pipe := make(chan interface{}, option.workers)
|
pipe := make(chan interface{}, option.workers)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -328,7 +413,7 @@ func (p Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
pool <- lang.Placeholder
|
pool <- lang.Placeholder
|
||||||
item, ok := <-p.source
|
item, ok := <-s.source
|
||||||
if !ok {
|
if !ok {
|
||||||
<-pool
|
<-pool
|
||||||
break
|
break
|
||||||
@@ -353,14 +438,14 @@ func (p Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
|||||||
return Range(pipe)
|
return Range(pipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Stream) walkUnlimited(fn WalkFunc, option *rxOptions) Stream {
|
func (s Stream) walkUnlimited(fn WalkFunc, option *rxOptions) Stream {
|
||||||
pipe := make(chan interface{}, defaultWorkers)
|
pipe := make(chan interface{}, defaultWorkers)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
for {
|
for {
|
||||||
item, ok := <-p.source
|
item, ok := <-s.source
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package fx
|
|||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -330,6 +333,29 @@ func TestWalk(t *testing.T) {
|
|||||||
assert.Equal(t, 9, result)
|
assert.Equal(t, 9, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkParallelMapReduce(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
mapper := func(v interface{}) interface{} {
|
||||||
|
return v.(int64) * v.(int64)
|
||||||
|
}
|
||||||
|
reducer := func(input <-chan interface{}) (interface{}, error) {
|
||||||
|
var result int64
|
||||||
|
for v := range input {
|
||||||
|
result += v.(int64)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
From(func(input chan<- interface{}) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
input <- int64(rand.Int())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).Map(mapper).Reduce(reducer)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkMapReduce(b *testing.B) {
|
func BenchmarkMapReduce(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
@@ -343,12 +369,103 @@ func BenchmarkMapReduce(b *testing.B) {
|
|||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
From(func(input chan<- interface{}) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
input <- int64(rand.Int())
|
||||||
|
}
|
||||||
|
}).Map(mapper).Reduce(reducer)
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
func equal(t *testing.T, stream Stream, data []interface{}) {
|
||||||
From(func(input chan<- interface{}) {
|
items := make([]interface{}, 0)
|
||||||
for j := 0; j < 2; j++ {
|
for item := range stream.source {
|
||||||
input <- int64(j)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
}).Map(mapper).Reduce(reducer)
|
if !reflect.DeepEqual(items, data) {
|
||||||
|
t.Errorf(" %v, want %v", items, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assetEqual(t *testing.T, except, data interface{}) {
|
||||||
|
if !reflect.DeepEqual(except, data) {
|
||||||
|
t.Errorf(" %v, want %v", data, except)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStream_AnyMach(t *testing.T) {
|
||||||
|
assetEqual(t, false, Just(1, 2, 3).AnyMach(func(item interface{}) bool {
|
||||||
|
return item.(int) == 4
|
||||||
|
}))
|
||||||
|
assetEqual(t, false, Just(1, 2, 3).AnyMach(func(item interface{}) bool {
|
||||||
|
return item.(int) == 0
|
||||||
|
}))
|
||||||
|
assetEqual(t, true, Just(1, 2, 3).AnyMach(func(item interface{}) bool {
|
||||||
|
return item.(int) == 2
|
||||||
|
}))
|
||||||
|
assetEqual(t, true, Just(1, 2, 3).AnyMach(func(item interface{}) bool {
|
||||||
|
return item.(int) == 2
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStream_AllMach(t *testing.T) {
|
||||||
|
assetEqual(
|
||||||
|
t, true, Just(1, 2, 3).AllMach(func(item interface{}) bool {
|
||||||
|
return true
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
assetEqual(
|
||||||
|
t, false, Just(1, 2, 3).AllMach(func(item interface{}) bool {
|
||||||
|
return false
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
assetEqual(
|
||||||
|
t, false, Just(1, 2, 3).AllMach(func(item interface{}) bool {
|
||||||
|
return item.(int) == 1
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConcat(t *testing.T) {
|
||||||
|
a1 := []interface{}{1, 2, 3}
|
||||||
|
a2 := []interface{}{4, 5, 6}
|
||||||
|
s1 := Just(a1...)
|
||||||
|
s2 := Just(a2...)
|
||||||
|
stream := Concat(s1, s2)
|
||||||
|
var items []interface{}
|
||||||
|
for item := range stream.source {
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
return items[i].(int) < items[j].(int)
|
||||||
|
})
|
||||||
|
ints := make([]interface{}, 0)
|
||||||
|
ints = append(ints, a1...)
|
||||||
|
ints = append(ints, a2...)
|
||||||
|
assetEqual(t, ints, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStream_Skip(t *testing.T) {
|
||||||
|
assetEqual(t, 3, Just(1, 2, 3, 4).Skip(1).Count())
|
||||||
|
assetEqual(t, 1, Just(1, 2, 3, 4).Skip(3).Count())
|
||||||
|
assetEqual(t, 4, Just(1, 2, 3, 4).Skip(0).Count())
|
||||||
|
equal(t, Just(1, 2, 3, 4).Skip(3), []interface{}{4})
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
Just(1, 2, 3, 4).Skip(-1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStream_Concat(t *testing.T) {
|
||||||
|
stream := Just(1).Concat(Just(2), Just(3))
|
||||||
|
var items []interface{}
|
||||||
|
for item := range stream.source {
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
return items[i].(int) < items[j].(int)
|
||||||
|
})
|
||||||
|
assetEqual(t, []interface{}{1, 2, 3}, items)
|
||||||
|
|
||||||
|
just := Just(1)
|
||||||
|
equal(t, just.Concat(just), []interface{}{1})
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package fx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,7 +33,8 @@ func DoWithTimeout(fn func() error, timeout time.Duration, opts ...DoOption) err
|
|||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if p := recover(); p != nil {
|
if p := recover(); p != nil {
|
||||||
panicChan <- p
|
// attach call stack to avoid missing in different goroutine
|
||||||
|
panicChan <- fmt.Sprintf("%+v\n\n%s", p, strings.TrimSpace(string(debug.Stack())))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
done <- fn()
|
done <- fn()
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func (h *ConsistentHash) AddWithReplicas(node interface{}, replicas int) {
|
|||||||
h.ring[hash] = append(h.ring[hash], node)
|
h.ring[hash] = append(h.ring[hash], node)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(h.keys, func(i int, j int) bool {
|
sort.Slice(h.keys, func(i, j int) bool {
|
||||||
return h.keys[i] < h.keys[j]
|
return h.keys[i] < h.keys[j]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ func TestMd5(t *testing.T) {
|
|||||||
assert.Equal(t, md5Digest, actual)
|
assert.Equal(t, md5Digest, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMd5Hex(t *testing.T) {
|
||||||
|
actual := Md5Hex([]byte(text))
|
||||||
|
assert.Equal(t, md5Digest, actual)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkHashFnv(b *testing.B) {
|
func BenchmarkHashFnv(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
h := fnv.New32()
|
h := fnv.New32()
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func TestTokenLimit_Rescue(t *testing.T) {
|
|||||||
rate = 5
|
rate = 5
|
||||||
burst = 10
|
burst = 10
|
||||||
)
|
)
|
||||||
l := NewTokenLimiter(rate, burst, redis.NewRedis(s.Addr(), redis.NodeType), "tokenlimit")
|
l := NewTokenLimiter(rate, burst, redis.New(s.Addr()), "tokenlimit")
|
||||||
s.Close()
|
s.Close()
|
||||||
|
|
||||||
var allowed int
|
var allowed int
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ var (
|
|||||||
|
|
||||||
// default to be enabled
|
// default to be enabled
|
||||||
enabled = syncx.ForAtomicBool(true)
|
enabled = syncx.ForAtomicBool(true)
|
||||||
|
// default to be enabled
|
||||||
|
logEnabled = syncx.ForAtomicBool(true)
|
||||||
// make it a variable for unit test
|
// make it a variable for unit test
|
||||||
systemOverloadChecker = func(cpuThreshold int64) bool {
|
systemOverloadChecker = func(cpuThreshold int64) bool {
|
||||||
return stat.CpuUsage() >= cpuThreshold
|
return stat.CpuUsage() >= cpuThreshold
|
||||||
@@ -80,6 +82,11 @@ func Disable() {
|
|||||||
enabled.Set(false)
|
enabled.Set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisableLog disables the stat logs for load shedding.
|
||||||
|
func DisableLog() {
|
||||||
|
logEnabled.Set(false)
|
||||||
|
}
|
||||||
|
|
||||||
// NewAdaptiveShedder returns an adaptive shedder.
|
// NewAdaptiveShedder returns an adaptive shedder.
|
||||||
// opts can be used to customize the Shedder.
|
// opts can be used to customize the Shedder.
|
||||||
func NewAdaptiveShedder(opts ...ShedderOption) Shedder {
|
func NewAdaptiveShedder(opts ...ShedderOption) Shedder {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAdaptiveShedder(t *testing.T) {
|
func TestAdaptiveShedder(t *testing.T) {
|
||||||
|
DisableLog()
|
||||||
shedder := NewAdaptiveShedder(WithWindow(bucketDuration), WithBuckets(buckets), WithCpuThreshold(100))
|
shedder := NewAdaptiveShedder(WithWindow(bucketDuration), WithBuckets(buckets), WithCpuThreshold(100))
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var drop int64
|
var drop int64
|
||||||
|
|||||||
@@ -48,6 +48,25 @@ func (s *SheddingStat) IncrementDrop() {
|
|||||||
atomic.AddInt64(&s.drop, 1)
|
atomic.AddInt64(&s.drop, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SheddingStat) loop(c <-chan time.Time) {
|
||||||
|
for range c {
|
||||||
|
st := s.reset()
|
||||||
|
|
||||||
|
if !logEnabled.True() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c := stat.CpuUsage()
|
||||||
|
if st.Drop == 0 {
|
||||||
|
logx.Statf("(%s) shedding_stat [1m], cpu: %d, total: %d, pass: %d, drop: %d",
|
||||||
|
s.name, c, st.Total, st.Pass, st.Drop)
|
||||||
|
} else {
|
||||||
|
logx.Statf("(%s) shedding_stat_drop [1m], cpu: %d, total: %d, pass: %d, drop: %d",
|
||||||
|
s.name, c, st.Total, st.Pass, st.Drop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SheddingStat) reset() snapshot {
|
func (s *SheddingStat) reset() snapshot {
|
||||||
return snapshot{
|
return snapshot{
|
||||||
Total: atomic.SwapInt64(&s.total, 0),
|
Total: atomic.SwapInt64(&s.total, 0),
|
||||||
@@ -59,15 +78,6 @@ func (s *SheddingStat) reset() snapshot {
|
|||||||
func (s *SheddingStat) run() {
|
func (s *SheddingStat) run() {
|
||||||
ticker := time.NewTicker(time.Minute)
|
ticker := time.NewTicker(time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for range ticker.C {
|
|
||||||
c := stat.CpuUsage()
|
s.loop(ticker.C)
|
||||||
st := s.reset()
|
|
||||||
if st.Drop == 0 {
|
|
||||||
logx.Statf("(%s) shedding_stat [1m], cpu: %d, total: %d, pass: %d, drop: %d",
|
|
||||||
s.name, c, st.Total, st.Pass, st.Drop)
|
|
||||||
} else {
|
|
||||||
logx.Statf("(%s) shedding_stat_drop [1m], cpu: %d, total: %d, pass: %d, drop: %d",
|
|
||||||
s.name, c, st.Total, st.Pass, st.Drop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package load
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -22,3 +23,32 @@ func TestSheddingStat(t *testing.T) {
|
|||||||
assert.Equal(t, int64(5), result.Pass)
|
assert.Equal(t, int64(5), result.Pass)
|
||||||
assert.Equal(t, int64(7), result.Drop)
|
assert.Equal(t, int64(7), result.Drop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoopTrue(t *testing.T) {
|
||||||
|
ch := make(chan time.Time, 1)
|
||||||
|
ch <- time.Now()
|
||||||
|
close(ch)
|
||||||
|
st := new(SheddingStat)
|
||||||
|
logEnabled.Set(true)
|
||||||
|
st.loop(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoopTrueAndDrop(t *testing.T) {
|
||||||
|
ch := make(chan time.Time, 1)
|
||||||
|
ch <- time.Now()
|
||||||
|
close(ch)
|
||||||
|
st := new(SheddingStat)
|
||||||
|
st.IncrementDrop()
|
||||||
|
logEnabled.Set(true)
|
||||||
|
st.loop(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoopFalseAndDrop(t *testing.T) {
|
||||||
|
ch := make(chan time.Time, 1)
|
||||||
|
ch <- time.Now()
|
||||||
|
close(ch)
|
||||||
|
st := new(SheddingStat)
|
||||||
|
st.IncrementDrop()
|
||||||
|
logEnabled.Set(false)
|
||||||
|
st.loop(ch)
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,49 +20,67 @@ func WithDuration(d time.Duration) Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *durationLogger) Error(v ...interface{}) {
|
func (l *durationLogger) Error(v ...interface{}) {
|
||||||
if shouldLog(ErrorLevel) {
|
if shallLog(ErrorLevel) {
|
||||||
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
|
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *durationLogger) Errorf(format string, v ...interface{}) {
|
func (l *durationLogger) Errorf(format string, v ...interface{}) {
|
||||||
if shouldLog(ErrorLevel) {
|
if shallLog(ErrorLevel) {
|
||||||
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
|
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *durationLogger) Errorv(v interface{}) {
|
||||||
|
if shallLog(ErrorLevel) {
|
||||||
|
l.write(errorLog, levelError, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *durationLogger) Info(v ...interface{}) {
|
func (l *durationLogger) Info(v ...interface{}) {
|
||||||
if shouldLog(InfoLevel) {
|
if shallLog(InfoLevel) {
|
||||||
l.write(infoLog, levelInfo, fmt.Sprint(v...))
|
l.write(infoLog, levelInfo, fmt.Sprint(v...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *durationLogger) Infof(format string, v ...interface{}) {
|
func (l *durationLogger) Infof(format string, v ...interface{}) {
|
||||||
if shouldLog(InfoLevel) {
|
if shallLog(InfoLevel) {
|
||||||
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
|
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *durationLogger) Infov(v interface{}) {
|
||||||
|
if shallLog(InfoLevel) {
|
||||||
|
l.write(infoLog, levelInfo, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *durationLogger) Slow(v ...interface{}) {
|
func (l *durationLogger) Slow(v ...interface{}) {
|
||||||
if shouldLog(ErrorLevel) {
|
if shallLog(ErrorLevel) {
|
||||||
l.write(slowLog, levelSlow, fmt.Sprint(v...))
|
l.write(slowLog, levelSlow, fmt.Sprint(v...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *durationLogger) Slowf(format string, v ...interface{}) {
|
func (l *durationLogger) Slowf(format string, v ...interface{}) {
|
||||||
if shouldLog(ErrorLevel) {
|
if shallLog(ErrorLevel) {
|
||||||
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
|
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *durationLogger) Slowv(v interface{}) {
|
||||||
|
if shallLog(ErrorLevel) {
|
||||||
|
l.write(slowLog, levelSlow, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *durationLogger) WithDuration(duration time.Duration) Logger {
|
func (l *durationLogger) WithDuration(duration time.Duration) Logger {
|
||||||
l.Duration = timex.ReprOfDuration(duration)
|
l.Duration = timex.ReprOfDuration(duration)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *durationLogger) write(writer io.Writer, level, content string) {
|
func (l *durationLogger) write(writer io.Writer, level string, val interface{}) {
|
||||||
l.Timestamp = getTimestamp()
|
l.Timestamp = getTimestamp()
|
||||||
l.Level = level
|
l.Level = level
|
||||||
l.Content = content
|
l.Content = val
|
||||||
outputJson(writer, logEntry(*l))
|
outputJson(writer, l)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,12 +65,14 @@ var (
|
|||||||
timeFormat = "2006-01-02T15:04:05.000Z07"
|
timeFormat = "2006-01-02T15:04:05.000Z07"
|
||||||
writeConsole bool
|
writeConsole bool
|
||||||
logLevel uint32
|
logLevel uint32
|
||||||
infoLog io.WriteCloser
|
// use uint32 for atomic operations
|
||||||
errorLog io.WriteCloser
|
disableStat uint32
|
||||||
severeLog io.WriteCloser
|
infoLog io.WriteCloser
|
||||||
slowLog io.WriteCloser
|
errorLog io.WriteCloser
|
||||||
statLog io.WriteCloser
|
severeLog io.WriteCloser
|
||||||
stackLog io.Writer
|
slowLog io.WriteCloser
|
||||||
|
statLog io.WriteCloser
|
||||||
|
stackLog io.Writer
|
||||||
|
|
||||||
once sync.Once
|
once sync.Once
|
||||||
initialized uint32
|
initialized uint32
|
||||||
@@ -79,10 +81,10 @@ var (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
logEntry struct {
|
logEntry struct {
|
||||||
Timestamp string `json:"@timestamp"`
|
Timestamp string `json:"@timestamp"`
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
Duration string `json:"duration,omitempty"`
|
Duration string `json:"duration,omitempty"`
|
||||||
Content string `json:"content"`
|
Content interface{} `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
logOptions struct {
|
logOptions struct {
|
||||||
@@ -98,10 +100,13 @@ type (
|
|||||||
Logger interface {
|
Logger interface {
|
||||||
Error(...interface{})
|
Error(...interface{})
|
||||||
Errorf(string, ...interface{})
|
Errorf(string, ...interface{})
|
||||||
|
Errorv(interface{})
|
||||||
Info(...interface{})
|
Info(...interface{})
|
||||||
Infof(string, ...interface{})
|
Infof(string, ...interface{})
|
||||||
|
Infov(interface{})
|
||||||
Slow(...interface{})
|
Slow(...interface{})
|
||||||
Slowf(string, ...interface{})
|
Slowf(string, ...interface{})
|
||||||
|
Slowv(interface{})
|
||||||
WithDuration(time.Duration) Logger
|
WithDuration(time.Duration) Logger
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -133,7 +138,7 @@ func SetUp(c LogConf) error {
|
|||||||
|
|
||||||
// Alert alerts v in alert level, and the message is written to error log.
|
// Alert alerts v in alert level, and the message is written to error log.
|
||||||
func Alert(v string) {
|
func Alert(v string) {
|
||||||
output(errorLog, levelAlert, v)
|
outputText(errorLog, levelAlert, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the logging.
|
// Close closes the logging.
|
||||||
@@ -195,24 +200,29 @@ func Disable() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisableStat disables the stat logs.
|
||||||
|
func DisableStat() {
|
||||||
|
atomic.StoreUint32(&disableStat, 1)
|
||||||
|
}
|
||||||
|
|
||||||
// Error writes v into error log.
|
// Error writes v into error log.
|
||||||
func Error(v ...interface{}) {
|
func Error(v ...interface{}) {
|
||||||
ErrorCaller(1, v...)
|
ErrorCaller(1, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf writes v with format into error log.
|
|
||||||
func Errorf(format string, v ...interface{}) {
|
|
||||||
ErrorCallerf(1, format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorCaller writes v with context into error log.
|
// ErrorCaller writes v with context into error log.
|
||||||
func ErrorCaller(callDepth int, v ...interface{}) {
|
func ErrorCaller(callDepth int, v ...interface{}) {
|
||||||
errorSync(fmt.Sprint(v...), callDepth+callerInnerDepth)
|
errorTextSync(fmt.Sprint(v...), callDepth+callerInnerDepth)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorCallerf writes v with context in format into error log.
|
// ErrorCallerf writes v with context in format into error log.
|
||||||
func ErrorCallerf(callDepth int, format string, v ...interface{}) {
|
func ErrorCallerf(callDepth int, format string, v ...interface{}) {
|
||||||
errorSync(fmt.Sprintf(format, v...), callDepth+callerInnerDepth)
|
errorTextSync(fmt.Sprintf(format, v...), callDepth+callerInnerDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf writes v with format into error log.
|
||||||
|
func Errorf(format string, v ...interface{}) {
|
||||||
|
ErrorCallerf(1, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorStack writes v along with call stack into error log.
|
// ErrorStack writes v along with call stack into error log.
|
||||||
@@ -227,14 +237,25 @@ func ErrorStackf(format string, v ...interface{}) {
|
|||||||
stackSync(fmt.Sprintf(format, v...))
|
stackSync(fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Errorv writes v into error log with json content.
|
||||||
|
// No call stack attached, because not elegant to pack the messages.
|
||||||
|
func Errorv(v interface{}) {
|
||||||
|
errorAnySync(v)
|
||||||
|
}
|
||||||
|
|
||||||
// Info writes v into access log.
|
// Info writes v into access log.
|
||||||
func Info(v ...interface{}) {
|
func Info(v ...interface{}) {
|
||||||
infoSync(fmt.Sprint(v...))
|
infoTextSync(fmt.Sprint(v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infof writes v with format into access log.
|
// Infof writes v with format into access log.
|
||||||
func Infof(format string, v ...interface{}) {
|
func Infof(format string, v ...interface{}) {
|
||||||
infoSync(fmt.Sprintf(format, v...))
|
infoTextSync(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infov writes v into access log with json content.
|
||||||
|
func Infov(v interface{}) {
|
||||||
|
infoAnySync(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must checks if err is nil, otherwise logs the err and exits.
|
// Must checks if err is nil, otherwise logs the err and exits.
|
||||||
@@ -242,7 +263,7 @@ func Must(err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
msg := formatWithCaller(err.Error(), 3)
|
msg := formatWithCaller(err.Error(), 3)
|
||||||
log.Print(msg)
|
log.Print(msg)
|
||||||
output(severeLog, levelFatal, msg)
|
outputText(severeLog, levelFatal, msg)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,12 +285,17 @@ func Severef(format string, v ...interface{}) {
|
|||||||
|
|
||||||
// Slow writes v into slow log.
|
// Slow writes v into slow log.
|
||||||
func Slow(v ...interface{}) {
|
func Slow(v ...interface{}) {
|
||||||
slowSync(fmt.Sprint(v...))
|
slowTextSync(fmt.Sprint(v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slowf writes v with format into slow log.
|
// Slowf writes v with format into slow log.
|
||||||
func Slowf(format string, v ...interface{}) {
|
func Slowf(format string, v ...interface{}) {
|
||||||
slowSync(fmt.Sprintf(format, v...))
|
slowTextSync(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slowv writes v into slow log with json content.
|
||||||
|
func Slowv(v interface{}) {
|
||||||
|
slowAnySync(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat writes v into stat log.
|
// Stat writes v into stat log.
|
||||||
@@ -312,8 +338,14 @@ func createOutput(path string) (io.WriteCloser, error) {
|
|||||||
options.gzipEnabled), options.gzipEnabled)
|
options.gzipEnabled), options.gzipEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorSync(msg string, callDepth int) {
|
func errorAnySync(v interface{}) {
|
||||||
if shouldLog(ErrorLevel) {
|
if shallLog(ErrorLevel) {
|
||||||
|
outputAny(errorLog, levelError, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorTextSync(msg string, callDepth int) {
|
||||||
|
if shallLog(ErrorLevel) {
|
||||||
outputError(errorLog, msg, callDepth)
|
outputError(errorLog, msg, callDepth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,13 +394,28 @@ func handleOptions(opts []LogOption) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func infoSync(msg string) {
|
func infoAnySync(val interface{}) {
|
||||||
if shouldLog(InfoLevel) {
|
if shallLog(InfoLevel) {
|
||||||
output(infoLog, levelInfo, msg)
|
outputAny(infoLog, levelInfo, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func output(writer io.Writer, level, msg string) {
|
func infoTextSync(msg string) {
|
||||||
|
if shallLog(InfoLevel) {
|
||||||
|
outputText(infoLog, levelInfo, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputAny(writer io.Writer, level string, val interface{}) {
|
||||||
|
info := logEntry{
|
||||||
|
Timestamp: getTimestamp(),
|
||||||
|
Level: level,
|
||||||
|
Content: val,
|
||||||
|
}
|
||||||
|
outputJson(writer, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputText(writer io.Writer, level, msg string) {
|
||||||
info := logEntry{
|
info := logEntry{
|
||||||
Timestamp: getTimestamp(),
|
Timestamp: getTimestamp(),
|
||||||
Level: level,
|
Level: level,
|
||||||
@@ -379,7 +426,7 @@ func output(writer io.Writer, level, msg string) {
|
|||||||
|
|
||||||
func outputError(writer io.Writer, msg string, callDepth int) {
|
func outputError(writer io.Writer, msg string, callDepth int) {
|
||||||
content := formatWithCaller(msg, callDepth)
|
content := formatWithCaller(msg, callDepth)
|
||||||
output(writer, levelError, content)
|
outputText(writer, levelError, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputJson(writer io.Writer, info interface{}) {
|
func outputJson(writer io.Writer, info interface{}) {
|
||||||
@@ -481,30 +528,40 @@ func setupWithVolume(c LogConf) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func severeSync(msg string) {
|
func severeSync(msg string) {
|
||||||
if shouldLog(SevereLevel) {
|
if shallLog(SevereLevel) {
|
||||||
output(severeLog, levelSevere, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
outputText(severeLog, levelSevere, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldLog(level uint32) bool {
|
func shallLog(level uint32) bool {
|
||||||
return atomic.LoadUint32(&logLevel) <= level
|
return atomic.LoadUint32(&logLevel) <= level
|
||||||
}
|
}
|
||||||
|
|
||||||
func slowSync(msg string) {
|
func shallLogStat() bool {
|
||||||
if shouldLog(ErrorLevel) {
|
return atomic.LoadUint32(&disableStat) == 0
|
||||||
output(slowLog, levelSlow, msg)
|
}
|
||||||
|
|
||||||
|
func slowAnySync(v interface{}) {
|
||||||
|
if shallLog(ErrorLevel) {
|
||||||
|
outputAny(slowLog, levelSlow, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func slowTextSync(msg string) {
|
||||||
|
if shallLog(ErrorLevel) {
|
||||||
|
outputText(slowLog, levelSlow, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackSync(msg string) {
|
func stackSync(msg string) {
|
||||||
if shouldLog(ErrorLevel) {
|
if shallLog(ErrorLevel) {
|
||||||
output(stackLog, levelError, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
outputText(stackLog, levelError, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func statSync(msg string) {
|
func statSync(msg string) {
|
||||||
if shouldLog(InfoLevel) {
|
if shallLogStat() && shallLog(InfoLevel) {
|
||||||
output(statLog, levelStat, msg)
|
outputText(statLog, levelStat, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,30 @@ func TestStructedLogAlert(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStructedLogError(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelError, func(writer io.WriteCloser) {
|
||||||
|
errorLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Error(v...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructedLogErrorf(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelError, func(writer io.WriteCloser) {
|
||||||
|
errorLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Errorf("%s", fmt.Sprint(v...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructedLogErrorv(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelError, func(writer io.WriteCloser) {
|
||||||
|
errorLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Errorv(fmt.Sprint(v...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestStructedLogInfo(t *testing.T) {
|
func TestStructedLogInfo(t *testing.T) {
|
||||||
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
|
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
|
||||||
infoLog = writer
|
infoLog = writer
|
||||||
@@ -100,6 +124,22 @@ func TestStructedLogInfo(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStructedLogInfof(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
|
||||||
|
infoLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Infof("%s", fmt.Sprint(v...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructedLogInfov(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
|
||||||
|
infoLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Infov(fmt.Sprint(v...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestStructedLogSlow(t *testing.T) {
|
func TestStructedLogSlow(t *testing.T) {
|
||||||
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) {
|
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) {
|
||||||
slowLog = writer
|
slowLog = writer
|
||||||
@@ -116,6 +156,14 @@ func TestStructedLogSlowf(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStructedLogSlowv(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) {
|
||||||
|
slowLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Slowv(fmt.Sprint(v...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestStructedLogStat(t *testing.T) {
|
func TestStructedLogStat(t *testing.T) {
|
||||||
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
|
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
|
||||||
statLog = writer
|
statLog = writer
|
||||||
@@ -246,6 +294,17 @@ func TestDisable(t *testing.T) {
|
|||||||
assert.Nil(t, Close())
|
assert.Nil(t, Close())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisableStat(t *testing.T) {
|
||||||
|
DisableStat()
|
||||||
|
|
||||||
|
const message = "hello there"
|
||||||
|
writer := new(mockWriter)
|
||||||
|
statLog = writer
|
||||||
|
atomic.StoreUint32(&initialized, 1)
|
||||||
|
Stat(message)
|
||||||
|
assert.Equal(t, 0, writer.builder.Len())
|
||||||
|
}
|
||||||
|
|
||||||
func TestWithGzip(t *testing.T) {
|
func TestWithGzip(t *testing.T) {
|
||||||
fn := WithGzip()
|
fn := WithGzip()
|
||||||
var opt logOptions
|
var opt logOptions
|
||||||
@@ -357,7 +416,9 @@ func doTestStructedLog(t *testing.T, level string, setup func(writer io.WriteClo
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, level, entry.Level)
|
assert.Equal(t, level, entry.Level)
|
||||||
assert.True(t, strings.Contains(entry.Content, message))
|
val, ok := entry.Content.(string)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.True(t, strings.Contains(val, message))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSetLevelTwiceWithMode(t *testing.T, mode string) {
|
func testSetLevelTwiceWithMode(t *testing.T, mode string) {
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ type (
|
|||||||
done chan lang.PlaceholderType
|
done chan lang.PlaceholderType
|
||||||
rule RotateRule
|
rule RotateRule
|
||||||
compress bool
|
compress bool
|
||||||
keepDays int
|
|
||||||
// can't use threading.RoutineGroup because of cycle import
|
// can't use threading.RoutineGroup because of cycle import
|
||||||
waitGroup sync.WaitGroup
|
waitGroup sync.WaitGroup
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package logx
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -97,7 +98,13 @@ func TestRotateLoggerRotate(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
err = logger.rotate()
|
err = logger.rotate()
|
||||||
assert.Nil(t, err)
|
switch v := err.(type) {
|
||||||
|
case *os.LinkError:
|
||||||
|
// avoid rename error on docker container
|
||||||
|
assert.Equal(t, syscall.EXDEV, v.Err)
|
||||||
|
default:
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRotateLoggerWrite(t *testing.T) {
|
func TestRotateLoggerWrite(t *testing.T) {
|
||||||
|
|||||||
@@ -44,5 +44,5 @@ func captureOutput(f func()) string {
|
|||||||
func getContent(jsonStr string) string {
|
func getContent(jsonStr string) string {
|
||||||
var entry logEntry
|
var entry logEntry
|
||||||
json.Unmarshal([]byte(jsonStr), &entry)
|
json.Unmarshal([]byte(jsonStr), &entry)
|
||||||
return entry.Content
|
return entry.Content.(string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/timex"
|
"github.com/tal-tech/go-zero/core/timex"
|
||||||
"github.com/tal-tech/go-zero/core/trace/tracespec"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
type traceLogger struct {
|
type traceLogger struct {
|
||||||
@@ -18,50 +18,68 @@ type traceLogger struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *traceLogger) Error(v ...interface{}) {
|
func (l *traceLogger) Error(v ...interface{}) {
|
||||||
if shouldLog(ErrorLevel) {
|
if shallLog(ErrorLevel) {
|
||||||
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
|
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *traceLogger) Errorf(format string, v ...interface{}) {
|
func (l *traceLogger) Errorf(format string, v ...interface{}) {
|
||||||
if shouldLog(ErrorLevel) {
|
if shallLog(ErrorLevel) {
|
||||||
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
|
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *traceLogger) Errorv(v interface{}) {
|
||||||
|
if shallLog(ErrorLevel) {
|
||||||
|
l.write(errorLog, levelError, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *traceLogger) Info(v ...interface{}) {
|
func (l *traceLogger) Info(v ...interface{}) {
|
||||||
if shouldLog(InfoLevel) {
|
if shallLog(InfoLevel) {
|
||||||
l.write(infoLog, levelInfo, fmt.Sprint(v...))
|
l.write(infoLog, levelInfo, fmt.Sprint(v...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *traceLogger) Infof(format string, v ...interface{}) {
|
func (l *traceLogger) Infof(format string, v ...interface{}) {
|
||||||
if shouldLog(InfoLevel) {
|
if shallLog(InfoLevel) {
|
||||||
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
|
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *traceLogger) Infov(v interface{}) {
|
||||||
|
if shallLog(InfoLevel) {
|
||||||
|
l.write(infoLog, levelInfo, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *traceLogger) Slow(v ...interface{}) {
|
func (l *traceLogger) Slow(v ...interface{}) {
|
||||||
if shouldLog(ErrorLevel) {
|
if shallLog(ErrorLevel) {
|
||||||
l.write(slowLog, levelSlow, fmt.Sprint(v...))
|
l.write(slowLog, levelSlow, fmt.Sprint(v...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *traceLogger) Slowf(format string, v ...interface{}) {
|
func (l *traceLogger) Slowf(format string, v ...interface{}) {
|
||||||
if shouldLog(ErrorLevel) {
|
if shallLog(ErrorLevel) {
|
||||||
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
|
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *traceLogger) Slowv(v interface{}) {
|
||||||
|
if shallLog(ErrorLevel) {
|
||||||
|
l.write(slowLog, levelSlow, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *traceLogger) WithDuration(duration time.Duration) Logger {
|
func (l *traceLogger) WithDuration(duration time.Duration) Logger {
|
||||||
l.Duration = timex.ReprOfDuration(duration)
|
l.Duration = timex.ReprOfDuration(duration)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *traceLogger) write(writer io.Writer, level, content string) {
|
func (l *traceLogger) write(writer io.Writer, level string, val interface{}) {
|
||||||
l.Timestamp = getTimestamp()
|
l.Timestamp = getTimestamp()
|
||||||
l.Level = level
|
l.Level = level
|
||||||
l.Content = content
|
l.Content = val
|
||||||
l.Trace = traceIdFromContext(l.ctx)
|
l.Trace = traceIdFromContext(l.ctx)
|
||||||
l.Span = spanIdFromContext(l.ctx)
|
l.Span = spanIdFromContext(l.ctx)
|
||||||
outputJson(writer, l)
|
outputJson(writer, l)
|
||||||
@@ -75,19 +93,19 @@ func WithContext(ctx context.Context) Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func spanIdFromContext(ctx context.Context) string {
|
func spanIdFromContext(ctx context.Context) string {
|
||||||
t, ok := ctx.Value(tracespec.TracingKey).(tracespec.Trace)
|
spanCtx := trace.SpanContextFromContext(ctx)
|
||||||
if !ok {
|
if spanCtx.HasSpanID() {
|
||||||
return ""
|
return spanCtx.SpanID().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.SpanId()
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func traceIdFromContext(ctx context.Context) string {
|
func traceIdFromContext(ctx context.Context) string {
|
||||||
t, ok := ctx.Value(tracespec.TracingKey).(tracespec.Trace)
|
spanCtx := trace.SpanContextFromContext(ctx)
|
||||||
if !ok {
|
if spanCtx.HasTraceID() {
|
||||||
return ""
|
return spanCtx.TraceID().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.TraceId()
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,71 +9,90 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/trace/tracespec"
|
"go.opentelemetry.io/otel"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
mockTraceID = "mock-trace-id"
|
traceKey = "trace"
|
||||||
mockSpanID = "mock-span-id"
|
spanKey = "span"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mock tracespec.Trace = new(mockTrace)
|
|
||||||
|
|
||||||
func TestTraceLog(t *testing.T) {
|
func TestTraceLog(t *testing.T) {
|
||||||
var buf mockWriter
|
var buf mockWriter
|
||||||
atomic.StoreUint32(&initialized, 1)
|
atomic.StoreUint32(&initialized, 1)
|
||||||
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
|
otp := otel.GetTracerProvider()
|
||||||
|
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
defer otel.SetTracerProvider(otp)
|
||||||
|
|
||||||
|
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar")
|
||||||
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog)
|
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog)
|
||||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTraceError(t *testing.T) {
|
func TestTraceError(t *testing.T) {
|
||||||
var buf mockWriter
|
var buf mockWriter
|
||||||
atomic.StoreUint32(&initialized, 1)
|
atomic.StoreUint32(&initialized, 1)
|
||||||
errorLog = newLogWriter(log.New(&buf, "", flags))
|
errorLog = newLogWriter(log.New(&buf, "", flags))
|
||||||
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
|
otp := otel.GetTracerProvider()
|
||||||
|
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
defer otel.SetTracerProvider(otp)
|
||||||
|
|
||||||
|
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar")
|
||||||
l := WithContext(ctx).(*traceLogger)
|
l := WithContext(ctx).(*traceLogger)
|
||||||
SetLevel(InfoLevel)
|
SetLevel(InfoLevel)
|
||||||
l.WithDuration(time.Second).Error(testlog)
|
l.WithDuration(time.Second).Error(testlog)
|
||||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
l.WithDuration(time.Second).Errorf(testlog)
|
l.WithDuration(time.Second).Errorf(testlog)
|
||||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTraceInfo(t *testing.T) {
|
func TestTraceInfo(t *testing.T) {
|
||||||
var buf mockWriter
|
var buf mockWriter
|
||||||
atomic.StoreUint32(&initialized, 1)
|
atomic.StoreUint32(&initialized, 1)
|
||||||
infoLog = newLogWriter(log.New(&buf, "", flags))
|
infoLog = newLogWriter(log.New(&buf, "", flags))
|
||||||
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
|
otp := otel.GetTracerProvider()
|
||||||
|
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
defer otel.SetTracerProvider(otp)
|
||||||
|
|
||||||
|
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar")
|
||||||
l := WithContext(ctx).(*traceLogger)
|
l := WithContext(ctx).(*traceLogger)
|
||||||
SetLevel(InfoLevel)
|
SetLevel(InfoLevel)
|
||||||
l.WithDuration(time.Second).Info(testlog)
|
l.WithDuration(time.Second).Info(testlog)
|
||||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
l.WithDuration(time.Second).Infof(testlog)
|
l.WithDuration(time.Second).Infof(testlog)
|
||||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTraceSlow(t *testing.T) {
|
func TestTraceSlow(t *testing.T) {
|
||||||
var buf mockWriter
|
var buf mockWriter
|
||||||
atomic.StoreUint32(&initialized, 1)
|
atomic.StoreUint32(&initialized, 1)
|
||||||
slowLog = newLogWriter(log.New(&buf, "", flags))
|
slowLog = newLogWriter(log.New(&buf, "", flags))
|
||||||
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
|
otp := otel.GetTracerProvider()
|
||||||
|
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
defer otel.SetTracerProvider(otp)
|
||||||
|
|
||||||
|
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar")
|
||||||
l := WithContext(ctx).(*traceLogger)
|
l := WithContext(ctx).(*traceLogger)
|
||||||
SetLevel(InfoLevel)
|
SetLevel(InfoLevel)
|
||||||
l.WithDuration(time.Second).Slow(testlog)
|
l.WithDuration(time.Second).Slow(testlog)
|
||||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
l.WithDuration(time.Second).Slowf(testlog)
|
l.WithDuration(time.Second).Slowf(testlog)
|
||||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTraceWithoutContext(t *testing.T) {
|
func TestTraceWithoutContext(t *testing.T) {
|
||||||
@@ -83,34 +102,10 @@ func TestTraceWithoutContext(t *testing.T) {
|
|||||||
l := WithContext(context.Background()).(*traceLogger)
|
l := WithContext(context.Background()).(*traceLogger)
|
||||||
SetLevel(InfoLevel)
|
SetLevel(InfoLevel)
|
||||||
l.WithDuration(time.Second).Info(testlog)
|
l.WithDuration(time.Second).Info(testlog)
|
||||||
assert.False(t, strings.Contains(buf.String(), mockTraceID))
|
assert.False(t, strings.Contains(buf.String(), traceKey))
|
||||||
assert.False(t, strings.Contains(buf.String(), mockSpanID))
|
assert.False(t, strings.Contains(buf.String(), spanKey))
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
l.WithDuration(time.Second).Infof(testlog)
|
l.WithDuration(time.Second).Infof(testlog)
|
||||||
assert.False(t, strings.Contains(buf.String(), mockTraceID))
|
assert.False(t, strings.Contains(buf.String(), traceKey))
|
||||||
assert.False(t, strings.Contains(buf.String(), mockSpanID))
|
assert.False(t, strings.Contains(buf.String(), spanKey))
|
||||||
}
|
|
||||||
|
|
||||||
type mockTrace struct{}
|
|
||||||
|
|
||||||
func (t mockTrace) TraceId() string {
|
|
||||||
return mockTraceID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t mockTrace) SpanId() string {
|
|
||||||
return mockSpanID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t mockTrace) Finish() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t mockTrace) Fork(ctx context.Context, serviceName, operationName string) (context.Context, tracespec.Trace) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t mockTrace) Follow(ctx context.Context, serviceName, operationName string) (context.Context, tracespec.Trace) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t mockTrace) Visit(fn func(key string, val string) bool) {
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ type (
|
|||||||
UnmarshalOption func(*unmarshalOptions)
|
UnmarshalOption func(*unmarshalOptions)
|
||||||
|
|
||||||
unmarshalOptions struct {
|
unmarshalOptions struct {
|
||||||
fromString bool
|
fromString bool
|
||||||
|
canonicalKey func(key string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
keyCache map[string][]string
|
keyCache map[string][]string
|
||||||
@@ -229,7 +230,7 @@ func (u *Unmarshaler) processFieldPrimitive(field reflect.StructField, value ref
|
|||||||
default:
|
default:
|
||||||
switch v := mapValue.(type) {
|
switch v := mapValue.(type) {
|
||||||
case json.Number:
|
case json.Number:
|
||||||
return u.processFieldPrimitiveWithJsonNumber(field, value, v, opts, fullName)
|
return u.processFieldPrimitiveWithJSONNumber(field, value, v, opts, fullName)
|
||||||
default:
|
default:
|
||||||
if typeKind == valueKind {
|
if typeKind == valueKind {
|
||||||
if err := validateValueInOptions(opts.options(), mapValue); err != nil {
|
if err := validateValueInOptions(opts.options(), mapValue); err != nil {
|
||||||
@@ -244,7 +245,7 @@ func (u *Unmarshaler) processFieldPrimitive(field reflect.StructField, value ref
|
|||||||
return newTypeMismatchError(fullName)
|
return newTypeMismatchError(fullName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processFieldPrimitiveWithJsonNumber(field reflect.StructField, value reflect.Value,
|
func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(field reflect.StructField, value reflect.Value,
|
||||||
v json.Number, opts *fieldOptionsWithContext, fullName string) error {
|
v json.Number, opts *fieldOptionsWithContext, fullName string) error {
|
||||||
fieldType := field.Type
|
fieldType := field.Type
|
||||||
fieldKind := fieldType.Kind()
|
fieldKind := fieldType.Kind()
|
||||||
@@ -323,7 +324,11 @@ func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect
|
|||||||
}
|
}
|
||||||
|
|
||||||
fullName = join(fullName, key)
|
fullName = join(fullName, key)
|
||||||
mapValue, hasValue := getValue(m, key)
|
canonicalKey := key
|
||||||
|
if u.opts.canonicalKey != nil {
|
||||||
|
canonicalKey = u.opts.canonicalKey(key)
|
||||||
|
}
|
||||||
|
mapValue, hasValue := getValue(m, canonicalKey)
|
||||||
if hasValue {
|
if hasValue {
|
||||||
return u.processNamedFieldWithValue(field, value, mapValue, key, opts, fullName)
|
return u.processNamedFieldWithValue(field, value, mapValue, key, opts, fullName)
|
||||||
}
|
}
|
||||||
@@ -457,6 +462,10 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
|
|||||||
} else {
|
} else {
|
||||||
conv.Index(i).Set(target.Elem())
|
conv.Index(i).Set(target.Elem())
|
||||||
}
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
if err := u.fillSlice(dereffedBaseType, conv.Index(i), ithValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if err := u.fillSliceValue(conv, i, dereffedBaseKind, ithValue); err != nil {
|
if err := u.fillSliceValue(conv, i, dereffedBaseKind, ithValue); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -492,17 +501,30 @@ func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, baseKind reflect.Kind, value interface{}) error {
|
func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, baseKind reflect.Kind, value interface{}) error {
|
||||||
|
ithVal := slice.Index(index)
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case json.Number:
|
case json.Number:
|
||||||
return setValue(baseKind, slice.Index(index), v.String())
|
return setValue(baseKind, ithVal, v.String())
|
||||||
default:
|
default:
|
||||||
// don't need to consider the difference between int, int8, int16, int32, int64,
|
// don't need to consider the difference between int, int8, int16, int32, int64,
|
||||||
// uint, uint8, uint16, uint32, uint64, because they're handled as json.Number.
|
// uint, uint8, uint16, uint32, uint64, because they're handled as json.Number.
|
||||||
if slice.Index(index).Kind() != reflect.TypeOf(value).Kind() {
|
if ithVal.Kind() == reflect.Ptr {
|
||||||
|
baseType := Deref(ithVal.Type())
|
||||||
|
if baseType.Kind() != reflect.TypeOf(value).Kind() {
|
||||||
|
return errTypeMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
target := reflect.New(baseType).Elem()
|
||||||
|
target.Set(reflect.ValueOf(value))
|
||||||
|
ithVal.Set(target.Addr())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ithVal.Kind() != reflect.TypeOf(value).Kind() {
|
||||||
return errTypeMismatch
|
return errTypeMismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
slice.Index(index).Set(reflect.ValueOf(value))
|
ithVal.Set(reflect.ValueOf(value))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -604,6 +626,13 @@ func WithStringValues() UnmarshalOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCanonicalKeyFunc customizes a Unmarshaler with Canonical Key func
|
||||||
|
func WithCanonicalKeyFunc(f func(string) string) UnmarshalOption {
|
||||||
|
return func(opt *unmarshalOptions) {
|
||||||
|
opt.canonicalKey = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fillDurationValue(fieldKind reflect.Kind, value reflect.Value, dur string) error {
|
func fillDurationValue(fieldKind reflect.Kind, value reflect.Value, dur string) error {
|
||||||
d, err := time.ParseDuration(dur)
|
d, err := time.ParseDuration(dur)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package mapping
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -2480,3 +2481,40 @@ func BenchmarkUnmarshal(b *testing.B) {
|
|||||||
UnmarshalKey(data, &an)
|
UnmarshalKey(data, &an)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonReaderMultiArray(t *testing.T) {
|
||||||
|
payload := `{"a": "133", "b": [["add", "cccd"], ["eeee"]]}`
|
||||||
|
var res struct {
|
||||||
|
A string `json:"a"`
|
||||||
|
B [][]string `json:"b"`
|
||||||
|
}
|
||||||
|
reader := strings.NewReader(payload)
|
||||||
|
err := UnmarshalJsonReader(reader, &res)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 2, len(res.B))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonReaderPtrMultiArray(t *testing.T) {
|
||||||
|
payload := `{"a": "133", "b": [["add", "cccd"], ["eeee"]]}`
|
||||||
|
var res struct {
|
||||||
|
A string `json:"a"`
|
||||||
|
B [][]*string `json:"b"`
|
||||||
|
}
|
||||||
|
reader := strings.NewReader(payload)
|
||||||
|
err := UnmarshalJsonReader(reader, &res)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 2, len(res.B))
|
||||||
|
assert.Equal(t, 2, len(res.B[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonReaderPtrArray(t *testing.T) {
|
||||||
|
payload := `{"a": "133", "b": ["add", "cccd", "eeee"]}`
|
||||||
|
var res struct {
|
||||||
|
A string `json:"a"`
|
||||||
|
B []*string `json:"b"`
|
||||||
|
}
|
||||||
|
reader := strings.NewReader(payload)
|
||||||
|
err := UnmarshalJsonReader(reader, &res)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 3, len(res.B))
|
||||||
|
}
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ func parseNumberRange(str string) (*numberRange, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOption(fieldOpts *fieldOptions, fieldName string, option string) error {
|
func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
|
||||||
switch {
|
switch {
|
||||||
case option == stringOption:
|
case option == stringOption:
|
||||||
fieldOpts.FromString = true
|
fieldOpts.FromString = true
|
||||||
|
|||||||
@@ -112,6 +112,12 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
|
|||||||
opts ...Option) (interface{}, error) {
|
opts ...Option) (interface{}, error) {
|
||||||
options := buildOptions(opts...)
|
options := buildOptions(opts...)
|
||||||
output := make(chan interface{})
|
output := make(chan interface{})
|
||||||
|
defer func() {
|
||||||
|
for range output {
|
||||||
|
panic("more than one element written in reducer")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
collector := make(chan interface{}, options.workers)
|
collector := make(chan interface{}, options.workers)
|
||||||
done := syncx.NewDoneChan()
|
done := syncx.NewDoneChan()
|
||||||
writer := newGuardedWriter(output, done.Done())
|
writer := newGuardedWriter(output, done.Done())
|
||||||
@@ -136,14 +142,16 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
drain(collector)
|
||||||
|
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
cancel(fmt.Errorf("%v", r))
|
cancel(fmt.Errorf("%v", r))
|
||||||
} else {
|
} else {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
reducer(collector, writer, cancel)
|
reducer(collector, writer, cancel)
|
||||||
drain(collector)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go executeMappers(func(item interface{}, w Writer) {
|
go executeMappers(func(item interface{}, w Writer) {
|
||||||
@@ -165,7 +173,6 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
|
|||||||
func MapReduceVoid(generate GenerateFunc, mapper MapperFunc, reducer VoidReducerFunc, opts ...Option) error {
|
func MapReduceVoid(generate GenerateFunc, mapper MapperFunc, reducer VoidReducerFunc, opts ...Option) error {
|
||||||
_, err := MapReduce(generate, mapper, func(input <-chan interface{}, writer Writer, cancel func(error)) {
|
_, err := MapReduce(generate, mapper, func(input <-chan interface{}, writer Writer, cancel func(error)) {
|
||||||
reducer(input, cancel)
|
reducer(input, cancel)
|
||||||
drain(input)
|
|
||||||
// We need to write a placeholder to let MapReduce to continue on reducer done,
|
// We need to write a placeholder to let MapReduce to continue on reducer done,
|
||||||
// otherwise, all goroutines are waiting. The placeholder will be discarded by MapReduce.
|
// otherwise, all goroutines are waiting. The placeholder will be discarded by MapReduce.
|
||||||
writer.Write(lang.Placeholder)
|
writer.Write(lang.Placeholder)
|
||||||
|
|||||||
@@ -202,6 +202,22 @@ func TestMapReduce(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMapReduceWithReduerWriteMoreThanOnce(t *testing.T) {
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
MapReduce(func(source chan<- interface{}) {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
source <- i
|
||||||
|
}
|
||||||
|
}, func(item interface{}, writer Writer, cancel func(error)) {
|
||||||
|
writer.Write(item)
|
||||||
|
}, func(pipe <-chan interface{}, writer Writer, cancel func(error)) {
|
||||||
|
drain(pipe)
|
||||||
|
writer.Write("one")
|
||||||
|
writer.Write("two")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestMapReduceVoid(t *testing.T) {
|
func TestMapReduceVoid(t *testing.T) {
|
||||||
var value uint32
|
var value uint32
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
// +build linux darwin
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
// +build linux darwin
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
@@ -14,5 +15,5 @@ func AddWrapUpListener(fn func()) func() {
|
|||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetTimeoutToForceQuit(duration time.Duration) {
|
func SetTimeToForceQuit(duration time.Duration) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
// +build linux darwin
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|||||||
10
core/proc/signals+polyfill.go
Normal file
10
core/proc/signals+polyfill.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package proc
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
func Done() <-chan struct{} {
|
||||||
|
return context.Background().Done()
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
// +build linux darwin
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
@@ -12,6 +13,8 @@ import (
|
|||||||
|
|
||||||
const timeFormat = "0102150405"
|
const timeFormat = "0102150405"
|
||||||
|
|
||||||
|
var done = make(chan struct{})
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
go func() {
|
go func() {
|
||||||
var profiler Stopper
|
var profiler Stopper
|
||||||
@@ -33,6 +36,13 @@ func init() {
|
|||||||
profiler = nil
|
profiler = nil
|
||||||
}
|
}
|
||||||
case syscall.SIGTERM:
|
case syscall.SIGTERM:
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// already closed
|
||||||
|
default:
|
||||||
|
close(done)
|
||||||
|
}
|
||||||
|
|
||||||
gracefulStop(signals)
|
gracefulStop(signals)
|
||||||
default:
|
default:
|
||||||
logx.Error("Got unregistered signal:", v)
|
logx.Error("Got unregistered signal:", v)
|
||||||
@@ -40,3 +50,8 @@ func init() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Done returns the channel that notifies the process quitting.
|
||||||
|
func Done() <-chan struct{} {
|
||||||
|
return done
|
||||||
|
}
|
||||||
|
|||||||
16
core/proc/signals_test.go
Normal file
16
core/proc/signals_test.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package proc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDone(t *testing.T) {
|
||||||
|
select {
|
||||||
|
case <-Done():
|
||||||
|
assert.Fail(t, "should run")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
assert.NotNil(t, Done())
|
||||||
|
}
|
||||||
@@ -71,7 +71,7 @@ func NewQueue(producerFactory ProducerFactory, consumerFactory ConsumerFactory)
|
|||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddListener adds a litener to q.
|
// AddListener adds a listener to q.
|
||||||
func (q *Queue) AddListener(listener Listener) {
|
func (q *Queue) AddListener(listener Listener) {
|
||||||
q.listeners = append(q.listeners, listener)
|
q.listeners = append(q.listeners, listener)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package search
|
package search
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
colon = ':'
|
colon = ':'
|
||||||
@@ -8,16 +11,16 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrDupItem means adding duplicated item.
|
// errDupItem means adding duplicated item.
|
||||||
ErrDupItem = errors.New("duplicated item")
|
errDupItem = errors.New("duplicated item")
|
||||||
// ErrDupSlash means item is started with more than one slash.
|
// errDupSlash means item is started with more than one slash.
|
||||||
ErrDupSlash = errors.New("duplicated slash")
|
errDupSlash = errors.New("duplicated slash")
|
||||||
// ErrEmptyItem means adding empty item.
|
// errEmptyItem means adding empty item.
|
||||||
ErrEmptyItem = errors.New("empty item")
|
errEmptyItem = errors.New("empty item")
|
||||||
// ErrInvalidState means search tree is in an invalid state.
|
// errInvalidState means search tree is in an invalid state.
|
||||||
ErrInvalidState = errors.New("search tree is in an invalid state")
|
errInvalidState = errors.New("search tree is in an invalid state")
|
||||||
// ErrNotFromRoot means path is not starting with slash.
|
// errNotFromRoot means path is not starting with slash.
|
||||||
ErrNotFromRoot = errors.New("path should start with /")
|
errNotFromRoot = errors.New("path should start with /")
|
||||||
|
|
||||||
// NotFound is used to hold the not found result.
|
// NotFound is used to hold the not found result.
|
||||||
NotFound Result
|
NotFound Result
|
||||||
@@ -58,14 +61,22 @@ func NewTree() *Tree {
|
|||||||
// Add adds item to associate with route.
|
// Add adds item to associate with route.
|
||||||
func (t *Tree) Add(route string, item interface{}) error {
|
func (t *Tree) Add(route string, item interface{}) error {
|
||||||
if len(route) == 0 || route[0] != slash {
|
if len(route) == 0 || route[0] != slash {
|
||||||
return ErrNotFromRoot
|
return errNotFromRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
if item == nil {
|
if item == nil {
|
||||||
return ErrEmptyItem
|
return errEmptyItem
|
||||||
}
|
}
|
||||||
|
|
||||||
return add(t.root, route[1:], item)
|
err := add(t.root, route[1:], item)
|
||||||
|
switch err {
|
||||||
|
case errDupItem:
|
||||||
|
return duplicatedItem(route)
|
||||||
|
case errDupSlash:
|
||||||
|
return duplicatedSlash(route)
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search searches item that associates with given route.
|
// Search searches item that associates with given route.
|
||||||
@@ -86,22 +97,22 @@ func (t *Tree) next(n *node, route string, result *Result) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range route {
|
for i := range route {
|
||||||
if route[i] == slash {
|
if route[i] != slash {
|
||||||
token := route[:i]
|
continue
|
||||||
return n.forEach(func(k string, v *node) bool {
|
|
||||||
if r := match(k, token); r.found {
|
|
||||||
if t.next(v, route[i+1:], result) {
|
|
||||||
if r.named {
|
|
||||||
addParam(result, r.key, r.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token := route[:i]
|
||||||
|
return n.forEach(func(k string, v *node) bool {
|
||||||
|
r := match(k, token)
|
||||||
|
if !r.found || !t.next(v, route[i+1:], result) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r.named {
|
||||||
|
addParam(result, r.key, r.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return n.forEach(func(k string, v *node) bool {
|
return n.forEach(func(k string, v *node) bool {
|
||||||
@@ -141,7 +152,7 @@ func (nd *node) getChildren(route string) map[string]*node {
|
|||||||
func add(nd *node, route string, item interface{}) error {
|
func add(nd *node, route string, item interface{}) error {
|
||||||
if len(route) == 0 {
|
if len(route) == 0 {
|
||||||
if nd.item != nil {
|
if nd.item != nil {
|
||||||
return ErrDupItem
|
return errDupItem
|
||||||
}
|
}
|
||||||
|
|
||||||
nd.item = item
|
nd.item = item
|
||||||
@@ -149,31 +160,33 @@ func add(nd *node, route string, item interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if route[0] == slash {
|
if route[0] == slash {
|
||||||
return ErrDupSlash
|
return errDupSlash
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range route {
|
for i := range route {
|
||||||
if route[i] == slash {
|
if route[i] != slash {
|
||||||
token := route[:i]
|
continue
|
||||||
children := nd.getChildren(token)
|
}
|
||||||
if child, ok := children[token]; ok {
|
|
||||||
if child != nil {
|
|
||||||
return add(child, route[i+1:], item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrInvalidState
|
token := route[:i]
|
||||||
|
children := nd.getChildren(token)
|
||||||
|
if child, ok := children[token]; ok {
|
||||||
|
if child != nil {
|
||||||
|
return add(child, route[i+1:], item)
|
||||||
}
|
}
|
||||||
|
|
||||||
child := newNode(nil)
|
return errInvalidState
|
||||||
children[token] = child
|
|
||||||
return add(child, route[i+1:], item)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
child := newNode(nil)
|
||||||
|
children[token] = child
|
||||||
|
return add(child, route[i+1:], item)
|
||||||
}
|
}
|
||||||
|
|
||||||
children := nd.getChildren(route)
|
children := nd.getChildren(route)
|
||||||
if child, ok := children[route]; ok {
|
if child, ok := children[route]; ok {
|
||||||
if child.item != nil {
|
if child.item != nil {
|
||||||
return ErrDupItem
|
return errDupItem
|
||||||
}
|
}
|
||||||
|
|
||||||
child.item = item
|
child.item = item
|
||||||
@@ -192,6 +205,14 @@ func addParam(result *Result, k, v string) {
|
|||||||
result.Params[k] = v
|
result.Params[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func duplicatedItem(item string) error {
|
||||||
|
return fmt.Errorf("duplicated item for %s", item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func duplicatedSlash(item string) error {
|
||||||
|
return fmt.Errorf("duplicated slash for %s", item)
|
||||||
|
}
|
||||||
|
|
||||||
func match(pat, token string) innerResult {
|
func match(pat, token string) innerResult {
|
||||||
if pat[0] == colon {
|
if pat[0] == colon {
|
||||||
return innerResult{
|
return innerResult{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build debug
|
||||||
// +build debug
|
// +build debug
|
||||||
|
|
||||||
package search
|
package search
|
||||||
|
|||||||
@@ -151,9 +151,9 @@ func TestAddDuplicate(t *testing.T) {
|
|||||||
err := tree.Add("/a/b", 1)
|
err := tree.Add("/a/b", 1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
err = tree.Add("/a/b", 2)
|
err = tree.Add("/a/b", 2)
|
||||||
assert.Equal(t, ErrDupItem, err)
|
assert.Error(t, errDupItem, err)
|
||||||
err = tree.Add("/a/b/", 2)
|
err = tree.Add("/a/b/", 2)
|
||||||
assert.Equal(t, ErrDupItem, err)
|
assert.Error(t, errDupItem, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlain(t *testing.T) {
|
func TestPlain(t *testing.T) {
|
||||||
@@ -169,19 +169,19 @@ func TestPlain(t *testing.T) {
|
|||||||
func TestSearchWithDoubleSlashes(t *testing.T) {
|
func TestSearchWithDoubleSlashes(t *testing.T) {
|
||||||
tree := NewTree()
|
tree := NewTree()
|
||||||
err := tree.Add("//a", 1)
|
err := tree.Add("//a", 1)
|
||||||
assert.Error(t, ErrDupSlash, err)
|
assert.Error(t, errDupSlash, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchInvalidRoute(t *testing.T) {
|
func TestSearchInvalidRoute(t *testing.T) {
|
||||||
tree := NewTree()
|
tree := NewTree()
|
||||||
err := tree.Add("", 1)
|
err := tree.Add("", 1)
|
||||||
assert.Equal(t, ErrNotFromRoot, err)
|
assert.Equal(t, errNotFromRoot, err)
|
||||||
err = tree.Add("bad", 1)
|
err = tree.Add("bad", 1)
|
||||||
assert.Equal(t, ErrNotFromRoot, err)
|
assert.Equal(t, errNotFromRoot, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchInvalidItem(t *testing.T) {
|
func TestSearchInvalidItem(t *testing.T) {
|
||||||
tree := NewTree()
|
tree := NewTree()
|
||||||
err := tree.Add("/", nil)
|
err := tree.Add("/", nil)
|
||||||
assert.Equal(t, ErrEmptyItem, err)
|
assert.Equal(t, errEmptyItem, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/prometheus"
|
"github.com/tal-tech/go-zero/core/prometheus"
|
||||||
"github.com/tal-tech/go-zero/core/stat"
|
"github.com/tal-tech/go-zero/core/stat"
|
||||||
|
"github.com/tal-tech/go-zero/core/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -29,6 +30,7 @@ type ServiceConf struct {
|
|||||||
Mode string `json:",default=pro,options=dev|test|rt|pre|pro"`
|
Mode string `json:",default=pro,options=dev|test|rt|pre|pro"`
|
||||||
MetricsUrl string `json:",optional"`
|
MetricsUrl string `json:",optional"`
|
||||||
Prometheus prometheus.Config `json:",optional"`
|
Prometheus prometheus.Config `json:",optional"`
|
||||||
|
Telemetry trace.Config `json:",optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustSetUp sets up the service, exits on error.
|
// MustSetUp sets up the service, exits on error.
|
||||||
@@ -49,6 +51,12 @@ func (sc ServiceConf) SetUp() error {
|
|||||||
|
|
||||||
sc.initMode()
|
sc.initMode()
|
||||||
prometheus.StartAgent(sc.Prometheus)
|
prometheus.StartAgent(sc.Prometheus)
|
||||||
|
|
||||||
|
if len(sc.Telemetry.Name) == 0 {
|
||||||
|
sc.Telemetry.Name = sc.Name
|
||||||
|
}
|
||||||
|
trace.StartAgent(sc.Telemetry)
|
||||||
|
|
||||||
if len(sc.MetricsUrl) > 0 {
|
if len(sc.MetricsUrl) > 0 {
|
||||||
stat.SetReportWriter(stat.NewRemoteWriter(sc.MetricsUrl))
|
stat.SetReportWriter(stat.NewRemoteWriter(sc.MetricsUrl))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build !linux
|
||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package stat
|
package stat
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package stat
|
package stat
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package stat
|
package stat
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ var (
|
|||||||
cores uint64
|
cores uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
// if /proc not present, ignore the cpu calcuation, like wsl linux
|
// if /proc not present, ignore the cpu calculation, like wsl linux
|
||||||
func init() {
|
func init() {
|
||||||
cpus, err := perCpuUsage()
|
cpus, err := perCpuUsage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestRefreshCpu(t *testing.T) {
|
func TestRefreshCpu(t *testing.T) {
|
||||||
assert.True(t, RefreshCpu() >= 0)
|
assert.NotPanics(t, func() {
|
||||||
|
RefreshCpu()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkRefreshCpu(b *testing.B) {
|
func BenchmarkRefreshCpu(b *testing.B) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build !linux
|
||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package internal
|
package internal
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ func init() {
|
|||||||
atomic.StoreInt64(&cpuUsage, usage)
|
atomic.StoreInt64(&cpuUsage, usage)
|
||||||
})
|
})
|
||||||
case <-allTicker.C:
|
case <-allTicker.C:
|
||||||
printUsage()
|
if logEnabled.True() {
|
||||||
|
printUsage()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
2
core/stores/cache/cache.go
vendored
2
core/stores/cache/cache.go
vendored
@@ -29,7 +29,7 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// New returns a Cache.
|
// New returns a Cache.
|
||||||
func New(c ClusterConf, barrier syncx.SharedCalls, st *Stat, errNotFound error,
|
func New(c ClusterConf, barrier syncx.SingleFlight, st *Stat, errNotFound error,
|
||||||
opts ...Option) Cache {
|
opts ...Option) Cache {
|
||||||
if len(c) == 0 || TotalWeights(c) <= 0 {
|
if len(c) == 0 || TotalWeights(c) <= 0 {
|
||||||
log.Fatal("no cache nodes")
|
log.Fatal("no cache nodes")
|
||||||
|
|||||||
6
core/stores/cache/cache_test.go
vendored
6
core/stores/cache/cache_test.go
vendored
@@ -23,6 +23,7 @@ type mockedNode struct {
|
|||||||
|
|
||||||
func (mc *mockedNode) Del(keys ...string) error {
|
func (mc *mockedNode) Del(keys ...string) error {
|
||||||
var be errorx.BatchError
|
var be errorx.BatchError
|
||||||
|
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if _, ok := mc.vals[key]; !ok {
|
if _, ok := mc.vals[key]; !ok {
|
||||||
be.Add(mc.errNotFound)
|
be.Add(mc.errNotFound)
|
||||||
@@ -30,6 +31,7 @@ func (mc *mockedNode) Del(keys ...string) error {
|
|||||||
delete(mc.vals, key)
|
delete(mc.vals, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return be.Err()
|
return be.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +104,7 @@ func TestCache_SetDel(t *testing.T) {
|
|||||||
Weight: 100,
|
Weight: 100,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c := New(conf, syncx.NewSharedCalls(), NewStat("mock"), errPlaceholder)
|
c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder)
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i))
|
assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i))
|
||||||
@@ -140,7 +142,7 @@ func TestCache_OneNode(t *testing.T) {
|
|||||||
Weight: 100,
|
Weight: 100,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c := New(conf, syncx.NewSharedCalls(), NewStat("mock"), errPlaceholder)
|
c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder)
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i))
|
assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i))
|
||||||
|
|||||||
21
core/stores/cache/cachenode.go
vendored
21
core/stores/cache/cachenode.go
vendored
@@ -29,7 +29,7 @@ type cacheNode struct {
|
|||||||
rds *redis.Redis
|
rds *redis.Redis
|
||||||
expiry time.Duration
|
expiry time.Duration
|
||||||
notFoundExpiry time.Duration
|
notFoundExpiry time.Duration
|
||||||
barrier syncx.SharedCalls
|
barrier syncx.SingleFlight
|
||||||
r *rand.Rand
|
r *rand.Rand
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
unstableExpiry mathx.Unstable
|
unstableExpiry mathx.Unstable
|
||||||
@@ -43,7 +43,7 @@ type cacheNode struct {
|
|||||||
// st is used to stat the cache.
|
// st is used to stat the cache.
|
||||||
// errNotFound defines the error that returned on cache not found.
|
// errNotFound defines the error that returned on cache not found.
|
||||||
// opts are the options that customize the cacheNode.
|
// opts are the options that customize the cacheNode.
|
||||||
func NewNode(rds *redis.Redis, barrier syncx.SharedCalls, st *Stat,
|
func NewNode(rds *redis.Redis, barrier syncx.SingleFlight, st *Stat,
|
||||||
errNotFound error, opts ...Option) Cache {
|
errNotFound error, opts ...Option) Cache {
|
||||||
o := newOptions(opts...)
|
o := newOptions(opts...)
|
||||||
return cacheNode{
|
return cacheNode{
|
||||||
@@ -65,9 +65,18 @@ func (c cacheNode) Del(keys ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := c.rds.Del(keys...); err != nil {
|
if len(keys) > 1 && c.rds.Type == redis.ClusterType {
|
||||||
logx.Errorf("failed to clear cache with keys: %q, error: %v", formatKeys(keys), err)
|
for _, key := range keys {
|
||||||
c.asyncRetryDelCache(keys...)
|
if _, err := c.rds.Del(key); err != nil {
|
||||||
|
logx.Errorf("failed to clear cache with key: %q, error: %v", key, err)
|
||||||
|
c.asyncRetryDelCache(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := c.rds.Del(keys...); err != nil {
|
||||||
|
logx.Errorf("failed to clear cache with keys: %q, error: %v", formatKeys(keys), err)
|
||||||
|
c.asyncRetryDelCache(keys...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -205,7 +214,7 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
|
|||||||
return jsonx.Unmarshal(val.([]byte), v)
|
return jsonx.Unmarshal(val.([]byte), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cacheNode) processCache(key string, data string, v interface{}) error {
|
func (c cacheNode) processCache(key, data string, v interface{}) error {
|
||||||
err := jsonx.Unmarshal([]byte(data), v)
|
err := jsonx.Unmarshal([]byte(data), v)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
30
core/stores/cache/cachenode_test.go
vendored
30
core/stores/cache/cachenode_test.go
vendored
@@ -29,6 +29,7 @@ func init() {
|
|||||||
func TestCacheNode_DelCache(t *testing.T) {
|
func TestCacheNode_DelCache(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
store.Type = redis.ClusterType
|
||||||
defer clean()
|
defer clean()
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
@@ -49,13 +50,30 @@ func TestCacheNode_DelCache(t *testing.T) {
|
|||||||
assert.Nil(t, cn.Del("first", "second"))
|
assert.Nil(t, cn.Del("first", "second"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCacheNode_DelCacheWithErrors(t *testing.T) {
|
||||||
|
store, clean, err := redistest.CreateRedis()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
store.Type = redis.ClusterType
|
||||||
|
clean()
|
||||||
|
|
||||||
|
cn := cacheNode{
|
||||||
|
rds: store,
|
||||||
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
lock: new(sync.Mutex),
|
||||||
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
|
stat: NewStat("any"),
|
||||||
|
errNotFound: errTestNotFound,
|
||||||
|
}
|
||||||
|
assert.Nil(t, cn.Del("third", "fourth"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestCacheNode_InvalidCache(t *testing.T) {
|
func TestCacheNode_InvalidCache(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
s, err := miniredis.Run()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
rds: redis.New(s.Addr()),
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
@@ -78,7 +96,7 @@ func TestCacheNode_Take(t *testing.T) {
|
|||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSingleFlight(),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
stat: NewStat("any"),
|
stat: NewStat("any"),
|
||||||
@@ -105,7 +123,7 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
|
|||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSingleFlight(),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
stat: NewStat("any"),
|
stat: NewStat("any"),
|
||||||
@@ -144,7 +162,7 @@ func TestCacheNode_TakeWithExpire(t *testing.T) {
|
|||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSingleFlight(),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
stat: NewStat("any"),
|
stat: NewStat("any"),
|
||||||
@@ -171,7 +189,7 @@ func TestCacheNode_String(t *testing.T) {
|
|||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSingleFlight(),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
stat: NewStat("any"),
|
stat: NewStat("any"),
|
||||||
@@ -188,7 +206,7 @@ func TestCacheValueWithBigInt(t *testing.T) {
|
|||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSingleFlight(),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
stat: NewStat("any"),
|
stat: NewStat("any"),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package clickhouse
|
package clickhouse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// imports the driver.
|
// imports the driver, don't remove this comment, golint requires.
|
||||||
_ "github.com/ClickHouse/clickhouse-go"
|
_ "github.com/ClickHouse/clickhouse-go"
|
||||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type (
|
|||||||
// Store interface represents a KV store.
|
// Store interface represents a KV store.
|
||||||
Store interface {
|
Store interface {
|
||||||
Del(keys ...string) (int, error)
|
Del(keys ...string) (int, error)
|
||||||
Eval(script string, key string, args ...interface{}) (interface{}, error)
|
Eval(script, key string, args ...interface{}) (interface{}, error)
|
||||||
Exists(key string) (bool, error)
|
Exists(key string) (bool, error)
|
||||||
Expire(key string, seconds int) error
|
Expire(key string, seconds int) error
|
||||||
Expireat(key string, expireTime int64) error
|
Expireat(key string, expireTime int64) error
|
||||||
@@ -39,7 +39,7 @@ type (
|
|||||||
Llen(key string) (int, error)
|
Llen(key string) (int, error)
|
||||||
Lpop(key string) (string, error)
|
Lpop(key string) (string, error)
|
||||||
Lpush(key string, values ...interface{}) (int, error)
|
Lpush(key string, values ...interface{}) (int, error)
|
||||||
Lrange(key string, start int, stop int) ([]string, error)
|
Lrange(key string, start, stop int) ([]string, error)
|
||||||
Lrem(key string, count int, value string) (int, error)
|
Lrem(key string, count int, value string) (int, error)
|
||||||
Persist(key string) (bool, error)
|
Persist(key string) (bool, error)
|
||||||
Pfadd(key string, values ...interface{}) (bool, error)
|
Pfadd(key string, values ...interface{}) (bool, error)
|
||||||
@@ -47,7 +47,7 @@ type (
|
|||||||
Rpush(key string, values ...interface{}) (int, error)
|
Rpush(key string, values ...interface{}) (int, error)
|
||||||
Sadd(key string, values ...interface{}) (int, error)
|
Sadd(key string, values ...interface{}) (int, error)
|
||||||
Scard(key string) (int64, error)
|
Scard(key string) (int64, error)
|
||||||
Set(key string, value string) error
|
Set(key, value string) error
|
||||||
Setex(key, value string, seconds int) error
|
Setex(key, value string, seconds int) error
|
||||||
Setnx(key, value string) (bool, error)
|
Setnx(key, value string) (bool, error)
|
||||||
SetnxEx(key, value string, seconds int) (bool, error)
|
SetnxEx(key, value string, seconds int) (bool, error)
|
||||||
@@ -74,7 +74,7 @@ type (
|
|||||||
Zrevrange(key string, start, stop int64) ([]string, error)
|
Zrevrange(key string, start, stop int64) ([]string, error)
|
||||||
ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
|
ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
|
||||||
ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
|
ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
|
||||||
Zscore(key string, value string) (int64, error)
|
Zscore(key, value string) (int64, error)
|
||||||
Zrevrank(key, field string) (int64, error)
|
Zrevrank(key, field string) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ func (cs clusterStore) Del(keys ...string) (int, error) {
|
|||||||
return val, be.Err()
|
return val, be.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs clusterStore) Eval(script string, key string, args ...interface{}) (interface{}, error) {
|
func (cs clusterStore) Eval(script, key string, args ...interface{}) (interface{}, error) {
|
||||||
node, err := cs.getRedis(key)
|
node, err := cs.getRedis(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -321,7 +321,7 @@ func (cs clusterStore) Lpush(key string, values ...interface{}) (int, error) {
|
|||||||
return node.Lpush(key, values...)
|
return node.Lpush(key, values...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs clusterStore) Lrange(key string, start int, stop int) ([]string, error) {
|
func (cs clusterStore) Lrange(key string, start, stop int) ([]string, error) {
|
||||||
node, err := cs.getRedis(key)
|
node, err := cs.getRedis(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -393,7 +393,7 @@ func (cs clusterStore) Scard(key string) (int64, error) {
|
|||||||
return node.Scard(key)
|
return node.Scard(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs clusterStore) Set(key string, value string) error {
|
func (cs clusterStore) Set(key, value string) error {
|
||||||
node, err := cs.getRedis(key)
|
node, err := cs.getRedis(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -648,7 +648,7 @@ func (cs clusterStore) Zrevrank(key, field string) (int64, error) {
|
|||||||
return node.Zrevrank(key, field)
|
return node.Zrevrank(key, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs clusterStore) Zscore(key string, value string) (int64, error) {
|
func (cs clusterStore) Zscore(key, value string) (int64, error) {
|
||||||
node, err := cs.getRedis(key)
|
node, err := cs.getRedis(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
mgo "github.com/globalsign/mgo"
|
mgo "github.com/globalsign/mgo"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
reflect "reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockMgoCollection is a mock of MgoCollection interface
|
// MockMgoCollection is a mock of MgoCollection interface
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
package mongo
|
package mongo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
bson "github.com/globalsign/mgo/bson"
|
bson "github.com/globalsign/mgo/bson"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
reflect "reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockIter is a mock of Iter interface
|
// MockIter is a mock of Iter interface
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ var (
|
|||||||
// ErrNotFound is an alias of mgo.ErrNotFound.
|
// ErrNotFound is an alias of mgo.ErrNotFound.
|
||||||
ErrNotFound = mgo.ErrNotFound
|
ErrNotFound = mgo.ErrNotFound
|
||||||
|
|
||||||
// can't use one SharedCalls per conn, because multiple conns may share the same cache key.
|
// can't use one SingleFlight per conn, because multiple conns may share the same cache key.
|
||||||
sharedCalls = syncx.NewSharedCalls()
|
sharedCalls = syncx.NewSingleFlight()
|
||||||
stats = cache.NewStat("mongoc")
|
stats = cache.NewStat("mongoc")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,11 +24,11 @@ type (
|
|||||||
CachedCollection interface {
|
CachedCollection interface {
|
||||||
Count(query interface{}) (int, error)
|
Count(query interface{}) (int, error)
|
||||||
DelCache(keys ...string) error
|
DelCache(keys ...string) error
|
||||||
FindAllNoCache(v interface{}, query interface{}, opts ...QueryOption) error
|
FindAllNoCache(v, query interface{}, opts ...QueryOption) error
|
||||||
FindOne(v interface{}, key string, query interface{}) error
|
FindOne(v interface{}, key string, query interface{}) error
|
||||||
FindOneNoCache(v interface{}, query interface{}) error
|
FindOneNoCache(v, query interface{}) error
|
||||||
FindOneId(v interface{}, key string, id interface{}) error
|
FindOneId(v interface{}, key string, id interface{}) error
|
||||||
FindOneIdNoCache(v interface{}, id interface{}) error
|
FindOneIdNoCache(v, id interface{}) error
|
||||||
GetCache(key string, v interface{}) error
|
GetCache(key string, v interface{}) error
|
||||||
Insert(docs ...interface{}) error
|
Insert(docs ...interface{}) error
|
||||||
Pipe(pipeline interface{}) mongo.Pipe
|
Pipe(pipeline interface{}) mongo.Pipe
|
||||||
@@ -68,7 +68,7 @@ func (c *cachedCollection) DelCache(keys ...string) error {
|
|||||||
return c.cache.Del(keys...)
|
return c.cache.Del(keys...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cachedCollection) FindAllNoCache(v interface{}, query interface{}, opts ...QueryOption) error {
|
func (c *cachedCollection) FindAllNoCache(v, query interface{}, opts ...QueryOption) error {
|
||||||
q := c.collection.Find(query)
|
q := c.collection.Find(query)
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
q = opt(q)
|
q = opt(q)
|
||||||
@@ -83,7 +83,7 @@ func (c *cachedCollection) FindOne(v interface{}, key string, query interface{})
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cachedCollection) FindOneNoCache(v interface{}, query interface{}) error {
|
func (c *cachedCollection) FindOneNoCache(v, query interface{}) error {
|
||||||
q := c.collection.Find(query)
|
q := c.collection.Find(query)
|
||||||
return q.One(v)
|
return q.One(v)
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ func (c *cachedCollection) FindOneId(v interface{}, key string, id interface{})
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cachedCollection) FindOneIdNoCache(v interface{}, id interface{}) error {
|
func (c *cachedCollection) FindOneIdNoCache(v, id interface{}) error {
|
||||||
q := c.collection.FindId(id)
|
q := c.collection.FindId(id)
|
||||||
return q.One(v)
|
return q.One(v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func TestStatCacheFails(t *testing.T) {
|
|||||||
log.SetOutput(ioutil.Discard)
|
log.SetOutput(ioutil.Discard)
|
||||||
defer log.SetOutput(os.Stdout)
|
defer log.SetOutput(os.Stdout)
|
||||||
|
|
||||||
r := redis.NewRedis("localhost:59999", redis.NodeType)
|
r := redis.New("localhost:59999")
|
||||||
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||||
c := newCollection(dummyConn{}, cach)
|
c := newCollection(dummyConn{}, cach)
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (mm *Model) GetCollection(session *mgo.Session) CachedCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindAllNoCache finds all records without cache.
|
// FindAllNoCache finds all records without cache.
|
||||||
func (mm *Model) FindAllNoCache(v interface{}, query interface{}, opts ...QueryOption) error {
|
func (mm *Model) FindAllNoCache(v, query interface{}, opts ...QueryOption) error {
|
||||||
return mm.execute(func(c CachedCollection) error {
|
return mm.execute(func(c CachedCollection) error {
|
||||||
return c.FindAllNoCache(v, query, opts...)
|
return c.FindAllNoCache(v, query, opts...)
|
||||||
})
|
})
|
||||||
@@ -89,7 +89,7 @@ func (mm *Model) FindOne(v interface{}, key string, query interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindOneNoCache unmarshals a record into v with query, without cache.
|
// FindOneNoCache unmarshals a record into v with query, without cache.
|
||||||
func (mm *Model) FindOneNoCache(v interface{}, query interface{}) error {
|
func (mm *Model) FindOneNoCache(v, query interface{}) error {
|
||||||
return mm.execute(func(c CachedCollection) error {
|
return mm.execute(func(c CachedCollection) error {
|
||||||
return c.FindOneNoCache(v, query)
|
return c.FindOneNoCache(v, query)
|
||||||
})
|
})
|
||||||
@@ -103,7 +103,7 @@ func (mm *Model) FindOneId(v interface{}, key string, id interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindOneIdNoCache unmarshals a record into v with query, without cache.
|
// FindOneIdNoCache unmarshals a record into v with query, without cache.
|
||||||
func (mm *Model) FindOneIdNoCache(v interface{}, id interface{}) error {
|
func (mm *Model) FindOneIdNoCache(v, id interface{}) error {
|
||||||
return mm.execute(func(c CachedCollection) error {
|
return mm.execute(func(c CachedCollection) error {
|
||||||
return c.FindOneIdNoCache(v, id)
|
return c.FindOneIdNoCache(v, id)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// imports the driver.
|
// imports the driver, don't remove this comment, golint requires.
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ type (
|
|||||||
IntCmd = red.IntCmd
|
IntCmd = red.IntCmd
|
||||||
// FloatCmd is an alias of redis.FloatCmd.
|
// FloatCmd is an alias of redis.FloatCmd.
|
||||||
FloatCmd = red.FloatCmd
|
FloatCmd = red.FloatCmd
|
||||||
|
// StringCmd is an alias of redis.StringCmd.
|
||||||
|
StringCmd = red.StringCmd
|
||||||
)
|
)
|
||||||
|
|
||||||
// New returns a Redis with given options.
|
// New returns a Redis with given options.
|
||||||
@@ -88,6 +90,7 @@ func New(addr string, opts ...Option) *Redis {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: use New instead, will be removed in v2.
|
||||||
// NewRedis returns a Redis.
|
// NewRedis returns a Redis.
|
||||||
func NewRedis(redisAddr, redisType string, redisPass ...string) *Redis {
|
func NewRedis(redisAddr, redisType string, redisPass ...string) *Redis {
|
||||||
var opts []Option
|
var opts []Option
|
||||||
@@ -180,7 +183,7 @@ func (s *Redis) BitOpXor(destKey string, keys ...string) (val int64, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BitPos is redis bitpos command implementation.
|
// BitPos is redis bitpos command implementation.
|
||||||
func (s *Redis) BitPos(key string, bit int64, start, end int64) (val int64, err error) {
|
func (s *Redis) BitPos(key string, bit, start, end int64) (val int64, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -346,7 +349,7 @@ func (s *Redis) GeoAdd(key string, geoLocation ...*GeoLocation) (val int64, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GeoDist is the implementation of redis geodist command.
|
// GeoDist is the implementation of redis geodist command.
|
||||||
func (s *Redis) GeoDist(key string, member1, member2, unit string) (val float64, err error) {
|
func (s *Redis) GeoDist(key, member1, member2, unit string) (val float64, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -795,7 +798,7 @@ func (s *Redis) Lpush(key string, values ...interface{}) (val int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lrange is the implementation of redis lrange command.
|
// Lrange is the implementation of redis lrange command.
|
||||||
func (s *Redis) Lrange(key string, start int, stop int) (val []string, err error) {
|
func (s *Redis) Lrange(key string, start, stop int) (val []string, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1074,7 +1077,7 @@ func (s *Redis) ScriptLoad(script string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set is the implementation of redis set command.
|
// Set is the implementation of redis set command.
|
||||||
func (s *Redis) Set(key string, value string) error {
|
func (s *Redis) Set(key, value string) error {
|
||||||
return s.brk.DoWithAcceptable(func() error {
|
return s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1282,6 +1285,41 @@ func (s *Redis) Sdiffstore(destination string, keys ...string) (val int, err err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sinter is the implementation of redis sinter command.
|
||||||
|
func (s *Redis) Sinter(keys ...string) (val []string, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err = conn.SInter(keys...).Result()
|
||||||
|
return err
|
||||||
|
}, acceptable)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sinterstore is the implementation of redis sinterstore command.
|
||||||
|
func (s *Redis) Sinterstore(destination string, keys ...string) (val int, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := conn.SInterStore(destination, keys...).Result()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val = int(v)
|
||||||
|
return nil
|
||||||
|
}, acceptable)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Ttl is the implementation of redis ttl command.
|
// Ttl is the implementation of redis ttl command.
|
||||||
func (s *Redis) Ttl(key string) (val int, err error) {
|
func (s *Redis) Ttl(key string) (val int, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
@@ -1412,7 +1450,7 @@ func (s *Redis) Zincrby(key string, increment int64, field string) (val int64, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Zscore is the implementation of redis zscore command.
|
// Zscore is the implementation of redis zscore command.
|
||||||
func (s *Redis) Zscore(key string, value string) (val int64, err error) {
|
func (s *Redis) Zscore(key, value string) (val int64, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1684,7 +1722,7 @@ func (s *Redis) ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Zrevrank is the implementation of redis zrevrank command.
|
// Zrevrank is the implementation of redis zrevrank command.
|
||||||
func (s *Redis) Zrevrank(key string, field string) (val int64, err error) {
|
func (s *Redis) Zrevrank(key, field string) (val int64, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -638,6 +638,16 @@ func TestRedis_Set(t *testing.T) {
|
|||||||
num, err = client.Sdiffstore("key4", "key1", "key2")
|
num, err = client.Sdiffstore("key4", "key1", "key2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, num)
|
assert.Equal(t, 1, num)
|
||||||
|
_, err = New(client.Addr, badType()).Sinter("key1", "key2")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
vals, err = client.Sinter("key1", "key2")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.ElementsMatch(t, []string{"2", "3", "4"}, vals)
|
||||||
|
_, err = New(client.Addr, badType()).Sinterstore("key4", "key1", "key2")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
num, err = client.Sinterstore("key4", "key1", "key2")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 3, num)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -953,7 +963,7 @@ func TestRedis_Pipelined(t *testing.T) {
|
|||||||
func TestRedisString(t *testing.T) {
|
func TestRedisString(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
client.Ping()
|
client.Ping()
|
||||||
_, err := getRedis(NewRedis(client.Addr, ClusterType))
|
_, err := getRedis(New(client.Addr, Cluster()))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, client.Addr, client.String())
|
assert.Equal(t, client.Addr, client.String())
|
||||||
assert.NotNil(t, New(client.Addr, badType()).Ping())
|
assert.NotNil(t, New(client.Addr, badType()).Ping())
|
||||||
@@ -1065,7 +1075,7 @@ func TestRedisGeo(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_WithPass(t *testing.T) {
|
func TestRedis_WithPass(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
err := NewRedis(client.Addr, NodeType, "any").Ping()
|
err := New(client.Addr, WithPass("any")).Ping()
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1085,7 +1095,7 @@ func runOnRedis(t *testing.T, fn func(client *Redis)) {
|
|||||||
client.Close()
|
client.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
fn(NewRedis(s.Addr(), NodeType))
|
fn(New(s.Addr()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func runOnRedisTLS(t *testing.T, fn func(client *Redis)) {
|
func runOnRedisTLS(t *testing.T, fn func(client *Redis)) {
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import (
|
|||||||
func TestBlockingNode(t *testing.T) {
|
func TestBlockingNode(t *testing.T) {
|
||||||
r, err := miniredis.Run()
|
r, err := miniredis.Run()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
node, err := CreateBlockingNode(NewRedis(r.Addr(), NodeType))
|
node, err := CreateBlockingNode(New(r.Addr()))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
node.Close()
|
node.Close()
|
||||||
node, err = CreateBlockingNode(NewRedis(r.Addr(), ClusterType))
|
node, err = CreateBlockingNode(New(r.Addr(), Cluster()))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
node.Close()
|
node.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func CreateRedis() (r *redis.Redis, clean func(), err error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return redis.NewRedis(mr.Addr(), redis.NodeType), func() {
|
return redis.New(mr.Addr()), func() {
|
||||||
ch := make(chan lang.PlaceholderType)
|
ch := make(chan lang.PlaceholderType)
|
||||||
go func() {
|
go func() {
|
||||||
mr.Close()
|
mr.Close()
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ var (
|
|||||||
// ErrNotFound is an alias of sqlx.ErrNotFound.
|
// ErrNotFound is an alias of sqlx.ErrNotFound.
|
||||||
ErrNotFound = sqlx.ErrNotFound
|
ErrNotFound = sqlx.ErrNotFound
|
||||||
|
|
||||||
// can't use one SharedCalls per conn, because multiple conns may share the same cache key.
|
// can't use one SingleFlight per conn, because multiple conns may share the same cache key.
|
||||||
exclusiveCalls = syncx.NewSharedCalls()
|
exclusiveCalls = syncx.NewSingleFlight()
|
||||||
stats = cache.NewStat("sqlc")
|
stats = cache.NewStat("sqlc")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ func TestStatCacheFails(t *testing.T) {
|
|||||||
log.SetOutput(ioutil.Discard)
|
log.SetOutput(ioutil.Discard)
|
||||||
defer log.SetOutput(os.Stdout)
|
defer log.SetOutput(os.Stdout)
|
||||||
|
|
||||||
r := redis.NewRedis("localhost:59999", redis.NodeType)
|
r := redis.New("localhost:59999")
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
@@ -485,7 +485,7 @@ func TestCachedConnExecDropCache(t *testing.T) {
|
|||||||
value = "any"
|
value = "any"
|
||||||
)
|
)
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
c := NewNodeConn(&conn, redis.NewRedis(r.Addr(), redis.NodeType), cache.WithExpiry(time.Second*30))
|
c := NewNodeConn(&conn, redis.New(r.Addr()), cache.WithExpiry(time.Second*30))
|
||||||
assert.Nil(t, c.SetCache(key, value))
|
assert.Nil(t, c.SetCache(key, value))
|
||||||
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
||||||
return conn.Exec("delete from user_table where id='kevin'")
|
return conn.Exec("delete from user_table where id='kevin'")
|
||||||
@@ -503,7 +503,7 @@ func TestCachedConnExecDropCache(t *testing.T) {
|
|||||||
func TestCachedConnExecDropCacheFailed(t *testing.T) {
|
func TestCachedConnExecDropCacheFailed(t *testing.T) {
|
||||||
const key = "user"
|
const key = "user"
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
r := redis.NewRedis("anyredis:8888", redis.NodeType)
|
r := redis.New("anyredis:8888")
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
||||||
_, err := c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
_, err := c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
||||||
return conn.Exec("delete from user_table where id='kevin'")
|
return conn.Exec("delete from user_table where id='kevin'")
|
||||||
@@ -600,6 +600,10 @@ func (d dummySqlConn) QueryRowsPartial(v interface{}, query string, args ...inte
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d dummySqlConn) RawDB() (*sql.DB, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d dummySqlConn) Transact(func(session sqlx.Session) error) error {
|
func (d dummySqlConn) Transact(func(session sqlx.Session) error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -621,6 +625,10 @@ func (c *trackedConn) QueryRows(v interface{}, query string, args ...interface{}
|
|||||||
return c.dummySqlConn.QueryRows(v, query, args...)
|
return c.dummySqlConn.QueryRows(v, query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *trackedConn) RawDB() (*sql.DB, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *trackedConn) Transact(fn func(session sqlx.Session) error) error {
|
func (c *trackedConn) Transact(fn func(session sqlx.Session) error) error {
|
||||||
c.transactValue = true
|
c.transactValue = true
|
||||||
return c.dummySqlConn.Transact(fn)
|
return c.dummySqlConn.Transact(fn)
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ func (c *mockedConn) QueryRowsPartial(v interface{}, query string, args ...inter
|
|||||||
panic("should not called")
|
panic("should not called")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *mockedConn) RawDB() (*sql.DB, error) {
|
||||||
|
panic("should not called")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *mockedConn) Transact(func(session Session) error) error {
|
func (c *mockedConn) Transact(func(session Session) error) error {
|
||||||
panic("should not called")
|
panic("should not called")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/breaker"
|
"github.com/tal-tech/go-zero/core/breaker"
|
||||||
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNotFound is an alias of sql.ErrNoRows
|
// ErrNotFound is an alias of sql.ErrNoRows
|
||||||
@@ -23,6 +24,8 @@ type (
|
|||||||
// SqlConn only stands for raw connections, so Transact method can be called.
|
// SqlConn only stands for raw connections, so Transact method can be called.
|
||||||
SqlConn interface {
|
SqlConn interface {
|
||||||
Session
|
Session
|
||||||
|
// RawDB is for other ORM to operate with, use it with caution.
|
||||||
|
RawDB() (*sql.DB, error)
|
||||||
Transact(func(session Session) error) error
|
Transact(func(session Session) error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,20 +46,23 @@ type (
|
|||||||
// Because CORBA doesn't support PREPARE, so we need to combine the
|
// Because CORBA doesn't support PREPARE, so we need to combine the
|
||||||
// query arguments into one string and do underlying query without arguments
|
// query arguments into one string and do underlying query without arguments
|
||||||
commonSqlConn struct {
|
commonSqlConn struct {
|
||||||
driverName string
|
connProv connProvider
|
||||||
datasource string
|
onError func(error)
|
||||||
beginTx beginnable
|
beginTx beginnable
|
||||||
brk breaker.Breaker
|
brk breaker.Breaker
|
||||||
accept func(error) bool
|
accept func(error) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connProvider func() (*sql.DB, error)
|
||||||
|
|
||||||
sessionConn interface {
|
sessionConn interface {
|
||||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
statement struct {
|
statement struct {
|
||||||
stmt *sql.Stmt
|
query string
|
||||||
|
stmt *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
stmtConn interface {
|
stmtConn interface {
|
||||||
@@ -68,10 +74,34 @@ type (
|
|||||||
// NewSqlConn returns a SqlConn with given driver name and datasource.
|
// NewSqlConn returns a SqlConn with given driver name and datasource.
|
||||||
func NewSqlConn(driverName, datasource string, opts ...SqlOption) SqlConn {
|
func NewSqlConn(driverName, datasource string, opts ...SqlOption) SqlConn {
|
||||||
conn := &commonSqlConn{
|
conn := &commonSqlConn{
|
||||||
driverName: driverName,
|
connProv: func() (*sql.DB, error) {
|
||||||
datasource: datasource,
|
return getSqlConn(driverName, datasource)
|
||||||
beginTx: begin,
|
},
|
||||||
brk: breaker.NewBreaker(),
|
onError: func(err error) {
|
||||||
|
logInstanceError(datasource, err)
|
||||||
|
},
|
||||||
|
beginTx: begin,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSqlConnFromDB returns a SqlConn with the given sql.DB.
|
||||||
|
// Use it with caution, it's provided for other ORM to interact with.
|
||||||
|
func NewSqlConnFromDB(db *sql.DB, opts ...SqlOption) SqlConn {
|
||||||
|
conn := &commonSqlConn{
|
||||||
|
connProv: func() (*sql.DB, error) {
|
||||||
|
return db, nil
|
||||||
|
},
|
||||||
|
onError: func(err error) {
|
||||||
|
logx.Errorf("Error on getting sql instance: %v", err)
|
||||||
|
},
|
||||||
|
beginTx: begin,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
}
|
}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(conn)
|
opt(conn)
|
||||||
@@ -83,9 +113,9 @@ func NewSqlConn(driverName, datasource string, opts ...SqlOption) SqlConn {
|
|||||||
func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result, err error) {
|
func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result, err error) {
|
||||||
err = db.brk.DoWithAcceptable(func() error {
|
err = db.brk.DoWithAcceptable(func() error {
|
||||||
var conn *sql.DB
|
var conn *sql.DB
|
||||||
conn, err = getSqlConn(db.driverName, db.datasource)
|
conn, err = db.connProv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logInstanceError(db.datasource, err)
|
db.onError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,9 +129,9 @@ func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result,
|
|||||||
func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
|
func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
|
||||||
err = db.brk.DoWithAcceptable(func() error {
|
err = db.brk.DoWithAcceptable(func() error {
|
||||||
var conn *sql.DB
|
var conn *sql.DB
|
||||||
conn, err = getSqlConn(db.driverName, db.datasource)
|
conn, err = db.connProv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logInstanceError(db.datasource, err)
|
db.onError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +141,8 @@ func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stmt = statement{
|
stmt = statement{
|
||||||
stmt: st,
|
query: query,
|
||||||
|
stmt: st,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, db.acceptable)
|
}, db.acceptable)
|
||||||
@@ -143,6 +174,10 @@ func (db *commonSqlConn) QueryRowsPartial(v interface{}, q string, args ...inter
|
|||||||
}, q, args...)
|
}, q, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *commonSqlConn) RawDB() (*sql.DB, error) {
|
||||||
|
return db.connProv()
|
||||||
|
}
|
||||||
|
|
||||||
func (db *commonSqlConn) Transact(fn func(Session) error) error {
|
func (db *commonSqlConn) Transact(fn func(Session) error) error {
|
||||||
return db.brk.DoWithAcceptable(func() error {
|
return db.brk.DoWithAcceptable(func() error {
|
||||||
return transact(db, db.beginTx, fn)
|
return transact(db, db.beginTx, fn)
|
||||||
@@ -161,9 +196,9 @@ func (db *commonSqlConn) acceptable(err error) bool {
|
|||||||
func (db *commonSqlConn) queryRows(scanner func(*sql.Rows) error, q string, args ...interface{}) error {
|
func (db *commonSqlConn) queryRows(scanner func(*sql.Rows) error, q string, args ...interface{}) error {
|
||||||
var qerr error
|
var qerr error
|
||||||
return db.brk.DoWithAcceptable(func() error {
|
return db.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getSqlConn(db.driverName, db.datasource)
|
conn, err := db.connProv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logInstanceError(db.datasource, err)
|
db.onError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,29 +216,29 @@ func (s statement) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s statement) Exec(args ...interface{}) (sql.Result, error) {
|
func (s statement) Exec(args ...interface{}) (sql.Result, error) {
|
||||||
return execStmt(s.stmt, args...)
|
return execStmt(s.stmt, s.query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s statement) QueryRow(v interface{}, args ...interface{}) error {
|
func (s statement) QueryRow(v interface{}, args ...interface{}) error {
|
||||||
return queryStmt(s.stmt, func(rows *sql.Rows) error {
|
return queryStmt(s.stmt, func(rows *sql.Rows) error {
|
||||||
return unmarshalRow(v, rows, true)
|
return unmarshalRow(v, rows, true)
|
||||||
}, args...)
|
}, s.query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s statement) QueryRowPartial(v interface{}, args ...interface{}) error {
|
func (s statement) QueryRowPartial(v interface{}, args ...interface{}) error {
|
||||||
return queryStmt(s.stmt, func(rows *sql.Rows) error {
|
return queryStmt(s.stmt, func(rows *sql.Rows) error {
|
||||||
return unmarshalRow(v, rows, false)
|
return unmarshalRow(v, rows, false)
|
||||||
}, args...)
|
}, s.query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s statement) QueryRows(v interface{}, args ...interface{}) error {
|
func (s statement) QueryRows(v interface{}, args ...interface{}) error {
|
||||||
return queryStmt(s.stmt, func(rows *sql.Rows) error {
|
return queryStmt(s.stmt, func(rows *sql.Rows) error {
|
||||||
return unmarshalRows(v, rows, true)
|
return unmarshalRows(v, rows, true)
|
||||||
}, args...)
|
}, s.query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s statement) QueryRowsPartial(v interface{}, args ...interface{}) error {
|
func (s statement) QueryRowsPartial(v interface{}, args ...interface{}) error {
|
||||||
return queryStmt(s.stmt, func(rows *sql.Rows) error {
|
return queryStmt(s.stmt, func(rows *sql.Rows) error {
|
||||||
return unmarshalRows(v, rows, false)
|
return unmarshalRows(v, rows, false)
|
||||||
}, args...)
|
}, s.query, args...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ func TestSqlConn(t *testing.T) {
|
|||||||
mock.ExpectExec("any")
|
mock.ExpectExec("any")
|
||||||
mock.ExpectQuery("any").WillReturnRows(sqlmock.NewRows([]string{"foo"}))
|
mock.ExpectQuery("any").WillReturnRows(sqlmock.NewRows([]string{"foo"}))
|
||||||
conn := NewMysql(mockedDatasource)
|
conn := NewMysql(mockedDatasource)
|
||||||
|
db, err := conn.RawDB()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
rawConn := NewSqlConnFromDB(db, withMysqlAcceptable())
|
||||||
badConn := NewMysql("badsql")
|
badConn := NewMysql("badsql")
|
||||||
_, err := conn.Exec("any", "value")
|
_, err = conn.Exec("any", "value")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
_, err = badConn.Exec("any", "value")
|
_, err = badConn.Exec("any", "value")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
_, err = conn.Prepare("any")
|
_, err = rawConn.Prepare("any")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
_, err = badConn.Prepare("any")
|
_, err = badConn.Prepare("any")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package sqlx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
@@ -12,10 +11,14 @@ import (
|
|||||||
const slowThreshold = time.Millisecond * 500
|
const slowThreshold = time.Millisecond * 500
|
||||||
|
|
||||||
func exec(conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
|
func exec(conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
|
||||||
|
stmt, err := format(q, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
startTime := timex.Now()
|
startTime := timex.Now()
|
||||||
result, err := conn.Exec(q, args...)
|
result, err := conn.Exec(q, args...)
|
||||||
duration := timex.Since(startTime)
|
duration := timex.Since(startTime)
|
||||||
stmt := formatForPrint(q, args)
|
|
||||||
if duration > slowThreshold {
|
if duration > slowThreshold {
|
||||||
logx.WithDuration(duration).Slowf("[SQL] exec: slowcall - %s", stmt)
|
logx.WithDuration(duration).Slowf("[SQL] exec: slowcall - %s", stmt)
|
||||||
} else {
|
} else {
|
||||||
@@ -28,11 +31,15 @@ func exec(conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func execStmt(conn stmtConn, args ...interface{}) (sql.Result, error) {
|
func execStmt(conn stmtConn, q string, args ...interface{}) (sql.Result, error) {
|
||||||
|
stmt, err := format(q, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
startTime := timex.Now()
|
startTime := timex.Now()
|
||||||
result, err := conn.Exec(args...)
|
result, err := conn.Exec(args...)
|
||||||
duration := timex.Since(startTime)
|
duration := timex.Since(startTime)
|
||||||
stmt := fmt.Sprint(args...)
|
|
||||||
if duration > slowThreshold {
|
if duration > slowThreshold {
|
||||||
logx.WithDuration(duration).Slowf("[SQL] execStmt: slowcall - %s", stmt)
|
logx.WithDuration(duration).Slowf("[SQL] execStmt: slowcall - %s", stmt)
|
||||||
} else {
|
} else {
|
||||||
@@ -46,10 +53,14 @@ func execStmt(conn stmtConn, args ...interface{}) (sql.Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func query(conn sessionConn, scanner func(*sql.Rows) error, q string, args ...interface{}) error {
|
func query(conn sessionConn, scanner func(*sql.Rows) error, q string, args ...interface{}) error {
|
||||||
|
stmt, err := format(q, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
startTime := timex.Now()
|
startTime := timex.Now()
|
||||||
rows, err := conn.Query(q, args...)
|
rows, err := conn.Query(q, args...)
|
||||||
duration := timex.Since(startTime)
|
duration := timex.Since(startTime)
|
||||||
stmt := fmt.Sprint(args...)
|
|
||||||
if duration > slowThreshold {
|
if duration > slowThreshold {
|
||||||
logx.WithDuration(duration).Slowf("[SQL] query: slowcall - %s", stmt)
|
logx.WithDuration(duration).Slowf("[SQL] query: slowcall - %s", stmt)
|
||||||
} else {
|
} else {
|
||||||
@@ -64,8 +75,12 @@ func query(conn sessionConn, scanner func(*sql.Rows) error, q string, args ...in
|
|||||||
return scanner(rows)
|
return scanner(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryStmt(conn stmtConn, scanner func(*sql.Rows) error, args ...interface{}) error {
|
func queryStmt(conn stmtConn, scanner func(*sql.Rows) error, q string, args ...interface{}) error {
|
||||||
stmt := fmt.Sprint(args...)
|
stmt, err := format(q, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
startTime := timex.Now()
|
startTime := timex.Now()
|
||||||
rows, err := conn.Query(args...)
|
rows, err := conn.Query(args...)
|
||||||
duration := timex.Since(startTime)
|
duration := timex.Since(startTime)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ var errMockedPlaceholder = errors.New("placeholder")
|
|||||||
func TestStmt_exec(t *testing.T) {
|
func TestStmt_exec(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
query string
|
||||||
args []interface{}
|
args []interface{}
|
||||||
delay bool
|
delay bool
|
||||||
hasError bool
|
hasError bool
|
||||||
@@ -23,18 +24,28 @@ func TestStmt_exec(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "normal",
|
name: "normal",
|
||||||
|
query: "select user from users where id=?",
|
||||||
args: []interface{}{1},
|
args: []interface{}{1},
|
||||||
lastInsertId: 1,
|
lastInsertId: 1,
|
||||||
rowsAffected: 2,
|
rowsAffected: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "exec error",
|
name: "exec error",
|
||||||
|
query: "select user from users where id=?",
|
||||||
|
args: []interface{}{1},
|
||||||
|
hasError: true,
|
||||||
|
err: errors.New("exec"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exec more args error",
|
||||||
|
query: "select user from users where id=? and name=?",
|
||||||
args: []interface{}{1},
|
args: []interface{}{1},
|
||||||
hasError: true,
|
hasError: true,
|
||||||
err: errors.New("exec"),
|
err: errors.New("exec"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "slowcall",
|
name: "slowcall",
|
||||||
|
query: "select user from users where id=?",
|
||||||
args: []interface{}{1},
|
args: []interface{}{1},
|
||||||
delay: true,
|
delay: true,
|
||||||
lastInsertId: 1,
|
lastInsertId: 1,
|
||||||
@@ -51,7 +62,7 @@ func TestStmt_exec(t *testing.T) {
|
|||||||
rowsAffected: test.rowsAffected,
|
rowsAffected: test.rowsAffected,
|
||||||
err: test.err,
|
err: test.err,
|
||||||
delay: test.delay,
|
delay: test.delay,
|
||||||
}, "select user from users where id=?", args...)
|
}, test.query, args...)
|
||||||
},
|
},
|
||||||
func(args ...interface{}) (sql.Result, error) {
|
func(args ...interface{}) (sql.Result, error) {
|
||||||
return execStmt(&mockedStmtConn{
|
return execStmt(&mockedStmtConn{
|
||||||
@@ -59,7 +70,7 @@ func TestStmt_exec(t *testing.T) {
|
|||||||
rowsAffected: test.rowsAffected,
|
rowsAffected: test.rowsAffected,
|
||||||
err: test.err,
|
err: test.err,
|
||||||
delay: test.delay,
|
delay: test.delay,
|
||||||
}, args...)
|
}, test.query, args...)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,23 +100,34 @@ func TestStmt_exec(t *testing.T) {
|
|||||||
func TestStmt_query(t *testing.T) {
|
func TestStmt_query(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
query string
|
||||||
args []interface{}
|
args []interface{}
|
||||||
delay bool
|
delay bool
|
||||||
hasError bool
|
hasError bool
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "normal",
|
name: "normal",
|
||||||
args: []interface{}{1},
|
query: "select user from users where id=?",
|
||||||
|
args: []interface{}{1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "query error",
|
name: "query error",
|
||||||
|
query: "select user from users where id=?",
|
||||||
|
args: []interface{}{1},
|
||||||
|
hasError: true,
|
||||||
|
err: errors.New("exec"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "query more args error",
|
||||||
|
query: "select user from users where id=? and name=?",
|
||||||
args: []interface{}{1},
|
args: []interface{}{1},
|
||||||
hasError: true,
|
hasError: true,
|
||||||
err: errors.New("exec"),
|
err: errors.New("exec"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "slowcall",
|
name: "slowcall",
|
||||||
|
query: "select user from users where id=?",
|
||||||
args: []interface{}{1},
|
args: []interface{}{1},
|
||||||
delay: true,
|
delay: true,
|
||||||
},
|
},
|
||||||
@@ -120,7 +142,7 @@ func TestStmt_query(t *testing.T) {
|
|||||||
delay: test.delay,
|
delay: test.delay,
|
||||||
}, func(rows *sql.Rows) error {
|
}, func(rows *sql.Rows) error {
|
||||||
return nil
|
return nil
|
||||||
}, "select user from users where id=?", args...)
|
}, test.query, args...)
|
||||||
},
|
},
|
||||||
func(args ...interface{}) error {
|
func(args ...interface{}) error {
|
||||||
return queryStmt(&mockedStmtConn{
|
return queryStmt(&mockedStmtConn{
|
||||||
@@ -128,7 +150,7 @@ func TestStmt_query(t *testing.T) {
|
|||||||
delay: test.delay,
|
delay: test.delay,
|
||||||
}, func(rows *sql.Rows) error {
|
}, func(rows *sql.Rows) error {
|
||||||
return nil
|
return nil
|
||||||
}, args...)
|
}, test.query, args...)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +165,7 @@ func TestStmt_query(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, errMockedPlaceholder, err)
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ func (t txSession) Prepare(q string) (StmtSession, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return statement{
|
return statement{
|
||||||
stmt: stmt,
|
query: q,
|
||||||
|
stmt: stmt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,9 +71,9 @@ func begin(db *sql.DB) (trans, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func transact(db *commonSqlConn, b beginnable, fn func(Session) error) (err error) {
|
func transact(db *commonSqlConn, b beginnable, fn func(Session) error) (err error) {
|
||||||
conn, err := getSqlConn(db.driverName, db.datasource)
|
conn, err := db.connProv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logInstanceError(db.datasource, err)
|
db.onError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package sqlx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
@@ -45,24 +46,6 @@ func escape(input string) string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatForPrint(query string, args ...interface{}) string {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
|
|
||||||
var vals []string
|
|
||||||
for _, arg := range args {
|
|
||||||
vals = append(vals, fmt.Sprintf("%q", mapping.Repr(arg)))
|
|
||||||
}
|
|
||||||
|
|
||||||
var b strings.Builder
|
|
||||||
b.WriteByte('[')
|
|
||||||
b.WriteString(strings.Join(vals, ", "))
|
|
||||||
b.WriteByte(']')
|
|
||||||
|
|
||||||
return strings.Join([]string{query, b.String()}, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func format(query string, args ...interface{}) (string, error) {
|
func format(query string, args ...interface{}) (string, error) {
|
||||||
numArgs := len(args)
|
numArgs := len(args)
|
||||||
if numArgs == 0 {
|
if numArgs == 0 {
|
||||||
@@ -70,38 +53,52 @@ func format(query string, args ...interface{}) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
argIndex := 0
|
var argIndex int
|
||||||
|
bytes := len(query)
|
||||||
|
|
||||||
for _, ch := range query {
|
for i := 0; i < bytes; i++ {
|
||||||
if ch == '?' {
|
ch := query[i]
|
||||||
|
switch ch {
|
||||||
|
case '?':
|
||||||
if argIndex >= numArgs {
|
if argIndex >= numArgs {
|
||||||
return "", fmt.Errorf("error: %d ? in sql, but less arguments provided", argIndex)
|
return "", fmt.Errorf("error: %d ? in sql, but less arguments provided", argIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
arg := args[argIndex]
|
writeValue(&b, args[argIndex])
|
||||||
argIndex++
|
argIndex++
|
||||||
|
case '$':
|
||||||
switch v := arg.(type) {
|
var j int
|
||||||
case bool:
|
for j = i + 1; j < bytes; j++ {
|
||||||
if v {
|
char := query[j]
|
||||||
b.WriteByte('1')
|
if char < '0' || '9' < char {
|
||||||
} else {
|
break
|
||||||
b.WriteByte('0')
|
|
||||||
}
|
}
|
||||||
case string:
|
|
||||||
b.WriteByte('\'')
|
|
||||||
b.WriteString(escape(v))
|
|
||||||
b.WriteByte('\'')
|
|
||||||
default:
|
|
||||||
b.WriteString(mapping.Repr(v))
|
|
||||||
}
|
}
|
||||||
} else {
|
if j > i+1 {
|
||||||
b.WriteRune(ch)
|
index, err := strconv.Atoi(query[i+1 : j])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// index starts from 1 for pg
|
||||||
|
if index > argIndex {
|
||||||
|
argIndex = index
|
||||||
|
}
|
||||||
|
index--
|
||||||
|
if index < 0 || numArgs <= index {
|
||||||
|
return "", fmt.Errorf("error: wrong index %d in sql", index)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeValue(&b, args[index])
|
||||||
|
i = j - 1
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
b.WriteByte(ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if argIndex < numArgs {
|
if argIndex < numArgs {
|
||||||
return "", fmt.Errorf("error: %d ? in sql, but more arguments provided", argIndex)
|
return "", fmt.Errorf("error: %d arguments provided, not matching sql", argIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.String(), nil
|
return b.String(), nil
|
||||||
@@ -117,3 +114,20 @@ func logSqlError(stmt string, err error) {
|
|||||||
logx.Errorf("stmt: %s, error: %s", stmt, err.Error())
|
logx.Errorf("stmt: %s, error: %s", stmt, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeValue(buf *strings.Builder, arg interface{}) {
|
||||||
|
switch v := arg.(type) {
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
buf.WriteByte('1')
|
||||||
|
} else {
|
||||||
|
buf.WriteByte('0')
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
buf.WriteByte('\'')
|
||||||
|
buf.WriteString(escape(v))
|
||||||
|
buf.WriteByte('\'')
|
||||||
|
default:
|
||||||
|
buf.WriteString(mapping.Repr(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,30 +29,63 @@ func TestDesensitize_WithoutAccount(t *testing.T) {
|
|||||||
assert.True(t, strings.Contains(datasource, "tcp(111.222.333.44:3306)"))
|
assert.True(t, strings.Contains(datasource, "tcp(111.222.333.44:3306)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatForPrint(t *testing.T) {
|
func TestFormat(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
query string
|
query string
|
||||||
args []interface{}
|
args []interface{}
|
||||||
expect string
|
expect string
|
||||||
|
hasErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no args",
|
name: "mysql normal",
|
||||||
query: "select user, name from table where id=?",
|
query: "select name, age from users where bool=? and phone=?",
|
||||||
expect: `select user, name from table where id=?`,
|
args: []interface{}{true, "133"},
|
||||||
|
expect: "select name, age from users where bool=1 and phone='133'",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one arg",
|
name: "mysql normal",
|
||||||
query: "select user, name from table where id=?",
|
query: "select name, age from users where bool=? and phone=?",
|
||||||
args: []interface{}{"kevin"},
|
args: []interface{}{false, "133"},
|
||||||
expect: `select user, name from table where id=? ["kevin"]`,
|
expect: "select name, age from users where bool=0 and phone='133'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pg normal",
|
||||||
|
query: "select name, age from users where bool=$1 and phone=$2",
|
||||||
|
args: []interface{}{true, "133"},
|
||||||
|
expect: "select name, age from users where bool=1 and phone='133'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pg normal reverse",
|
||||||
|
query: "select name, age from users where bool=$2 and phone=$1",
|
||||||
|
args: []interface{}{"133", false},
|
||||||
|
expect: "select name, age from users where bool=0 and phone='133'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pg error not number",
|
||||||
|
query: "select name, age from users where bool=$a and phone=$1",
|
||||||
|
args: []interface{}{"133", false},
|
||||||
|
hasErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pg error more args",
|
||||||
|
query: "select name, age from users where bool=$2 and phone=$1 and nickname=$3",
|
||||||
|
args: []interface{}{"133", false},
|
||||||
|
hasErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
actual := formatForPrint(test.query, test.args...)
|
t.Parallel()
|
||||||
assert.Equal(t, test.expect, actual)
|
|
||||||
|
actual, err := format(test.query, test.args...)
|
||||||
|
if test.hasErr {
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, test.expect, actual)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user