Compare commits
255 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 | ||
|
|
8478474f7f | ||
|
|
df5ae9507f | ||
|
|
faf4d7e3bb | ||
|
|
f64fe5eb5e | ||
|
|
97d889103a | ||
|
|
9a44310d00 | ||
|
|
06eeef2cf3 | ||
|
|
9adc7d4cb9 | ||
|
|
006f78c3d5 | ||
|
|
64a8e65f4a | ||
|
|
8fd1e76d29 | ||
|
|
0466af5e49 | ||
|
|
7405d7f506 | ||
|
|
afd9ff889e | ||
|
|
7e087de6e6 | ||
|
|
5aded99df5 | ||
|
|
08fb980ad2 | ||
|
|
b94d7aa532 | ||
|
|
ee630b8b57 | ||
|
|
bd82b7d8de | ||
|
|
3d729c77a6 | ||
|
|
e944b59bb3 | ||
|
|
54b5e3f4b2 | ||
|
|
b913229028 | ||
|
|
9963ffb1c1 | ||
|
|
8cb6490724 | ||
|
|
05e37ee20f | ||
|
|
d88da4cc88 | ||
|
|
425430f67c | ||
|
|
4e0d91f6c0 | ||
|
|
8584351b6d | ||
|
|
b19c5223a9 | ||
|
|
99a2d95433 | ||
|
|
9db222bf5b | ||
|
|
ac648d08cb | ||
|
|
6df7fa619c | ||
|
|
bbb4ce586f |
@@ -1,4 +1,3 @@
|
||||
comment: false
|
||||
ignore:
|
||||
- "doc"
|
||||
- "example"
|
||||
- "tools"
|
||||
- "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
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
go-version: ^1.14
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
@@ -25,10 +25,14 @@ jobs:
|
||||
run: |
|
||||
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
|
||||
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
- name: Codecov
|
||||
uses: codecov/codecov-action@v1.0.6
|
||||
with:
|
||||
token: ${{secrets.CODECOV_TOKEN}}
|
||||
uses: codecov/codecov-action@v2
|
||||
|
||||
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
|
||||
|
||||
# ignore
|
||||
.idea
|
||||
**/.DS_Store
|
||||
**/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
|
||||
@@ -122,8 +122,7 @@ func BenchmarkGoogleBreaker(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
type mockedPromise struct {
|
||||
}
|
||||
type mockedPromise struct{}
|
||||
|
||||
func (m *mockedPromise) Accept() {
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func (b *googleBreaker) markFailure() {
|
||||
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) {
|
||||
accepts += int64(b.Sum)
|
||||
total += b.Count
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestAesEcbBase64(t *testing.T) {
|
||||
// more than 32 chars
|
||||
badKey2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
)
|
||||
var key = []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
|
||||
key := []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
|
||||
b64Key := base64.StdEncoding.EncodeToString(key)
|
||||
b64Val := base64.StdEncoding.EncodeToString([]byte(val))
|
||||
_, err := EcbEncryptBase64(badKey1, val)
|
||||
|
||||
@@ -34,7 +34,7 @@ type (
|
||||
expire time.Duration
|
||||
timingWheel *TimingWheel
|
||||
lruCache lru
|
||||
barrier syncx.SharedCalls
|
||||
barrier syncx.SingleFlight
|
||||
unstableExpiry mathx.Unstable
|
||||
stats *cacheStat
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func NewCache(expire time.Duration, opts ...CacheOption) (*Cache, error) {
|
||||
data: make(map[string]interface{}),
|
||||
expire: expire,
|
||||
lruCache: emptyLruCache,
|
||||
barrier: syncx.NewSharedCalls(),
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ func TestRollingWindowBucketTimeBoundary(t *testing.T) {
|
||||
func TestRollingWindowDataRace(t *testing.T) {
|
||||
const size = 3
|
||||
r := NewRollingWindow(size, duration)
|
||||
var stop = make(chan bool)
|
||||
stop := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -106,9 +106,7 @@ func (s *Set) KeysInt() []int {
|
||||
var keys []int
|
||||
|
||||
for key := range s.data {
|
||||
if intKey, ok := key.(int); !ok {
|
||||
continue
|
||||
} else {
|
||||
if intKey, ok := key.(int); ok {
|
||||
keys = append(keys, intKey)
|
||||
}
|
||||
}
|
||||
@@ -121,9 +119,7 @@ func (s *Set) KeysInt64() []int64 {
|
||||
var keys []int64
|
||||
|
||||
for key := range s.data {
|
||||
if intKey, ok := key.(int64); !ok {
|
||||
continue
|
||||
} else {
|
||||
if intKey, ok := key.(int64); ok {
|
||||
keys = append(keys, intKey)
|
||||
}
|
||||
}
|
||||
@@ -136,9 +132,7 @@ func (s *Set) KeysUint() []uint {
|
||||
var keys []uint
|
||||
|
||||
for key := range s.data {
|
||||
if intKey, ok := key.(uint); !ok {
|
||||
continue
|
||||
} else {
|
||||
if intKey, ok := key.(uint); ok {
|
||||
keys = append(keys, intKey)
|
||||
}
|
||||
}
|
||||
@@ -151,9 +145,7 @@ func (s *Set) KeysUint64() []uint64 {
|
||||
var keys []uint64
|
||||
|
||||
for key := range s.data {
|
||||
if intKey, ok := key.(uint64); !ok {
|
||||
continue
|
||||
} else {
|
||||
if intKey, ok := key.(uint64); ok {
|
||||
keys = append(keys, intKey)
|
||||
}
|
||||
}
|
||||
@@ -166,9 +158,7 @@ func (s *Set) KeysStr() []string {
|
||||
var keys []string
|
||||
|
||||
for key := range s.data {
|
||||
if strKey, ok := key.(string); !ok {
|
||||
continue
|
||||
} else {
|
||||
if strKey, ok := key.(string); ok {
|
||||
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)
|
||||
pos = (tw.tickedPos + steps) % tw.numSlots
|
||||
circle = (steps - 1) / tw.numSlots
|
||||
|
||||
@@ -25,7 +25,7 @@ func LoadConfig(file string, v interface{}, opts ...Option) error {
|
||||
|
||||
loader, ok := loaders[path.Ext(file)]
|
||||
if !ok {
|
||||
return fmt.Errorf("unrecoginized file type: %s", file)
|
||||
return fmt.Errorf("unrecognized file type: %s", file)
|
||||
}
|
||||
|
||||
var opt options
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package contextx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ShrinkDeadline returns a new Context with proper deadline base on the given ctx and timeout.
|
||||
// And returns a cancel function as well.
|
||||
func ShrinkDeadline(ctx context.Context, timeout time.Duration) (context.Context, func()) {
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
leftTime := time.Until(deadline)
|
||||
if leftTime < timeout {
|
||||
timeout = leftTime
|
||||
}
|
||||
}
|
||||
|
||||
return context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package contextx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShrinkDeadlineLess(t *testing.T) {
|
||||
deadline := time.Now().Add(time.Second)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel()
|
||||
ctx, cancel = ShrinkDeadline(ctx, time.Minute)
|
||||
defer cancel()
|
||||
dl, ok := ctx.Deadline()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, deadline, dl)
|
||||
}
|
||||
|
||||
func TestShrinkDeadlineMore(t *testing.T) {
|
||||
deadline := time.Now().Add(time.Minute)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel()
|
||||
ctx, cancel = ShrinkDeadline(ctx, time.Second)
|
||||
defer cancel()
|
||||
dl, ok := ctx.Deadline()
|
||||
assert.True(t, ok)
|
||||
assert.True(t, dl.Before(deadline))
|
||||
}
|
||||
@@ -47,9 +47,11 @@ func TestUnmarshalContextWithMissing(t *testing.T) {
|
||||
Name string `ctx:"name"`
|
||||
Age int `ctx:"age"`
|
||||
}
|
||||
type name string
|
||||
const PersonNameKey name = "name"
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "name", "kevin")
|
||||
ctx = context.WithValue(ctx, PersonNameKey, "kevin")
|
||||
|
||||
var person Person
|
||||
err := For(ctx, &person)
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
)
|
||||
|
||||
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)
|
||||
o := ValueOnlyFrom(c1)
|
||||
c2, cancel2 := context.WithCancel(o)
|
||||
|
||||
@@ -5,7 +5,7 @@ package internal
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@ package internal
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
clientv3 "go.etcd.io/etcd/clientv3"
|
||||
grpc "google.golang.org/grpc"
|
||||
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
|
||||
|
||||
@@ -2,5 +2,5 @@ package internal
|
||||
|
||||
// Listener interface wraps the OnUpdate method.
|
||||
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/syncx"
|
||||
"github.com/tal-tech/go-zero/core/threading"
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -260,26 +260,34 @@ func (c *cluster) reload(cli EtcdClient) {
|
||||
}
|
||||
|
||||
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())
|
||||
for {
|
||||
select {
|
||||
case wresp, ok := <-rch:
|
||||
if !ok {
|
||||
logx.Error("etcd monitor chan has been closed")
|
||||
return
|
||||
return false
|
||||
}
|
||||
if wresp.Canceled {
|
||||
logx.Error("etcd monitor chan has been canceled")
|
||||
return
|
||||
logx.Errorf("etcd monitor chan has been canceled, error: %v", wresp.Err())
|
||||
return false
|
||||
}
|
||||
if wresp.Err() != nil {
|
||||
logx.Error(fmt.Sprintf("etcd monitor chan error: %v", wresp.Err()))
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
c.handleWatchEvents(key, wresp.Events)
|
||||
case <-c.done:
|
||||
return
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import (
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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/stringx"
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
"go.etcd.io/etcd/mvcc/mvccpb"
|
||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
var mockLock sync.Mutex
|
||||
@@ -202,11 +203,13 @@ func TestClusterWatch_RespFailures(t *testing.T) {
|
||||
restore := setMockClient(cli)
|
||||
defer restore()
|
||||
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()
|
||||
c := new(cluster)
|
||||
c.done = make(chan lang.PlaceholderType)
|
||||
go func() {
|
||||
ch <- resp
|
||||
close(c.done)
|
||||
}()
|
||||
c.watch(cli, "any")
|
||||
})
|
||||
@@ -220,11 +223,13 @@ func TestClusterWatch_CloseChan(t *testing.T) {
|
||||
restore := setMockClient(cli)
|
||||
defer restore()
|
||||
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()
|
||||
c := new(cluster)
|
||||
c.done = make(chan lang.PlaceholderType)
|
||||
go func() {
|
||||
close(ch)
|
||||
close(c.done)
|
||||
}()
|
||||
c.watch(cli, "any")
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ package internal
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
connectivity "google.golang.org/grpc/connectivity"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MocketcdConn is a mock of etcdConn interface
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// 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/syncx"
|
||||
"github.com/tal-tech/go-zero/core/threading"
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/tal-tech/go-zero/core/discov/internal"
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -9,7 +9,9 @@ type AtomicError struct {
|
||||
|
||||
// Set sets the error.
|
||||
func (ae *AtomicError) Set(err error) {
|
||||
ae.err.Store(err)
|
||||
if err != nil {
|
||||
ae.err.Store(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load returns the error.
|
||||
|
||||
@@ -17,6 +17,15 @@ func TestAtomicError(t *testing.T) {
|
||||
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) {
|
||||
var err AtomicError
|
||||
assert.Nil(t, err.Load())
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestChain(t *testing.T) {
|
||||
var errDummy = errors.New("dummy")
|
||||
errDummy := errors.New("dummy")
|
||||
assert.Nil(t, Chain(func() error {
|
||||
return nil
|
||||
}, func() error {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package fs
|
||||
|
||||
@@ -15,7 +15,7 @@ type (
|
||||
|
||||
// DoWithRetry runs fn, and retries if failed. Default to retry 3 times.
|
||||
func DoWithRetry(fn func() error, opts ...RetryOption) error {
|
||||
var options = newRetryOptions()
|
||||
options := newRetryOptions()
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestRetry(t *testing.T) {
|
||||
return errors.New("any")
|
||||
}))
|
||||
|
||||
var total = 2 * defaultRetryTimes
|
||||
total := 2 * defaultRetryTimes
|
||||
times = 0
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
times++
|
||||
|
||||
@@ -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.
|
||||
func From(generate GenerateFunc) Stream {
|
||||
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.
|
||||
// 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 {
|
||||
n = 0
|
||||
}
|
||||
|
||||
source := make(chan interface{}, n)
|
||||
go func() {
|
||||
for item := range p.source {
|
||||
for item := range s.source {
|
||||
source <- item
|
||||
}
|
||||
close(source)
|
||||
@@ -97,23 +128,51 @@ func (p Stream) Buffer(n int) Stream {
|
||||
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.
|
||||
func (p Stream) Count() (count int) {
|
||||
for range p.source {
|
||||
func (s Stream) Count() (count int) {
|
||||
for range s.source {
|
||||
count++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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{})
|
||||
|
||||
threading.GoSafe(func() {
|
||||
defer close(source)
|
||||
|
||||
keys := make(map[interface{}]lang.PlaceholderType)
|
||||
for item := range p.source {
|
||||
for item := range s.source {
|
||||
key := fn(item)
|
||||
if _, ok := keys[key]; !ok {
|
||||
source <- item
|
||||
@@ -126,14 +185,14 @@ func (p Stream) Distinct(fn KeyFunc) Stream {
|
||||
}
|
||||
|
||||
// Done waits all upstreaming operations to be done.
|
||||
func (p Stream) Done() {
|
||||
for range p.source {
|
||||
func (s Stream) Done() {
|
||||
for range s.source {
|
||||
}
|
||||
}
|
||||
|
||||
// Filter filters the items by the given FilterFunc.
|
||||
func (p Stream) Filter(fn FilterFunc, opts ...Option) Stream {
|
||||
return p.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||
func (s Stream) Filter(fn FilterFunc, opts ...Option) Stream {
|
||||
return s.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||
if fn(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.
|
||||
func (p Stream) ForAll(fn ForAllFunc) {
|
||||
fn(p.source)
|
||||
func (s Stream) ForAll(fn ForAllFunc) {
|
||||
fn(s.source)
|
||||
}
|
||||
|
||||
// ForEach seals the Stream with the ForEachFunc on each item, no successive operations.
|
||||
func (p Stream) ForEach(fn ForEachFunc) {
|
||||
for item := range p.source {
|
||||
func (s Stream) ForEach(fn ForEachFunc) {
|
||||
for item := range s.source {
|
||||
fn(item)
|
||||
}
|
||||
}
|
||||
|
||||
// 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{})
|
||||
for item := range p.source {
|
||||
for item := range s.source {
|
||||
key := fn(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.
|
||||
func (p Stream) Head(n int64) Stream {
|
||||
func (s Stream) Head(n int64) Stream {
|
||||
if n < 1 {
|
||||
panic("n must be greater than 0")
|
||||
}
|
||||
@@ -180,7 +239,7 @@ func (p Stream) Head(n int64) Stream {
|
||||
source := make(chan interface{})
|
||||
|
||||
go func() {
|
||||
for item := range p.source {
|
||||
for item := range s.source {
|
||||
n--
|
||||
if n >= 0 {
|
||||
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.
|
||||
func (p Stream) Map(fn MapFunc, opts ...Option) Stream {
|
||||
return p.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||
func (s Stream) Map(fn MapFunc, opts ...Option) Stream {
|
||||
return s.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||
pipe <- fn(item)
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// 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{}
|
||||
for item := range p.source {
|
||||
for item := range s.source {
|
||||
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.
|
||||
func (p Stream) Parallel(fn ParallelFunc, opts ...Option) {
|
||||
p.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||
func (s Stream) Parallel(fn ParallelFunc, opts ...Option) {
|
||||
s.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||
fn(item)
|
||||
}, opts...).Done()
|
||||
}
|
||||
|
||||
// Reduce is a utility method to let the caller deal with the underlying channel.
|
||||
func (p Stream) Reduce(fn ReduceFunc) (interface{}, error) {
|
||||
return fn(p.source)
|
||||
func (s Stream) Reduce(fn ReduceFunc) (interface{}, error) {
|
||||
return fn(s.source)
|
||||
}
|
||||
|
||||
// Reverse reverses the elements in the stream.
|
||||
func (p Stream) Reverse() Stream {
|
||||
func (s Stream) Reverse() Stream {
|
||||
var items []interface{}
|
||||
for item := range p.source {
|
||||
for item := range s.source {
|
||||
items = append(items, item)
|
||||
}
|
||||
// reverse, official method
|
||||
@@ -248,10 +307,36 @@ func (p Stream) Reverse() Stream {
|
||||
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.
|
||||
func (p Stream) Sort(less LessFunc) Stream {
|
||||
func (s Stream) Sort(less LessFunc) Stream {
|
||||
var items []interface{}
|
||||
for item := range p.source {
|
||||
for item := range s.source {
|
||||
items = append(items, item)
|
||||
}
|
||||
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,
|
||||
// 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 {
|
||||
panic("n should be greater than 0")
|
||||
}
|
||||
@@ -271,7 +356,7 @@ func (p Stream) Split(n int) Stream {
|
||||
source := make(chan interface{})
|
||||
go func() {
|
||||
var chunk []interface{}
|
||||
for item := range p.source {
|
||||
for item := range s.source {
|
||||
chunk = append(chunk, item)
|
||||
if len(chunk) == n {
|
||||
source <- chunk
|
||||
@@ -288,7 +373,7 @@ func (p Stream) Split(n int) Stream {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
panic("n should be greater than 0")
|
||||
}
|
||||
@@ -297,7 +382,7 @@ func (p Stream) Tail(n int64) Stream {
|
||||
|
||||
go func() {
|
||||
ring := collection.NewRing(int(n))
|
||||
for item := range p.source {
|
||||
for item := range s.source {
|
||||
ring.Add(item)
|
||||
}
|
||||
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.
|
||||
func (p Stream) Walk(fn WalkFunc, opts ...Option) Stream {
|
||||
func (s Stream) Walk(fn WalkFunc, opts ...Option) Stream {
|
||||
option := buildOptions(opts...)
|
||||
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)
|
||||
|
||||
go func() {
|
||||
@@ -328,7 +413,7 @@ func (p Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
||||
|
||||
for {
|
||||
pool <- lang.Placeholder
|
||||
item, ok := <-p.source
|
||||
item, ok := <-s.source
|
||||
if !ok {
|
||||
<-pool
|
||||
break
|
||||
@@ -353,14 +438,14 @@ func (p Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
||||
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)
|
||||
|
||||
go func() {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for {
|
||||
item, ok := <-p.source
|
||||
item, ok := <-s.source
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ package fx
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -330,6 +333,29 @@ func TestWalk(t *testing.T) {
|
||||
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) {
|
||||
b.ReportAllocs()
|
||||
|
||||
@@ -343,12 +369,103 @@ func BenchmarkMapReduce(b *testing.B) {
|
||||
}
|
||||
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++ {
|
||||
From(func(input chan<- interface{}) {
|
||||
for j := 0; j < 2; j++ {
|
||||
input <- int64(j)
|
||||
}
|
||||
}).Map(mapper).Reduce(reducer)
|
||||
func equal(t *testing.T, stream Stream, data []interface{}) {
|
||||
items := make([]interface{}, 0)
|
||||
for item := range stream.source {
|
||||
items = append(items, item)
|
||||
}
|
||||
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,9 +2,10 @@ package fx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/contextx"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -23,7 +24,7 @@ func DoWithTimeout(fn func() error, timeout time.Duration, opts ...DoOption) err
|
||||
for _, opt := range opts {
|
||||
parentCtx = opt()
|
||||
}
|
||||
ctx, cancel := contextx.ShrinkDeadline(parentCtx, timeout)
|
||||
ctx, cancel := context.WithTimeout(parentCtx, timeout)
|
||||
defer cancel()
|
||||
|
||||
// create channel with buffer size 1 to avoid goroutine leak
|
||||
@@ -32,7 +33,8 @@ func DoWithTimeout(fn func() error, timeout time.Duration, opts ...DoOption) err
|
||||
go func() {
|
||||
defer func() {
|
||||
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()
|
||||
|
||||
@@ -83,7 +83,7 @@ func (h *ConsistentHash) AddWithReplicas(node interface{}, replicas int) {
|
||||
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]
|
||||
})
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func (h *ConsistentHash) Remove(node interface{}) {
|
||||
index := sort.Search(len(h.keys), func(i int) bool {
|
||||
return h.keys[i] >= hash
|
||||
})
|
||||
if index < len(h.keys) {
|
||||
if index < len(h.keys) && h.keys[index] == hash {
|
||||
h.keys = append(h.keys[:index], h.keys[index+1:]...)
|
||||
}
|
||||
h.removeRingNode(hash, nodeRepr)
|
||||
|
||||
@@ -20,6 +20,11 @@ func TestMd5(t *testing.T) {
|
||||
assert.Equal(t, md5Digest, actual)
|
||||
}
|
||||
|
||||
func TestMd5Hex(t *testing.T) {
|
||||
actual := Md5Hex([]byte(text))
|
||||
assert.Equal(t, md5Digest, actual)
|
||||
}
|
||||
|
||||
func BenchmarkHashFnv(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
h := fnv.New32()
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestTokenLimit_Rescue(t *testing.T) {
|
||||
rate = 5
|
||||
burst = 10
|
||||
)
|
||||
l := NewTokenLimiter(rate, burst, redis.NewRedis(s.Addr(), redis.NodeType), "tokenlimit")
|
||||
l := NewTokenLimiter(rate, burst, redis.New(s.Addr()), "tokenlimit")
|
||||
s.Close()
|
||||
|
||||
var allowed int
|
||||
|
||||
@@ -31,6 +31,8 @@ var (
|
||||
|
||||
// default to be enabled
|
||||
enabled = syncx.ForAtomicBool(true)
|
||||
// default to be enabled
|
||||
logEnabled = syncx.ForAtomicBool(true)
|
||||
// make it a variable for unit test
|
||||
systemOverloadChecker = func(cpuThreshold int64) bool {
|
||||
return stat.CpuUsage() >= cpuThreshold
|
||||
@@ -80,6 +82,11 @@ func Disable() {
|
||||
enabled.Set(false)
|
||||
}
|
||||
|
||||
// DisableLog disables the stat logs for load shedding.
|
||||
func DisableLog() {
|
||||
logEnabled.Set(false)
|
||||
}
|
||||
|
||||
// NewAdaptiveShedder returns an adaptive shedder.
|
||||
// opts can be used to customize the Shedder.
|
||||
func NewAdaptiveShedder(opts ...ShedderOption) Shedder {
|
||||
@@ -168,7 +175,7 @@ func (as *adaptiveShedder) maxPass() int64 {
|
||||
}
|
||||
|
||||
func (as *adaptiveShedder) minRt() float64 {
|
||||
var result = defaultMinRt
|
||||
result := defaultMinRt
|
||||
|
||||
as.rtCounter.Reduce(func(b *collection.Bucket) {
|
||||
if b.Count <= 0 {
|
||||
|
||||
@@ -25,6 +25,7 @@ func init() {
|
||||
}
|
||||
|
||||
func TestAdaptiveShedder(t *testing.T) {
|
||||
DisableLog()
|
||||
shedder := NewAdaptiveShedder(WithWindow(bucketDuration), WithBuckets(buckets), WithCpuThreshold(100))
|
||||
var wg sync.WaitGroup
|
||||
var drop int64
|
||||
@@ -201,7 +202,7 @@ func BenchmarkAdaptiveShedder_Allow(b *testing.B) {
|
||||
logx.Disable()
|
||||
|
||||
bench := func(b *testing.B) {
|
||||
var shedder = NewAdaptiveShedder()
|
||||
shedder := NewAdaptiveShedder()
|
||||
proba := mathx.NewProba()
|
||||
for i := 0; i < 6000; i++ {
|
||||
p, err := shedder.Allow()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package load
|
||||
|
||||
type nopShedder struct {
|
||||
}
|
||||
type nopShedder struct{}
|
||||
|
||||
func newNopShedder() Shedder {
|
||||
return nopShedder{}
|
||||
@@ -11,8 +10,7 @@ func (s nopShedder) Allow() (Promise, error) {
|
||||
return nopPromise{}, nil
|
||||
}
|
||||
|
||||
type nopPromise struct {
|
||||
}
|
||||
type nopPromise struct{}
|
||||
|
||||
func (p nopPromise) Pass() {
|
||||
}
|
||||
|
||||
@@ -48,6 +48,25 @@ func (s *SheddingStat) IncrementDrop() {
|
||||
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 {
|
||||
return snapshot{
|
||||
Total: atomic.SwapInt64(&s.total, 0),
|
||||
@@ -59,15 +78,6 @@ func (s *SheddingStat) reset() snapshot {
|
||||
func (s *SheddingStat) run() {
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
c := stat.CpuUsage()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
s.loop(ticker.C)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package load
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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(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{}) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *durationLogger) Errorf(format string, v ...interface{}) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
if shallLog(ErrorLevel) {
|
||||
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{}) {
|
||||
if shouldLog(InfoLevel) {
|
||||
if shallLog(InfoLevel) {
|
||||
l.write(infoLog, levelInfo, fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *durationLogger) Infof(format string, v ...interface{}) {
|
||||
if shouldLog(InfoLevel) {
|
||||
if shallLog(InfoLevel) {
|
||||
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{}) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(slowLog, levelSlow, fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *durationLogger) Slowf(format string, v ...interface{}) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
if shallLog(ErrorLevel) {
|
||||
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 {
|
||||
l.Duration = timex.ReprOfDuration(duration)
|
||||
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.Level = level
|
||||
l.Content = content
|
||||
outputJson(writer, logEntry(*l))
|
||||
l.Content = val
|
||||
outputJson(writer, l)
|
||||
}
|
||||
|
||||
@@ -65,12 +65,14 @@ var (
|
||||
timeFormat = "2006-01-02T15:04:05.000Z07"
|
||||
writeConsole bool
|
||||
logLevel uint32
|
||||
infoLog io.WriteCloser
|
||||
errorLog io.WriteCloser
|
||||
severeLog io.WriteCloser
|
||||
slowLog io.WriteCloser
|
||||
statLog io.WriteCloser
|
||||
stackLog io.Writer
|
||||
// use uint32 for atomic operations
|
||||
disableStat uint32
|
||||
infoLog io.WriteCloser
|
||||
errorLog io.WriteCloser
|
||||
severeLog io.WriteCloser
|
||||
slowLog io.WriteCloser
|
||||
statLog io.WriteCloser
|
||||
stackLog io.Writer
|
||||
|
||||
once sync.Once
|
||||
initialized uint32
|
||||
@@ -79,10 +81,10 @@ var (
|
||||
|
||||
type (
|
||||
logEntry struct {
|
||||
Timestamp string `json:"@timestamp"`
|
||||
Level string `json:"level"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
Content string `json:"content"`
|
||||
Timestamp string `json:"@timestamp"`
|
||||
Level string `json:"level"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
Content interface{} `json:"content"`
|
||||
}
|
||||
|
||||
logOptions struct {
|
||||
@@ -98,10 +100,13 @@ type (
|
||||
Logger interface {
|
||||
Error(...interface{})
|
||||
Errorf(string, ...interface{})
|
||||
Errorv(interface{})
|
||||
Info(...interface{})
|
||||
Infof(string, ...interface{})
|
||||
Infov(interface{})
|
||||
Slow(...interface{})
|
||||
Slowf(string, ...interface{})
|
||||
Slowv(interface{})
|
||||
WithDuration(time.Duration) Logger
|
||||
}
|
||||
)
|
||||
@@ -133,7 +138,7 @@ func SetUp(c LogConf) error {
|
||||
|
||||
// Alert alerts v in alert level, and the message is written to error log.
|
||||
func Alert(v string) {
|
||||
output(errorLog, levelAlert, v)
|
||||
outputText(errorLog, levelAlert, v)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func Error(v ...interface{}) {
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
@@ -227,14 +237,25 @@ func ErrorStackf(format string, v ...interface{}) {
|
||||
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.
|
||||
func Info(v ...interface{}) {
|
||||
infoSync(fmt.Sprint(v...))
|
||||
infoTextSync(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Infof writes v with format into access log.
|
||||
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.
|
||||
@@ -242,7 +263,7 @@ func Must(err error) {
|
||||
if err != nil {
|
||||
msg := formatWithCaller(err.Error(), 3)
|
||||
log.Print(msg)
|
||||
output(severeLog, levelFatal, msg)
|
||||
outputText(severeLog, levelFatal, msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -264,12 +285,17 @@ func Severef(format string, v ...interface{}) {
|
||||
|
||||
// Slow writes v into slow log.
|
||||
func Slow(v ...interface{}) {
|
||||
slowSync(fmt.Sprint(v...))
|
||||
slowTextSync(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Slowf writes v with format into slow log.
|
||||
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.
|
||||
@@ -312,8 +338,14 @@ func createOutput(path string) (io.WriteCloser, error) {
|
||||
options.gzipEnabled), options.gzipEnabled)
|
||||
}
|
||||
|
||||
func errorSync(msg string, callDepth int) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
func errorAnySync(v interface{}) {
|
||||
if shallLog(ErrorLevel) {
|
||||
outputAny(errorLog, levelError, v)
|
||||
}
|
||||
}
|
||||
|
||||
func errorTextSync(msg string, callDepth int) {
|
||||
if shallLog(ErrorLevel) {
|
||||
outputError(errorLog, msg, callDepth)
|
||||
}
|
||||
}
|
||||
@@ -362,13 +394,28 @@ func handleOptions(opts []LogOption) {
|
||||
}
|
||||
}
|
||||
|
||||
func infoSync(msg string) {
|
||||
if shouldLog(InfoLevel) {
|
||||
output(infoLog, levelInfo, msg)
|
||||
func infoAnySync(val interface{}) {
|
||||
if shallLog(InfoLevel) {
|
||||
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{
|
||||
Timestamp: getTimestamp(),
|
||||
Level: level,
|
||||
@@ -379,7 +426,7 @@ func output(writer io.Writer, level, msg string) {
|
||||
|
||||
func outputError(writer io.Writer, msg string, callDepth int) {
|
||||
content := formatWithCaller(msg, callDepth)
|
||||
output(writer, levelError, content)
|
||||
outputText(writer, levelError, content)
|
||||
}
|
||||
|
||||
func outputJson(writer io.Writer, info interface{}) {
|
||||
@@ -481,30 +528,40 @@ func setupWithVolume(c LogConf) error {
|
||||
}
|
||||
|
||||
func severeSync(msg string) {
|
||||
if shouldLog(SevereLevel) {
|
||||
output(severeLog, levelSevere, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||
if shallLog(SevereLevel) {
|
||||
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
|
||||
}
|
||||
|
||||
func slowSync(msg string) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
output(slowLog, levelSlow, msg)
|
||||
func shallLogStat() bool {
|
||||
return atomic.LoadUint32(&disableStat) == 0
|
||||
}
|
||||
|
||||
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) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
output(stackLog, levelError, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||
if shallLog(ErrorLevel) {
|
||||
outputText(stackLog, levelError, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||
}
|
||||
}
|
||||
|
||||
func statSync(msg string) {
|
||||
if shouldLog(InfoLevel) {
|
||||
output(statLog, levelStat, msg)
|
||||
if shallLogStat() && shallLog(InfoLevel) {
|
||||
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) {
|
||||
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
|
||||
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) {
|
||||
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) {
|
||||
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) {
|
||||
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
|
||||
statLog = writer
|
||||
@@ -246,6 +294,17 @@ func TestDisable(t *testing.T) {
|
||||
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) {
|
||||
fn := WithGzip()
|
||||
var opt logOptions
|
||||
@@ -357,7 +416,9 @@ func doTestStructedLog(t *testing.T, level string, setup func(writer io.WriteClo
|
||||
t.Error(err)
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -22,8 +22,8 @@ const (
|
||||
dateFormat = "2006-01-02"
|
||||
hoursPerDay = 24
|
||||
bufferSize = 100
|
||||
defaultDirMode = 0755
|
||||
defaultFileMode = 0600
|
||||
defaultDirMode = 0o755
|
||||
defaultFileMode = 0o600
|
||||
)
|
||||
|
||||
// ErrLogFileClosed is an error that indicates the log file is already closed.
|
||||
@@ -47,7 +47,6 @@ type (
|
||||
done chan lang.PlaceholderType
|
||||
rule RotateRule
|
||||
compress bool
|
||||
keepDays int
|
||||
// can't use threading.RoutineGroup because of cycle import
|
||||
waitGroup sync.WaitGroup
|
||||
closeOnce sync.Once
|
||||
|
||||
@@ -3,6 +3,7 @@ package logx
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -97,7 +98,13 @@ func TestRotateLoggerRotate(t *testing.T) {
|
||||
}()
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -44,5 +44,5 @@ func captureOutput(f func()) string {
|
||||
func getContent(jsonStr string) string {
|
||||
var entry logEntry
|
||||
json.Unmarshal([]byte(jsonStr), &entry)
|
||||
return entry.Content
|
||||
return entry.Content.(string)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
@@ -18,50 +18,68 @@ type traceLogger struct {
|
||||
}
|
||||
|
||||
func (l *traceLogger) Error(v ...interface{}) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *traceLogger) Errorf(format string, v ...interface{}) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
if shallLog(ErrorLevel) {
|
||||
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{}) {
|
||||
if shouldLog(InfoLevel) {
|
||||
if shallLog(InfoLevel) {
|
||||
l.write(infoLog, levelInfo, fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *traceLogger) Infof(format string, v ...interface{}) {
|
||||
if shouldLog(InfoLevel) {
|
||||
if shallLog(InfoLevel) {
|
||||
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{}) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
if shallLog(ErrorLevel) {
|
||||
l.write(slowLog, levelSlow, fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *traceLogger) Slowf(format string, v ...interface{}) {
|
||||
if shouldLog(ErrorLevel) {
|
||||
if shallLog(ErrorLevel) {
|
||||
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 {
|
||||
l.Duration = timex.ReprOfDuration(duration)
|
||||
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.Level = level
|
||||
l.Content = content
|
||||
l.Content = val
|
||||
l.Trace = traceIdFromContext(l.ctx)
|
||||
l.Span = spanIdFromContext(l.ctx)
|
||||
outputJson(writer, l)
|
||||
@@ -75,19 +93,19 @@ func WithContext(ctx context.Context) Logger {
|
||||
}
|
||||
|
||||
func spanIdFromContext(ctx context.Context) string {
|
||||
t, ok := ctx.Value(tracespec.TracingKey).(tracespec.Trace)
|
||||
if !ok {
|
||||
return ""
|
||||
spanCtx := trace.SpanContextFromContext(ctx)
|
||||
if spanCtx.HasSpanID() {
|
||||
return spanCtx.SpanID().String()
|
||||
}
|
||||
|
||||
return t.SpanId()
|
||||
return ""
|
||||
}
|
||||
|
||||
func traceIdFromContext(ctx context.Context) string {
|
||||
t, ok := ctx.Value(tracespec.TracingKey).(tracespec.Trace)
|
||||
if !ok {
|
||||
return ""
|
||||
spanCtx := trace.SpanContextFromContext(ctx)
|
||||
if spanCtx.HasTraceID() {
|
||||
return spanCtx.TraceID().String()
|
||||
}
|
||||
|
||||
return t.TraceId()
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -9,71 +9,90 @@ import (
|
||||
"time"
|
||||
|
||||
"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 (
|
||||
mockTraceID = "mock-trace-id"
|
||||
mockSpanID = "mock-span-id"
|
||||
traceKey = "trace"
|
||||
spanKey = "span"
|
||||
)
|
||||
|
||||
var mock tracespec.Trace = new(mockTrace)
|
||||
|
||||
func TestTraceLog(t *testing.T) {
|
||||
var buf mockWriter
|
||||
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)
|
||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
}
|
||||
|
||||
func TestTraceError(t *testing.T) {
|
||||
var buf mockWriter
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
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)
|
||||
SetLevel(InfoLevel)
|
||||
l.WithDuration(time.Second).Error(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
l.WithDuration(time.Second).Errorf(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
}
|
||||
|
||||
func TestTraceInfo(t *testing.T) {
|
||||
var buf mockWriter
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
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)
|
||||
SetLevel(InfoLevel)
|
||||
l.WithDuration(time.Second).Info(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
l.WithDuration(time.Second).Infof(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
}
|
||||
|
||||
func TestTraceSlow(t *testing.T) {
|
||||
var buf mockWriter
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
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)
|
||||
SetLevel(InfoLevel)
|
||||
l.WithDuration(time.Second).Slow(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
l.WithDuration(time.Second).Slowf(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), mockTraceID))
|
||||
assert.True(t, strings.Contains(buf.String(), mockSpanID))
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
}
|
||||
|
||||
func TestTraceWithoutContext(t *testing.T) {
|
||||
@@ -83,34 +102,10 @@ func TestTraceWithoutContext(t *testing.T) {
|
||||
l := WithContext(context.Background()).(*traceLogger)
|
||||
SetLevel(InfoLevel)
|
||||
l.WithDuration(time.Second).Info(testlog)
|
||||
assert.False(t, strings.Contains(buf.String(), mockTraceID))
|
||||
assert.False(t, strings.Contains(buf.String(), mockSpanID))
|
||||
assert.False(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.False(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
l.WithDuration(time.Second).Infof(testlog)
|
||||
assert.False(t, strings.Contains(buf.String(), mockTraceID))
|
||||
assert.False(t, strings.Contains(buf.String(), mockSpanID))
|
||||
}
|
||||
|
||||
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) {
|
||||
assert.False(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.False(t, strings.Contains(buf.String(), spanKey))
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ type (
|
||||
UnmarshalOption func(*unmarshalOptions)
|
||||
|
||||
unmarshalOptions struct {
|
||||
fromString bool
|
||||
fromString bool
|
||||
canonicalKey func(key string) string
|
||||
}
|
||||
|
||||
keyCache map[string][]string
|
||||
@@ -229,7 +230,7 @@ func (u *Unmarshaler) processFieldPrimitive(field reflect.StructField, value ref
|
||||
default:
|
||||
switch v := mapValue.(type) {
|
||||
case json.Number:
|
||||
return u.processFieldPrimitiveWithJsonNumber(field, value, v, opts, fullName)
|
||||
return u.processFieldPrimitiveWithJSONNumber(field, value, v, opts, fullName)
|
||||
default:
|
||||
if typeKind == valueKind {
|
||||
if err := validateValueInOptions(opts.options(), mapValue); err != nil {
|
||||
@@ -244,7 +245,7 @@ func (u *Unmarshaler) processFieldPrimitive(field reflect.StructField, value ref
|
||||
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 {
|
||||
fieldType := field.Type
|
||||
fieldKind := fieldType.Kind()
|
||||
@@ -323,7 +324,11 @@ func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
conv.Index(i).Set(target.Elem())
|
||||
}
|
||||
case reflect.Slice:
|
||||
if err := u.fillSlice(dereffedBaseType, conv.Index(i), ithValue); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if err := u.fillSliceValue(conv, i, dereffedBaseKind, ithValue); err != nil {
|
||||
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 {
|
||||
ithVal := slice.Index(index)
|
||||
switch v := value.(type) {
|
||||
case json.Number:
|
||||
return setValue(baseKind, slice.Index(index), v.String())
|
||||
return setValue(baseKind, ithVal, v.String())
|
||||
default:
|
||||
// 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.
|
||||
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
|
||||
}
|
||||
|
||||
slice.Index(index).Set(reflect.ValueOf(value))
|
||||
ithVal.Set(reflect.ValueOf(value))
|
||||
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 {
|
||||
d, err := time.ParseDuration(dur)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package mapping
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -752,7 +753,7 @@ func TestUnmarshalJsonNumberInt64(t *testing.T) {
|
||||
for i := 0; i <= maxUintBitsToTest; i++ {
|
||||
var intValue int64 = 1 << uint(i)
|
||||
strValue := strconv.FormatInt(intValue, 10)
|
||||
var number = json.Number(strValue)
|
||||
number := json.Number(strValue)
|
||||
m := map[string]interface{}{
|
||||
"ID": number,
|
||||
}
|
||||
@@ -768,7 +769,7 @@ func TestUnmarshalJsonNumberUint64(t *testing.T) {
|
||||
for i := 0; i <= maxUintBitsToTest; i++ {
|
||||
var intValue uint64 = 1 << uint(i)
|
||||
strValue := strconv.FormatUint(intValue, 10)
|
||||
var number = json.Number(strValue)
|
||||
number := json.Number(strValue)
|
||||
m := map[string]interface{}{
|
||||
"ID": number,
|
||||
}
|
||||
@@ -784,7 +785,7 @@ func TestUnmarshalJsonNumberUint64Ptr(t *testing.T) {
|
||||
for i := 0; i <= maxUintBitsToTest; i++ {
|
||||
var intValue uint64 = 1 << uint(i)
|
||||
strValue := strconv.FormatUint(intValue, 10)
|
||||
var number = json.Number(strValue)
|
||||
number := json.Number(strValue)
|
||||
m := map[string]interface{}{
|
||||
"ID": number,
|
||||
}
|
||||
@@ -2480,3 +2481,40 @@ func BenchmarkUnmarshal(b *testing.B) {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -170,6 +170,28 @@ func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func isLeftInclude(b byte) (bool, error) {
|
||||
switch b {
|
||||
case '[':
|
||||
return true, nil
|
||||
case '(':
|
||||
return false, nil
|
||||
default:
|
||||
return false, errNumberRange
|
||||
}
|
||||
}
|
||||
|
||||
func isRightInclude(b byte) (bool, error) {
|
||||
switch b {
|
||||
case ']':
|
||||
return true, nil
|
||||
case ')':
|
||||
return false, nil
|
||||
default:
|
||||
return false, errNumberRange
|
||||
}
|
||||
}
|
||||
|
||||
func maybeNewValue(field reflect.StructField, value reflect.Value) {
|
||||
if field.Type.Kind() == reflect.Ptr && value.IsNil() {
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
@@ -211,14 +233,9 @@ func parseNumberRange(str string) (*numberRange, error) {
|
||||
return nil, errNumberRange
|
||||
}
|
||||
|
||||
var leftInclude bool
|
||||
switch str[0] {
|
||||
case '[':
|
||||
leftInclude = true
|
||||
case '(':
|
||||
leftInclude = false
|
||||
default:
|
||||
return nil, errNumberRange
|
||||
leftInclude, err := isLeftInclude(str[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
str = str[1:]
|
||||
@@ -226,14 +243,9 @@ func parseNumberRange(str string) (*numberRange, error) {
|
||||
return nil, errNumberRange
|
||||
}
|
||||
|
||||
var rightInclude bool
|
||||
switch str[len(str)-1] {
|
||||
case ']':
|
||||
rightInclude = true
|
||||
case ')':
|
||||
rightInclude = false
|
||||
default:
|
||||
return nil, errNumberRange
|
||||
rightInclude, err := isRightInclude(str[len(str)-1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
str = str[:len(str)-1]
|
||||
@@ -274,7 +286,7 @@ func parseNumberRange(str string) (*numberRange, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseOption(fieldOpts *fieldOptions, fieldName string, option string) error {
|
||||
func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
|
||||
switch {
|
||||
case option == stringOption:
|
||||
fieldOpts.FromString = true
|
||||
|
||||
@@ -16,8 +16,8 @@ type Foo struct {
|
||||
}
|
||||
|
||||
func TestDeferInt(t *testing.T) {
|
||||
var i = 1
|
||||
var s = "hello"
|
||||
i := 1
|
||||
s := "hello"
|
||||
number := struct {
|
||||
f float64
|
||||
}{
|
||||
|
||||
@@ -36,7 +36,7 @@ func (u Unstable) AroundDuration(base time.Duration) time.Duration {
|
||||
return val
|
||||
}
|
||||
|
||||
// AroundInt returns a randome int64 with given base and deviation.
|
||||
// AroundInt returns a random int64 with given base and deviation.
|
||||
func (u Unstable) AroundInt(base int64) int64 {
|
||||
u.lock.Lock()
|
||||
val := int64((1 + u.deviation - 2*u.deviation*u.r.Float64()) * float64(base))
|
||||
|
||||
@@ -12,7 +12,7 @@ type (
|
||||
// CounterVec interface represents a counter vector.
|
||||
CounterVec interface {
|
||||
// Inc increments labels.
|
||||
Inc(lables ...string)
|
||||
Inc(labels ...string)
|
||||
// Add adds labels with v.
|
||||
Add(v float64, labels ...string)
|
||||
close() bool
|
||||
@@ -50,8 +50,8 @@ func (cv *promCounterVec) Inc(labels ...string) {
|
||||
cv.counter.WithLabelValues(labels...).Inc()
|
||||
}
|
||||
|
||||
func (cv *promCounterVec) Add(v float64, lables ...string) {
|
||||
cv.counter.WithLabelValues(lables...).Add(v)
|
||||
func (cv *promCounterVec) Add(v float64, labels ...string) {
|
||||
cv.counter.WithLabelValues(labels...).Add(v)
|
||||
}
|
||||
|
||||
func (cv *promCounterVec) close() bool {
|
||||
|
||||
@@ -20,7 +20,7 @@ type (
|
||||
close() bool
|
||||
}
|
||||
|
||||
promGuageVec struct {
|
||||
promGaugeVec struct {
|
||||
gauge *prom.GaugeVec
|
||||
}
|
||||
)
|
||||
@@ -39,7 +39,7 @@ func NewGaugeVec(cfg *GaugeVecOpts) GaugeVec {
|
||||
Help: cfg.Help,
|
||||
}, cfg.Labels)
|
||||
prom.MustRegister(vec)
|
||||
gv := &promGuageVec{
|
||||
gv := &promGaugeVec{
|
||||
gauge: vec,
|
||||
}
|
||||
proc.AddShutdownListener(func() {
|
||||
@@ -49,18 +49,18 @@ func NewGaugeVec(cfg *GaugeVecOpts) GaugeVec {
|
||||
return gv
|
||||
}
|
||||
|
||||
func (gv *promGuageVec) Inc(labels ...string) {
|
||||
func (gv *promGaugeVec) Inc(labels ...string) {
|
||||
gv.gauge.WithLabelValues(labels...).Inc()
|
||||
}
|
||||
|
||||
func (gv *promGuageVec) Add(v float64, lables ...string) {
|
||||
gv.gauge.WithLabelValues(lables...).Add(v)
|
||||
func (gv *promGaugeVec) Add(v float64, labels ...string) {
|
||||
gv.gauge.WithLabelValues(labels...).Add(v)
|
||||
}
|
||||
|
||||
func (gv *promGuageVec) Set(v float64, lables ...string) {
|
||||
gv.gauge.WithLabelValues(lables...).Set(v)
|
||||
func (gv *promGaugeVec) Set(v float64, labels ...string) {
|
||||
gv.gauge.WithLabelValues(labels...).Set(v)
|
||||
}
|
||||
|
||||
func (gv *promGuageVec) close() bool {
|
||||
func (gv *promGaugeVec) close() bool {
|
||||
return prom.Unregister(gv.gauge)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestGaugeInc(t *testing.T) {
|
||||
Labels: []string{"path"},
|
||||
})
|
||||
defer gaugeVec.close()
|
||||
gv, _ := gaugeVec.(*promGuageVec)
|
||||
gv, _ := gaugeVec.(*promGaugeVec)
|
||||
gv.Inc("/users")
|
||||
gv.Inc("/users")
|
||||
r := testutil.ToFloat64(gv.gauge)
|
||||
@@ -45,7 +45,7 @@ func TestGaugeAdd(t *testing.T) {
|
||||
Labels: []string{"path"},
|
||||
})
|
||||
defer gaugeVec.close()
|
||||
gv, _ := gaugeVec.(*promGuageVec)
|
||||
gv, _ := gaugeVec.(*promGaugeVec)
|
||||
gv.Add(-10, "/classroom")
|
||||
gv.Add(30, "/classroom")
|
||||
r := testutil.ToFloat64(gv.gauge)
|
||||
@@ -61,7 +61,7 @@ func TestGaugeSet(t *testing.T) {
|
||||
Labels: []string{"path"},
|
||||
})
|
||||
gaugeVec.close()
|
||||
gv, _ := gaugeVec.(*promGuageVec)
|
||||
gv, _ := gaugeVec.(*promGaugeVec)
|
||||
gv.Set(666, "/users")
|
||||
r := testutil.ToFloat64(gv.gauge)
|
||||
assert.Equal(t, float64(666), r)
|
||||
|
||||
@@ -19,7 +19,7 @@ type (
|
||||
// A HistogramVec interface represents a histogram vector.
|
||||
HistogramVec interface {
|
||||
// Observe adds observation v to labels.
|
||||
Observe(v int64, lables ...string)
|
||||
Observe(v int64, labels ...string)
|
||||
close() bool
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ func Map(generate GenerateFunc, mapper MapFunc, opts ...Option) chan interface{}
|
||||
}
|
||||
|
||||
// MapReduce maps all elements generated from given generate func,
|
||||
// and reduces the output elemenets with given reducer.
|
||||
// and reduces the output elements with given reducer.
|
||||
func MapReduce(generate GenerateFunc, mapper MapperFunc, reducer ReducerFunc, opts ...Option) (interface{}, error) {
|
||||
source := buildSource(generate)
|
||||
return MapReduceWithSource(source, mapper, reducer, opts...)
|
||||
@@ -112,6 +112,12 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
|
||||
opts ...Option) (interface{}, error) {
|
||||
options := buildOptions(opts...)
|
||||
output := make(chan interface{})
|
||||
defer func() {
|
||||
for range output {
|
||||
panic("more than one element written in reducer")
|
||||
}
|
||||
}()
|
||||
|
||||
collector := make(chan interface{}, options.workers)
|
||||
done := syncx.NewDoneChan()
|
||||
writer := newGuardedWriter(output, done.Done())
|
||||
@@ -136,14 +142,16 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
drain(collector)
|
||||
|
||||
if r := recover(); r != nil {
|
||||
cancel(fmt.Errorf("%v", r))
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}()
|
||||
|
||||
reducer(collector, writer, cancel)
|
||||
drain(collector)
|
||||
}()
|
||||
|
||||
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 {
|
||||
_, err := MapReduce(generate, mapper, func(input <-chan interface{}, writer Writer, cancel func(error)) {
|
||||
reducer(input, cancel)
|
||||
drain(input)
|
||||
// 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.
|
||||
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) {
|
||||
var value uint32
|
||||
tests := []struct {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package proc
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package proc
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package proc
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package proc
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package proc
|
||||
@@ -14,5 +15,5 @@ func AddWrapUpListener(fn func()) func() {
|
||||
return fn
|
||||
}
|
||||
|
||||
func SetTimeoutToForceQuit(duration time.Duration) {
|
||||
func SetTimeToForceQuit(duration time.Duration) {
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
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
|
||||
|
||||
package proc
|
||||
@@ -12,6 +13,8 @@ import (
|
||||
|
||||
const timeFormat = "0102150405"
|
||||
|
||||
var done = make(chan struct{})
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
var profiler Stopper
|
||||
@@ -33,6 +36,13 @@ func init() {
|
||||
profiler = nil
|
||||
}
|
||||
case syscall.SIGTERM:
|
||||
select {
|
||||
case <-done:
|
||||
// already closed
|
||||
default:
|
||||
close(done)
|
||||
}
|
||||
|
||||
gracefulStop(signals)
|
||||
default:
|
||||
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())
|
||||
}
|
||||
@@ -7,10 +7,19 @@ import (
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/core/syncx"
|
||||
"github.com/tal-tech/go-zero/core/threading"
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
var (
|
||||
once sync.Once
|
||||
enabled syncx.AtomicBool
|
||||
)
|
||||
|
||||
// Enabled returns if prometheus is enabled.
|
||||
func Enabled() bool {
|
||||
return enabled.True()
|
||||
}
|
||||
|
||||
// StartAgent starts a prometheus agent.
|
||||
func StartAgent(c Config) {
|
||||
@@ -19,6 +28,7 @@ func StartAgent(c Config) {
|
||||
return
|
||||
}
|
||||
|
||||
enabled.Set(true)
|
||||
threading.GoSafe(func() {
|
||||
http.Handle(c.Path, promhttp.Handler())
|
||||
addr := fmt.Sprintf("%s:%d", c.Host, c.Port)
|
||||
|
||||
@@ -71,7 +71,7 @@ func NewQueue(producerFactory ProducerFactory, consumerFactory ConsumerFactory)
|
||||
return q
|
||||
}
|
||||
|
||||
// AddListener adds a litener to q.
|
||||
// AddListener adds a listener to q.
|
||||
func (q *Queue) AddListener(listener Listener) {
|
||||
q.listeners = append(q.listeners, listener)
|
||||
}
|
||||
|
||||
@@ -84,8 +84,7 @@ func (p *mockedProducer) Produce() (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
type mockedListener struct {
|
||||
}
|
||||
type mockedListener struct{}
|
||||
|
||||
func (l *mockedListener) OnPause() {
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package search
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
colon = ':'
|
||||
@@ -8,16 +11,16 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDupItem means adding duplicated item.
|
||||
ErrDupItem = errors.New("duplicated item")
|
||||
// ErrDupSlash means item is started with more than one slash.
|
||||
ErrDupSlash = errors.New("duplicated slash")
|
||||
// ErrEmptyItem means adding empty item.
|
||||
ErrEmptyItem = errors.New("empty item")
|
||||
// ErrInvalidState means 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 = errors.New("path should start with /")
|
||||
// errDupItem means adding duplicated item.
|
||||
errDupItem = errors.New("duplicated item")
|
||||
// errDupSlash means item is started with more than one slash.
|
||||
errDupSlash = errors.New("duplicated slash")
|
||||
// errEmptyItem means adding empty item.
|
||||
errEmptyItem = errors.New("empty item")
|
||||
// errInvalidState means 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 = errors.New("path should start with /")
|
||||
|
||||
// NotFound is used to hold the not found result.
|
||||
NotFound Result
|
||||
@@ -58,14 +61,22 @@ func NewTree() *Tree {
|
||||
// Add adds item to associate with route.
|
||||
func (t *Tree) Add(route string, item interface{}) error {
|
||||
if len(route) == 0 || route[0] != slash {
|
||||
return ErrNotFromRoot
|
||||
return errNotFromRoot
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -86,22 +97,22 @@ func (t *Tree) next(n *node, route string, result *Result) bool {
|
||||
}
|
||||
|
||||
for i := range route {
|
||||
if route[i] == slash {
|
||||
token := route[:i]
|
||||
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
|
||||
})
|
||||
if route[i] != slash {
|
||||
continue
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -141,7 +152,7 @@ func (nd *node) getChildren(route string) map[string]*node {
|
||||
func add(nd *node, route string, item interface{}) error {
|
||||
if len(route) == 0 {
|
||||
if nd.item != nil {
|
||||
return ErrDupItem
|
||||
return errDupItem
|
||||
}
|
||||
|
||||
nd.item = item
|
||||
@@ -149,31 +160,33 @@ func add(nd *node, route string, item interface{}) error {
|
||||
}
|
||||
|
||||
if route[0] == slash {
|
||||
return ErrDupSlash
|
||||
return errDupSlash
|
||||
}
|
||||
|
||||
for i := range route {
|
||||
if route[i] == slash {
|
||||
token := route[:i]
|
||||
children := nd.getChildren(token)
|
||||
if child, ok := children[token]; ok {
|
||||
if child != nil {
|
||||
return add(child, route[i+1:], item)
|
||||
}
|
||||
if route[i] != slash {
|
||||
continue
|
||||
}
|
||||
|
||||
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)
|
||||
children[token] = child
|
||||
return add(child, route[i+1:], item)
|
||||
return errInvalidState
|
||||
}
|
||||
|
||||
child := newNode(nil)
|
||||
children[token] = child
|
||||
return add(child, route[i+1:], item)
|
||||
}
|
||||
|
||||
children := nd.getChildren(route)
|
||||
if child, ok := children[route]; ok {
|
||||
if child.item != nil {
|
||||
return ErrDupItem
|
||||
return errDupItem
|
||||
}
|
||||
|
||||
child.item = item
|
||||
@@ -192,6 +205,14 @@ func addParam(result *Result, k, v string) {
|
||||
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 {
|
||||
if pat[0] == colon {
|
||||
return innerResult{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build debug
|
||||
// +build debug
|
||||
|
||||
package search
|
||||
|
||||
@@ -151,9 +151,9 @@ func TestAddDuplicate(t *testing.T) {
|
||||
err := tree.Add("/a/b", 1)
|
||||
assert.Nil(t, err)
|
||||
err = tree.Add("/a/b", 2)
|
||||
assert.Equal(t, ErrDupItem, err)
|
||||
assert.Error(t, errDupItem, err)
|
||||
err = tree.Add("/a/b/", 2)
|
||||
assert.Equal(t, ErrDupItem, err)
|
||||
assert.Error(t, errDupItem, err)
|
||||
}
|
||||
|
||||
func TestPlain(t *testing.T) {
|
||||
@@ -169,19 +169,19 @@ func TestPlain(t *testing.T) {
|
||||
func TestSearchWithDoubleSlashes(t *testing.T) {
|
||||
tree := NewTree()
|
||||
err := tree.Add("//a", 1)
|
||||
assert.Error(t, ErrDupSlash, err)
|
||||
assert.Error(t, errDupSlash, err)
|
||||
}
|
||||
|
||||
func TestSearchInvalidRoute(t *testing.T) {
|
||||
tree := NewTree()
|
||||
err := tree.Add("", 1)
|
||||
assert.Equal(t, ErrNotFromRoot, err)
|
||||
assert.Equal(t, errNotFromRoot, err)
|
||||
err = tree.Add("bad", 1)
|
||||
assert.Equal(t, ErrNotFromRoot, err)
|
||||
assert.Equal(t, errNotFromRoot, err)
|
||||
}
|
||||
|
||||
func TestSearchInvalidItem(t *testing.T) {
|
||||
tree := NewTree()
|
||||
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/prometheus"
|
||||
"github.com/tal-tech/go-zero/core/stat"
|
||||
"github.com/tal-tech/go-zero/core/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -14,6 +15,8 @@ const (
|
||||
DevMode = "dev"
|
||||
// TestMode means test mode.
|
||||
TestMode = "test"
|
||||
// RtMode means regression test mode.
|
||||
RtMode = "rt"
|
||||
// PreMode means pre-release mode.
|
||||
PreMode = "pre"
|
||||
// ProMode means production mode.
|
||||
@@ -27,6 +30,7 @@ type ServiceConf struct {
|
||||
Mode string `json:",default=pro,options=dev|test|rt|pre|pro"`
|
||||
MetricsUrl string `json:",optional"`
|
||||
Prometheus prometheus.Config `json:",optional"`
|
||||
Telemetry trace.Config `json:",optional"`
|
||||
}
|
||||
|
||||
// MustSetUp sets up the service, exits on error.
|
||||
@@ -47,6 +51,12 @@ func (sc ServiceConf) SetUp() error {
|
||||
|
||||
sc.initMode()
|
||||
prometheus.StartAgent(sc.Prometheus)
|
||||
|
||||
if len(sc.Telemetry.Name) == 0 {
|
||||
sc.Telemetry.Name = sc.Name
|
||||
}
|
||||
trace.StartAgent(sc.Telemetry)
|
||||
|
||||
if len(sc.MetricsUrl) > 0 {
|
||||
stat.SetReportWriter(stat.NewRemoteWriter(sc.MetricsUrl))
|
||||
}
|
||||
@@ -56,7 +66,7 @@ func (sc ServiceConf) SetUp() error {
|
||||
|
||||
func (sc ServiceConf) initMode() {
|
||||
switch sc.Mode {
|
||||
case DevMode, TestMode, PreMode:
|
||||
case DevMode, TestMode, RtMode, PreMode:
|
||||
load.Disable()
|
||||
stat.SetReporter(nil)
|
||||
}
|
||||
|
||||
@@ -95,8 +95,7 @@ func WithStarter(start Starter) Service {
|
||||
}
|
||||
|
||||
type (
|
||||
stopper struct {
|
||||
}
|
||||
stopper struct{}
|
||||
|
||||
startOnlyService struct {
|
||||
start func()
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestServiceGroup_WithStart(t *testing.T) {
|
||||
wait.Add(len(multipliers))
|
||||
group := NewServiceGroup()
|
||||
for _, multiplier := range multipliers {
|
||||
var mul = multiplier
|
||||
mul := multiplier
|
||||
group.Add(WithStart(func() {
|
||||
lock.Lock()
|
||||
want *= mul
|
||||
@@ -97,7 +97,7 @@ func TestServiceGroup_WithStarter(t *testing.T) {
|
||||
wait.Add(len(multipliers))
|
||||
group := NewServiceGroup()
|
||||
for _, multiplier := range multipliers {
|
||||
var mul = multiplier
|
||||
mul := multiplier
|
||||
group.Add(WithStarter(mockedStarter{
|
||||
fn: func() {
|
||||
lock.Lock()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package stat
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package stat
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package stat
|
||||
|
||||
@@ -22,7 +22,7 @@ var (
|
||||
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() {
|
||||
cpus, err := perCpuUsage()
|
||||
if err != nil {
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
)
|
||||
|
||||
func TestRefreshCpu(t *testing.T) {
|
||||
assert.True(t, RefreshCpu() >= 0)
|
||||
assert.NotPanics(t, func() {
|
||||
RefreshCpu()
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkRefreshCpu(b *testing.B) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package internal
|
||||
|
||||
@@ -38,7 +38,9 @@ func init() {
|
||||
atomic.StoreInt64(&cpuUsage, usage)
|
||||
})
|
||||
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.
|
||||
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 {
|
||||
if len(c) == 0 || TotalWeights(c) <= 0 {
|
||||
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 {
|
||||
var be errorx.BatchError
|
||||
|
||||
for _, key := range keys {
|
||||
if _, ok := mc.vals[key]; !ok {
|
||||
be.Add(mc.errNotFound)
|
||||
@@ -30,6 +31,7 @@ func (mc *mockedNode) Del(keys ...string) error {
|
||||
delete(mc.vals, key)
|
||||
}
|
||||
}
|
||||
|
||||
return be.Err()
|
||||
}
|
||||
|
||||
@@ -102,7 +104,7 @@ func TestCache_SetDel(t *testing.T) {
|
||||
Weight: 100,
|
||||
},
|
||||
}
|
||||
c := New(conf, syncx.NewSharedCalls(), NewStat("mock"), errPlaceholder)
|
||||
c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder)
|
||||
for i := 0; i < total; i++ {
|
||||
if i%2 == 0 {
|
||||
assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i))
|
||||
@@ -140,7 +142,7 @@ func TestCache_OneNode(t *testing.T) {
|
||||
Weight: 100,
|
||||
},
|
||||
}
|
||||
c := New(conf, syncx.NewSharedCalls(), NewStat("mock"), errPlaceholder)
|
||||
c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder)
|
||||
for i := 0; i < total; i++ {
|
||||
if i%2 == 0 {
|
||||
assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i))
|
||||
|
||||
31
core/stores/cache/cachenode.go
vendored
31
core/stores/cache/cachenode.go
vendored
@@ -29,7 +29,7 @@ type cacheNode struct {
|
||||
rds *redis.Redis
|
||||
expiry time.Duration
|
||||
notFoundExpiry time.Duration
|
||||
barrier syncx.SharedCalls
|
||||
barrier syncx.SingleFlight
|
||||
r *rand.Rand
|
||||
lock *sync.Mutex
|
||||
unstableExpiry mathx.Unstable
|
||||
@@ -43,7 +43,7 @@ type cacheNode struct {
|
||||
// st is used to stat the cache.
|
||||
// errNotFound defines the error that returned on cache not found.
|
||||
// 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 {
|
||||
o := newOptions(opts...)
|
||||
return cacheNode{
|
||||
@@ -59,21 +59,30 @@ func NewNode(rds *redis.Redis, barrier syncx.SharedCalls, st *Stat,
|
||||
}
|
||||
}
|
||||
|
||||
// DelCache deletes cached values with keys.
|
||||
// Del deletes cached values with keys.
|
||||
func (c cacheNode) Del(keys ...string) error {
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
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...)
|
||||
if len(keys) > 1 && c.rds.Type == redis.ClusterType {
|
||||
for _, key := range 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
|
||||
}
|
||||
|
||||
// GetCache gets the cache with key and fills into v.
|
||||
// Get gets the cache with key and fills into v.
|
||||
func (c cacheNode) Get(key string, v interface{}) error {
|
||||
err := c.doGetCache(key, v)
|
||||
if err == errPlaceholder {
|
||||
@@ -88,12 +97,12 @@ func (c cacheNode) IsNotFound(err error) bool {
|
||||
return err == c.errNotFound
|
||||
}
|
||||
|
||||
// SetCache sets the cache with key and v, using c.expiry.
|
||||
// Set sets the cache with key and v, using c.expiry.
|
||||
func (c cacheNode) Set(key string, v interface{}) error {
|
||||
return c.SetWithExpire(key, v, c.aroundDuration(c.expiry))
|
||||
}
|
||||
|
||||
// SetCacheWithExpire sets the cache with key and v, using given expire.
|
||||
// SetWithExpire sets the cache with key and v, using given expire.
|
||||
func (c cacheNode) SetWithExpire(key string, v interface{}, expire time.Duration) error {
|
||||
data, err := jsonx.Marshal(v)
|
||||
if err != nil {
|
||||
@@ -108,7 +117,7 @@ func (c cacheNode) String() string {
|
||||
return c.rds.Addr
|
||||
}
|
||||
|
||||
// TakeWithExpire takes the result from cache first, if not found,
|
||||
// Take takes the result from cache first, if not found,
|
||||
// query from DB and set cache using c.expiry, then return the result.
|
||||
func (c cacheNode) Take(v interface{}, key string, query func(v interface{}) error) error {
|
||||
return c.doTake(v, key, query, func(v interface{}) error {
|
||||
@@ -205,7 +214,7 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
|
||||
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)
|
||||
if err == nil {
|
||||
return nil
|
||||
|
||||
32
core/stores/cache/cachenode_test.go
vendored
32
core/stores/cache/cachenode_test.go
vendored
@@ -29,6 +29,7 @@ func init() {
|
||||
func TestCacheNode_DelCache(t *testing.T) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
store.Type = redis.ClusterType
|
||||
defer clean()
|
||||
|
||||
cn := cacheNode{
|
||||
@@ -49,13 +50,30 @@ func TestCacheNode_DelCache(t *testing.T) {
|
||||
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) {
|
||||
s, err := miniredis.Run()
|
||||
assert.Nil(t, err)
|
||||
defer s.Close()
|
||||
|
||||
cn := cacheNode{
|
||||
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
||||
rds: redis.New(s.Addr()),
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
@@ -78,7 +96,7 @@ func TestCacheNode_Take(t *testing.T) {
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
barrier: syncx.NewSharedCalls(),
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
@@ -105,7 +123,7 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
barrier: syncx.NewSharedCalls(),
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
@@ -129,7 +147,7 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
|
||||
assert.True(t, cn.IsNotFound(cn.Get("any", &str)))
|
||||
|
||||
store.Del("any")
|
||||
var errDummy = errors.New("dummy")
|
||||
errDummy := errors.New("dummy")
|
||||
err = cn.Take(&str, "any", func(v interface{}) error {
|
||||
return errDummy
|
||||
})
|
||||
@@ -144,7 +162,7 @@ func TestCacheNode_TakeWithExpire(t *testing.T) {
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
barrier: syncx.NewSharedCalls(),
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
@@ -171,7 +189,7 @@ func TestCacheNode_String(t *testing.T) {
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
barrier: syncx.NewSharedCalls(),
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
@@ -188,7 +206,7 @@ func TestCacheValueWithBigInt(t *testing.T) {
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
barrier: syncx.NewSharedCalls(),
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
// imports the driver.
|
||||
// imports the driver, don't remove this comment, golint requires.
|
||||
_ "github.com/ClickHouse/clickhouse-go"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ type (
|
||||
// Store interface represents a KV store.
|
||||
Store interface {
|
||||
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)
|
||||
Expire(key string, seconds int) error
|
||||
Expireat(key string, expireTime int64) error
|
||||
@@ -39,7 +39,7 @@ type (
|
||||
Llen(key string) (int, error)
|
||||
Lpop(key string) (string, 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)
|
||||
Persist(key string) (bool, error)
|
||||
Pfadd(key string, values ...interface{}) (bool, error)
|
||||
@@ -47,7 +47,7 @@ type (
|
||||
Rpush(key string, values ...interface{}) (int, error)
|
||||
Sadd(key string, values ...interface{}) (int, error)
|
||||
Scard(key string) (int64, error)
|
||||
Set(key string, value string) error
|
||||
Set(key, value string) error
|
||||
Setex(key, value string, seconds int) error
|
||||
Setnx(key, value string) (bool, error)
|
||||
SetnxEx(key, value string, seconds int) (bool, error)
|
||||
@@ -74,7 +74,7 @@ type (
|
||||
Zrevrange(key string, start, stop int64) ([]string, error)
|
||||
ZrevrangebyscoreWithScores(key string, start, stop int64) ([]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)
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ func (cs clusterStore) Del(keys ...string) (int, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -321,7 +321,7 @@ func (cs clusterStore) Lpush(key string, values ...interface{}) (int, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -393,7 +393,7 @@ func (cs clusterStore) Scard(key string) (int64, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -648,7 +648,7 @@ func (cs clusterStore) Zrevrank(key, field string) (int64, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user