Compare commits

...

484 Commits

Author SHA1 Message Date
Kevin Wan
731b3ebf6f Update readme-cn.md 2022-08-07 16:11:43 +08:00
Kevin Wan
1e0f94ba86 Update readme.md 2022-08-07 16:11:27 +08:00
Kevin Wan
a987512c7b feat: more meaningful error messages, close body on httpc requests (#2238)
* feat: more meaningful error messages, close body on httpc requests

* fix: test failure
2022-08-07 16:09:54 +08:00
Kevin Wan
c1c7584de1 Update readme.md 2022-08-07 16:08:16 +08:00
Kevin Wan
98b9a25cc7 Update readme.md 2022-08-07 11:13:34 +08:00
Kevin Wan
a8305def3d docs: update docs for gateway (#2236) 2022-08-07 11:11:46 +08:00
Kevin Wan
d20d8324e7 fix: #2216 (#2235) 2022-08-06 17:48:59 +08:00
Kevin Wan
c638fce31c chore: renaming configs (#2234) 2022-08-06 16:32:12 +08:00
dependabot[bot]
34294702b0 chore(deps): bump go.mongodb.org/mongo-driver from 1.10.0 to 1.10.1 (#2225) 2022-08-04 20:25:56 +08:00
chen quan
4fad067a0e fix(logx): need to wait for the first caller to complete the execution. (#2213) 2022-08-03 23:59:39 +08:00
safeoy
3f3c811e08 fix: fix comment typo (#2220)
Use an instead of 'a' if the following word starts with a vowel sound, e.g. 'an in-memory cache'.
2022-08-03 23:57:49 +08:00
dependabot[bot]
dbdbb68676 chore(deps): bump go.opentelemetry.io/otel/exporters/zipkin (#2222)
Bumps [go.opentelemetry.io/otel/exporters/zipkin](https://github.com/open-telemetry/opentelemetry-go) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/exporters/zipkin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2022-08-03 23:56:22 +08:00
dependabot[bot]
83772344b0 chore(deps): bump go.opentelemetry.io/otel/exporters/jaeger (#2223)
Bumps [go.opentelemetry.io/otel/exporters/jaeger](https://github.com/open-telemetry/opentelemetry-go) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/exporters/jaeger
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-03 23:41:46 +08:00
Kevin Wan
49367f1713 fix: handling rpc error on gateway (#2212) 2022-08-01 00:01:24 +08:00
Kevin Wan
91b8effb24 chore: refactor redislock (#2210)
* chore: refactor redislock

* chore: add more tests
2022-07-30 19:46:10 +08:00
cong
4879d4dfcd feat(redislock): support set context (#2208)
* feat(redislock): support set context

* chore: fix test
2022-07-30 18:38:36 +08:00
dependabot[bot]
b18479dd43 chore(deps): bump google.golang.org/protobuf from 1.28.0 to 1.28.1 (#2205)
Bumps [google.golang.org/protobuf](https://github.com/protocolbuffers/protobuf-go) from 1.28.0 to 1.28.1.
- [Release notes](https://github.com/protocolbuffers/protobuf-go/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf-go/blob/master/release.bash)
- [Commits](https://github.com/protocolbuffers/protobuf-go/compare/v1.28.0...v1.28.1)

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-29 23:53:35 +08:00
Kevin Wan
5cd9229986 fix: only setup logx once (#2188)
* fix: only setup logx once

* fix: test failure

* chore: not reset logging level in reset

* chore: refactoring
2022-07-28 22:08:48 +08:00
施国鹏
3d38d36605 fix: logx test foo (#2144)
constant testlog "Stay hungry, stay foolish." contains foo(foolish), changed to foo1
2022-07-28 21:29:56 +08:00
chen quan
003adae51f fix(httpc): fix typo errors (#2189) 2022-07-27 09:11:15 +08:00
马守越
5348375b99 support mulitple protoset files (#2190) 2022-07-27 09:10:23 +08:00
benqi
5d7919a9f5 fix: remove invalid log fields in notLoggingContentMethods (#2187) 2022-07-24 22:18:04 +08:00
Kevin Wan
9b334b5428 chore: let logx.SetWriter can be called anytime (#2186) 2022-07-24 14:15:57 +08:00
fisnone
685d14e662 fix:duplicate route check (#2154)
Co-authored-by: 黄志荣 <huangzhirong@shuinfo.com>
2022-07-24 10:48:50 +08:00
benqi
edbf1a3b63 fix: fix switch doesn't work bug (#2183) 2022-07-23 12:15:37 +08:00
Kevin Wan
92145b56dc chore: refactoring (#2182) 2022-07-22 23:16:38 +08:00
Kevin Wan
34eb3fc12e chore: refactoring logx (#2181) 2022-07-22 22:28:01 +08:00
SgtDaJim
101304be53 feat: logx support logs rotation based on size limitation. (#1652) (#2167)
* feat: logx support logs rotation based on size limitation. (#1652)

implementation of #1652

Totally compatible with the old logx.LogConf. No effect if users do not change their options.

* feat: logx support logs rotation based on size limitation. (#1652)

implementation of #1652

Totally compatible with the old logx.LogConf. No effect if users do not change their options.

* feat: logx support logs rotation based on size limitation. (#1652)

implementation of #1652

Totally compatible with the old logx.LogConf. No effect if users do not change their options.

* feat: logx support logs rotation based on size limitation. (#1652)

implementation of #1652

Totally compatible with the old logx.LogConf. No effect if users do not change their options.
2022-07-22 21:13:10 +08:00
anqiansong
f630bc735b Update goctl version (#2178) 2022-07-21 15:29:50 +08:00
anqiansong
ca3c687f1c feat: Support for multiple rpc service generation and rpc grouping (#1972)
* Add group & compatible flag

* Add group & compatible flag

* Support for multiple rpc service generation and rpc grouping

* Support for multiple rpc service generation and rpc grouping

* Format code

* Format code

* Add comments

* Fix unit test

* Refactor function name

* Add example & Update grpc readme

* go mod tidy

* update mod

* update mod
2022-07-21 12:47:46 +08:00
anqiansong
1b51d0ce82 fix: fix #2102, #2108 (#2131)
* g4 code generation

* Update grammar

* g4 code generation

* fix #2108

* fix #2102

* Remove comments
2022-07-20 22:49:41 +08:00
Kevin Wan
d9218e1551 Update readme-cn.md
add go-zero users.
2022-07-20 09:40:32 +08:00
anqiansong
9c448c64ef Update api template (#2172) 2022-07-19 23:49:20 +08:00
杨圆建
bc85eaa9b1 fix: goctl genhandler duplicate rest/httpx & goctl genhandler template support custom import httpx package (#2152) 2022-07-19 23:24:47 +08:00
Kevin Wan
2a6f801978 chore: refactoring mapping name (#2168) 2022-07-19 09:58:46 +08:00
Kevin Wan
8d567b5508 feat: support customized header to metadata processor (#2162)
* chore: add more tests

* feat: support customized header processor
2022-07-17 23:21:19 +08:00
Kevin Wan
0dd2768d09 feat: support google.api.http in gateway (#2161) 2022-07-17 14:57:25 +08:00
Kevin Wan
4324ddc024 feat: set content-type to application/json (#2160) 2022-07-17 13:52:46 +08:00
Kevin Wan
557383fbbf feat: verify RpcPath on startup (#2159)
* feat: verify RpcPath on startup

* feat: support http header Grpc-Timeout
2022-07-17 12:37:23 +08:00
Kevin Wan
b206dd28a3 feat: support form values in gateway (#2158) 2022-07-16 23:40:53 +08:00
Kevin Wan
453fa309b1 feat: export gateway.Server to let users add middlewares (#2157) 2022-07-16 22:59:25 +08:00
Kevin Wan
4d7dae9cea Update readme-cn.md 2022-07-16 14:53:00 +08:00
Kevin Wan
d228b9038d Update readme.md 2022-07-16 14:52:45 +08:00
Kevin Wan
13477238a3 feat: restful -> grpc gateway (#2155)
* Revert "chore: remove unimplemented gateway (#2139)"

This reverts commit d70e73ec66.

* feat: working gateway

* feat: use mr to make it faster

* feat: working gateway

* chore: add comments

* feat: support protoset besides reflection

* feat: support zrpc client conf

* docs: update readme

* feat: support grpc-metadata- header to gateway- header conversion

* chore: add docs
2022-07-16 14:11:34 +08:00
dependabot[bot]
95a574e9e9 chore(deps): bump google.golang.org/grpc from 1.47.0 to 1.48.0 (#2147)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.47.0 to 1.48.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.47.0...v1.48.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-15 10:02:25 +08:00
dependabot[bot]
453100e0e2 chore(deps): bump go.mongodb.org/mongo-driver from 1.9.1 to 1.10.0 (#2150)
Bumps [go.mongodb.org/mongo-driver](https://github.com/mongodb/mongo-go-driver) from 1.9.1 to 1.10.0.
- [Release notes](https://github.com/mongodb/mongo-go-driver/releases)
- [Commits](https://github.com/mongodb/mongo-go-driver/compare/v1.9.1...v1.10.0)

---
updated-dependencies:
- dependency-name: go.mongodb.org/mongo-driver
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-15 10:02:08 +08:00
Kevin Wan
d70e73ec66 chore: remove unimplemented gateway (#2139) 2022-07-13 21:55:19 +08:00
Kevin Wan
300b124e42 docs: update goctl readme (#2136) 2022-07-12 23:16:40 +08:00
Kevin Wan
3bad043413 chore: refactor (#2130) 2022-07-11 23:50:50 +08:00
Kevin Wan
23f34234d0 chore: add more tests (#2129) 2022-07-11 23:32:57 +08:00
虫子樱桃
d71b3c841f feat:Add Routes method for server (#2125)
Co-authored-by: czyt <czyt@w.cn>
2022-07-11 23:23:38 +08:00
Kevin Wan
24787a946b feat: support logx.WithFields (#2128) 2022-07-11 23:19:26 +08:00
Richard Yi
6e50c87dca fix: generated sql query fields do not match template (#2004)
* Fix typo

* Match generated sql query fields with template
2022-07-11 23:06:00 +08:00
Kevin Wan
e672b3f8e1 feat: add Wrap and Wrapf in errorx (#2126) 2022-07-11 23:04:38 +08:00
Kevin Wan
1c09db6d5d chore: coding style (#2120) 2022-07-10 11:05:21 +08:00
LeeDF
96acf1f5a6 fix goctl rpc protoc strings.EqualFold Service.Name GoPackage (#2046) 2022-07-09 23:40:32 +08:00
Kevin Wan
97a171441d chore: remove blank lines (#2117)
* chore: remove blank lines

* chore: refactor
2022-07-09 15:59:25 +08:00
虫子樱桃
725e6056e1 feat:goctl model mongo add easy flag for easy declare. (#2073)
* fix:typo in readme.md

* feat:`goctl model mongo ` add `easy` flag to generate code with Auto generated CollectionName for easy declare.

* fix:`goctl api doc ` when referenced api file contains no route,will generate an empty markdown file.

* code: adjust code.

Co-authored-by: 虫子樱桃 <czyt@w.cn>
2022-07-09 15:34:01 +08:00
Kevin Wan
1410f7dc20 fix #2109 (#2116) 2022-07-09 15:05:59 +08:00
warrior
8afe68f3f1 refactor:remove duplicate codes (#2101)
Co-authored-by: 沈四胜 <sisheng.shen@71360.com>
2022-07-09 14:56:49 +08:00
dependabot[bot]
74c41e8c5e chore(deps): bump go.opentelemetry.io/otel/exporters/jaeger (#2115)
Bumps [go.opentelemetry.io/otel/exporters/jaeger](https://github.com/open-telemetry/opentelemetry-go) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/exporters/jaeger
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2022-07-09 14:42:16 +08:00
Minghong Fang
48f7e01158 feat: add method to jsonx (#2049) 2022-07-09 14:20:53 +08:00
dependabot[bot]
f6f6ee5c8c chore(deps): bump go.opentelemetry.io/otel/exporters/zipkin (#2112)
Bumps [go.opentelemetry.io/otel/exporters/zipkin](https://github.com/open-telemetry/opentelemetry-go) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/exporters/zipkin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-09 14:16:08 +08:00
Kevin Wan
b364c54940 chore: update goctl version to 1.3.9 (#2111) 2022-07-08 22:31:50 +08:00
Kevin Wan
e0e3f97c7c chore: refactor (#2087) 2022-07-02 14:03:11 +08:00
taobig
6a2d6786c6 remove legacy code (#2086) 2022-07-02 00:22:42 +08:00
Kevin Wan
18035bd4d4 chore: refactor (#2085) 2022-07-02 00:15:38 +08:00
家福
f3b8fef34f fix: type matching supports string to int (#2038)
* fix: type matching supports string to int

* feat: type matching supports string to int

Co-authored-by: 程家福 <chengjiafu@uniontech.com>
2022-07-01 23:21:31 +08:00
givemeafish
6a4885ba64 fix concurrent map writes (#2079)
Co-authored-by: wero <wero@werodeMacBook-Pro.local>
2022-07-01 23:07:25 +08:00
Kevin Wan
f2cef2b963 Update readme-cn.md 2022-07-01 23:00:19 +08:00
taobig
bfd0869ee2 remove legacy code (#2084) 2022-07-01 22:41:16 +08:00
Kevin Wan
4e26e0407e Update readme.md 2022-07-01 22:17:05 +08:00
wxc
d200ba4a7b feat: CompareAndSwapInt32 may be better than AddInt32 (#2077) 2022-07-01 12:41:32 +08:00
dependabot[bot]
ce7e2a2a9a chore(deps): bump github.com/pelletier/go-toml/v2 from 2.0.1 to 2.0.2 (#2072)
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.1...v2.0.2)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-30 23:27:53 +08:00
taobig
c92400ead2 fix 当表有唯一键时,update()的形参和实参不一致 (#2010) 2022-06-30 23:25:54 +08:00
dependabot[bot]
0b109c1954 chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.4.1 to 4.4.2 (#2066)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.4.1 to 4.4.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.4.1...v4.4.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-30 00:21:08 +08:00
dependabot[bot]
d42979f705 chore(deps): bump github.com/stretchr/testify from 1.7.2 to 1.8.0 (#2068)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.2 to 1.8.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.2...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-29 22:08:01 +08:00
dependabot[bot]
29d81381c1 chore(deps): bump github.com/alicebob/miniredis/v2 from 2.21.0 to 2.22.0 (#2067)
Bumps [github.com/alicebob/miniredis/v2](https://github.com/alicebob/miniredis) from 2.21.0 to 2.22.0.
- [Release notes](https://github.com/alicebob/miniredis/releases)
- [Changelog](https://github.com/alicebob/miniredis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/alicebob/miniredis/compare/v2.21.0...v2.22.0)

---
updated-dependencies:
- dependency-name: github.com/alicebob/miniredis/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-29 21:53:02 +08:00
dependabot[bot]
89f6c97097 chore(deps): bump github.com/ClickHouse/clickhouse-go/v2 (#2064)
Bumps [github.com/ClickHouse/clickhouse-go/v2](https://github.com/ClickHouse/clickhouse-go) from 2.0.15 to 2.2.0.
- [Release notes](https://github.com/ClickHouse/clickhouse-go/releases)
- [Commits](https://github.com/ClickHouse/clickhouse-go/compare/v2.0.15...v2.2.0)

---
updated-dependencies:
- dependency-name: github.com/ClickHouse/clickhouse-go/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-29 21:52:07 +08:00
Kevin Wan
ff6f109065 Create dependabot.yml
add dependabot.
2022-06-29 21:38:47 +08:00
Zhang.Y
7da77302f4 fix: \u003cnil\u003e log output when http server shutdown. (#2055) 2022-06-29 21:35:01 +08:00
虫子樱桃
76086fc717 fix:typo in readme.md (#2061)
Co-authored-by: 虫子樱桃 <czyt@w.cn>
2022-06-29 19:38:23 +08:00
Kevin Wan
555c4ecd1a fix: quickstart wrong package when go.mod exists in parent dir (#2048)
* chore: fix typo

* fix: quickstart in dir with go.mod

* fix: runner failed

* chore: refine code

* chore: simplify quickstart mono
2022-06-26 22:37:15 +08:00
lord63
630dfa0887 [ci skip] Fix dead doc link (#2047) 2022-06-25 11:18:47 +08:00
Kevin Wan
38cd7b7df0 chore: remove lifecycle preStop because sh not exist in scratch (#2042) 2022-06-24 21:30:07 +08:00
Kevin Wan
9148f8df2a Update readme-cn.md 2022-06-24 20:22:35 +08:00
Kevin Wan
13f051d0e5 Update readme-cn.md 2022-06-22 22:01:00 +08:00
anqiansong
93b3f5030f chore: Add command desc & color commands (#2013)
* Add link & Color sub-commands

* Color sub-commands for unix-like OS

* Remove useless code

* Remove redundant dependency
2022-06-21 20:21:38 +08:00
anqiansong
b44e8f5c75 fix #1977 (#2034) 2022-06-21 20:01:42 +08:00
Kevin Wan
b9eb03e9a9 Update readme.md 2022-06-19 20:50:55 +08:00
Kevin Wan
86b531406b Update readme.md 2022-06-19 20:48:21 +08:00
Kevin Wan
47c49de94e feat: rest.WithChain to replace builtin middlewares (#2033)
* feat: rest.WithChain to replace builtin middlewares

* chore: add comments

* chore: refine code
2022-06-19 17:41:33 +08:00
Kevin Wan
50f16e2892 Update readme-cn.md 2022-06-19 14:22:29 +08:00
Kevin Wan
018ca82048 chore: refactor to simplify disabling builtin middlewares (#2031)
* chore: refactor to simplify disabling builtin middlewares

* chore: rename methods
2022-06-18 20:16:34 +08:00
magickeha
6976ba7e13 add user middleware chain function (#1913)
* add user middleware chain function

* fix staticcheck SA4006

* chang code Implementation style

Co-authored-by: kemq1 <kemq1@spdb.com.cn>
2022-06-18 18:45:47 +08:00
anqiansong
9b6e4c440c Add fig (#2008)
Co-authored-by: SH00414ml <sh00414ml@SH00414mldeMacBook-Pro.local>
2022-06-18 18:34:48 +08:00
Kevin Wan
9eea311a4d feat: support build Dockerfile from current dir (#2021) 2022-06-18 18:32:07 +08:00
chen quan
86d70317bf chore: upgrade action version (#2027) 2022-06-17 19:55:19 +08:00
chen quan
6518eb10b3 feat: add trace in httpc (#2011) 2022-06-17 15:01:14 +08:00
Kevin Wan
0147d7a9d1 Update readme-cn.md 2022-06-14 08:23:46 +08:00
Kevin Wan
1b2b7647d6 chore: coding style (#2012) 2022-06-14 07:25:54 +08:00
Atlan
af6d37c33d fix: 修复 clientinterceptors/tracinginterceptor.go 显示接受消息字节为0 (#2003) 2022-06-14 00:11:10 +08:00
Kevin Wan
3da5c5f530 Update readme.md 2022-06-13 19:39:59 +08:00
Kevin Wan
1694a92db0 Update readme.md 2022-06-13 19:35:51 +08:00
anqiansong
c27e00b45c feat: Replace mongo package with monc & mon (#2002)
* Replace mongo package with monc & mon

* Add terminal whitespace

* format code
2022-06-12 23:02:34 +08:00
Kevin Wan
ed1c937998 feat: convert grpc errors to http status codes (#1997)
* feat: convert grpc errors to http status codes

* chore: circuit break include unimplemented grpc error

* chore: add reference link in comments
2022-06-11 23:07:26 +08:00
Kevin Wan
db9a1f3e27 chore: rename methods (#1998) 2022-06-11 12:34:10 +08:00
马守越
392a390a3f periodlimit new function TakeWithContext (#1983)
Co-authored-by: mashouyue's m1max <mashouyue@toowow.cn>
2022-06-11 12:07:57 +08:00
Gaffey
2a900e1795 typo: add type keyword (#1992) 2022-06-11 11:46:50 +08:00
swliao425
0f5d8c6be3 feat: add 'imagePullPolicy' parameter for 'goctl kube deploy' (#1996) 2022-06-11 09:49:14 +08:00
MarkJoyMa
f2caf9237a fix goctl api clone template fail (#1990) 2022-06-09 23:35:03 +08:00
Kevin Wan
2f0e4e3ebf chore: update dependencies (#1985) 2022-06-09 23:34:06 +08:00
Kevin Wan
2c6b422f6b chore: update goctl version to 1.3.8 (#1981) 2022-06-06 20:07:39 +08:00
Kevin Wan
4d34998338 fix: generate bad Dockerfile on given dir (#1980) 2022-06-06 19:50:54 +08:00
anqiansong
8be47b9c99 Fix pg subcommand level error (#1979) 2022-06-06 19:42:31 +08:00
Kevin Wan
1d95e95cf8 chore: make methods consistent in signatures (#1971)
* chore: make methods consistent in signatures

* test: fix fails
2022-06-05 12:56:13 +08:00
taobig
3fa8c5940d fix: The validation of tag "options" is not working with int/uint type (#1969) 2022-06-05 11:51:43 +08:00
Kevin Wan
c44edd7cac test: fix fails (#1970) 2022-06-05 11:51:21 +08:00
Kevin Wan
af05219b70 test: make tests stable (#1968)
* test: make tests stable

* test: fix fails
2022-06-04 23:46:29 +08:00
Kevin Wan
f366e1d936 chore: make print pretty (#1967) 2022-06-04 19:53:22 +08:00
Kevin Wan
6c94e4652e chore: better mongo logs (#1965)
* chore: better mongo logs

* chore: add comments
2022-06-04 16:11:31 +08:00
kevin
edfaa6d906 🐞 fix: fixed typo (#1916)
Co-authored-by: kevinzhang <kevinzhang@moonton.com>
2022-06-04 14:50:06 +08:00
Kevin Wan
b6b96d9dad feat: print routes (#1964)
* feat: print rest routes

* feat: print rest routes
2022-06-04 13:26:14 +08:00
Kevin Wan
87800419f5 chore: update dependencies (#1963) 2022-06-04 12:31:59 +08:00
Kevin Wan
50a5fb7715 Update readme-cn.md 2022-06-03 23:03:31 +08:00
Kevin Wan
aa8f07d064 Update readme.md 2022-06-03 23:02:54 +08:00
Kevin Wan
7868bdf660 Chore/goctl version (#1962)
* chore: update version to v1.3.7

* docs: update migrate versions

* chore: remove debug prints

* chore: remove debug prints
2022-06-03 20:46:21 +08:00
Kevin Wan
46078e716d chore: update version (#1961) 2022-06-03 20:08:29 +08:00
Kevin Wan
bb33a20bc8 Update readme-cn.md 2022-06-03 19:18:58 +08:00
Kevin Wan
5536473a08 Update readme.md 2022-06-03 19:18:07 +08:00
Kevin Wan
323b35ed2d Update readme.md
update docs.
2022-06-03 19:15:34 +08:00
Kevin Wan
30958a91f7 docs: add docs for logx (#1960) 2022-06-03 19:11:06 +08:00
Kevin Wan
b94b68a427 chore: refactoring mapping string to slice (#1959) 2022-06-03 10:49:22 +08:00
家福
07145b210e fix: panic on convert to string on fillSliceFromString() (#1951)
* Update unmarshaler.go

 fix: 修复fillSliceFromString()方法中mapValue 强转string后的panic 错误

* test: 增加单元测试

增加单元测试

* Update unmarshaler_test.go
2022-06-03 00:27:48 +08:00
Kevin Wan
321a20add6 chore: update roadmap (#1948) 2022-06-02 09:28:29 +08:00
kunyu
65098d4737 Delete duplicated crash recover logic. (#1950)
* Update statinterceptor.go

* Update statinterceptor_test.go
2022-06-01 22:53:05 +08:00
Kevin Wan
35425f6164 Update readme-cn.md 2022-06-01 12:34:13 +08:00
Kevin Wan
a0060ff81b Update readme-cn.md 2022-05-31 10:05:59 +08:00
Kevin Wan
289a325757 chore: refine docker for better compatible with package main (#1944)
* chore: refine docker for better compatible with package main

* chore: default to current dir on goctl docker command
2022-05-30 13:26:58 +08:00
Kevin Wan
3fbe0f87b7 Update readme-cn.md 2022-05-28 18:54:45 +08:00
Kevin Wan
ea98d210fd Update readme-cn.md 2022-05-28 14:40:44 +08:00
Kevin Wan
b9bc1fdcf8 Update readme.md 2022-05-28 14:39:25 +08:00
Kevin Wan
6dc570bcd7 Update readme-cn.md 2022-05-28 14:36:13 +08:00
Kevin Wan
e21997f0d7 Update readme.md 2022-05-28 14:31:07 +08:00
Kevin Wan
92c0b7c3c5 Update readme-cn.md 2022-05-27 18:45:32 +08:00
vic
6d3ed98744 优化代码 (#1939) 2022-05-27 18:36:18 +08:00
NoTryNoSuccess
fb519fa547 core/mr:a little optimization for collector initialization in ForEach function (#1937)
Co-authored-by: notrynosuccess <daihongshan@gmail.com>
2022-05-27 17:19:40 +08:00
chen quan
e9501c3fb3 chore(action): simplified release configuration (#1935) 2022-05-27 16:31:05 +08:00
chen quan
fd12659729 chore: add release action to auto build binaries (#1884)
* chore: add release action to auto build binaries

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* fix: test bugs

Signed-off-by: chenquan <chenquan.dev@gmail.com>
2022-05-25 23:42:24 +08:00
Kevin Wan
72ebbb9774 feat: update docker alpine package mirror (#1924)
* feat: update docker alpine package mirror

* chore: format code
2022-05-23 09:13:21 +08:00
anqiansong
f1fdd55b38 Support built-in shorthand flags (#1925) 2022-05-23 09:13:12 +08:00
anqiansong
58787746db fix: Useless delete cache logic in update (#1923)
* Fix bug: useless delete cache logic in update

* Format code
2022-05-23 09:12:06 +08:00
Kevin Wan
ca88b69d24 feat: set default connection idle time for grpc servers (#1922)
* feat: set default connection idle time for grpc servers

* feat: add grpc health check
2022-05-21 19:38:27 +08:00
Kevin Wan
6b1e15cab1 chore: update k8s.io/client-go for security reason, go is upgrade to 1.16 (#1912)
* chore: fix jwt dependency security issue

* chore: update clickhouse driver

* chore: fix a security issue

* chore: update dependencies
2022-05-21 14:34:01 +08:00
Kevin Wan
6f86e5bff8 Update readme-cn.md 2022-05-20 19:13:49 +08:00
Kevin Wan
3f492df74e Update readme-cn.md 2022-05-17 23:23:48 +08:00
anqiansong
5e7b1f6bfe Fix process blocking problem during check (#1911) 2022-05-17 09:42:18 +08:00
Kevin Wan
e80a64fa67 feat: support WithStreamClientInterceptor for zrpc clients (#1907)
* feat: support WithStreamClientInterceptor for zrpc clients

* fix: data race
2022-05-14 19:58:17 +08:00
Kevin Wan
95282edb78 Update FUNDING.yml
update sponsor
2022-05-14 17:29:26 +08:00
Kevin Wan
7b82eda993 chore: use get for quickstart, plain logs for easy understanding (#1905) 2022-05-14 17:01:37 +08:00
Kevin Wan
5d09cd0e7c use goproxy properly, remove files (#1903) 2022-05-14 16:00:20 +08:00
Kevin Wan
1e717f9f5c feat: add toml config (#1899) 2022-05-13 23:17:43 +08:00
Kevin Wan
c6e2b4a43a chore: coding style for quickstart (#1902) 2022-05-13 23:10:55 +08:00
chen quan
e567a0c718 refactor: refactor trace in redis & sql & mongo (#1865)
* refactor: refactor tracing in redis & sql & mongo

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* fix: fix some tests

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: add missing content

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: adjust `log` and `return`

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: reformat code

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: reformat code

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: reformat code

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: simpler span name

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: fix a bug

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* refactor: fix a bug

Signed-off-by: chenquan <chenquan.dev@gmail.com>
2022-05-13 12:32:34 +08:00
anqiansong
52f060caae feat: Add goctl quickstart (#1889)
* Add goctl quickstart

* Format code

* Format code
2022-05-13 12:23:24 +08:00
anqiansong
f486685e99 Fix code generation (#1897) 2022-05-13 00:16:17 +08:00
过客龙门
3ae874d75d fix ts tpl (#1879) 2022-05-11 23:45:32 +08:00
Kevin Wan
c58eb13328 Update readme-cn.md
update logo
2022-05-11 23:33:54 +08:00
givemeafish
14ca39bc86 fix:tools/goctl/rpc/generator/template_test.go file has wrong parameters (#1882) 2022-05-11 23:24:34 +08:00
Kevin Wan
3ea8a2d4b6 Update readme-cn.md 2022-05-11 18:19:00 +08:00
Kevin Wan
6d2b9fd904 chore: improve codecov (#1878) 2022-05-08 13:17:48 +08:00
Kevin Wan
5451d96a81 chore: update some logs (#1875) 2022-05-07 23:34:55 +08:00
Kevin Wan
69c2bad410 feat: logx with color (#1872)
* feat: logx with color

* chore: update logs

* fix test error

* chore: change colors of http codes

* chore: add comments

* chore: use faith/color instead of ascii code color

* chore: update colors

* chore: update colors

* chore: fix duplicated slowcall text

* chore: remove slowcall colors
2022-05-07 23:22:39 +08:00
anqiansong
5383e29ce6 feat: Replace cli to cobra (#1855)
* Replace cli

* Replace cli

* Replace cli

* Format code

* Add compare case

* Add compare case

* Add compare case

* Support go style flag

* Support go style flag

* Add test case
2022-05-07 15:40:11 +08:00
Kevin Wan
51472004a3 Update readme.md 2022-05-07 10:11:21 +08:00
Kevin Wan
caf5b7b1f1 Update readme-cn.md 2022-05-07 10:10:44 +08:00
Kevin Wan
bef9aa55e6 Update readme.md 2022-05-07 10:08:25 +08:00
Kevin Wan
d0a59b13a6 chore: fix deprecated usages (#1871)
* add conf documents

* chore: use {} instead of () for environment variables

* chore: fix deprecated usages

* chore: fix unstable tests

* chore: show stack on github actions
2022-05-06 15:13:46 +08:00
Kevin Wan
469e62067c add conf documents (#1869)
* add conf documents

* chore: use {} instead of () for environment variables
2022-05-06 11:05:06 +08:00
Kevin Wan
a36d58aac9 fix time, duration, slice types on logx.Field (#1868)
* chore: refine tests

* fix #1866
2022-05-05 23:37:18 +08:00
Kevin Wan
aa5118c2aa chore: refine tests (#1864) 2022-05-04 17:52:58 +08:00
Kevin Wan
974ba5c9aa test: add codecov (#1863) 2022-05-04 16:19:51 +08:00
Kevin Wan
ec1de4f48d test: add codecov (#1861)
* test: add codecov

* test: add codecov
2022-05-03 21:22:15 +08:00
Kevin Wan
bab72b7630 chore: use time.Now() instead of timex.Time() because go optimized it (#1860) 2022-05-03 19:51:47 +08:00
Kevin Wan
ac321fc146 feat: add fields with logx methods, support using third party logging libs. (#1847)
* backup

* simplify

* chore: remove unused pool

* chore: fix lint errors

* chore: use strings.Builder instead of bytes.Buffer

* test: add more tests

* chore: fix reviewdog

* test: fix data race

* feat: make logger customizable

* chore: fix reviewdog

* test: fix fails

* chore: fix set writer twice

* chore: use context instead of golang.org context

* chore: specify uint32 for level types
2022-05-03 17:34:26 +08:00
全自动盒子
ae2c76765c fix typo (#1857) 2022-05-03 16:25:13 +08:00
Kevin Wan
f21970c117 test: add more tests (#1856) 2022-05-02 21:24:20 +08:00
Kevin Wan
d0a58d1f2d docs: update readme (#1849) 2022-05-01 12:48:47 +08:00
Kevin Wan
3bbc90ec24 refactor: move json related header vars to internal (#1840)
* refactor: move json related header vars to internal

* refactor: use header.ContentType
2022-04-28 15:12:04 +08:00
Kevin Wan
cef83efd4e fix #1838 (#1839) 2022-04-28 11:25:26 +08:00
anqiansong
cc09ab2aba feat: Support model code generation for multi tables (#1836)
* Support model code generation for multi tables

* Format code

* Format code

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-28 10:01:04 +08:00
Kevin Wan
f7a60cdc24 fix: remove deprecated dependencies (#1837)
* fix: remove deprecated dependencies

* backup

* fix test error
2022-04-27 21:34:54 +08:00
Kevin Wan
c3a49ece8d Update readme-cn.md
add go-zero users.
2022-04-27 13:50:54 +08:00
Kevin Wan
1a38eddffe refactor: simplify the code (#1835) 2022-04-27 10:44:24 +08:00
Kevin Wan
5bcee4cf7c fix #1806 (#1833)
* fix #1806

* chore: refine error text
2022-04-27 00:01:31 +08:00
Kevin Wan
5c9fae7e62 feat: support sub domain for cors (#1827) 2022-04-25 21:56:59 +08:00
Kevin Wan
ec3e02624c feat: upgrade grpc to 1.46, and remove the deprecated grpc.WithBalancerName (#1820) 2022-04-24 22:42:40 +08:00
chen quan
22b157bb6c chore: optimize code (#1818)
Signed-off-by: chenquan <chenquan.dev@gmail.com>
2022-04-23 22:02:04 +08:00
Kevin Wan
095b603788 chore: remove gofumpt -s flag, default to be enabled (#1816) 2022-04-22 14:37:17 +08:00
Kevin Wan
bc3c9484d1 chore: refactor (#1814) 2022-04-22 09:37:09 +08:00
chen quan
162e9cef86 feat: add trace in redis & mon & sql (#1799)
* feat: add sub spanId with redis

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* add tests

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* fix a bug

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* feat: add sub spanId in sql

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* feat: add sub spanId in mon

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* chore: optimize code

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* feat: add breaker in warpSession

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* chore: optimize code

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* test: add tests

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* chore: reformat code

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* fix: fix typo

Signed-off-by: chenquan <chenquan.dev@gmail.com>

* fix a bug

Signed-off-by: chenquan <chenquan.dev@gmail.com>
2022-04-22 09:04:44 +08:00
Vee Zhang
94ddb3380e fix: rest: WriteJson get 200 when Marshal failed. (#1803)
Only the first WriteHeader call takes effect.
2022-04-21 21:55:01 +08:00
anqiansong
16c61c6657 chore: Embed unit test data (#1812)
* Embed unit test data

* Add testdata

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-21 21:49:09 +08:00
chowyu12
14bf2f33f7 add go-grpc_opt and go_opt for grpc new command (#1769)
Co-authored-by: zhouyy <zhouyy@ickey.cn>
2022-04-21 16:45:56 +08:00
anqiansong
305587aa81 fix: Fix issue #1810 (#1811)
* Fix #1810

* Remove go embed

* Format code

* Remove useless code

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-21 15:22:43 +08:00
Kevin Wan
2cdff97934 feat: use mongodb official driver instead of mgo (#1782)
* wip: backup

* wip: backup

* wip: backup

* backup

* backup

* backup

* add more tests

* fix wrong dependency

* fix lint errors

* remove test due to data race

* add tests

* fix test error

* add mon.Model

* add mon.Model unmarshal

* add monc

* add more tests for monc

* add more tests for monc

* add docs for mon and monc packages

* fix doc errors

* chhore: add comment

* chore: fix test bug

* chore: refine tests

* chore: remove primitive.NewObjectID in test code

* chore: rename test files for typo
2022-04-19 14:03:04 +08:00
fang duan
bbe1249ecb update rpc generate sample proto file (#1709)
* update rpc generate sample proto file

* update
2022-04-19 10:59:16 +08:00
Fyn
e62870e268 feat(goctl): go work multi-module support (#1800)
* feat(goctl): go work multi-module support

Resolve: #1793

* chore: print log when getting project ctx fails
2022-04-18 20:36:41 +08:00
Kevin Wan
92b450eb11 fix: ignore timeout on websocket (#1802) 2022-04-18 20:14:46 +08:00
杨圆建
d58cf7a12a fix: Hdel check result & Pfadd check result (#1801) 2022-04-18 17:38:36 +08:00
Fyn
036d803fbb docs(goctl): goctl 1.3.4 migration note (#1780)
* docs(goctl): goctl 1.3.4 migration note

* adds a simple lang check
* adds migration notes

* chore: remove i18n

* chore: remove todo
2022-04-18 14:42:13 +08:00
chen quan
c6ab11b14f chore: use grpc.WithTransportCredentials and insecure.NewCredentials() instead of grpc.WithInsecure (#1798)
Signed-off-by: chenquan <chenquan.dev@gmail.com>
2022-04-18 14:15:09 +08:00
Kevin Wan
9e20b1bbfe chhore: fix usage typo (#1797) 2022-04-17 21:17:31 +08:00
fang duan
fadef0ccd9 goctl api new should given a service_name explictly (#1688) 2022-04-17 20:59:18 +08:00
fang duan
4382ec0e0d show help when running goctl api without any flags (#1678)
close #1676
2022-04-17 20:58:12 +08:00
fang duan
db99addc64 show help when running goctl docker without any flags (#1679)
close #1677
2022-04-17 20:57:46 +08:00
fang duan
97bf3856c1 show help when running goctl rpc protoc without any flags (#1683) 2022-04-17 20:57:26 +08:00
fang duan
ff6c6558dd improve goctl rpc new (#1687) 2022-04-17 20:56:56 +08:00
Kevin Wan
5d4e7c84ee revert postgres package refactor (#1796)
* Revert "refactor: move postgres to pg package (#1781)"

This reverts commit ba8ac974aa.

* remove pg, use postgres
2022-04-17 12:07:48 +08:00
Kevin Wan
cb4fcf2c6c fix marshal ptr in httpc (#1789)
* fix marshal ptr in httpc

* add more tests

* add more tests

* add more tests

* fix issue on options and optional both provided
2022-04-15 19:07:34 +08:00
Fyn
ee88abce14 fix(goctl): api/new/api.tpl (#1788) 2022-04-14 23:43:48 +08:00
Kevin Wan
ecc3653d44 fix #1729 (#1783) 2022-04-13 19:06:00 +08:00
Kevin Wan
ba8ac974aa refactor: move postgres to pg package (#1781) 2022-04-13 12:46:09 +08:00
Kevin Wan
50de01fb49 feat: add httpc.Do & httpc.Service.Do (#1775)
* backup

* backup

* backup

* feat: add httpc.Do & httpc.Service.Do

* fix: not using strings.Cut, it's from Go 1.18

* chore: remove redudant code

* feat: httpc.Do finished

* chore: fix reviewdog

* chore: break loop if found

* add more tests
2022-04-11 11:00:28 +08:00
方航
fabea4c448 fix bug: crash when generate model with goctl. (#1777)
* fix bug: crash when generate model with goctl.

situation: column name with line.

CREATE TABLE test (
id int NOT NULL AUTO_INCREMENT,
zh-cn text CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '中文简体',
PRIMARY KEY (id) USING BTREE,
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

* group imports

group imports

* Use

go-zero/tools/goctl/util/string.go
 func SafeString(in string) string {
instead of ReplaceAll

Co-authored-by: 方航 <fanghang@tange.ai>
2022-04-11 10:11:40 +08:00
Fyn
6d9dfc08f9 feat(goctl): supports api multi-level importing (#1747)
* feat(goctl): supports api  multi-level importing

Resolves: #1744

* fix(goctl): import-cycle, etc.

import-cycle will not be allowed
e.g., a.api -> b.api -> a.api
regular multiple-import will be allowed
e.g., a.api -> b.api -> c.api
                   -> c.api

* refactor(goctl): adds comments to exported var

* fix(goctl): typo in a comment
2022-04-09 23:26:57 +08:00
anqiansong
252fabcc4b fix nil pointer if group not exists (#1773)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-09 11:54:30 +08:00
Kevin Wan
415c4c91fc fix: model unique keys generated differently in each re-generation (#1771) 2022-04-09 00:25:23 +08:00
fang duan
0cc9d4ff8d show help when running goctl rpc template without any flags (#1685)
close #1684
2022-04-08 22:28:45 +08:00
Kevin Wan
8bc34defc4 chore: avoid deadlock after stopping TimingWheel (#1768) 2022-04-07 11:50:18 +08:00
anqiansong
8dd764679c Fix #1765 (#1767)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-07 10:40:21 +08:00
Kevin Wan
9fe868ade9 chore: remove legacy code (#1766) 2022-04-06 23:24:20 +08:00
Kevin Wan
4e48286838 chore: add doc (#1764) 2022-04-06 22:42:40 +08:00
Kevin Wan
ab01442d46 add more tests (#1763)
* feat: add goctl docker build scripts

* chore: add more tests
2022-04-06 16:09:06 +08:00
Kevin Wan
8694e38384 feat: add goctl docker build scripts (#1760) 2022-04-05 13:07:05 +08:00
Kevin Wan
d5e550e79b Update readme-cn.md 2022-04-05 11:51:53 +08:00
Kevin Wan
affdab660e Update readme.md 2022-04-05 11:51:09 +08:00
Kevin Wan
7d5858e83a Update readme.md 2022-04-05 11:08:00 +08:00
Kevin Wan
815a6a6485 Update readme-cn.md 2022-04-05 11:07:37 +08:00
benqi
475d17e17d feat: support ctx in kv methods (#1759) 2022-04-04 23:19:58 +08:00
Kevin Wan
8472415472 fix #1754 (#1757) 2022-04-04 22:13:08 +08:00
Kevin Wan
faad6e27e3 feat: use go:embed to embed templates (#1756) 2022-04-04 13:12:05 +08:00
anqiansong
58a0b17451 Support goctl env install (#1752)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-03 21:58:43 +08:00
Kevin Wan
89eccfdb97 chore: update go-zero to v1.3.2 in goctl (#1750) 2022-04-03 20:44:33 +08:00
Kevin Wan
78ea0769fd feat: simplify httpc (#1748)
* feat: simplify httpc

* chore: fix lint errors

* chore: fix log url issue

* chore: fix log url issue

* refactor: handle resp & err in ResponseHandler

* chore: remove unnecessary var names in return clause
2022-04-03 14:32:27 +08:00
Kevin Wan
e0fa8d820d feat: return original value of setbit in redis (#1746) 2022-04-02 20:25:51 +08:00
Kevin Wan
dfd58c213c fix: model generation bug on with cache (#1743)
* fix: model generation bug on with cache

* chore: refine template

* chore: fix test failure
2022-04-02 15:36:06 +08:00
Kevin Wan
83cacf51b7 chore: update goctl version to 1.3.4 (#1742) 2022-04-02 14:19:34 +08:00
Kevin Wan
6dccfa29fd feat: let model customizable (#1738) 2022-04-01 22:19:52 +08:00
anqiansong
7e0b0ab0b1 Fix zrpc code generation error with --remote (#1739)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-01 22:19:33 +08:00
Kevin Wan
ac18cc470d chore: refactor to use const instead of var (#1731) 2022-04-01 15:23:45 +08:00
Fyn
f4471846ff feat(goctl): supports model code 'DO NOT EDIT' (#1728)
Resolves: #1710
2022-04-01 14:48:45 +08:00
anqiansong
9c2d526a11 Fix unit test (#1730)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-01 14:46:12 +08:00
Kevin Wan
2b9fc26c38 refactor: guard timeout on API files (#1726) 2022-03-31 21:39:02 +08:00
Xiaoju Jiang
321dc2d410 Added support for setting the parameter size accepted by the interface and custom timeout and maxbytes in API file (#1713)
* Added support for setting the parameter size accepted by the interface

* support custom timeout and maxbytes in API file

* support timeout used unit

* remove goctl maxBytes
2022-03-31 20:20:00 +08:00
Fyn
500bd87c85 fix(goctl): api format with reader input (#1722)
resolves #1721
2022-03-31 00:20:51 +08:00
Kevin Wan
e9620c8c05 chore: refactor code (#1708) 2022-03-24 22:10:15 +08:00
aimuz
70e51bb352 fix: empty slice are set to nil (#1702)
support for empty slce, Same behavior as json.Unmarshal
2022-03-24 21:41:38 +08:00
Kevin Wan
278cd123c8 feat: remove reentrance in redislock, timeout bug (#1704) 2022-03-24 16:17:01 +08:00
Kevin Wan
3febb1a5d0 chore: refactor code (#1700) 2022-03-23 19:09:45 +08:00
Mikael
d8054d8def fix -cache=true insert no clean cache (#1672)
* fix -cache=true insert no clean cache

* fix -cache=true insert no clean cache
2022-03-23 18:55:16 +08:00
Kevin Wan
ec271db7a0 chore: refactor code (#1699) 2022-03-23 18:24:44 +08:00
benqi
bbac994c8a feat: add getset command in redis and kv (#1693) 2022-03-23 18:02:56 +08:00
Kevin Wan
c1d9e6a00b feat: add httpc.Parse (#1698) 2022-03-23 17:58:21 +08:00
anqiansong
0aeb49a6b0 Add verbose flag (#1696)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-22 21:00:26 +08:00
Kevin Wan
fe262766b4 chore: fix lint issue (#1694) 2022-03-22 13:31:05 +08:00
Kevin Wan
7181505c8a Update LICENSE 2022-03-21 10:32:41 +08:00
Kevin Wan
f060a226bc refactor: simplify the code (#1670) 2022-03-20 17:26:12 +08:00
Mervin.Wong
93d524b797 fix: the new RawFieldNames considers the tag with options. (#1663)
Co-authored-by: JinfaWang <wangjinfa@iie.ac.cn>
2022-03-20 16:59:19 +08:00
anqiansong
5c169f4f49 Remove debug log (#1669)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-20 16:28:36 +08:00
Kevin Wan
d29dfa12e3 feat: support -base to specify base image for goctl docker (#1668)
* feat: support -base to specify base image for goctl docker

* chore: update usage
2022-03-20 11:17:55 +08:00
anqiansong
194f55e08e Remove unused code (#1667)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-19 23:15:11 +08:00
Kevin Wan
c0f9892fe3 feat: add Dockerfile for goctl (#1666) 2022-03-19 23:07:17 +08:00
anqiansong
227104d7d7 feat: Remove command goctl rpc proto (#1665)
* Fix goctl completion expression

* Fix code generation error if the pkg of pb/grpc is same to zrpc call client pkg

* Remove deprecated comment on action goctl rpc new

* Remove zrpc code generation on action goctl rpc proto

* Remove zrpc code generation on action goctl rpc proto

* Remove Generator interface

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-19 22:50:22 +08:00
anqiansong
448029aa4b Mkdir if not exists (#1659)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-17 21:44:22 +08:00
Fyn
17e0afeac0 fix(goctl): model method FindOneCtx should be FindOne (#1656) 2022-03-17 17:16:53 +08:00
ronething-bot
18916b5189 [fix] typo (#1655) 2022-03-17 10:00:29 +08:00
Kevin Wan
c11a09be23 chore: remove unnecessary env (#1654) 2022-03-16 17:31:10 +08:00
ronething-bot
56e1ecf2f3 fix: typo (#1646) 2022-03-15 17:46:13 +08:00
Kevin Wan
f9e6013a6c refactor: httpc package for easy to use (#1645) 2022-03-15 14:18:46 +08:00
Kevin Wan
b5d1d8b0d1 refactor: httpc package for easy to use (#1643) 2022-03-14 20:15:14 +08:00
xybingbing
09e6d94f9e FindOneBy 漏 Context (#1642) 2022-03-14 18:56:26 +08:00
Kevin Wan
2a5717d7fb feat: add httpc/Service for convinience (#1641) 2022-03-14 15:36:06 +08:00
Kevin Wan
85cf662c6f feat: add httpc/Get httpc/Post (#1640) 2022-03-13 14:49:14 +08:00
Kevin Wan
3279a7ef0f feat: add rest/httpc to make http requests governacible (#1638)
* feat: change x-trace-id to traceparent to follow opentelemetry

* feat: add rest/httpc to make http requests governacible

* chore: remove blank lines
2022-03-13 14:11:14 +08:00
Kevin Wan
fec908a19b Update ROADMAP.md
update roadmap.
2022-03-13 14:09:11 +08:00
Kevin Wan
f5ed0cda58 Update ROADMAP.md
update roadmap.
2022-03-13 14:08:28 +08:00
anqiansong
cc9d16f505 fix: Update unix-like path regex (#1637)
* Revert import value regex

* Update linux path regex

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-12 22:21:17 +08:00
Kevin Wan
c05d74b44c feat: support cpu stat on cgroups v2 (#1636)
* feat: cpu stat

* feat: add cpu stat for cgroup2

* feat: add cpu stat for cgroup2, tidy mod

* feat: support cpu stat in cgroup v2
2022-03-12 21:00:04 +08:00
mlr3000
32c88b6352 feat: support oracle :N dynamic parameters (#1552)
* chore:use struct pointer

* feat: support oracle :N dynamic parameters

* Update utils.go

* Update utils.go

* Update utils.go

pg argIndex will not always go up

* Update utils_test.go

* Keep the original

* Update utils_test.go
2022-03-12 18:49:07 +08:00
Kevin Wan
7dabec260f Update readme-cn.md
update readme.
2022-03-12 16:09:18 +08:00
Kevin Wan
4feb88f9b5 Update readme-cn.md
update readme.
2022-03-12 15:23:52 +08:00
Kevin Wan
2776caed0e Update readme.md
update readme.
2022-03-12 15:19:51 +08:00
chensy
c55694d957 Support for referencing types in different API files using format (#1630) 2022-03-12 15:17:31 +08:00
Ziyi Zhang
209ffb934b fix(goctl): kotlin code generation (#1632)
Signed-off-by: Ziyi Zhang <soasurs@gmail.com>
2022-03-11 13:44:18 +08:00
Kevin Wan
26a33932cd feat: support scratch as the base docker image (#1634) 2022-03-11 12:15:38 +08:00
Kevin Wan
d6a692971f chore: reduce the docker image size (#1633)
* chore: reduce the docker image size

* chore: format dockerfile
2022-03-11 11:30:21 +08:00
anqiansong
4624390e54 Fix #1585 #1547 (#1624) 2022-03-09 19:26:35 +08:00
Kevin Wan
63b7d292c1 chore: update goctl version to 1.3.3, change docker build temp dir (#1621) 2022-03-07 14:44:12 +08:00
Fyn
365c569d7c fix(goctl): dart gen user defined struct array (#1620) 2022-03-07 14:11:47 +08:00
anqiansong
68a81fea8a Fix #1609 (#1617) 2022-03-05 22:52:32 +08:00
anqiansong
08a8bd7ef7 Fix #1614 (#1616) 2022-03-05 21:40:41 +08:00
Kevin Wan
b939ce75ba chore: refactor code (#1613) 2022-03-04 17:55:13 +08:00
Kevin Wan
3b7ca86e4f chore: add unit tests (#1615)
* test: add more tests

* test: add more tests
2022-03-04 17:54:09 +08:00
Javen
60760b52ab model中db标签增加'-'符号以支持数据库查询时忽略对应字段. (#1612) 2022-03-04 17:00:46 +08:00
qi
96c128c58a fix: HitQuota should be returned instead of Allowed when limit is equal to 1. (#1581) 2022-03-04 16:14:45 +08:00
Fyn
0c35f39a7d fix: fix(gctl): apiparser_parser auto format (#1607) 2022-03-04 15:36:20 +08:00
Fyn
6a66dde0a1 feat(goctl): api dart support flutter v2 (#1603)
0. support null-safety code gen
1. supports -legacy flag for legacy code gen
2. supports -hostname flag for server hostname
3. use dart official format
4. fix some some bugs

Resolves: #1602
2022-03-04 15:34:13 +08:00
Kevin Wan
36b9fcba44 Update readme-cn.md 2022-03-03 14:35:48 +08:00
Kevin Wan
bf99dda620 Update readme-cn.md 2022-03-03 14:35:10 +08:00
Kevin Wan
511dfcb409 Update readme.md 2022-03-03 14:34:34 +08:00
Kevin Wan
900bc96420 test: add more tests (#1604) 2022-03-02 21:19:04 +08:00
Kevin Wan
be277a7376 Update readme-cn.md
add go-zero users.
2022-03-02 21:18:31 +08:00
Kevin Wan
f15a4f9188 chore: update go-zero to v1.3.1 in goctl (#1599) 2022-03-01 20:56:57 +08:00
Kevin Wan
e31128650e Revert "🐞 fix(gen): pg gen of insert (#1591)" (#1598)
This reverts commit cc4c4928e0.
2022-03-01 20:27:59 +08:00
Kevin Wan
168740b64d chore: upgrade etcd (#1597) 2022-03-01 20:16:44 +08:00
toutou_o
cc4c4928e0 🐞 fix(gen): pg gen of insert (#1591)
Co-authored-by: kurimi1 <d0n41df@gmail.com>
2022-03-01 19:53:23 +08:00
Fyn
fba6543b23 fix: goctl api dart support form tag (#1596) 2022-03-01 16:17:37 +08:00
Kevin Wan
877eb6ac56 Update readme.md
add producthunt.
2022-03-01 16:11:17 +08:00
Kevin Wan
259a5a13e7 chore: fix data race (#1593) 2022-02-28 23:17:51 +08:00
Fyn
cf7c7cb392 build: update goctl dependency ddl-parser to v1.0.3 (#1586)
* build: update goctl dependency ddl-parser to v1.0.3

* fix: race condition when testing logx

Resolves: #1587
2022-02-28 17:31:59 +08:00
ccx
86d01e2e99 test: add testcase for FIFO Queue in collection module (#1589)
cover the case of non-zero value for q.Header when q.Elements expands
2022-02-28 17:15:11 +08:00
Kevin Wan
7a28e19a27 Update readme-cn.md 2022-02-27 23:30:14 +08:00
Kevin Wan
900ea63d68 Update readme-cn.md
add migration notice.
2022-02-27 23:29:45 +08:00
Kevin Wan
87ab86cdd0 Update readme.md 2022-02-27 23:26:03 +08:00
Kevin Wan
0697494ffd Update readme.md
Add migrate steps.
2022-02-27 23:25:22 +08:00
anqiansong
ffd69a2f5e Fix bug int overflow while build goctl on arch 386 (#1582)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-02-27 10:51:57 +08:00
Kevin Wan
66f10bb5e6 chore: add goctl command help (#1578) 2022-02-26 17:02:04 +08:00
Kevin Wan
8131a0e777 Update readme.md
update chat link.
2022-02-25 23:02:09 +08:00
Kevin Wan
32a557dff6 Update readme.md
add discord.
2022-02-25 23:01:15 +08:00
Fyn
db949e40f1 feat: supports importValue for more path formats (#1569)
`importValueRegex` now can match more path formats

Resolves: #1568
2022-02-25 11:16:57 +08:00
Kevin Wan
e0454138e0 update goctl to go 1.16 for io/fs usage (#1571)
* update goctl to go 1.16 for io/fs usage

* feat: support pg serial type for auto_increment (#1563)

* add correct example for pg's url

* 🐞 fix: merge

* 🐞 fix: pg default port

*  feat: support serial type

Co-authored-by: kurimi1 <d0n41df@gmail.com>

* chore: format code

Co-authored-by: toutou_o <33993460+kurimi1@users.noreply.github.com>
Co-authored-by: kurimi1 <d0n41df@gmail.com>
2022-02-24 13:58:53 +08:00
toutou_o
3b07ed1b97 feat: support pg serial type for auto_increment (#1563)
* add correct example for pg's url

* 🐞 fix: merge

* 🐞 fix: pg default port

*  feat: support serial type

Co-authored-by: kurimi1 <d0n41df@gmail.com>
2022-02-24 13:39:31 +08:00
anqiansong
daa98f5a27 Feature: Add goctl env (#1557) 2022-02-21 10:19:33 +08:00
Kevin Wan
842656aa90 feat: log 404 requests with traceid (#1554) 2022-02-19 20:50:33 +08:00
Kevin Wan
aa29036cb3 feat: support ctx in sql model generation (#1551) 2022-02-17 10:28:55 +08:00
Kevin Wan
607bae27fa feat: support ctx in sqlx/sqlc, listed in ROADMAP (#1535)
* feat: support ctx in sqlx/sqlc

* chore: update roadmap

* fix: context.Canceled should be acceptable

* use %w to wrap errors

* chore: remove unused vars
2022-02-16 19:31:43 +08:00
Kevin Wan
7c63676be4 docs: add go-zero users (#1546) 2022-02-16 12:02:52 +08:00
Kevin Wan
9e113909b3 ignore context.Canceled for redis breaker (#1545) 2022-02-15 21:31:30 +08:00
Kevin Wan
bd105474ca chore: update help message (#1544) 2022-02-15 21:19:40 +08:00
Mikael
a078f5d764 add the serviceAccount of deployment (#1543)
Co-authored-by: 977231903@qq.com <>
2022-02-15 20:57:14 +08:00
Kevin Wan
b215fa3ee6 fix #1541 (#1542) 2022-02-15 18:40:26 +08:00
mlr3000
50b1928502 chore:use struct pointer (#1538) 2022-02-15 11:34:48 +08:00
Kevin Wan
493e3bcf4b docs: update roadmap (#1537) 2022-02-15 08:37:03 +08:00
Kevin Wan
6deb80625d fix issue of default migrate version (#1536)
* fix issue of default migrate version

* chore: update console colors
2022-02-14 23:09:32 +08:00
Kevin Wan
6ab051568c Update readme-cn.md
add go-zero users
2022-02-14 16:57:48 +08:00
Kevin Wan
2732d3cdae chore: refactor cache (#1532) 2022-02-13 18:04:31 +08:00
chenquan
e8c307e4dc feat: support ctx in Cache (#1518)
* feature: support ctx in `Cache`

Signed-off-by: chenquan <chenquan.dev@foxmail.com>

* fix: `errors.Is` instead of `=`

Signed-off-by: chenquan <chenquan.dev@foxmail.com>
2022-02-13 17:28:14 +08:00
Kevin Wan
84ddc660c4 chore: goctl format issue (#1531) 2022-02-13 13:17:19 +08:00
Kevin Wan
e60e707955 upgrade grpc version (#1530) 2022-02-12 23:58:41 +08:00
Kevin Wan
cf4321b2d0 fix #1525 (#1527) 2022-02-11 23:04:57 +08:00
chenquan
1993faf2f8 fix: fix a typo (#1522)
Signed-off-by: chenquan <chenquan.dev@foxmail.com>
2022-02-11 21:15:45 +08:00
Kevin Wan
0ce85376bf chore: update goctl version to 1.3.2 (#1524) 2022-02-11 21:02:50 +08:00
Kevin Wan
a40254156f refactor: refactor yaml unmarshaler (#1517) 2022-02-09 17:22:52 +08:00
chenquan
05cc62f5ff chore: optimize yaml unmarshaler (#1513) 2022-02-09 16:57:00 +08:00
chenquan
9c2c90e533 chore: make error clearer (#1514) 2022-02-09 14:40:05 +08:00
Kevin Wan
822ee2e1c5 feat: update go-redis to v8, support ctx in redis methods (#1507)
* feat: update go-redis to v8, support ctx in redis methods

* fix compile errors

* chore: remove unused const

* chore: add tracing log on redis
2022-02-09 11:06:06 +08:00
anqiansong
77482c8946 fixes typo (#1511)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-02-08 22:16:38 +08:00
Kevin Wan
7ef0ab3119 Update readme-cn.md 2022-02-08 11:47:33 +08:00
anqiansong
8bd89a297a feature: Add goctl completion (#1505)
* feature: Add `goctl completion`

* Update const

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-02-08 10:50:21 +08:00
Kevin Wan
bb75cc796e test: change fuzz tests (#1504) 2022-02-05 09:44:01 +08:00
Kevin Wan
0fdd8f54eb ci: add test for win (#1503)
* ci: add test for win

* ci: update check names

* ci: use go build instead of go test to verify win test

* fix: windows test failure

* chore: disable logs in tests
2022-02-05 00:06:23 +08:00
anqiansong
b1ffc464cd fix typo: goctl protoc usage (#1502)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-02-03 22:13:02 +08:00
Kevin Wan
50174960e4 chore: update command comment (#1501) 2022-02-02 22:02:08 +08:00
Kevin Wan
8f46eab977 fix: goctl not compile on windows (#1500) 2022-02-01 13:58:08 +08:00
Kevin Wan
ec299085f5 docs: update tal-tech to zeromico in docs (#1498) 2022-02-01 13:03:30 +08:00
Kevin Wan
7727d70634 chore: update goctl version (#1497) 2022-02-01 09:50:26 +08:00
Kevin Wan
5f9d101bc6 feat: add runtime stats monitor (#1496) 2022-02-01 01:34:25 +08:00
Kevin Wan
6c2abe7474 fix: goroutine stuck on edge case (#1495)
* fix: goroutine stuck on edge case

* refactor: simplify mapreduce implementation
2022-01-30 13:09:21 +08:00
Kevin Wan
14a902c1a7 feat: handling panic in mapreduce, panic in calling goroutine, not inside goroutines (#1490)
* feat: handle panic

* chore: update fuzz test

* chore: optimize square sum algorithm
2022-01-28 10:59:41 +08:00
Kevin Wan
5ad6a6d229 Update readme-cn.md
add slogan
2022-01-27 17:16:30 +08:00
Kevin Wan
6f4b97864a chore: improve migrate confirmation (#1488) 2022-01-27 11:30:35 +08:00
Kevin Wan
0e0abc3a95 chore: update warning message (#1487) 2022-01-26 23:47:57 +08:00
anqiansong
696fda1db4 patch: goctl migrate (#1485)
* * Add signal check
* Add deprecated pkg check

* fix typo `replacementBuilderx`

* output to console if verbose

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-01-26 23:24:25 +08:00
Kevin Wan
c1d2634427 chore: update go version for goctl (#1484) 2022-01-26 14:27:43 +08:00
Kevin Wan
4b7a680ac5 refactor: rename from tal-tech to zeromicro for goctl (#1481) 2022-01-25 23:15:07 +08:00
Kevin Wan
b3e7d2901f Feature/trie ac automation (#1479)
* fix: trie ac automation issues

* fix: trie ac automation issues

* fix: trie ac automation issues

* fix: trie ac automation issues
2022-01-25 11:14:56 +08:00
anqiansong
cdf7ec213c fix #1468 (#1478)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-01-24 22:23:20 +08:00
Kevin Wan
f1102fb262 chore: optimize string search with Aho–Corasick algorithm (#1476)
* chore: optimize string search with Aho–Corasick algorithm

* chore: optimize keywords replacer

* fix: replacer bugs

* chore: reorder members
2022-01-23 23:37:02 +08:00
Keqi Huang
09d1fad6e0 Polish the words in readme.md (#1475) 2022-01-22 12:20:11 +08:00
Kevin Wan
379c65a3ef docs: add go-zero users (#1473) 2022-01-20 22:36:17 +08:00
Kevin Wan
fdc7f64d6f chore: update unauthorized callback calling order (#1469)
* chore: update unauthorized callback calling order

* chore: add comments
2022-01-20 21:09:45 +08:00
anqiansong
df0f8ed59e Fix/issue#1289 (#1460)
* fix #1289

* Add unit test case

* fix `jwtTransKey`

* fix `jwtTransKey`

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-01-18 11:52:30 +08:00
anqiansong
c903966fc7 patch: save missing templates to disk (#1463)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-01-18 10:45:05 +08:00
anqiansong
e57fa8ff53 Fix/issue#1447 (#1458)
* Add data for template to render

* fix #1447

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-01-18 10:36:38 +08:00
Kevin Wan
bf2feee5b7 feat: implement console plain output for debug logs (#1456)
* feat: implement console plain output for debug logs

* chore: rename console encoding to plain

* chore: refactor names
2022-01-17 12:43:15 +08:00
Letian Jiang
ce05c429fc chore: check interface satisfaction w/o allocating new variable (#1454) 2022-01-16 23:34:42 +08:00
Kevin Wan
272a3f347d chore: remove jwt deprecated (#1452) 2022-01-16 10:34:44 +08:00
shenbaise9527
13db7a1931 feat: 支持redis的LTrim方法 (#1443) 2022-01-16 10:27:34 +08:00
Kevin Wan
468c237189 chore: upgrade dependencies (#1444)
* chore: upgrade dependencies

* ci: upgrade go to 1.15
2022-01-14 11:01:02 +08:00
Kevin Wan
b9b80c068b ci: add translator action (#1441) 2022-01-12 17:57:39 +08:00
anqiansong
9b592b3dee Feature rpc protoc (#1251)
* code generation by protoc

* generate pb by protoc direct

* support: grpc code generation by protoc directly

* format code

* check --go_out & --go-grpc_out

* fix typo

* Update version

* fix typo

* optimize: remove deprecated unit test

* format code

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-01-11 20:34:25 +08:00
Kevin Wan
2203809e5e chore: fix typo (#1437) 2022-01-11 20:23:59 +08:00
Kevin Wan
8d6d37f71e remove unnecessary drain, fix data race (#1435)
* remove unnecessary drain, fix data race

* chore: fix parameter order

* refactor: rename MapVoid to ForEach in mr
2022-01-11 16:17:51 +08:00
Kevin Wan
ea4f2af67f fix: mr goroutine leak on context deadline (#1433)
* fix: mr goroutine leak on context deadline

* test: update fx test check
2022-01-10 22:06:10 +08:00
Kevin Wan
53af194ef9 chore: refactor periodlimit (#1428)
* chore: refactor periodlimit

* chore: add comments
2022-01-09 16:22:34 +08:00
Kevin Wan
5e0e2d2b14 docs: add go-zero users (#1425) 2022-01-08 21:41:27 +08:00
Kevin Wan
74c99184c5 docs: add go-zero users (#1424) 2022-01-08 17:08:44 +08:00
Kevin Wan
eb4b86137a fix: golint issue (#1423) 2022-01-08 16:06:56 +08:00
Kevin Wan
9c4f4f3b4e update docs (#1421) 2022-01-07 12:08:45 +08:00
spectatorMrZ
240132e7c7 Fix pg model generation without tag (#1407)
1. fix pg model struct haven't tag
2. add pg command test from datasource
2022-01-07 10:45:26 +08:00
anqiansong
9d67fc4cfb feat: Add migrate (#1419)
* Add migrate

* Remove unused module

* refactor filename

* rename refactor to migrate

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-01-06 18:48:34 +08:00
Kevin Wan
892f93a716 docs: update install readme (#1417) 2022-01-05 12:31:49 +08:00
Kevin Wan
ba6a7c9dc8 chore: refactor rest/timeouthandler (#1415) 2022-01-05 11:17:10 +08:00
Kevin Wan
a91c3907a8 feat: rename module from tal-tech to zeromicro (#1413) 2022-01-04 15:51:32 +08:00
Kevin Wan
e267d94ee1 chore: update go-zero to v1.2.5 (#1410) 2022-01-03 21:54:53 +08:00
anqiansong
89ce5e492b refactor file|path (#1409)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-01-03 21:32:40 +08:00
Kevin Wan
290de6aa96 docs: update roadmap (#1405) 2022-01-02 21:30:02 +08:00
Kevin Wan
a7aeb8ac0e feat: support tls for etcd client (#1390)
* feat: support tls for etcd client

* chore: fix typo

* refactor: rename TrustedCAFile to CACertFile

* docs: add comments

* fix: missing tls registration

* feat: add InsecureSkipVerify config for testing
2022-01-02 20:23:50 +08:00
Kevin Wan
a8e7fafebf refactor: optimize fx (#1404)
* refactor: optimize fx

* chore: add more comments

* ci: make test robust
2022-01-02 14:56:30 +08:00
Kevin Wan
7cc64070b1 docs: update goctl installation command (#1403) 2022-01-02 14:31:31 +08:00
Kevin Wan
c19d2637ea feat: implement fx.NoneMatch, fx.First, fx.Last (#1402)
* chore: use workers from options in fx.unlimitedWalk

* feat: add fx.NoneMatch

* feat: add fx.First, fx.Last

* chore: add more comments

* docs: add mr readme
2022-01-02 13:33:15 +08:00
Kevin Wan
fe1da14332 chore: simplify mapreduce (#1401) 2022-01-01 19:24:35 +08:00
anqiansong
8e9110cedf fix #1330 (#1382)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-12-30 20:44:04 +08:00
Kevin Wan
d6ff30a570 chore: fix golint issues (#1396) 2021-12-30 17:44:15 +08:00
Kevin Wan
b98d46bfd6 chore: update goctl version (#1394) 2021-12-30 15:30:16 +08:00
Kevin Wan
768936b256 ci: remove 386 binaries (#1393) 2021-12-30 15:18:24 +08:00
Kevin Wan
c6eb1a9670 ci: remove windows 386 binary (#1392)
* ci: remove windows 386 binary

* chore: update go-zero

* chore: update go-zero
2021-12-30 14:47:53 +08:00
Kevin Wan
e4ab518576 test: add more tests (#1391) 2021-12-30 14:21:55 +08:00
moyrne
dfc67b5fac fix readme-cn (#1388) 2021-12-30 10:42:23 +08:00
Kevin Wan
62266d8f91 fix #1070 (#1389)
* fix #1070

* test: add more tests
2021-12-29 21:34:28 +08:00
anqiansong
b8ea16a88e feat: Add --remote (#1387)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-12-29 18:16:42 +08:00
Kevin Wan
23deaf50e6 feat: support array in default and options tags (#1386)
* feat: support array in default and options tags

* feat: ignore spaces in tags

* test: add more tests
2021-12-29 17:37:36 +08:00
Kevin Wan
38a36ed8d3 docs: add go-zero users (#1381) 2021-12-28 17:12:51 +08:00
anqiansong
49bab23c54 fix #1376 (#1380)
* fix #1376

* fix #1376

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-12-28 16:40:26 +08:00
Leizhengzi
78ba00d3a7 fix: command system info missing go version (#1377) 2021-12-27 22:05:27 +08:00
Kevin Wan
787b046a70 docs: update slack invitation link (#1378) 2021-12-27 16:52:08 +08:00
Kevin Wan
f827a7b985 chore: update goctl version to 1.2.4 for release tools/goctl/v1.2.4 (#1372) 2021-12-27 10:57:55 +08:00
行者
f5f2097d14 Updated MySQL生成表结构体遇到关键字db部分保持原字段名定义 (#1369) 2021-12-26 21:56:04 +08:00
Kevin Wan
cfcfb87fd4 ci: add release action to auto build binaries (#1371) 2021-12-26 21:44:33 +08:00
Kevin Wan
1d223fc114 docs: update goctl markdown (#1370) 2021-12-26 20:32:31 +08:00
Kevin Wan
c0647f0719 feat: support context in MapReduce (#1368) 2021-12-25 20:42:52 +08:00
Kevin Wan
8745ed9c61 chore: add 1s for tolerance in redislock (#1367) 2021-12-25 19:44:27 +08:00
种豆得豆
836726e710 fix redis try-lock bug (#1366)
#issue_id: 1338

Co-authored-by: zhangwei <>
2021-12-25 19:20:53 +08:00
JiangYiJun
a67c118dcf go-zero tools ,fix a func,api new can not choose style (#1356) 2021-12-23 10:28:46 +08:00
Kevin Wan
cd289465fd chore: coding style and comments (#1361)
* chore: coding style and comments

* chore: optimize `ParseJsonBody` (#1353)

* chore: optimize `ParseJsonBody`

* chore: optimize `ParseJsonBody`

* fix: fix a test

* chore: optimize `ParseJsonBody`

* fix a test

* chore: add comment

* chore: refactor

Co-authored-by: chenquan <chenquan.dev@foxmail.com>
2021-12-22 21:43:37 +08:00
chenquan
263e426ae1 chore: optimize ParseJsonBody (#1353)
* chore: optimize `ParseJsonBody`

* chore: optimize `ParseJsonBody`

* fix: fix a test

* chore: optimize `ParseJsonBody`

* fix a test

* chore: add comment
2021-12-22 20:24:55 +08:00
charliecen
d5e493383a chose: cancel the assignment and judge later (#1359)
Co-authored-by: charliecen <chq@abierr.com>
2021-12-22 20:05:35 +08:00
Kevin Wan
6f1d27354a chore: put error message in error.log for verbose mode (#1355) 2021-12-21 11:36:01 +08:00
Kevin Wan
26101732d2 test: add more tests (#1352) 2021-12-20 22:42:36 +08:00
Kevin Wan
71d40e0c08 Revert "排除客户端中断导致的503错误 (#1343)" (#1351)
This reverts commit 2cdf5e7395.
2021-12-20 20:34:43 +08:00
Kevin Wan
4ba2ff7cdd feat: treat client closed requests as code 499 (#1350)
* feat: treat client closed requests as code 499

* chore: add comments
2021-12-20 19:43:38 +08:00
vic
2cdf5e7395 排除客户端中断导致的503错误 (#1343) 2021-12-20 19:43:13 +08:00
Kevin Wan
8315a55b3f Update FUNDING.yml
enable sponsorship.
2021-12-20 15:27:05 +08:00
Kevin Wan
d1c2a31af7 chore: add tests & refactor (#1346)
* chore: add tests & refactor

* chore: refactor
2021-12-18 23:11:38 +08:00
MarkJoyMa
3e6c217408 Feature: support adding custom cache to mongoc and sqlc (#1313)
* merge

* Feature: support adding custom cache to mongoc and sqlc
2021-12-18 22:45:07 +08:00
Kevin Wan
b299f350be chore: add comments (#1345) 2021-12-18 22:39:14 +08:00
Kevin Wan
8fd16c17dc chore: update goctl version to 1.2.5 (#1337) 2021-12-16 00:21:54 +08:00
anqiansong
5979b2aa0f Update template (#1335)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-12-15 23:24:32 +08:00
anqiansong
0b17e0e5d9 Feat goctl bug (#1332)
* Support goctl bug

* fix typo

* format code

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-12-15 22:43:58 +08:00
Kevin Wan
3d8ad5e4f6 feat: tidy mod, update go-zero to latest (#1334) 2021-12-15 22:34:58 +08:00
Kevin Wan
ff1752dd39 feat: tidy mod, update go-zero to latest (#1333) 2021-12-15 22:23:06 +08:00
Kevin Wan
1becaeb7be chore: refactor (#1331) 2021-12-15 20:44:23 +08:00
yangkequn
171afaadb9 Update types.go (#1314) 2021-12-15 20:16:17 +08:00
Kevin Wan
776e6e647d feat: tidy mod, add go.mod for goctl (#1328) 2021-12-15 19:44:49 +08:00
Kevin Wan
4ccdf4ec72 chore: format code (#1327) 2021-12-15 13:43:05 +08:00
CrazyZard
a7bd993c0c commit missing method for redis (#1325)
* commit `decr ` `decrby` `lindex` missing method for redis

* fix(store_test):TestRedis_DecrBy

* add unit tests for redis commands. And put the functions in alphabetical order

* put the functions in alphabetical order

* add `lindex` unit test

* sort func
2021-12-15 13:15:39 +08:00
Kevin Wan
a290ff4486 docs: add go-zero users (#1323) 2021-12-14 13:37:49 +08:00
Kevin Wan
490ef13822 style: format code (#1322) 2021-12-14 11:29:44 +08:00
anqiansong
1b14de2ff9 fix: #1318 (#1321)
* fix #1318

* fix #1318

* remove never used code

* fix unit tes

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-12-13 22:55:11 +08:00
Kevin Wan
914692cc82 fix #1309 (#1317) 2021-12-13 11:58:58 +08:00
anqiansong
07191dc430 fix #1305 (#1307)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-12-07 22:24:18 +08:00
BYT0723
af3fb2b04d fix: go issue 16206 (#1298) 2021-12-07 15:52:37 +08:00
Kevin Wan
0240fa131a chore: rename service context from ctx to svcCtx (#1299) 2021-12-05 22:10:47 +08:00
Kevin Wan
e96577dd38 docs: add go-zero users (#1294) 2021-12-03 22:32:35 +08:00
Kevin Wan
403dd7367a fix #1288 (#1292)
* fix #1288

* chore: make wrapup & shutdown callbacks run simulatenously
2021-12-02 22:41:57 +08:00
Kevin Wan
8086ad120b Revert "feat: reduce dependencies of framework by add go.mod in goctl (#1290)" (#1291)
This reverts commit 87a445689c.
2021-12-02 19:40:23 +08:00
Kevin Wan
87a445689c feat: reduce dependencies of framework by add go.mod in goctl (#1290) 2021-12-02 16:57:07 +08:00
Kevin Wan
b6bda54870 chore: update cli version (#1287) 2021-12-01 23:33:23 +08:00
Kevin Wan
9d528dddd6 feat: support third party orm to interact with go-zero (#1286)
* fixes #987

* chore: fix test failure

* chore: add comments

* feat: support third party orm to interact with go-zero

* chore: refactor
2021-12-01 20:22:15 +08:00
Kevin Wan
543d590710 fixes #987 (#1283)
* fixes #987

* chore: fix test failure

* chore: add comments
2021-12-01 17:45:48 +08:00
anqiansong
f1d70eb6b2 Feature api root path (#1261) 2021-12-01 10:09:07 +08:00
Kevin Wan
d828c3f37e feat: add etcd resolver scheme, fix discov minor issue (#1281) 2021-11-28 20:08:18 +08:00
Kevin Wan
038491b7bc chore: cleanup zRPC retry code (#1280) 2021-11-27 18:39:52 +08:00
chenquan
cf683411ee feature(retry): Delete retry mechanism (#1279) 2021-11-27 11:32:33 +08:00
Kevin Wan
de5ed6a677 feat: support %w in logx.Errorf (#1278) 2021-11-26 15:57:23 +08:00
Kevin Wan
3dda557410 chore: only allow cors middleware to change headers (#1276) 2021-11-26 14:14:06 +08:00
Kevin Wan
c800f6f723 chore: avoid superfluous WriteHeader call errors (#1275) 2021-11-26 11:09:57 +08:00
Kevin Wan
0395ba1816 feat: add rest.WithCustomCors to let caller customize the response (#1274) 2021-11-25 23:03:37 +08:00
Kevin Wan
86f9f63b46 Cli (#1272)
* Fix issue 1260 (#1262)

* Fix #1238 (#1266)

* docs: update readme to use goctl@cli (#1255)

* chore: update goctl version

* style: coding style

* docs: update readme to use goctl@cli

* fix #1238

* format code

* format code

Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
Co-authored-by: anqiansong <anqiansong@bytedance.com>

Co-authored-by: anqiansong <anqiansong@gmail.com>
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-11-25 11:08:49 +08:00
Kevin Wan
a7a6753118 fixes #1257 (#1271)
* fixes #1257

* chore: format code

* test: add more tests
2021-11-25 10:26:16 +08:00
Kevin Wan
2e80d12d6a docs: update readme to use goctl@cli (#1255)
* chore: update goctl version

* style: coding style

* docs: update readme to use goctl@cli
2021-11-17 21:10:45 +08:00
Kevin Wan
417a96cbf2 chore: update goctl version (#1250)
* chore: update goctl version

* style: coding style
2021-11-16 21:57:55 +08:00
Kevin Wan
2d4c29ea7c Revert "Revert "feat: enable retry for zrpc (#1237)"" (#1246) 2021-11-16 10:29:31 +08:00
795 changed files with 36941 additions and 13699 deletions

1
.github/FUNDING.yml vendored
View File

@@ -10,3 +10,4 @@ liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username otechie: # Replace with a single Otechie username
custom: # https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg custom: # https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg
ethereum: 0x5052b7f6B937B02563996D23feb69b38D06Ca150 | kevwan

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

View File

@@ -35,11 +35,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -50,7 +50,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -64,4 +64,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2

View File

@@ -7,32 +7,50 @@ on:
branches: [ master ] branches: [ master ]
jobs: jobs:
build: test-linux:
name: Build name: Linux
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.15
id: go
- name: Set up Go 1.x - name: Check out code into the Go module directory
uses: actions/setup-go@v2 uses: actions/checkout@v3
with:
go-version: ^1.14
id: go
- name: Check out code into the Go module directory - name: Get dependencies
uses: actions/checkout@v2 run: |
go get -v -t -d ./...
- name: Get dependencies - name: Lint
run: | run: |
go get -v -t -d ./... go vet -stdmethods=false $(go list ./...)
go install mvdan.cc/gofumpt@latest
test -z "$(gofumpt -l -extra .)" || echo "Please run 'gofumpt -l -w -extra .'"
- name: Lint - name: Test
run: | run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
go vet -stdmethods=false $(go list ./...)
go install mvdan.cc/gofumpt@latest
test -z "$(gofumpt -s -l -extra .)" || echo "Please run 'gofumpt -l -w -extra .'"
- name: Test - name: Codecov
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./... uses: codecov/codecov-action@v2
- name: Codecov test-win:
uses: codecov/codecov-action@v2 name: Windows
runs-on: windows-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.15
- name: Checkout codebase
uses: actions/checkout@v3
- name: Test
run: |
go mod verify
go mod download
go test -v -race ./...
cd tools/goctl && go build -v goctl.go

18
.github/workflows/issue-translator.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: 'issue-translator'
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: tomsun28/issues-translate-action@v2.6
with:
IS_MODIFY_TITLE: true
# not require, default false, . Decide whether to modify the issue title
# if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot.
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿
# not require. Customize the translation robot prefix message.

28
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,28 @@
on:
push:
tags:
- "tools/goctl/*"
jobs:
releases-matrix:
name: Release goctl binary
runs-on: ubuntu-latest
strategy:
matrix:
# build and publish in parallel: linux/386, linux/amd64, linux/arm64,
# windows/386, windows/amd64, windows/arm64, darwin/amd64, darwin/arm64
goos: [ linux, windows, darwin ]
goarch: [ "386", amd64, arm64 ]
exclude:
- goarch: "386"
goos: darwin
steps:
- uses: actions/checkout@v3
- uses: zeromicro/go-zero-release-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
goversion: "https://dl.google.com/go/go1.17.5.linux-amd64.tar.gz"
project_path: "tools/goctl"
binary_name: "goctl"
extra_files: tools/goctl/goctl.md

View File

@@ -5,7 +5,7 @@ jobs:
name: runner / staticcheck name: runner / staticcheck
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: reviewdog/action-staticcheck@v1 - uses: reviewdog/action-staticcheck@v1
with: with:
github_token: ${{ secrets.github_token }} github_token: ${{ secrets.github_token }}

4
.gitignore vendored
View File

@@ -16,7 +16,9 @@
**/logs **/logs
# for test purpose # for test purpose
adhoc **/adhoc
go.work
go.work.sum
# gitlab ci # gitlab ci
.cache .cache

View File

@@ -40,7 +40,7 @@ We will help you to contribute in different areas like filing issues, developing
getting your work reviewed and merged. getting your work reviewed and merged.
If you have questions about the development process, If you have questions about the development process,
feel free to [file an issue](https://github.com/tal-tech/go-zero/issues/new/choose). feel free to [file an issue](https://github.com/zeromicro/go-zero/issues/new/choose).
## Find something to work on ## Find something to work on
@@ -50,10 +50,10 @@ Here is how you get started.
### Find a good first topic ### Find a good first topic
[go-zero](https://github.com/tal-tech/go-zero) has beginner-friendly issues that provide a good first issue. [go-zero](https://github.com/zeromicro/go-zero) has beginner-friendly issues that provide a good first issue.
For example, [go-zero](https://github.com/tal-tech/go-zero) has For example, [go-zero](https://github.com/zeromicro/go-zero) has
[help wanted](https://github.com/tal-tech/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) and [help wanted](https://github.com/zeromicro/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) and
[good first issue](https://github.com/tal-tech/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) [good first issue](https://github.com/zeromicro/go-zero/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
labels for issues that should not need deep knowledge of the system. labels for issues that should not need deep knowledge of the system.
We can help new contributors who wish to work on such issues. We can help new contributors who wish to work on such issues.
@@ -79,7 +79,7 @@ This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where to base the contribution. This is usually master. - Create a topic branch from where to base the contribution. This is usually master.
- Make commits of logical units. - Make commits of logical units.
- Push changes in a topic branch to a personal fork of the repository. - Push changes in a topic branch to a personal fork of the repository.
- Submit a pull request to [go-zero](https://github.com/tal-tech/go-zero). - Submit a pull request to [go-zero](https://github.com/zeromicro/go-zero).
## Creating Pull Requests ## Creating Pull Requests

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020 xiaoheiban_server_go Copyright (c) 2022 zeromicro
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -14,14 +14,15 @@ We hope that the items listed below will inspire further engagement from the com
## 2021 Q4 ## 2021 Q4
- [x] Support `username/password` authentication in ETCD - [x] Support `username/password` authentication in ETCD
- [x] Support `SSL/TLS` in ETCD
- [x] Support `SSL/TLS` in `zRPC` - [x] Support `SSL/TLS` in `zRPC`
- [x] Support `TLS` in redis connections - [x] Support `TLS` in redis connections
- [ ] Support `retry strategies` in `zRPC` - [x] Support `goctl bug` to report bugs conveniently
## 2022 ## 2022
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file - [x] Support `context` in redis related methods for timeout and tracing
- [ ] Add `httpx.Client` with governance, like circuit breaker etc. - [x] Support `context` in sql related methods for timeout and tracing
- [x] Support `context` in mongodb related methods for timeout and tracing
- [x] Add `httpc.Do` with HTTP call governance, like circuit breaker etc.
- [ ] Support `goctl doctor` command to report potential issues for given service - [ ] Support `goctl doctor` command to report potential issues for given service
- [ ] Support `context` in redis related methods for timeout and tracing - [ ] Support `goctl mock` command to start a mocking server with given `.api` file
- [ ] Support `context` in sql related methods for timeout and tracing
- [ ] Support `context` in mongodb related methods for timeout and tracing

View File

@@ -4,8 +4,8 @@ import (
"errors" "errors"
"strconv" "strconv"
"github.com/tal-tech/go-zero/core/hash" "github.com/zeromicro/go-zero/core/hash"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/redis"
) )
const ( const (
@@ -69,11 +69,8 @@ func (f *Filter) Exists(data []byte) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
if !isSet {
return false, nil
}
return true, nil return isSet, nil
} }
func (f *Filter) getLocations(data []byte) []uint { func (f *Filter) getLocations(data []byte) []uint {

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stores/redis/redistest" "github.com/zeromicro/go-zero/core/stores/redis/redistest"
) )
func TestRedisBitSet_New_Set_Test(t *testing.T) { func TestRedisBitSet_New_Set_Test(t *testing.T) {

View File

@@ -5,12 +5,12 @@ import (
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"time"
"github.com/tal-tech/go-zero/core/mathx" "github.com/zeromicro/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/proc" "github.com/zeromicro/go-zero/core/proc"
"github.com/tal-tech/go-zero/core/stat" "github.com/zeromicro/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
"github.com/tal-tech/go-zero/core/timex"
) )
const ( const (
@@ -171,7 +171,7 @@ func (lt loggedThrottle) allow() (Promise, error) {
func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error { func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool { return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool {
accept := acceptable(err) accept := acceptable(err)
if !accept { if !accept && err != nil {
lt.errWin.add(err.Error()) lt.errWin.add(err.Error())
} }
return accept return accept
@@ -198,7 +198,7 @@ type errorWindow struct {
func (ew *errorWindow) add(reason string) { func (ew *errorWindow) add(reason string) {
ew.lock.Lock() ew.lock.Lock()
ew.reasons[ew.index] = fmt.Sprintf("%s %s", timex.Time().Format(timeFormat), reason) ew.reasons[ew.index] = fmt.Sprintf("%s %s", time.Now().Format(timeFormat), reason)
ew.index = (ew.index + 1) % numHistoryReasons ew.index = (ew.index + 1) % numHistoryReasons
ew.count = mathx.MinInt(ew.count+1, numHistoryReasons) ew.count = mathx.MinInt(ew.count+1, numHistoryReasons)
ew.lock.Unlock() ew.lock.Unlock()

View File

@@ -8,7 +8,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stat" "github.com/zeromicro/go-zero/core/stat"
) )
func init() { func init() {

View File

@@ -6,7 +6,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stat" "github.com/zeromicro/go-zero/core/stat"
) )
func init() { func init() {

View File

@@ -4,8 +4,8 @@ import (
"math" "math"
"time" "time"
"github.com/tal-tech/go-zero/core/collection" "github.com/zeromicro/go-zero/core/collection"
"github.com/tal-tech/go-zero/core/mathx" "github.com/zeromicro/go-zero/core/mathx"
) )
const ( const (

View File

@@ -7,9 +7,9 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/collection" "github.com/zeromicro/go-zero/core/collection"
"github.com/tal-tech/go-zero/core/mathx" "github.com/zeromicro/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/stat" "github.com/zeromicro/go-zero/core/stat"
) )
const ( const (

View File

@@ -8,8 +8,8 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/iox" "github.com/zeromicro/go-zero/core/iox"
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
) )
func TestEnterToContinue(t *testing.T) { func TestEnterToContinue(t *testing.T) {

View File

@@ -7,7 +7,7 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
// ErrPaddingSize indicates bad padding size. // ErrPaddingSize indicates bad padding size.

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs" "github.com/zeromicro/go-zero/core/fs"
) )
const ( const (

View File

@@ -6,9 +6,9 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/mathx" "github.com/zeromicro/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
) )
const ( const (
@@ -26,7 +26,7 @@ type (
// CacheOption defines the method to customize a Cache. // CacheOption defines the method to customize a Cache.
CacheOption func(cache *Cache) CacheOption func(cache *Cache)
// A Cache object is a in-memory cache. // A Cache object is an in-memory cache.
Cache struct { Cache struct {
name string name string
lock sync.Mutex lock sync.Mutex
@@ -98,13 +98,18 @@ func (c *Cache) Get(key string) (interface{}, bool) {
// Set sets value into c with key. // Set sets value into c with key.
func (c *Cache) Set(key string, value interface{}) { func (c *Cache) Set(key string, value interface{}) {
c.SetWithExpire(key, value, c.expire)
}
// SetWithExpire sets value into c with key and expire with the given value.
func (c *Cache) SetWithExpire(key string, value interface{}, expire time.Duration) {
c.lock.Lock() c.lock.Lock()
_, ok := c.data[key] _, ok := c.data[key]
c.data[key] = value c.data[key] = value
c.lruCache.add(key) c.lruCache.add(key)
c.lock.Unlock() c.lock.Unlock()
expiry := c.unstableExpiry.AroundDuration(c.expire) expiry := c.unstableExpiry.AroundDuration(expire)
if ok { if ok {
c.timingWheel.MoveTimer(key, expiry) c.timingWheel.MoveTimer(key, expiry)
} else { } else {

View File

@@ -18,7 +18,7 @@ func TestCacheSet(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
cache.Set("first", "first element") cache.Set("first", "first element")
cache.Set("second", "second element") cache.SetWithExpire("second", "second element", time.Second*3)
value, ok := cache.Get("first") value, ok := cache.Get("first")
assert.True(t, ok) assert.True(t, ok)

View File

@@ -61,3 +61,41 @@ func TestPutMore(t *testing.T) {
assert.Equal(t, string(element), string(body.([]byte))) assert.Equal(t, string(element), string(body.([]byte)))
} }
} }
func TestPutMoreWithHeaderNotZero(t *testing.T) {
elements := [][]byte{
[]byte("hello"),
[]byte("world"),
[]byte("again"),
}
queue := NewQueue(4)
for i := range elements {
queue.Put(elements[i])
}
// take 1
body, ok := queue.Take()
assert.True(t, ok)
element, ok := body.([]byte)
assert.True(t, ok)
assert.Equal(t, element, []byte("hello"))
// put more
queue.Put([]byte("b4"))
queue.Put([]byte("b5")) // will store in elements[0]
queue.Put([]byte("b6")) // cause expansion
results := [][]byte{
[]byte("world"),
[]byte("again"),
[]byte("b4"),
[]byte("b5"),
[]byte("b6"),
}
for _, element := range results {
body, ok := queue.Take()
assert.True(t, ok)
assert.Equal(t, string(element), string(body.([]byte)))
}
}

View File

@@ -4,7 +4,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
type ( type (

View File

@@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
) )
const duration = time.Millisecond * 50 const duration = time.Millisecond * 50

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
) )
func TestSafeMap(t *testing.T) { func TestSafeMap(t *testing.T) {

View File

@@ -1,8 +1,8 @@
package collection package collection
import ( import (
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
const ( const (

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
func init() { func init() {

View File

@@ -2,16 +2,22 @@ package collection
import ( import (
"container/list" "container/list"
"errors"
"fmt" "fmt"
"time" "time"
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/threading" "github.com/zeromicro/go-zero/core/threading"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
const drainWorkers = 8 const drainWorkers = 8
var (
ErrClosed = errors.New("TimingWheel is closed already")
ErrArgument = errors.New("incorrect task argument")
)
type ( type (
// Execute defines the method to execute the task. // Execute defines the method to execute the task.
Execute func(key, value interface{}) Execute func(key, value interface{})
@@ -59,14 +65,15 @@ type (
// NewTimingWheel returns a TimingWheel. // NewTimingWheel returns a TimingWheel.
func NewTimingWheel(interval time.Duration, numSlots int, execute Execute) (*TimingWheel, error) { func NewTimingWheel(interval time.Duration, numSlots int, execute Execute) (*TimingWheel, error) {
if interval <= 0 || numSlots <= 0 || execute == nil { if interval <= 0 || numSlots <= 0 || execute == nil {
return nil, fmt.Errorf("interval: %v, slots: %d, execute: %p", interval, numSlots, execute) return nil, fmt.Errorf("interval: %v, slots: %d, execute: %p",
interval, numSlots, execute)
} }
return newTimingWheelWithClock(interval, numSlots, execute, timex.NewTicker(interval)) return newTimingWheelWithClock(interval, numSlots, execute, timex.NewTicker(interval))
} }
func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execute, ticker timex.Ticker) ( func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execute,
*TimingWheel, error) { ticker timex.Ticker) (*TimingWheel, error) {
tw := &TimingWheel{ tw := &TimingWheel{
interval: interval, interval: interval,
ticker: ticker, ticker: ticker,
@@ -89,47 +96,67 @@ func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execu
} }
// Drain drains all items and executes them. // Drain drains all items and executes them.
func (tw *TimingWheel) Drain(fn func(key, value interface{})) { func (tw *TimingWheel) Drain(fn func(key, value interface{})) error {
tw.drainChannel <- fn select {
case tw.drainChannel <- fn:
return nil
case <-tw.stopChannel:
return ErrClosed
}
} }
// MoveTimer moves the task with the given key to the given delay. // MoveTimer moves the task with the given key to the given delay.
func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) { func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) error {
if delay <= 0 || key == nil { if delay <= 0 || key == nil {
return return ErrArgument
} }
tw.moveChannel <- baseEntry{ select {
case tw.moveChannel <- baseEntry{
delay: delay, delay: delay,
key: key, key: key,
}:
return nil
case <-tw.stopChannel:
return ErrClosed
} }
} }
// RemoveTimer removes the task with the given key. // RemoveTimer removes the task with the given key.
func (tw *TimingWheel) RemoveTimer(key interface{}) { func (tw *TimingWheel) RemoveTimer(key interface{}) error {
if key == nil { if key == nil {
return return ErrArgument
} }
tw.removeChannel <- key select {
case tw.removeChannel <- key:
return nil
case <-tw.stopChannel:
return ErrClosed
}
} }
// SetTimer sets the task value with the given key to the delay. // SetTimer sets the task value with the given key to the delay.
func (tw *TimingWheel) SetTimer(key, value interface{}, delay time.Duration) { func (tw *TimingWheel) SetTimer(key, value interface{}, delay time.Duration) error {
if delay <= 0 || key == nil { if delay <= 0 || key == nil {
return return ErrArgument
} }
tw.setChannel <- timingEntry{ select {
case tw.setChannel <- timingEntry{
baseEntry: baseEntry{ baseEntry: baseEntry{
delay: delay, delay: delay,
key: key, key: key,
}, },
value: value, value: value,
}:
return nil
case <-tw.stopChannel:
return ErrClosed
} }
} }
// Stop stops tw. // Stop stops tw. No more actions after stopping a TimingWheel.
func (tw *TimingWheel) Stop() { func (tw *TimingWheel) Stop() {
close(tw.stopChannel) close(tw.stopChannel)
} }

View File

@@ -8,10 +8,10 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
const ( const (
@@ -28,7 +28,6 @@ func TestTimingWheel_Drain(t *testing.T) {
ticker := timex.NewFakeTicker() ticker := timex.NewFakeTicker()
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) { tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
}, ticker) }, ticker)
defer tw.Stop()
tw.SetTimer("first", 3, testStep*4) tw.SetTimer("first", 3, testStep*4)
tw.SetTimer("second", 5, testStep*7) tw.SetTimer("second", 5, testStep*7)
tw.SetTimer("third", 7, testStep*7) tw.SetTimer("third", 7, testStep*7)
@@ -56,6 +55,8 @@ func TestTimingWheel_Drain(t *testing.T) {
}) })
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
assert.Equal(t, 0, count) assert.Equal(t, 0, count)
tw.Stop()
assert.Equal(t, ErrClosed, tw.Drain(func(key, value interface{}) {}))
} }
func TestTimingWheel_SetTimerSoon(t *testing.T) { func TestTimingWheel_SetTimerSoon(t *testing.T) {
@@ -102,6 +103,13 @@ func TestTimingWheel_SetTimerWrongDelay(t *testing.T) {
}) })
} }
func TestTimingWheel_SetTimerAfterClose(t *testing.T) {
ticker := timex.NewFakeTicker()
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
tw.Stop()
assert.Equal(t, ErrClosed, tw.SetTimer("any", 3, testStep))
}
func TestTimingWheel_MoveTimer(t *testing.T) { func TestTimingWheel_MoveTimer(t *testing.T) {
run := syncx.NewAtomicBool() run := syncx.NewAtomicBool()
ticker := timex.NewFakeTicker() ticker := timex.NewFakeTicker()
@@ -111,7 +119,6 @@ func TestTimingWheel_MoveTimer(t *testing.T) {
assert.Equal(t, 3, v.(int)) assert.Equal(t, 3, v.(int))
ticker.Done() ticker.Done()
}, ticker) }, ticker)
defer tw.Stop()
tw.SetTimer("any", 3, testStep*4) tw.SetTimer("any", 3, testStep*4)
tw.MoveTimer("any", testStep*7) tw.MoveTimer("any", testStep*7)
tw.MoveTimer("any", -testStep) tw.MoveTimer("any", -testStep)
@@ -125,6 +132,8 @@ func TestTimingWheel_MoveTimer(t *testing.T) {
} }
assert.Nil(t, ticker.Wait(waitTime)) assert.Nil(t, ticker.Wait(waitTime))
assert.True(t, run.True()) assert.True(t, run.True())
tw.Stop()
assert.Equal(t, ErrClosed, tw.MoveTimer("any", time.Millisecond))
} }
func TestTimingWheel_MoveTimerSoon(t *testing.T) { func TestTimingWheel_MoveTimerSoon(t *testing.T) {
@@ -175,6 +184,7 @@ func TestTimingWheel_RemoveTimer(t *testing.T) {
ticker.Tick() ticker.Tick()
} }
tw.Stop() tw.Stop()
assert.Equal(t, ErrClosed, tw.RemoveTimer("any"))
} }
func TestTimingWheel_SetTimer(t *testing.T) { func TestTimingWheel_SetTimer(t *testing.T) {

73
core/color/color.go Normal file
View File

@@ -0,0 +1,73 @@
package color
import "github.com/fatih/color"
const (
// NoColor is no color for both foreground and background.
NoColor Color = iota
// FgBlack is the foreground color black.
FgBlack
// FgRed is the foreground color red.
FgRed
// FgGreen is the foreground color green.
FgGreen
// FgYellow is the foreground color yellow.
FgYellow
// FgBlue is the foreground color blue.
FgBlue
// FgMagenta is the foreground color magenta.
FgMagenta
// FgCyan is the foreground color cyan.
FgCyan
// FgWhite is the foreground color white.
FgWhite
// BgBlack is the background color black.
BgBlack
// BgRed is the background color red.
BgRed
// BgGreen is the background color green.
BgGreen
// BgYellow is the background color yellow.
BgYellow
// BgBlue is the background color blue.
BgBlue
// BgMagenta is the background color magenta.
BgMagenta
// BgCyan is the background color cyan.
BgCyan
// BgWhite is the background color white.
BgWhite
)
var colors = map[Color][]color.Attribute{
FgBlack: {color.FgBlack, color.Bold},
FgRed: {color.FgRed, color.Bold},
FgGreen: {color.FgGreen, color.Bold},
FgYellow: {color.FgYellow, color.Bold},
FgBlue: {color.FgBlue, color.Bold},
FgMagenta: {color.FgMagenta, color.Bold},
FgCyan: {color.FgCyan, color.Bold},
FgWhite: {color.FgWhite, color.Bold},
BgBlack: {color.BgBlack, color.FgHiWhite, color.Bold},
BgRed: {color.BgRed, color.FgHiWhite, color.Bold},
BgGreen: {color.BgGreen, color.FgHiWhite, color.Bold},
BgYellow: {color.BgHiYellow, color.FgHiBlack, color.Bold},
BgBlue: {color.BgBlue, color.FgHiWhite, color.Bold},
BgMagenta: {color.BgMagenta, color.FgHiWhite, color.Bold},
BgCyan: {color.BgCyan, color.FgHiWhite, color.Bold},
BgWhite: {color.BgHiWhite, color.FgHiBlack, color.Bold},
}
type Color uint32
// WithColor returns a string with the given color applied.
func WithColor(text string, colour Color) string {
c := color.New(colors[colour]...)
return c.Sprint(text)
}
// WithColorPadding returns a string with the given color applied with leading and trailing spaces.
func WithColorPadding(text string, colour Color) string {
return WithColor(" "+text+" ", colour)
}

17
core/color/color_test.go Normal file
View File

@@ -0,0 +1,17 @@
package color
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithColor(t *testing.T) {
output := WithColor("Hello", BgRed)
assert.Equal(t, "Hello", output)
}
func TestWithColorPadding(t *testing.T) {
output := WithColorPadding("Hello", BgRed)
assert.Equal(t, " Hello ", output)
}

View File

@@ -6,24 +6,26 @@ import (
"log" "log"
"os" "os"
"path" "path"
"strings"
"github.com/tal-tech/go-zero/core/mapping" "github.com/zeromicro/go-zero/core/mapping"
) )
var loaders = map[string]func([]byte, interface{}) error{ var loaders = map[string]func([]byte, interface{}) error{
".json": LoadConfigFromJsonBytes, ".json": LoadFromJsonBytes,
".yaml": LoadConfigFromYamlBytes, ".toml": LoadFromTomlBytes,
".yml": LoadConfigFromYamlBytes, ".yaml": LoadFromYamlBytes,
".yml": LoadFromYamlBytes,
} }
// LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable. // Load loads config into v from file, .json, .yaml and .yml are acceptable.
func LoadConfig(file string, v interface{}, opts ...Option) error { func Load(file string, v interface{}, opts ...Option) error {
content, err := ioutil.ReadFile(file) content, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
return err return err
} }
loader, ok := loaders[path.Ext(file)] loader, ok := loaders[strings.ToLower(path.Ext(file))]
if !ok { if !ok {
return fmt.Errorf("unrecognized file type: %s", file) return fmt.Errorf("unrecognized file type: %s", file)
} }
@@ -40,19 +42,42 @@ func LoadConfig(file string, v interface{}, opts ...Option) error {
return loader(content, v) return loader(content, v)
} }
// LoadConfigFromJsonBytes loads config into v from content json bytes. // LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
func LoadConfigFromJsonBytes(content []byte, v interface{}) error { // Deprecated: use Load instead.
func LoadConfig(file string, v interface{}, opts ...Option) error {
return Load(file, v, opts...)
}
// LoadFromJsonBytes loads config into v from content json bytes.
func LoadFromJsonBytes(content []byte, v interface{}) error {
return mapping.UnmarshalJsonBytes(content, v) return mapping.UnmarshalJsonBytes(content, v)
} }
// LoadConfigFromYamlBytes loads config into v from content yaml bytes. // LoadConfigFromJsonBytes loads config into v from content json bytes.
func LoadConfigFromYamlBytes(content []byte, v interface{}) error { // Deprecated: use LoadFromJsonBytes instead.
func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
return LoadFromJsonBytes(content, v)
}
// LoadFromTomlBytes loads config into v from content toml bytes.
func LoadFromTomlBytes(content []byte, v interface{}) error {
return mapping.UnmarshalTomlBytes(content, v)
}
// LoadFromYamlBytes loads config into v from content yaml bytes.
func LoadFromYamlBytes(content []byte, v interface{}) error {
return mapping.UnmarshalYamlBytes(content, v) return mapping.UnmarshalYamlBytes(content, v)
} }
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
// Deprecated: use LoadFromYamlBytes instead.
func LoadConfigFromYamlBytes(content []byte, v interface{}) error {
return LoadFromYamlBytes(content, v)
}
// MustLoad loads config into v from path, exits on error. // MustLoad loads config into v from path, exits on error.
func MustLoad(path string, v interface{}, opts ...Option) { func MustLoad(path string, v interface{}, opts ...Option) {
if err := LoadConfig(path, v, opts...); err != nil { if err := Load(path, v, opts...); err != nil {
log.Fatalf("error: config file %s, %s", path, err.Error()) log.Fatalf("error: config file %s, %s", path, err.Error())
} }
} }

View File

@@ -6,19 +6,19 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs" "github.com/zeromicro/go-zero/core/fs"
"github.com/tal-tech/go-zero/core/hash" "github.com/zeromicro/go-zero/core/hash"
) )
func TestLoadConfig_notExists(t *testing.T) { func TestLoadConfig_notExists(t *testing.T) {
assert.NotNil(t, LoadConfig("not_a_file", nil)) assert.NotNil(t, Load("not_a_file", nil))
} }
func TestLoadConfig_notRecogFile(t *testing.T) { func TestLoadConfig_notRecogFile(t *testing.T) {
filename, err := fs.TempFilenameWithText("hello") filename, err := fs.TempFilenameWithText("hello")
assert.Nil(t, err) assert.Nil(t, err)
defer os.Remove(filename) defer os.Remove(filename)
assert.NotNil(t, LoadConfig(filename, nil)) assert.NotNil(t, Load(filename, nil))
} }
func TestConfigJson(t *testing.T) { func TestConfigJson(t *testing.T) {
@@ -57,6 +57,58 @@ func TestConfigJson(t *testing.T) {
} }
} }
func TestConfigToml(t *testing.T) {
text := `a = "foo"
b = 1
c = "${FOO}"
d = "abcd!@#$112"
`
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(".toml", text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val)
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C)
assert.Equal(t, "abcd!@#$112", val.D)
}
func TestConfigTomlEnv(t *testing.T) {
text := `a = "foo"
b = 1
c = "${FOO}"
d = "abcd!@#112"
`
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(".toml", text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val, UseEnv())
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "2", val.C)
assert.Equal(t, "abcd!@#112", val.D)
}
func TestConfigJsonEnv(t *testing.T) { func TestConfigJsonEnv(t *testing.T) {
tests := []string{ tests := []string{
".json", ".json",

View File

@@ -7,7 +7,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/tal-tech/go-zero/core/iox" "github.com/zeromicro/go-zero/core/iox"
) )
// PropertyError represents a configuration error message. // PropertyError represents a configuration error message.

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs" "github.com/zeromicro/go-zero/core/fs"
) )
func TestProperties(t *testing.T) { func TestProperties(t *testing.T) {

57
core/conf/readme.md Normal file
View File

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

View File

@@ -3,7 +3,7 @@ package contextx
import ( import (
"context" "context"
"github.com/tal-tech/go-zero/core/mapping" "github.com/zeromicro/go-zero/core/mapping"
) )
const contextTagKey = "ctx" const contextTagKey = "ctx"

View File

@@ -1,7 +1,14 @@
package discov package discov
import "github.com/tal-tech/go-zero/core/discov/internal" import "github.com/zeromicro/go-zero/core/discov/internal"
// RegisterAccount registers the username/password to the given etcd cluster.
func RegisterAccount(endpoints []string, user, pass string) { func RegisterAccount(endpoints []string, user, pass string) {
internal.AddAccount(endpoints, user, pass) internal.AddAccount(endpoints, user, pass)
} }
// RegisterTLS registers the CertFile/CertKeyFile/CACertFile to the given etcd.
func RegisterTLS(endpoints []string, certFile, certKeyFile, caFile string,
insecureSkipVerify bool) error {
return internal.AddTLS(endpoints, certFile, certKeyFile, caFile, insecureSkipVerify)
}

View File

@@ -4,8 +4,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/discov/internal" "github.com/zeromicro/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
) )
func TestRegisterAccount(t *testing.T) { func TestRegisterAccount(t *testing.T) {

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/tal-tech/go-zero/core/discov/internal" "github.com/zeromicro/go-zero/core/discov/internal"
) )
const ( const (

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/discov/internal" "github.com/zeromicro/go-zero/core/discov/internal"
) )
var mockLock sync.Mutex var mockLock sync.Mutex

View File

@@ -2,12 +2,23 @@ package discov
import "errors" import "errors"
var (
// errEmptyEtcdHosts indicates that etcd hosts are empty.
errEmptyEtcdHosts = errors.New("empty etcd hosts")
// errEmptyEtcdKey indicates that etcd key is empty.
errEmptyEtcdKey = errors.New("empty etcd key")
)
// EtcdConf is the config item with the given key on etcd. // EtcdConf is the config item with the given key on etcd.
type EtcdConf struct { type EtcdConf struct {
Hosts []string Hosts []string
Key string Key string
User string `json:",optional"` User string `json:",optional"`
Pass string `json:",optional"` Pass string `json:",optional"`
CertFile string `json:",optional"`
CertKeyFile string `json:",optional=CertFile"`
CACertFile string `json:",optional=CertFile"`
InsecureSkipVerify bool `json:",optional"`
} }
// HasAccount returns if account provided. // HasAccount returns if account provided.
@@ -15,12 +26,17 @@ func (c EtcdConf) HasAccount() bool {
return len(c.User) > 0 && len(c.Pass) > 0 return len(c.User) > 0 && len(c.Pass) > 0
} }
// HasTLS returns if TLS CertFile/CertKeyFile/CACertFile are provided.
func (c EtcdConf) HasTLS() bool {
return len(c.CertFile) > 0 && len(c.CertKeyFile) > 0 && len(c.CACertFile) > 0
}
// Validate validates c. // Validate validates c.
func (c EtcdConf) Validate() error { func (c EtcdConf) Validate() error {
if len(c.Hosts) == 0 { if len(c.Hosts) == 0 {
return errors.New("empty etcd hosts") return errEmptyEtcdHosts
} else if len(c.Key) == 0 { } else if len(c.Key) == 0 {
return errors.New("empty etcd key") return errEmptyEtcdKey
} else { } else {
return nil return nil
} }

View File

@@ -1,17 +1,25 @@
package internal package internal
import "sync" import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"sync"
)
var (
accounts = make(map[string]Account)
tlsConfigs = make(map[string]*tls.Config)
lock sync.RWMutex
)
// Account holds the username/password for an etcd cluster.
type Account struct { type Account struct {
User string User string
Pass string Pass string
} }
var ( // AddAccount adds the username/password for the given etcd cluster.
accounts = make(map[string]Account)
lock sync.RWMutex
)
func AddAccount(endpoints []string, user, pass string) { func AddAccount(endpoints []string, user, pass string) {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
@@ -22,6 +30,33 @@ func AddAccount(endpoints []string, user, pass string) {
} }
} }
// AddTLS adds the tls cert files for the given etcd cluster.
func AddTLS(endpoints []string, certFile, certKeyFile, caFile string, insecureSkipVerify bool) error {
cert, err := tls.LoadX509KeyPair(certFile, certKeyFile)
if err != nil {
return err
}
caData, err := ioutil.ReadFile(caFile)
if err != nil {
return err
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(caData)
lock.Lock()
defer lock.Unlock()
tlsConfigs[getClusterKey(endpoints)] = &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: pool,
InsecureSkipVerify: insecureSkipVerify,
}
return nil
}
// GetAccount gets the username/password for the given etcd cluster.
func GetAccount(endpoints []string) (Account, bool) { func GetAccount(endpoints []string) (Account, bool) {
lock.RLock() lock.RLock()
defer lock.RUnlock() defer lock.RUnlock()
@@ -29,3 +64,12 @@ func GetAccount(endpoints []string) (Account, bool) {
account, ok := accounts[getClusterKey(endpoints)] account, ok := accounts[getClusterKey(endpoints)]
return account, ok return account, ok
} }
// GetTLS gets the tls config for the given etcd cluster.
func GetTLS(endpoints []string) (*tls.Config, bool) {
lock.RLock()
defer lock.RUnlock()
cfg, ok := tlsConfigs[getClusterKey(endpoints)]
return cfg, ok
}

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
) )
func TestAccount(t *testing.T) { func TestAccount(t *testing.T) {

View File

@@ -9,11 +9,11 @@ import (
"sync" "sync"
"time" "time"
"github.com/tal-tech/go-zero/core/contextx" "github.com/zeromicro/go-zero/core/contextx"
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
"github.com/tal-tech/go-zero/core/threading" "github.com/zeromicro/go-zero/core/threading"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
@@ -37,25 +37,35 @@ func GetRegistry() *Registry {
// GetConn returns an etcd client connection associated with given endpoints. // GetConn returns an etcd client connection associated with given endpoints.
func (r *Registry) GetConn(endpoints []string) (EtcdClient, error) { func (r *Registry) GetConn(endpoints []string) (EtcdClient, error) {
return r.getCluster(endpoints).getClient() c, _ := r.getCluster(endpoints)
return c.getClient()
} }
// Monitor monitors the key on given etcd endpoints, notify with the given UpdateListener. // Monitor monitors the key on given etcd endpoints, notify with the given UpdateListener.
func (r *Registry) Monitor(endpoints []string, key string, l UpdateListener) error { func (r *Registry) Monitor(endpoints []string, key string, l UpdateListener) error {
return r.getCluster(endpoints).monitor(key, l) c, exists := r.getCluster(endpoints)
// if exists, the existing values should be updated to the listener.
if exists {
kvs := c.getCurrent(key)
for _, kv := range kvs {
l.OnAdd(kv)
}
}
return c.monitor(key, l)
} }
func (r *Registry) getCluster(endpoints []string) *cluster { func (r *Registry) getCluster(endpoints []string) (c *cluster, exists bool) {
clusterKey := getClusterKey(endpoints) clusterKey := getClusterKey(endpoints)
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
c, ok := r.clusters[clusterKey] c, exists = r.clusters[clusterKey]
if !ok { if !exists {
c = newCluster(endpoints) c = newCluster(endpoints)
r.clusters[clusterKey] = c r.clusters[clusterKey] = c
} }
return c return
} }
type cluster struct { type cluster struct {
@@ -94,6 +104,21 @@ func (c *cluster) getClient() (EtcdClient, error) {
return val.(EtcdClient), nil return val.(EtcdClient), nil
} }
func (c *cluster) getCurrent(key string) []KV {
c.lock.Lock()
defer c.lock.Unlock()
var kvs []KV
for k, v := range c.values[key] {
kvs = append(kvs, KV{
Key: k,
Val: v,
})
}
return kvs
}
func (c *cluster) handleChanges(key string, kvs []KV) { func (c *cluster) handleChanges(key string, kvs []KV) {
var add []KV var add []KV
var remove []KV var remove []KV
@@ -166,9 +191,11 @@ func (c *cluster) handleWatchEvents(key string, events []*clientv3.Event) {
}) })
} }
case clientv3.EventTypeDelete: case clientv3.EventTypeDelete:
c.lock.Lock()
if vals, ok := c.values[key]; ok { if vals, ok := c.values[key]; ok {
delete(vals, string(ev.Kv.Key)) delete(vals, string(ev.Kv.Key))
} }
c.lock.Unlock()
for _, l := range listeners { for _, l := range listeners {
l.OnDelete(KV{ l.OnDelete(KV{
Key: string(ev.Kv.Key), Key: string(ev.Kv.Key),
@@ -197,14 +224,12 @@ func (c *cluster) load(cli EtcdClient, key string) {
} }
var kvs []KV var kvs []KV
c.lock.Lock()
for _, ev := range resp.Kvs { for _, ev := range resp.Kvs {
kvs = append(kvs, KV{ kvs = append(kvs, KV{
Key: string(ev.Key), Key: string(ev.Key),
Val: string(ev.Value), Val: string(ev.Value),
}) })
} }
c.lock.Unlock()
c.handleChanges(key, kvs) c.handleChanges(key, kvs)
} }
@@ -314,6 +339,9 @@ func DialClient(endpoints []string) (EtcdClient, error) {
cfg.Username = account.User cfg.Username = account.User
cfg.Password = account.Pass cfg.Password = account.Pass
} }
if tlsCfg, ok := GetTLS(endpoints); ok {
cfg.TLS = tlsCfg
}
return clientv3.New(cfg) return clientv3.New(cfg)
} }

View File

@@ -7,10 +7,10 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/contextx" "github.com/zeromicro/go-zero/core/contextx"
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
"go.etcd.io/etcd/api/v3/mvccpb" "go.etcd.io/etcd/api/v3/mvccpb"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
@@ -34,9 +34,9 @@ func setMockClient(cli EtcdClient) func() {
func TestGetCluster(t *testing.T) { func TestGetCluster(t *testing.T) {
AddAccount([]string{"first"}, "foo", "bar") AddAccount([]string{"first"}, "foo", "bar")
c1 := GetRegistry().getCluster([]string{"first"}) c1, _ := GetRegistry().getCluster([]string{"first"})
c2 := GetRegistry().getCluster([]string{"second"}) c2, _ := GetRegistry().getCluster([]string{"second"})
c3 := GetRegistry().getCluster([]string{"first"}) c3, _ := GetRegistry().getCluster([]string{"first"})
assert.Equal(t, c1, c3) assert.Equal(t, c1, c3)
assert.NotEqual(t, c1, c2) assert.NotEqual(t, c1, c2)
} }

View File

@@ -1,12 +1,12 @@
package discov package discov
import ( import (
"github.com/tal-tech/go-zero/core/discov/internal" "github.com/zeromicro/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/proc" "github.com/zeromicro/go-zero/core/proc"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
"github.com/tal-tech/go-zero/core/threading" "github.com/zeromicro/go-zero/core/threading"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
@@ -145,16 +145,23 @@ func (p *Publisher) revoke(cli internal.EtcdClient) {
} }
} }
// WithPubEtcdAccount provides the etcd username/password.
func WithPubEtcdAccount(user, pass string) PubOption {
return func(pub *Publisher) {
internal.AddAccount(pub.endpoints, user, pass)
}
}
// WithId customizes a Publisher with the id. // WithId customizes a Publisher with the id.
func WithId(id int64) PubOption { func WithId(id int64) PubOption {
return func(publisher *Publisher) { return func(publisher *Publisher) {
publisher.id = id publisher.id = id
} }
} }
// WithPubEtcdAccount provides the etcd username/password.
func WithPubEtcdAccount(user, pass string) PubOption {
return func(pub *Publisher) {
RegisterAccount(pub.endpoints, user, pass)
}
}
// WithPubEtcdTLS provides the etcd CertFile/CertKeyFile/CACertFile.
func WithPubEtcdTLS(certFile, certKeyFile, caFile string, insecureSkipVerify bool) PubOption {
return func(pub *Publisher) {
logx.Must(RegisterTLS(pub.endpoints, certFile, certKeyFile, caFile, insecureSkipVerify))
}
}

View File

@@ -8,10 +8,10 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/discov/internal" "github.com/zeromicro/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )

View File

@@ -4,8 +4,9 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/tal-tech/go-zero/core/discov/internal" "github.com/zeromicro/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/syncx"
) )
type ( type (
@@ -58,9 +59,17 @@ func Exclusive() SubOption {
} }
} }
// WithSubEtcdAccount provides the etcd username/password.
func WithSubEtcdAccount(user, pass string) SubOption { func WithSubEtcdAccount(user, pass string) SubOption {
return func(sub *Subscriber) { return func(sub *Subscriber) {
internal.AddAccount(sub.endpoints, user, pass) RegisterAccount(sub.endpoints, user, pass)
}
}
// WithSubEtcdTLS provides the etcd CertFile/CertKeyFile/CACertFile.
func WithSubEtcdTLS(certFile, certKeyFile, caFile string, insecureSkipVerify bool) SubOption {
return func(sub *Subscriber) {
logx.Must(RegisterTLS(sub.endpoints, certFile, certKeyFile, caFile, insecureSkipVerify))
} }
} }

View File

@@ -5,8 +5,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/discov/internal" "github.com/zeromicro/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
) )
const ( const (

View File

@@ -11,10 +11,12 @@ type (
errorArray []error errorArray []error
) )
// Add adds err to be. // Add adds errs to be, nil errors are ignored.
func (be *BatchError) Add(err error) { func (be *BatchError) Add(errs ...error) {
if err != nil { for _, err := range errs {
be.errs = append(be.errs, err) if err != nil {
be.errs = append(be.errs, err)
}
} }
} }

21
core/errorx/wrap.go Normal file
View File

@@ -0,0 +1,21 @@
package errorx
import "fmt"
// Wrap returns an error that wraps err with given message.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %w", message, err)
}
// Wrapf returns an error that wraps err with given format and args.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err)
}

24
core/errorx/wrap_test.go Normal file
View File

@@ -0,0 +1,24 @@
package errorx
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWrap(t *testing.T) {
assert.Nil(t, Wrap(nil, "test"))
assert.Equal(t, "foo: bar", Wrap(errors.New("bar"), "foo").Error())
err := errors.New("foo")
assert.True(t, errors.Is(Wrap(err, "bar"), err))
}
func TestWrapf(t *testing.T) {
assert.Nil(t, Wrapf(nil, "%s", "test"))
assert.Equal(t, "foo bar: quz", Wrapf(errors.New("quz"), "foo %s", "bar").Error())
err := errors.New("foo")
assert.True(t, errors.Is(Wrapf(err, "foo %s", "bar"), err))
}

View File

@@ -4,7 +4,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/tal-tech/go-zero/core/threading" "github.com/zeromicro/go-zero/core/threading"
) )
// A DelayExecutor delays a tasks on given delay interval. // A DelayExecutor delays a tasks on given delay interval.

View File

@@ -3,8 +3,8 @@ package executors
import ( import (
"time" "time"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
// A LessExecutor is an executor to limit execution once within given time interval. // A LessExecutor is an executor to limit execution once within given time interval.

View File

@@ -5,7 +5,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
func TestLessExecutor_DoOrDiscard(t *testing.T) { func TestLessExecutor_DoOrDiscard(t *testing.T) {

View File

@@ -6,11 +6,11 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/proc" "github.com/zeromicro/go-zero/core/proc"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
"github.com/tal-tech/go-zero/core/threading" "github.com/zeromicro/go-zero/core/threading"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
const idleRound = 10 const idleRound = 10

View File

@@ -8,7 +8,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
const threshold = 10 const threshold = 10

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs" "github.com/zeromicro/go-zero/core/fs"
) )
const ( const (

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs" "github.com/zeromicro/go-zero/core/fs"
) )
func TestSplitLineChunks(t *testing.T) { func TestSplitLineChunks(t *testing.T) {

View File

@@ -5,6 +5,9 @@ import (
"os" "os"
) )
// errExceedFileSize indicates that the file size is exceeded.
var errExceedFileSize = errors.New("exceed file size")
// A RangeReader is used to read a range of content from a file. // A RangeReader is used to read a range of content from a file.
type RangeReader struct { type RangeReader struct {
file *os.File file *os.File
@@ -29,7 +32,7 @@ func (rr *RangeReader) Read(p []byte) (n int, err error) {
} }
if rr.stop < rr.start || rr.start >= stat.Size() { if rr.stop < rr.start || rr.start >= stat.Size() {
return 0, errors.New("exceed file size") return 0, errExceedFileSize
} }
if rr.stop-rr.start < int64(len(p)) { if rr.stop-rr.start < int64(len(p)) {

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs" "github.com/zeromicro/go-zero/core/fs"
) )
func TestRangeReader(t *testing.T) { func TestRangeReader(t *testing.T) {

View File

@@ -4,7 +4,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/tal-tech/go-zero/core/hash" "github.com/zeromicro/go-zero/core/hash"
) )
// TempFileWithText creates the temporary file with the given content, // TempFileWithText creates the temporary file with the given content,

49
core/fs/temps_test.go Normal file
View File

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

View File

@@ -1,6 +1,6 @@
package fx package fx
import "github.com/tal-tech/go-zero/core/threading" import "github.com/zeromicro/go-zero/core/threading"
// Parallel runs fns parallelly and waits for done. // Parallel runs fns parallelly and waits for done.
func Parallel(fns ...func()) { func Parallel(fns ...func()) {

View File

@@ -1,6 +1,6 @@
package fx package fx
import "github.com/tal-tech/go-zero/core/errorx" import "github.com/zeromicro/go-zero/core/errorx"
const defaultRetryTimes = 3 const defaultRetryTimes = 3

View File

@@ -4,9 +4,9 @@ import (
"sort" "sort"
"sync" "sync"
"github.com/tal-tech/go-zero/core/collection" "github.com/zeromicro/go-zero/core/collection"
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/threading" "github.com/zeromicro/go-zero/core/threading"
) )
const ( const (
@@ -90,6 +90,8 @@ func Range(source <-chan interface{}) Stream {
func (s Stream) AllMach(predicate func(item interface{}) bool) bool { func (s Stream) AllMach(predicate func(item interface{}) bool) bool {
for item := range s.source { for item := range s.source {
if !predicate(item) { if !predicate(item) {
// make sure the former goroutine not block, and current func returns fast.
go drain(s.source)
return false return false
} }
} }
@@ -103,6 +105,8 @@ func (s Stream) AllMach(predicate func(item interface{}) bool) bool {
func (s Stream) AnyMach(predicate func(item interface{}) bool) bool { func (s Stream) AnyMach(predicate func(item interface{}) bool) bool {
for item := range s.source { for item := range s.source {
if predicate(item) { if predicate(item) {
// make sure the former goroutine not block, and current func returns fast.
go drain(s.source)
return true return true
} }
} }
@@ -186,8 +190,7 @@ func (s Stream) Distinct(fn KeyFunc) Stream {
// Done waits all upstreaming operations to be done. // Done waits all upstreaming operations to be done.
func (s Stream) Done() { func (s Stream) Done() {
for range s.source { drain(s.source)
}
} }
// Filter filters the items by the given FilterFunc. // Filter filters the items by the given FilterFunc.
@@ -199,9 +202,22 @@ func (s Stream) Filter(fn FilterFunc, opts ...Option) Stream {
}, opts...) }, opts...)
} }
// First returns the first item, nil if no items.
func (s Stream) First() interface{} {
for item := range s.source {
// make sure the former goroutine not block, and current func returns fast.
go drain(s.source)
return item
}
return nil
}
// ForAll handles the streaming elements from the source and no later streams. // ForAll handles the streaming elements from the source and no later streams.
func (s Stream) ForAll(fn ForAllFunc) { func (s Stream) ForAll(fn ForAllFunc) {
fn(s.source) fn(s.source)
// avoid goroutine leak on fn not consuming all items.
go drain(s.source)
} }
// ForEach seals the Stream with the ForEachFunc on each item, no successive operations. // ForEach seals the Stream with the ForEachFunc on each item, no successive operations.
@@ -246,11 +262,14 @@ func (s Stream) Head(n int64) Stream {
} }
if n == 0 { if n == 0 {
// let successive method go ASAP even we have more items to skip // let successive method go ASAP even we have more items to skip
// why we don't just break the loop, because if break,
// this former goroutine will block forever, which will cause goroutine leak.
close(source) close(source)
// why we don't just break the loop, and drain to consume all items.
// because if breaks, this former goroutine will block forever,
// which will cause goroutine leak.
drain(s.source)
} }
} }
// not enough items in s.source, but we need to let successive method to go ASAP.
if n > 0 { if n > 0 {
close(source) close(source)
} }
@@ -259,6 +278,13 @@ func (s Stream) Head(n int64) Stream {
return Range(source) return Range(source)
} }
// Last returns the last item, or nil if no items.
func (s Stream) Last() (item interface{}) {
for item = range s.source {
}
return
}
// Map converts each item to another corresponding item, which means it's a 1:1 model. // Map converts each item to another corresponding item, which means it's a 1:1 model.
func (s Stream) Map(fn MapFunc, opts ...Option) Stream { func (s Stream) Map(fn MapFunc, opts ...Option) Stream {
return s.Walk(func(item interface{}, pipe chan<- interface{}) { return s.Walk(func(item interface{}, pipe chan<- interface{}) {
@@ -280,6 +306,21 @@ func (s Stream) Merge() Stream {
return Range(source) return Range(source)
} }
// NoneMatch returns whether all elements of this stream don't 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) NoneMatch(predicate func(item interface{}) bool) bool {
for item := range s.source {
if predicate(item) {
// make sure the former goroutine not block, and current func returns fast.
go drain(s.source)
return false
}
}
return true
}
// Parallel applies the given ParallelFunc to each item concurrently with given number of workers. // Parallel applies the given ParallelFunc to each item concurrently with given number of workers.
func (s Stream) Parallel(fn ParallelFunc, opts ...Option) { func (s Stream) Parallel(fn ParallelFunc, opts ...Option) {
s.Walk(func(item interface{}, pipe chan<- interface{}) { s.Walk(func(item interface{}, pipe chan<- interface{}) {
@@ -411,15 +452,12 @@ func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
var wg sync.WaitGroup var wg sync.WaitGroup
pool := make(chan lang.PlaceholderType, option.workers) pool := make(chan lang.PlaceholderType, option.workers)
for { for item := range s.source {
// important, used in another goroutine
val := item
pool <- lang.Placeholder pool <- lang.Placeholder
item, ok := <-s.source
if !ok {
<-pool
break
}
wg.Add(1) wg.Add(1)
// better to safely run caller defined method // better to safely run caller defined method
threading.GoSafe(func() { threading.GoSafe(func() {
defer func() { defer func() {
@@ -427,7 +465,7 @@ func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
<-pool <-pool
}() }()
fn(item, pipe) fn(val, pipe)
}) })
} }
@@ -439,22 +477,19 @@ func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
} }
func (s Stream) walkUnlimited(fn WalkFunc, option *rxOptions) Stream { func (s Stream) walkUnlimited(fn WalkFunc, option *rxOptions) Stream {
pipe := make(chan interface{}, defaultWorkers) pipe := make(chan interface{}, option.workers)
go func() { go func() {
var wg sync.WaitGroup var wg sync.WaitGroup
for { for item := range s.source {
item, ok := <-s.source // important, used in another goroutine
if !ok { val := item
break
}
wg.Add(1) wg.Add(1)
// better to safely run caller defined method // better to safely run caller defined method
threading.GoSafe(func() { threading.GoSafe(func() {
defer wg.Done() defer wg.Done()
fn(item, pipe) fn(val, pipe)
}) })
} }
@@ -465,14 +500,14 @@ func (s Stream) walkUnlimited(fn WalkFunc, option *rxOptions) Stream {
return Range(pipe) return Range(pipe)
} }
// UnlimitedWorkers lets the caller to use as many workers as the tasks. // UnlimitedWorkers lets the caller use as many workers as the tasks.
func UnlimitedWorkers() Option { func UnlimitedWorkers() Option {
return func(opts *rxOptions) { return func(opts *rxOptions) {
opts.unlimitedWorkers = true opts.unlimitedWorkers = true
} }
} }
// WithWorkers lets the caller to customize the concurrent workers. // WithWorkers lets the caller customize the concurrent workers.
func WithWorkers(workers int) Option { func WithWorkers(workers int) Option {
return func(opts *rxOptions) { return func(opts *rxOptions) {
if workers < minWorkers { if workers < minWorkers {
@@ -483,6 +518,7 @@ func WithWorkers(workers int) Option {
} }
} }
// buildOptions returns a rxOptions with given customizations.
func buildOptions(opts ...Option) *rxOptions { func buildOptions(opts ...Option) *rxOptions {
options := newOptions() options := newOptions()
for _, opt := range opts { for _, opt := range opts {
@@ -492,6 +528,13 @@ func buildOptions(opts ...Option) *rxOptions {
return options return options
} }
// drain drains the given channel.
func drain(channel <-chan interface{}) {
for range channel {
}
}
// newOptions returns a default rxOptions.
func newOptions() *rxOptions { func newOptions() *rxOptions {
return &rxOptions{ return &rxOptions{
workers: defaultWorkers, workers: defaultWorkers,

View File

@@ -13,324 +13,494 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
"go.uber.org/goleak"
) )
func TestBuffer(t *testing.T) { func TestBuffer(t *testing.T) {
const N = 5 runCheckedTest(t, func(t *testing.T) {
var count int32 const N = 5
var wait sync.WaitGroup var count int32
wait.Add(1) var wait sync.WaitGroup
From(func(source chan<- interface{}) { wait.Add(1)
ticker := time.NewTicker(10 * time.Millisecond) From(func(source chan<- interface{}) {
defer ticker.Stop() ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
for i := 0; i < 2*N; i++ { for i := 0; i < 2*N; i++ {
select { select {
case source <- i: case source <- i:
atomic.AddInt32(&count, 1) atomic.AddInt32(&count, 1)
case <-ticker.C: case <-ticker.C:
wait.Done() wait.Done()
return return
}
} }
} }).Buffer(N).ForAll(func(pipe <-chan interface{}) {
}).Buffer(N).ForAll(func(pipe <-chan interface{}) { wait.Wait()
wait.Wait() // why N+1, because take one more to wait for sending into the channel
// why N+1, because take one more to wait for sending into the channel assert.Equal(t, int32(N+1), atomic.LoadInt32(&count))
assert.Equal(t, int32(N+1), atomic.LoadInt32(&count)) })
}) })
} }
func TestBufferNegative(t *testing.T) { func TestBufferNegative(t *testing.T) {
var result int runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4).Buffer(-1).Reduce(func(pipe <-chan interface{}) (interface{}, error) { var result int
for item := range pipe { Just(1, 2, 3, 4).Buffer(-1).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
result += item.(int) for item := range pipe {
} result += item.(int)
return result, nil }
return result, nil
})
assert.Equal(t, 10, result)
}) })
assert.Equal(t, 10, result)
} }
func TestCount(t *testing.T) { func TestCount(t *testing.T) {
tests := []struct { runCheckedTest(t, func(t *testing.T) {
name string tests := []struct {
elements []interface{} name string
}{ elements []interface{}
{ }{
name: "no elements with nil", {
}, name: "no elements with nil",
{ },
name: "no elements", {
elements: []interface{}{}, name: "no elements",
}, elements: []interface{}{},
{ },
name: "1 element", {
elements: []interface{}{1}, name: "1 element",
}, elements: []interface{}{1},
{ },
name: "multiple elements", {
elements: []interface{}{1, 2, 3}, name: "multiple elements",
}, elements: []interface{}{1, 2, 3},
} },
}
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
val := Just(test.elements...).Count() val := Just(test.elements...).Count()
assert.Equal(t, len(test.elements), val) assert.Equal(t, len(test.elements), val)
}) })
} }
})
} }
func TestDone(t *testing.T) { func TestDone(t *testing.T) {
var count int32 runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3).Walk(func(item interface{}, pipe chan<- interface{}) { var count int32
time.Sleep(time.Millisecond * 100) Just(1, 2, 3).Walk(func(item interface{}, pipe chan<- interface{}) {
atomic.AddInt32(&count, int32(item.(int))) time.Sleep(time.Millisecond * 100)
}).Done() atomic.AddInt32(&count, int32(item.(int)))
assert.Equal(t, int32(6), count) }).Done()
assert.Equal(t, int32(6), count)
})
} }
func TestJust(t *testing.T) { func TestJust(t *testing.T) {
var result int runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4).Reduce(func(pipe <-chan interface{}) (interface{}, error) { var result int
for item := range pipe { Just(1, 2, 3, 4).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
result += item.(int) for item := range pipe {
} result += item.(int)
return result, nil }
return result, nil
})
assert.Equal(t, 10, result)
}) })
assert.Equal(t, 10, result)
} }
func TestDistinct(t *testing.T) { func TestDistinct(t *testing.T) {
var result int runCheckedTest(t, func(t *testing.T) {
Just(4, 1, 3, 2, 3, 4).Distinct(func(item interface{}) interface{} { var result int
return item Just(4, 1, 3, 2, 3, 4).Distinct(func(item interface{}) interface{} {
}).Reduce(func(pipe <-chan interface{}) (interface{}, error) { return item
for item := range pipe { }).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
result += item.(int) for item := range pipe {
} result += item.(int)
return result, nil }
return result, nil
})
assert.Equal(t, 10, result)
}) })
assert.Equal(t, 10, result)
} }
func TestFilter(t *testing.T) { func TestFilter(t *testing.T) {
var result int runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4).Filter(func(item interface{}) bool { var result int
return item.(int)%2 == 0 Just(1, 2, 3, 4).Filter(func(item interface{}) bool {
}).Reduce(func(pipe <-chan interface{}) (interface{}, error) { return item.(int)%2 == 0
for item := range pipe { }).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
result += item.(int) for item := range pipe {
} result += item.(int)
return result, nil }
return result, nil
})
assert.Equal(t, 6, result)
})
}
func TestFirst(t *testing.T) {
runCheckedTest(t, func(t *testing.T) {
assert.Nil(t, Just().First())
assert.Equal(t, "foo", Just("foo").First())
assert.Equal(t, "foo", Just("foo", "bar").First())
}) })
assert.Equal(t, 6, result)
} }
func TestForAll(t *testing.T) { func TestForAll(t *testing.T) {
var result int runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4).Filter(func(item interface{}) bool { var result int
return item.(int)%2 == 0 Just(1, 2, 3, 4).Filter(func(item interface{}) bool {
}).ForAll(func(pipe <-chan interface{}) { return item.(int)%2 == 0
for item := range pipe { }).ForAll(func(pipe <-chan interface{}) {
result += item.(int) for item := range pipe {
} result += item.(int)
}
})
assert.Equal(t, 6, result)
}) })
assert.Equal(t, 6, result)
} }
func TestGroup(t *testing.T) { func TestGroup(t *testing.T) {
var groups [][]int runCheckedTest(t, func(t *testing.T) {
Just(10, 11, 20, 21).Group(func(item interface{}) interface{} { var groups [][]int
v := item.(int) Just(10, 11, 20, 21).Group(func(item interface{}) interface{} {
return v / 10 v := item.(int)
}).ForEach(func(item interface{}) { return v / 10
v := item.([]interface{}) }).ForEach(func(item interface{}) {
var group []int v := item.([]interface{})
for _, each := range v { var group []int
group = append(group, each.(int)) for _, each := range v {
} group = append(group, each.(int))
groups = append(groups, group) }
}) groups = append(groups, group)
})
assert.Equal(t, 2, len(groups)) assert.Equal(t, 2, len(groups))
for _, group := range groups { for _, group := range groups {
assert.Equal(t, 2, len(group)) assert.Equal(t, 2, len(group))
assert.True(t, group[0]/10 == group[1]/10) assert.True(t, group[0]/10 == group[1]/10)
} }
})
} }
func TestHead(t *testing.T) { func TestHead(t *testing.T) {
var result int runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4).Head(2).Reduce(func(pipe <-chan interface{}) (interface{}, error) { var result int
for item := range pipe { Just(1, 2, 3, 4).Head(2).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
result += item.(int) for item := range pipe {
} result += item.(int)
return result, nil }
return result, nil
})
assert.Equal(t, 3, result)
}) })
assert.Equal(t, 3, result)
} }
func TestHeadZero(t *testing.T) { func TestHeadZero(t *testing.T) {
assert.Panics(t, func() { runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4).Head(0).Reduce(func(pipe <-chan interface{}) (interface{}, error) { assert.Panics(t, func() {
return nil, nil Just(1, 2, 3, 4).Head(0).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
return nil, nil
})
}) })
}) })
} }
func TestHeadMore(t *testing.T) { func TestHeadMore(t *testing.T) {
var result int runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4).Head(6).Reduce(func(pipe <-chan interface{}) (interface{}, error) { var result int
for item := range pipe { Just(1, 2, 3, 4).Head(6).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
result += item.(int) for item := range pipe {
} result += item.(int)
return result, nil }
return result, nil
})
assert.Equal(t, 10, result)
})
}
func TestLast(t *testing.T) {
runCheckedTest(t, func(t *testing.T) {
goroutines := runtime.NumGoroutine()
assert.Nil(t, Just().Last())
assert.Equal(t, "foo", Just("foo").Last())
assert.Equal(t, "bar", Just("foo", "bar").Last())
// let scheduler schedule first
runtime.Gosched()
assert.Equal(t, goroutines, runtime.NumGoroutine())
}) })
assert.Equal(t, 10, result)
} }
func TestMap(t *testing.T) { func TestMap(t *testing.T) {
log.SetOutput(ioutil.Discard) runCheckedTest(t, func(t *testing.T) {
log.SetOutput(ioutil.Discard)
tests := []struct { tests := []struct {
mapper MapFunc mapper MapFunc
expect int expect int
}{ }{
{ {
mapper: func(item interface{}) interface{} { mapper: func(item interface{}) interface{} {
v := item.(int) v := item.(int)
return v * v return v * v
},
expect: 30,
}, },
expect: 30, {
}, mapper: func(item interface{}) interface{} {
{ v := item.(int)
mapper: func(item interface{}) interface{} { if v%2 == 0 {
v := item.(int) return 0
if v%2 == 0 {
return 0
}
return v * v
},
expect: 10,
},
{
mapper: func(item interface{}) interface{} {
v := item.(int)
if v%2 == 0 {
panic(v)
}
return v * v
},
expect: 10,
},
}
// Map(...) works even WithWorkers(0)
for i, test := range tests {
t.Run(stringx.Rand(), func(t *testing.T) {
var result int
var workers int
if i%2 == 0 {
workers = 0
} else {
workers = runtime.NumCPU()
}
From(func(source chan<- interface{}) {
for i := 1; i < 5; i++ {
source <- i
}
}).Map(test.mapper, WithWorkers(workers)).Reduce(
func(pipe <-chan interface{}) (interface{}, error) {
for item := range pipe {
result += item.(int)
} }
return result, nil return v * v
}) },
expect: 10,
},
{
mapper: func(item interface{}) interface{} {
v := item.(int)
if v%2 == 0 {
panic(v)
}
return v * v
},
expect: 10,
},
}
assert.Equal(t, test.expect, result) // Map(...) works even WithWorkers(0)
}) for i, test := range tests {
} t.Run(stringx.Rand(), func(t *testing.T) {
var result int
var workers int
if i%2 == 0 {
workers = 0
} else {
workers = runtime.NumCPU()
}
From(func(source chan<- interface{}) {
for i := 1; i < 5; i++ {
source <- i
}
}).Map(test.mapper, WithWorkers(workers)).Reduce(
func(pipe <-chan interface{}) (interface{}, error) {
for item := range pipe {
result += item.(int)
}
return result, nil
})
assert.Equal(t, test.expect, result)
})
}
})
} }
func TestMerge(t *testing.T) { func TestMerge(t *testing.T) {
Just(1, 2, 3, 4).Merge().ForEach(func(item interface{}) { runCheckedTest(t, func(t *testing.T) {
assert.ElementsMatch(t, []interface{}{1, 2, 3, 4}, item.([]interface{})) Just(1, 2, 3, 4).Merge().ForEach(func(item interface{}) {
assert.ElementsMatch(t, []interface{}{1, 2, 3, 4}, item.([]interface{}))
})
}) })
} }
func TestParallelJust(t *testing.T) { func TestParallelJust(t *testing.T) {
var count int32 runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3).Parallel(func(item interface{}) { var count int32
time.Sleep(time.Millisecond * 100) Just(1, 2, 3).Parallel(func(item interface{}) {
atomic.AddInt32(&count, int32(item.(int))) time.Sleep(time.Millisecond * 100)
}, UnlimitedWorkers()) atomic.AddInt32(&count, int32(item.(int)))
assert.Equal(t, int32(6), count) }, UnlimitedWorkers())
assert.Equal(t, int32(6), count)
})
} }
func TestReverse(t *testing.T) { func TestReverse(t *testing.T) {
Just(1, 2, 3, 4).Reverse().Merge().ForEach(func(item interface{}) { runCheckedTest(t, func(t *testing.T) {
assert.ElementsMatch(t, []interface{}{4, 3, 2, 1}, item.([]interface{})) Just(1, 2, 3, 4).Reverse().Merge().ForEach(func(item interface{}) {
assert.ElementsMatch(t, []interface{}{4, 3, 2, 1}, item.([]interface{}))
})
}) })
} }
func TestSort(t *testing.T) { func TestSort(t *testing.T) {
var prev int runCheckedTest(t, func(t *testing.T) {
Just(5, 3, 7, 1, 9, 6, 4, 8, 2).Sort(func(a, b interface{}) bool { var prev int
return a.(int) < b.(int) Just(5, 3, 7, 1, 9, 6, 4, 8, 2).Sort(func(a, b interface{}) bool {
}).ForEach(func(item interface{}) { return a.(int) < b.(int)
next := item.(int) }).ForEach(func(item interface{}) {
assert.True(t, prev < next) next := item.(int)
prev = next assert.True(t, prev < next)
prev = next
})
}) })
} }
func TestSplit(t *testing.T) { func TestSplit(t *testing.T) {
assert.Panics(t, func() { runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(0).Done() assert.Panics(t, func() {
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(0).Done()
})
var chunks [][]interface{}
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(4).ForEach(func(item interface{}) {
chunk := item.([]interface{})
chunks = append(chunks, chunk)
})
assert.EqualValues(t, [][]interface{}{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10},
}, chunks)
}) })
var chunks [][]interface{}
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(4).ForEach(func(item interface{}) {
chunk := item.([]interface{})
chunks = append(chunks, chunk)
})
assert.EqualValues(t, [][]interface{}{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10},
}, chunks)
} }
func TestTail(t *testing.T) { func TestTail(t *testing.T) {
var result int runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4).Tail(2).Reduce(func(pipe <-chan interface{}) (interface{}, error) { var result int
for item := range pipe { Just(1, 2, 3, 4).Tail(2).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
result += item.(int) for item := range pipe {
} result += item.(int)
return result, nil }
return result, nil
})
assert.Equal(t, 7, result)
}) })
assert.Equal(t, 7, result)
} }
func TestTailZero(t *testing.T) { func TestTailZero(t *testing.T) {
assert.Panics(t, func() { runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4).Tail(0).Reduce(func(pipe <-chan interface{}) (interface{}, error) { assert.Panics(t, func() {
return nil, nil Just(1, 2, 3, 4).Tail(0).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
return nil, nil
})
}) })
}) })
} }
func TestWalk(t *testing.T) { func TestWalk(t *testing.T) {
var result int runCheckedTest(t, func(t *testing.T) {
Just(1, 2, 3, 4, 5).Walk(func(item interface{}, pipe chan<- interface{}) { var result int
if item.(int)%2 != 0 { Just(1, 2, 3, 4, 5).Walk(func(item interface{}, pipe chan<- interface{}) {
pipe <- item if item.(int)%2 != 0 {
} pipe <- item
}, UnlimitedWorkers()).ForEach(func(item interface{}) { }
result += item.(int) }, UnlimitedWorkers()).ForEach(func(item interface{}) {
result += item.(int)
})
assert.Equal(t, 9, result)
})
}
func TestStream_AnyMach(t *testing.T) {
runCheckedTest(t, func(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) {
runCheckedTest(t, func(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 TestStream_NoneMatch(t *testing.T) {
runCheckedTest(t, func(t *testing.T) {
assetEqual(
t, true, Just(1, 2, 3).NoneMatch(func(item interface{}) bool {
return false
}),
)
assetEqual(
t, false, Just(1, 2, 3).NoneMatch(func(item interface{}) bool {
return true
}),
)
assetEqual(
t, true, Just(1, 2, 3).NoneMatch(func(item interface{}) bool {
return item.(int) == 4
}),
)
})
}
func TestConcat(t *testing.T) {
runCheckedTest(t, func(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) {
runCheckedTest(t, func(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) {
runCheckedTest(t, func(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})
}) })
assert.Equal(t, 9, result)
} }
func BenchmarkParallelMapReduce(b *testing.B) { func BenchmarkParallelMapReduce(b *testing.B) {
@@ -377,6 +547,12 @@ func BenchmarkMapReduce(b *testing.B) {
}).Map(mapper).Reduce(reducer) }).Map(mapper).Reduce(reducer)
} }
func assetEqual(t *testing.T, except, data interface{}) {
if !reflect.DeepEqual(except, data) {
t.Errorf(" %v, want %v", data, except)
}
}
func equal(t *testing.T, stream Stream, data []interface{}) { func equal(t *testing.T, stream Stream, data []interface{}) {
items := make([]interface{}, 0) items := make([]interface{}, 0)
for item := range stream.source { for item := range stream.source {
@@ -387,85 +563,7 @@ func equal(t *testing.T, stream Stream, data []interface{}) {
} }
} }
func assetEqual(t *testing.T, except, data interface{}) { func runCheckedTest(t *testing.T, fn func(t *testing.T)) {
if !reflect.DeepEqual(except, data) { defer goleak.VerifyNone(t)
t.Errorf(" %v, want %v", data, except) fn(t)
}
}
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})
} }

View File

@@ -6,8 +6,8 @@ import (
"strconv" "strconv"
"sync" "sync"
"github.com/tal-tech/go-zero/core/lang" "github.com/zeromicro/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/mapping" "github.com/zeromicro/go-zero/core/mapping"
) )
const ( const (

View File

@@ -6,7 +6,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/mathx" "github.com/zeromicro/go-zero/core/mathx"
) )
const ( const (

View File

@@ -9,8 +9,8 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs" "github.com/zeromicro/go-zero/core/fs"
"github.com/tal-tech/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
) )
func TestReadText(t *testing.T) { func TestReadText(t *testing.T) {

View File

@@ -13,6 +13,16 @@ func Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v) return json.Marshal(v)
} }
// MarshalToString marshals v into a string.
func MarshalToString(v interface{}) (string, error) {
data, err := Marshal(v)
if err != nil {
return "", err
}
return string(data), nil
}
// Unmarshal unmarshals data bytes into v. // Unmarshal unmarshals data bytes into v.
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v interface{}) error {
decoder := json.NewDecoder(bytes.NewReader(data)) decoder := json.NewDecoder(bytes.NewReader(data))
@@ -51,5 +61,5 @@ func unmarshalUseNumber(decoder *json.Decoder, v interface{}) error {
} }
func formatError(v string, err error) error { func formatError(v string, err error) error {
return fmt.Errorf("string: `%s`, error: `%s`", v, err.Error()) return fmt.Errorf("string: `%s`, error: `%w`", v, err)
} }

103
core/jsonx/json_test.go Normal file
View File

@@ -0,0 +1,103 @@
package jsonx
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMarshal(t *testing.T) {
var v = struct {
Name string `json:"name"`
Age int `json:"age"`
}{
Name: "John",
Age: 30,
}
bs, err := Marshal(v)
assert.Nil(t, err)
assert.Equal(t, `{"name":"John","age":30}`, string(bs))
}
func TestMarshalToString(t *testing.T) {
var v = struct {
Name string `json:"name"`
Age int `json:"age"`
}{
Name: "John",
Age: 30,
}
toString, err := MarshalToString(v)
assert.Nil(t, err)
assert.Equal(t, `{"name":"John","age":30}`, toString)
_, err = MarshalToString(make(chan int))
assert.NotNil(t, err)
}
func TestUnmarshal(t *testing.T) {
const s = `{"name":"John","age":30}`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := Unmarshal([]byte(s), &v)
assert.Nil(t, err)
assert.Equal(t, "John", v.Name)
assert.Equal(t, 30, v.Age)
}
func TestUnmarshalError(t *testing.T) {
const s = `{"name":"John","age":30`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := Unmarshal([]byte(s), &v)
assert.NotNil(t, err)
}
func TestUnmarshalFromString(t *testing.T) {
const s = `{"name":"John","age":30}`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := UnmarshalFromString(s, &v)
assert.Nil(t, err)
assert.Equal(t, "John", v.Name)
assert.Equal(t, 30, v.Age)
}
func TestUnmarshalFromStringError(t *testing.T) {
const s = `{"name":"John","age":30`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := UnmarshalFromString(s, &v)
assert.NotNil(t, err)
}
func TestUnmarshalFromRead(t *testing.T) {
const s = `{"name":"John","age":30}`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := UnmarshalFromReader(strings.NewReader(s), &v)
assert.Nil(t, err)
assert.Equal(t, "John", v.Name)
assert.Equal(t, 30, v.Age)
}
func TestUnmarshalFromReaderError(t *testing.T) {
const s = `{"name":"John","age":30`
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := UnmarshalFromReader(strings.NewReader(s), &v)
assert.NotNil(t, err)
}

View File

@@ -4,8 +4,8 @@ package lang
var Placeholder PlaceholderType var Placeholder PlaceholderType
type ( type (
// GenericType can be used to hold any type. // AnyType can be used to hold any type.
GenericType = interface{} AnyType = interface{}
// PlaceholderType represents a placeholder type. // PlaceholderType represents a placeholder type.
PlaceholderType = struct{} PlaceholderType = struct{}
) )

View File

@@ -1,30 +1,28 @@
package limit package limit
import ( import (
"context"
"errors" "errors"
"strconv" "strconv"
"time" "time"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/redis"
) )
const ( // to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key const periodScript = `local limit = tonumber(ARGV[1])
periodScript = `local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2]) local window = tonumber(ARGV[2])
local current = redis.call("INCRBY", KEYS[1], 1) local current = redis.call("INCRBY", KEYS[1], 1)
if current == 1 then if current == 1 then
redis.call("expire", KEYS[1], window) redis.call("expire", KEYS[1], window)
return 1 end
elseif current < limit then if current < limit then
return 1 return 1
elseif current == limit then elseif current == limit then
return 2 return 2
else else
return 0 return 0
end` end`
zoneDiff = 3600 * 8 // GMT+8 for our services
)
const ( const (
// Unknown means not initialized state. // Unknown means not initialized state.
@@ -77,7 +75,12 @@ func NewPeriodLimit(period, quota int, limitStore *redis.Redis, keyPrefix string
// Take requests a permit, it returns the permit state. // Take requests a permit, it returns the permit state.
func (h *PeriodLimit) Take(key string) (int, error) { func (h *PeriodLimit) Take(key string) (int, error) {
resp, err := h.limitStore.Eval(periodScript, []string{h.keyPrefix + key}, []string{ return h.TakeCtx(context.Background(), key)
}
// TakeCtx requests a permit with context, it returns the permit state.
func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
resp, err := h.limitStore.EvalCtx(ctx, periodScript, []string{h.keyPrefix + key}, []string{
strconv.Itoa(h.quota), strconv.Itoa(h.quota),
strconv.Itoa(h.calcExpireSeconds()), strconv.Itoa(h.calcExpireSeconds()),
}) })
@@ -104,7 +107,9 @@ func (h *PeriodLimit) Take(key string) (int, error) {
func (h *PeriodLimit) calcExpireSeconds() int { func (h *PeriodLimit) calcExpireSeconds() int {
if h.align { if h.align {
unix := time.Now().Unix() + zoneDiff now := time.Now()
_, offset := now.Zone()
unix := now.Unix() + int64(offset)
return h.period - int(unix%int64(h.period)) return h.period - int(unix%int64(h.period))
} }
@@ -112,6 +117,8 @@ func (h *PeriodLimit) calcExpireSeconds() int {
} }
// Align returns a func to customize a PeriodLimit with alignment. // Align returns a func to customize a PeriodLimit with alignment.
// For example, if we want to limit end users with 5 sms verification messages every day,
// we need to align with the local timezone and the start of the day.
func Align() PeriodOption { func Align() PeriodOption {
return func(l *PeriodLimit) { return func(l *PeriodLimit) {
l.align = true l.align = true

View File

@@ -5,8 +5,8 @@ import (
"github.com/alicebob/miniredis/v2" "github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest" "github.com/zeromicro/go-zero/core/stores/redis/redistest"
) )
func TestPeriodLimit_Take(t *testing.T) { func TestPeriodLimit_Take(t *testing.T) {
@@ -23,10 +23,9 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
const ( const (
seconds = 1 seconds = 1
total = 100
quota = 5 quota = 5
) )
l := NewPeriodLimit(seconds, quota, redis.NewRedis(s.Addr(), redis.NodeType), "periodlimit") l := NewPeriodLimit(seconds, quota, redis.New(s.Addr()), "periodlimit")
s.Close() s.Close()
val, err := l.Take("first") val, err := l.Take("first")
assert.NotNil(t, err) assert.NotNil(t, err)
@@ -66,3 +65,13 @@ func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
assert.Equal(t, 1, hitQuota) assert.Equal(t, 1, hitQuota)
assert.Equal(t, total-quota, overQuota) assert.Equal(t, total-quota, overQuota)
} }
func TestQuotaFull(t *testing.T) {
s, err := miniredis.Run()
assert.Nil(t, err)
l := NewPeriodLimit(1, 1, redis.New(s.Addr()), "periodlimit")
val, err := l.Take("first")
assert.Nil(t, err)
assert.Equal(t, HitQuota, val)
}

View File

@@ -7,8 +7,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/redis"
xrate "golang.org/x/time/rate" xrate "golang.org/x/time/rate"
) )
@@ -85,8 +85,8 @@ func (lim *TokenLimiter) Allow() bool {
} }
// AllowN reports whether n events may happen at time now. // AllowN reports whether n events may happen at time now.
// Use this method if you intend to drop / skip events that exceed the rate rate. // Use this method if you intend to drop / skip events that exceed the rate.
// Otherwise use Reserve or Wait. // Otherwise, use Reserve or Wait.
func (lim *TokenLimiter) AllowN(now time.Time, n int) bool { func (lim *TokenLimiter) AllowN(now time.Time, n int) bool {
return lim.reserveN(now, n) return lim.reserveN(now, n)
} }
@@ -112,7 +112,8 @@ func (lim *TokenLimiter) reserveN(now time.Time, n int) bool {
// Lua boolean false -> r Nil bulk reply // Lua boolean false -> r Nil bulk reply
if err == redis.Nil { if err == redis.Nil {
return false return false
} else if err != nil { }
if err != nil {
logx.Errorf("fail to use rate limiter: %s, use in-process limiter for rescue", err) logx.Errorf("fail to use rate limiter: %s, use in-process limiter for rescue", err)
lim.startMonitor() lim.startMonitor()
return lim.rescueLimiter.AllowN(now, n) return lim.rescueLimiter.AllowN(now, n)

View File

@@ -6,9 +6,9 @@ import (
"github.com/alicebob/miniredis/v2" "github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest" "github.com/zeromicro/go-zero/core/stores/redis/redistest"
) )
func init() { func init() {

View File

@@ -7,11 +7,11 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/tal-tech/go-zero/core/collection" "github.com/zeromicro/go-zero/core/collection"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stat" "github.com/zeromicro/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
const ( const (

View File

@@ -8,11 +8,11 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/collection" "github.com/zeromicro/go-zero/core/collection"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/mathx" "github.com/zeromicro/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/stat" "github.com/zeromicro/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
) )
const ( const (

View File

@@ -3,7 +3,7 @@ package load
import ( import (
"io" "io"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
) )
// A ShedderGroup is a manager to manage key based shedders. // A ShedderGroup is a manager to manage key based shedders.

View File

@@ -4,8 +4,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/tal-tech/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stat" "github.com/zeromicro/go-zero/core/stat"
) )
type ( type (

26
core/logx/color.go Normal file
View File

@@ -0,0 +1,26 @@
package logx
import (
"sync/atomic"
"github.com/zeromicro/go-zero/core/color"
)
// WithColor is a helper function to add color to a string, only in plain encoding.
func WithColor(text string, colour color.Color) string {
if atomic.LoadUint32(&encoding) == plainEncodingType {
return color.WithColor(text, colour)
}
return text
}
// WithColorPadding is a helper function to add color to a string with leading and trailing spaces,
// only in plain encoding.
func WithColorPadding(text string, colour color.Color) string {
if atomic.LoadUint32(&encoding) == plainEncodingType {
return color.WithColorPadding(text, colour)
}
return text
}

33
core/logx/color_test.go Normal file
View File

@@ -0,0 +1,33 @@
package logx
import (
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/color"
)
func TestWithColor(t *testing.T) {
old := atomic.SwapUint32(&encoding, plainEncodingType)
defer atomic.StoreUint32(&encoding, old)
output := WithColor("hello", color.BgBlue)
assert.Equal(t, "hello", output)
atomic.StoreUint32(&encoding, jsonEncodingType)
output = WithColor("hello", color.BgBlue)
assert.Equal(t, "hello", output)
}
func TestWithColorPadding(t *testing.T) {
old := atomic.SwapUint32(&encoding, plainEncodingType)
defer atomic.StoreUint32(&encoding, old)
output := WithColorPadding("hello", color.BgBlue)
assert.Equal(t, " hello ", output)
atomic.StoreUint32(&encoding, jsonEncodingType)
output = WithColorPadding("hello", color.BgBlue)
assert.Equal(t, "hello", output)
}

View File

@@ -3,11 +3,24 @@ package logx
// A LogConf is a logging config. // A LogConf is a logging config.
type LogConf struct { type LogConf struct {
ServiceName string `json:",optional"` ServiceName string `json:",optional"`
Mode string `json:",default=console,options=console|file|volume"` Mode string `json:",default=console,options=[console,file,volume]"`
Encoding string `json:",default=json,options=[json,plain]"`
TimeFormat string `json:",optional"` TimeFormat string `json:",optional"`
Path string `json:",default=logs"` Path string `json:",default=logs"`
Level string `json:",default=info,options=info|error|severe"` Level string `json:",default=info,options=[info,error,severe]"`
Compress bool `json:",optional"` Compress bool `json:",optional"`
KeepDays int `json:",optional"` KeepDays int `json:",optional"`
StackCooldownMillis int `json:",default=100"` StackCooldownMillis int `json:",default=100"`
// MaxBackups represents how many backup log files will be kept. 0 means all files will be kept forever.
// Only take effect when RotationRuleType is `size`.
// Even thougth `MaxBackups` sets 0, log files will still be removed
// if the `KeepDays` limitation is reached.
MaxBackups int `json:",default=0"`
// MaxSize represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`.
// Only take effect when RotationRuleType is `size`
MaxSize int `json:",default=0"`
// RotationRuleType represents the type of log rotation rule. Default is `daily`.
// daily: daily rotation.
// size: size limited rotation.
Rotation string `json:",default=daily,options=[daily,size]"`
} }

145
core/logx/contextlogger.go Normal file
View File

@@ -0,0 +1,145 @@
package logx
import (
"context"
"fmt"
"time"
"github.com/zeromicro/go-zero/core/timex"
"go.opentelemetry.io/otel/trace"
)
// WithContext sets ctx to log, for keeping tracing information.
func WithContext(ctx context.Context) Logger {
return &contextLogger{
ctx: ctx,
}
}
type contextLogger struct {
logEntry
ctx context.Context
}
func (l *contextLogger) Error(v ...interface{}) {
l.err(fmt.Sprint(v...))
}
func (l *contextLogger) Errorf(format string, v ...interface{}) {
l.err(fmt.Sprintf(format, v...))
}
func (l *contextLogger) Errorv(v interface{}) {
l.err(fmt.Sprint(v))
}
func (l *contextLogger) Errorw(msg string, fields ...LogField) {
l.err(msg, fields...)
}
func (l *contextLogger) Info(v ...interface{}) {
l.info(fmt.Sprint(v...))
}
func (l *contextLogger) Infof(format string, v ...interface{}) {
l.info(fmt.Sprintf(format, v...))
}
func (l *contextLogger) Infov(v interface{}) {
l.info(v)
}
func (l *contextLogger) Infow(msg string, fields ...LogField) {
l.info(msg, fields...)
}
func (l *contextLogger) Slow(v ...interface{}) {
l.slow(fmt.Sprint(v...))
}
func (l *contextLogger) Slowf(format string, v ...interface{}) {
l.slow(fmt.Sprintf(format, v...))
}
func (l *contextLogger) Slowv(v interface{}) {
l.slow(v)
}
func (l *contextLogger) Sloww(msg string, fields ...LogField) {
l.slow(msg, fields...)
}
func (l *contextLogger) WithContext(ctx context.Context) Logger {
if ctx == nil {
return l
}
l.ctx = ctx
return l
}
func (l *contextLogger) WithDuration(duration time.Duration) Logger {
l.Duration = timex.ReprOfDuration(duration)
return l
}
func (l *contextLogger) buildFields(fields ...LogField) []LogField {
if len(l.Duration) > 0 {
fields = append(fields, Field(durationKey, l.Duration))
}
traceID := traceIdFromContext(l.ctx)
if len(traceID) > 0 {
fields = append(fields, Field(traceKey, traceID))
}
spanID := spanIdFromContext(l.ctx)
if len(spanID) > 0 {
fields = append(fields, Field(spanKey, spanID))
}
val := l.ctx.Value(fieldsContextKey)
if val != nil {
if arr, ok := val.([]LogField); ok {
fields = append(fields, arr...)
}
}
return fields
}
func (l *contextLogger) err(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Error(v, l.buildFields(fields...)...)
}
}
func (l *contextLogger) info(v interface{}, fields ...LogField) {
if shallLog(InfoLevel) {
getWriter().Info(v, l.buildFields(fields...)...)
}
}
func (l *contextLogger) slow(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Slow(v, l.buildFields(fields...)...)
}
}
func spanIdFromContext(ctx context.Context) string {
spanCtx := trace.SpanContextFromContext(ctx)
if spanCtx.HasSpanID() {
return spanCtx.SpanID().String()
}
return ""
}
func traceIdFromContext(ctx context.Context) string {
spanCtx := trace.SpanContextFromContext(ctx)
if spanCtx.HasTraceID() {
return spanCtx.TraceID().String()
}
return ""
}

View File

@@ -0,0 +1,240 @@
package logx
import (
"context"
"encoding/json"
"io"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func TestTraceLog(t *testing.T) {
SetLevel(InfoLevel)
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
WithContext(ctx).Info(testlog)
validate(t, w.String(), true, true)
}
func TestTraceError(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
var nilCtx context.Context
l := WithContext(context.Background())
l = l.WithContext(nilCtx)
l = l.WithContext(ctx)
SetLevel(ErrorLevel)
l.WithDuration(time.Second).Error(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorf(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorv(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorw(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceInfo(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
SetLevel(InfoLevel)
l := WithContext(ctx)
l.WithDuration(time.Second).Info(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infof(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infov(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infow(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceInfoConsole(t *testing.T) {
old := atomic.SwapUint32(&encoding, jsonEncodingType)
defer atomic.StoreUint32(&encoding, old)
w := new(mockWriter)
o := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(o)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
l := WithContext(ctx)
SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infof(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infov(testlog)
validate(t, w.String(), true, true)
}
func TestTraceSlow(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
l := WithContext(ctx)
SetLevel(InfoLevel)
l.WithDuration(time.Second).Slow(testlog)
assert.True(t, strings.Contains(w.String(), traceKey))
assert.True(t, strings.Contains(w.String(), spanKey))
w.Reset()
l.WithDuration(time.Second).Slowf(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Slowv(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Sloww(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceWithoutContext(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
l := WithContext(context.Background())
SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog)
validate(t, w.String(), false, false)
w.Reset()
l.WithDuration(time.Second).Infof(testlog)
validate(t, w.String(), false, false)
}
func TestLogWithFields(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
ctx := WithFields(context.Background(), Field("foo", "bar"))
l := WithContext(ctx)
SetLevel(InfoLevel)
l.Info(testlog)
var val mockValue
assert.Nil(t, json.Unmarshal([]byte(w.String()), &val))
assert.Equal(t, "bar", val.Foo)
}
func validate(t *testing.T, body string, expectedTrace, expectedSpan bool) {
var val mockValue
dec := json.NewDecoder(strings.NewReader(body))
for {
var doc mockValue
err := dec.Decode(&doc)
if err == io.EOF {
// all done
break
}
if err != nil {
continue
}
val = doc
}
assert.Equal(t, expectedTrace, len(val.Trace) > 0, body)
assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
}
type mockValue struct {
Trace string `json:"trace"`
Span string `json:"span"`
Foo string `json:"foo"`
}

View File

@@ -1,17 +1,13 @@
package logx package logx
import ( import (
"context"
"fmt" "fmt"
"io"
"time" "time"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
const durationCallerDepth = 3
type durationLogger logEntry
// WithDuration returns a Logger which logs the given duration. // WithDuration returns a Logger which logs the given duration.
func WithDuration(d time.Duration) Logger { func WithDuration(d time.Duration) Logger {
return &durationLogger{ return &durationLogger{
@@ -19,57 +15,62 @@ func WithDuration(d time.Duration) Logger {
} }
} }
type durationLogger logEntry
func (l *durationLogger) Error(v ...interface{}) { func (l *durationLogger) Error(v ...interface{}) {
if shallLog(ErrorLevel) { l.err(fmt.Sprint(v...))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
}
} }
func (l *durationLogger) Errorf(format string, v ...interface{}) { func (l *durationLogger) Errorf(format string, v ...interface{}) {
if shallLog(ErrorLevel) { l.err(fmt.Sprintf(format, v...))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
}
} }
func (l *durationLogger) Errorv(v interface{}) { func (l *durationLogger) Errorv(v interface{}) {
if shallLog(ErrorLevel) { l.err(v)
l.write(errorLog, levelError, v) }
}
func (l *durationLogger) Errorw(msg string, fields ...LogField) {
l.err(msg, fields...)
} }
func (l *durationLogger) Info(v ...interface{}) { func (l *durationLogger) Info(v ...interface{}) {
if shallLog(InfoLevel) { l.info(fmt.Sprint(v...))
l.write(infoLog, levelInfo, fmt.Sprint(v...))
}
} }
func (l *durationLogger) Infof(format string, v ...interface{}) { func (l *durationLogger) Infof(format string, v ...interface{}) {
if shallLog(InfoLevel) { l.info(fmt.Sprintf(format, v...))
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
}
} }
func (l *durationLogger) Infov(v interface{}) { func (l *durationLogger) Infov(v interface{}) {
if shallLog(InfoLevel) { l.info(v)
l.write(infoLog, levelInfo, v) }
}
func (l *durationLogger) Infow(msg string, fields ...LogField) {
l.info(msg, fields...)
} }
func (l *durationLogger) Slow(v ...interface{}) { func (l *durationLogger) Slow(v ...interface{}) {
if shallLog(ErrorLevel) { l.slow(fmt.Sprint(v...))
l.write(slowLog, levelSlow, fmt.Sprint(v...))
}
} }
func (l *durationLogger) Slowf(format string, v ...interface{}) { func (l *durationLogger) Slowf(format string, v ...interface{}) {
if shallLog(ErrorLevel) { l.slow(fmt.Sprintf(format, v...))
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
}
} }
func (l *durationLogger) Slowv(v interface{}) { func (l *durationLogger) Slowv(v interface{}) {
if shallLog(ErrorLevel) { l.slow(v)
l.write(slowLog, levelSlow, v) }
func (l *durationLogger) Sloww(msg string, fields ...LogField) {
l.slow(msg, fields...)
}
func (l *durationLogger) WithContext(ctx context.Context) Logger {
return &contextLogger{
ctx: ctx,
logEntry: logEntry{
Duration: l.Duration,
},
} }
} }
@@ -78,9 +79,23 @@ func (l *durationLogger) WithDuration(duration time.Duration) Logger {
return l return l
} }
func (l *durationLogger) write(writer io.Writer, level string, val interface{}) { func (l *durationLogger) err(v interface{}, fields ...LogField) {
l.Timestamp = getTimestamp() if shallLog(ErrorLevel) {
l.Level = level fields = append(fields, Field(durationKey, l.Duration))
l.Content = val getWriter().Error(v, fields...)
outputJson(writer, l) }
}
func (l *durationLogger) info(v interface{}, fields ...LogField) {
if shallLog(InfoLevel) {
fields = append(fields, Field(durationKey, l.Duration))
getWriter().Info(v, fields...)
}
}
func (l *durationLogger) slow(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
fields = append(fields, Field(durationKey, l.Duration))
getWriter().Slow(v, fields...)
}
} }

View File

@@ -1,52 +1,161 @@
package logx package logx
import ( import (
"log" "context"
"strings" "strings"
"sync/atomic"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
) )
func TestWithDurationError(t *testing.T) { func TestWithDurationError(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Error("foo") WithDuration(time.Second).Error("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
} }
func TestWithDurationErrorf(t *testing.T) { func TestWithDurationErrorf(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Errorf("foo") WithDuration(time.Second).Errorf("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationErrorv(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Errorv("foo")
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationErrorw(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Errorw("foo", Field("foo", "bar"))
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
} }
func TestWithDurationInfo(t *testing.T) { func TestWithDurationInfo(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Info("foo") WithDuration(time.Second).Info("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationInfoConsole(t *testing.T) {
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
atomic.StoreUint32(&encoding, old)
}()
w := new(mockWriter)
o := writer.Swap(w)
defer writer.Store(o)
WithDuration(time.Second).Info("foo")
assert.True(t, strings.Contains(w.String(), "ms"), w.String())
} }
func TestWithDurationInfof(t *testing.T) { func TestWithDurationInfof(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Infof("foo") WithDuration(time.Second).Infof("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationInfov(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Infov("foo")
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationInfow(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Infow("foo", Field("foo", "bar"))
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
}
func TestWithDurationWithContextInfow(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, _ := tp.Tracer("foo").Start(context.Background(), "bar")
WithDuration(time.Second).WithContext(ctx).Infow("foo", Field("foo", "bar"))
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
assert.True(t, strings.Contains(w.String(), "trace"), w.String())
assert.True(t, strings.Contains(w.String(), "span"), w.String())
} }
func TestWithDurationSlow(t *testing.T) { func TestWithDurationSlow(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Slow("foo") WithDuration(time.Second).Slow("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
} }
func TestWithDurationSlowf(t *testing.T) { func TestWithDurationSlowf(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).WithDuration(time.Hour).Slowf("foo") WithDuration(time.Second).WithDuration(time.Hour).Slowf("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String()) assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationSlowv(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).WithDuration(time.Hour).Slowv("foo")
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationSloww(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).WithDuration(time.Hour).Sloww("foo", Field("foo", "bar"))
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
} }

18
core/logx/fields.go Normal file
View File

@@ -0,0 +1,18 @@
package logx
import "context"
var fieldsContextKey contextKey
type contextKey struct{}
// WithFields returns a new context with the given fields.
func WithFields(ctx context.Context, fields ...LogField) context.Context {
if val := ctx.Value(fieldsContextKey); val != nil {
if arr, ok := val.([]LogField); ok {
return context.WithValue(ctx, fieldsContextKey, append(arr, fields...))
}
}
return context.WithValue(ctx, fieldsContextKey, fields)
}

35
core/logx/fields_test.go Normal file
View File

@@ -0,0 +1,35 @@
package logx
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithFields(t *testing.T) {
ctx := WithFields(context.Background(), Field("a", 1), Field("b", 2))
vals := ctx.Value(fieldsContextKey)
assert.NotNil(t, vals)
fields, ok := vals.([]LogField)
assert.True(t, ok)
assert.EqualValues(t, []LogField{Field("a", 1), Field("b", 2)}, fields)
}
func TestWithFieldsAppend(t *testing.T) {
var dummyKey struct{}
ctx := context.WithValue(context.Background(), dummyKey, "dummy")
ctx = WithFields(ctx, Field("a", 1), Field("b", 2))
ctx = WithFields(ctx, Field("c", 3), Field("d", 4))
vals := ctx.Value(fieldsContextKey)
assert.NotNil(t, vals)
fields, ok := vals.([]LogField)
assert.True(t, ok)
assert.Equal(t, "dummy", ctx.Value(dummyKey))
assert.EqualValues(t, []LogField{
Field("a", 1),
Field("b", 2),
Field("c", 3),
Field("d", 4),
}, fields)
}

View File

@@ -1,7 +1,6 @@
package logx package logx
import ( import (
"log"
"strings" "strings"
"testing" "testing"
@@ -9,23 +8,27 @@ import (
) )
func TestLessLogger_Error(t *testing.T) { func TestLessLogger_Error(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
l := NewLessLogger(500) l := NewLessLogger(500)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
l.Error("hello") l.Error("hello")
} }
assert.Equal(t, 1, strings.Count(builder.String(), "\n")) assert.Equal(t, 1, strings.Count(w.String(), "\n"))
} }
func TestLessLogger_Errorf(t *testing.T) { func TestLessLogger_Errorf(t *testing.T) {
var builder strings.Builder w := new(mockWriter)
log.SetOutput(&builder) old := writer.Swap(w)
defer writer.Store(old)
l := NewLessLogger(500) l := NewLessLogger(500)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
l.Errorf("hello") l.Errorf("hello")
} }
assert.Equal(t, 1, strings.Count(builder.String(), "\n")) assert.Equal(t, 1, strings.Count(w.String(), "\n"))
} }

View File

@@ -4,8 +4,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/tal-tech/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
type limitedExecutor struct { type limitedExecutor struct {

View File

@@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
func TestLimitedExecutor_logOrDiscard(t *testing.T) { func TestLimitedExecutor_logOrDiscard(t *testing.T) {

38
core/logx/logger.go Normal file
View File

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

View File

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

View File

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

22
core/logx/logwriter.go Normal file
View File

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

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