Compare commits

..

220 Commits

Author SHA1 Message Date
Kevin Wan
7a75dce465 refactor: remove duplicated code (#2705) 2022-12-14 23:36:56 +08:00
anqiansong
801f1adf71 Remove useless file (#2699) 2022-12-14 23:22:01 +08:00
Kevin Wan
f76b976262 fix: #2684 (#2693) 2022-12-13 00:16:31 +08:00
anqiansong
a49f9060c2 Add more test (#2692) 2022-12-12 22:45:18 +08:00
fyyang
ebe28882eb fix: Fix string.title (#2687)
* fix: unsignedTypeMap type error

* fix: string.Title

* style: string.Title test
2022-12-11 23:44:19 +08:00
Kevin Wan
fdc57d07d7 fix: #2672 (#2681)
* fix: #2672

* chore: fix more cases

* chore: update deps

* chore: update deps

* chore: refactor

* chore: refactor

* chore: refactor
2022-12-11 00:41:50 +08:00
re-dylan
ef22042f4d feat: add dev server and health (#2665)
* feat: add dev server and health

* fix: fix ci

* fix: fix comment.

* feat: add enabled

* remove no need test

* feat: mv devServer to internal

* feat: default enable pprof

Co-authored-by: dylan.wang <dylan.wang@yijinin.com>
2022-12-10 20:40:23 +08:00
Tim Xiao
944193ce25 fix:Remove duplicate code (#2686) 2022-12-10 20:13:37 +08:00
Kevin Wan
dcfc9b79f1 feat: accept camelcase for config keys (#2651)
* feat: accept camelcase for config keys

* chore: refactor

* chore: refactor

* chore: add more tests

* chore: refactor

* fix: map elements of array
2022-12-08 22:01:36 +08:00
Kevin Wan
b7052854bb chore: tidy go.sum (#2675) 2022-12-08 06:50:42 +08:00
Kevin Wan
4729a16142 chore: update deps (#2674) 2022-12-07 23:33:30 +08:00
benqi
3604659027 fix: fix client side in zeromicro#2109 (zeromicro#2116) (#2659)
* fix: fix client side in zeromicro#2109 (zeromicro#2116)

* fix: fix client side in zeromicro#2109 (zeromicro#2116)

* fix: fix client side in zeromicro#2109 (zeromicro#2116)
2022-12-04 00:25:52 +08:00
Kevin Wan
9f7f94b673 chore: upgrade dependencies (#2658) 2022-12-03 22:06:45 +08:00
bensonfx
0b3629b636 Fixes #2603 bump goctl cobra version to macos completion help bug (#2656)
Co-authored-by: Benson Yan <yanyong@axera-tech.com>
2022-12-03 19:03:38 +08:00
heyehang
a644ec7edd feature : responses whit context (#2637) 2022-12-03 18:48:02 +08:00
Kevin Wan
9941055eaa feat: add trace.SpanIDFromContext and trace.TraceIDFromContext (#2654) 2022-12-02 11:00:44 +08:00
EinfachePhy
10fd9131a1 replace strings.Title to cases.Title (#2650) 2022-12-02 00:15:51 +08:00
bigrocs
90828a0d4a The default port is used when there is no port number for k8s (#2598)
* k8s 没有端口号时使用默认端口

* Modify the not port test
2022-11-27 09:00:11 +08:00
edieruby
b1c3c21c81 fix: log currentSize should not be 0 when file exists and size is not 0 (#2639) 2022-11-25 23:48:32 +08:00
chen quan
97a8b3ade5 fix(rest): fix issues#2628 (#2629) 2022-11-23 22:50:08 +08:00
Kevin Wan
95a5f64493 feat: add stringx.ToCamelCase (#2622) 2022-11-20 17:41:39 +08:00
chensy
20e659749a fix: fix conflict with the import package name (#2610)
Co-authored-by: chenjieping <chenjieping@kezaihui.com>
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2022-11-20 15:23:25 +08:00
Kevin Wan
94708cc78f chore: update deps (#2621) 2022-11-19 22:28:40 +08:00
Kevin Wan
06fafd2153 feat: validate value in options for mapping (#2616) 2022-11-18 19:46:23 +08:00
Kevin Wan
79de932646 chore: update dependencies (#2594)
* chore: update deps

* chore: update deps

* chore: update deps

* chore: update deps
2022-11-12 18:04:39 +08:00
Kevin Wan
b562e940e7 feat: support bool for env tag (#2593) 2022-11-12 13:58:35 +08:00
Kevin Wan
69068cdaf0 feat: support env tag in config (#2577)
* feat: support env tag in config

* chore: add more tests

* chore: add more tests, add stringx.Join

* fix: test fail

* chore: remove print code

* chore: rename variable
2022-11-11 23:17:09 +08:00
dependabot[bot]
f25788ebea chore(deps): bump go.mongodb.org/mongo-driver from 1.10.3 to 1.11.0 (#2588) 2022-11-11 07:01:54 +08:00
dependabot[bot]
1293c4321b chore(deps): bump github.com/alicebob/miniredis/v2 from 2.23.0 to 2.23.1 (#2587) 2022-11-11 06:59:13 +08:00
Kevin Wan
e3e08a7396 fix: inherit issue when parent after inherits (#2586)
* fix: inherit issue when parent after inherits

* chore: add more tests
2022-11-10 22:13:05 +08:00
dependabot[bot]
4b071f4c33 chore(deps): bump github.com/jhump/protoreflect from 1.13.0 to 1.14.0 (#2579)
Bumps [github.com/jhump/protoreflect](https://github.com/jhump/protoreflect) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/jhump/protoreflect/releases)
- [Commits](https://github.com/jhump/protoreflect/compare/v1.13.0...v1.14.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-10 14:42:53 +08:00
骑着毛驴背单词
81831b60a9 fix(change model template file type): All model template variables ar… (#2573)
* fix(change model template file type): All model template variables are stored in tpl format files with the same name as the template generated using the template init command

* fix(change model template file type): All model template variables are stored in tpl format files with the same name as the template generated using the template init command

Co-authored-by: qilvge <qilvge@.qilvge.com>
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2022-11-10 13:38:35 +08:00
Kevin Wan
1677a4dceb feat: conf inherit (#2568)
* feat: add ValuerWithParent

* feat: make etcd config inherit from parents

* chore: add more tests

* chore: add more tests

* chore: add more comments

* chore: refactor

* chore: add more comments

* fix: fix duplicated code and refactor

* fix: remove unnecessary code

* fix: fix test case for removing print

* feat: support partial inherit
2022-11-08 15:27:48 +08:00
王哈哈
dac3600b53 Modify comment syntax error (#2572) 2022-11-04 21:55:17 +08:00
anqiansong
3db64c7d47 fix(goctl): Fix #2561 (#2562)
* Fix #2561

* format code
2022-10-29 22:40:56 +08:00
Kevin Wan
7eb6aae949 fix: potential slice append issue (#2560) 2022-10-28 08:14:03 +08:00
foliet
07128213d6 chore: update "DO NOT EDIT" format (#2559)
* chore: update "DO NOT EDIT" format

* Update readme.md

* Update head.go
2022-10-27 19:41:24 +08:00
anqiansong
9504d30049 fix(goctl): fix redundant import (#2551) 2022-10-25 06:58:51 +08:00
chen quan
ce73b9a85c chore(action): enable cache dependency (#2549) 2022-10-24 22:50:24 +08:00
re-dylan
4d2a146733 typo(mapping): fix typo for key (#2548) 2022-10-24 21:43:53 +08:00
Kevin Wan
46e236fef7 chore: add more tests (#2547) 2022-10-23 10:54:41 +08:00
Kevin Wan
06e4914e41 feat: add logger.WithFields (#2546) 2022-10-22 23:28:34 +08:00
Kevin Wan
9cadab2684 chore: refactor (#2545)
* chore: refactor

* chore: refactor
2022-10-22 22:52:40 +08:00
chen quan
7fe2492009 feat(trace): support for disabling tracing of specified spanName (#2363) 2022-10-22 22:14:12 +08:00
chen quan
22bdf0bbd5 chore: adjust rpc comment format (#2501) 2022-10-22 22:07:55 +08:00
chowyu12
c92a2d1b77 feat: remove info log when disable log (#2525)
* add go-grpc_opt and go_opt for grpc new command

* feat: remove log when disable log

Co-authored-by: zhouyy <zhouyy@ickey.cn>
2022-10-22 22:07:17 +08:00
swliao425
b21162d638 fix: redis's pipeline logs are not printed completely (#2538)
* fix: redis's pipeline logs are not printed completely

* add unit test

Signed-off-by: liaoshiwei <liaoshiwei@uniontech.com>

Signed-off-by: liaoshiwei <liaoshiwei@uniontech.com>
2022-10-22 21:57:40 +08:00
anqiansong
7c9ef3ca67 fix(goctl): Fix issues (#2543)
* fix #2541

* fix #2432

* Fix review comment

* foramt code

* foramt code
2022-10-22 21:01:15 +08:00
chen quan
bbadbe0175 chore(action): upgrade action (#2521)
- codecov/codecov-action
- actions/setup-go
- usthe/issues-translate-action(origin:omsun28/issues-translate-action)
2022-10-22 19:06:53 +08:00
Kevin Wan
f9beab1095 feat: support uuid.UUID in mapping (#2537) 2022-10-20 20:11:19 +08:00
Kevin Wan
de5c59aad3 chore: add more tests (#2536) 2022-10-19 20:39:46 +08:00
Gang Wu
36d3765c5c Fix typo (#2531) 2022-10-18 17:27:06 +08:00
dependabot[bot]
d326e6f813 chore(deps): bump google.golang.org/grpc from 1.50.0 to 1.50.1 (#2527) 2022-10-18 17:02:11 +08:00
wuleiming2009
ea52fe2e0d Fix the wrong key about FindOne in mongo of goctl. (#2523) 2022-10-17 19:58:57 +08:00
Kevin Wan
05a5de7c6d chore: fix lint errors (#2520) 2022-10-17 06:30:58 +08:00
Kevin Wan
d4c9fd2aff chore: add golangci-lint config file (#2519)
* chore: add golangci-lint config file

* chore: member alignment
2022-10-14 22:45:48 +08:00
dependabot[bot]
776673d57d chore(deps): bump go.opentelemetry.io/otel/exporters/jaeger (#2514)
Bumps [go.opentelemetry.io/otel/exporters/jaeger](https://github.com/open-telemetry/opentelemetry-go) from 1.10.0 to 1.11.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.10.0...v1.11.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>

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-10-14 12:42:15 +08:00
anqiansong
1b87f5e30d Fix mongo insert tpl (#2512) 2022-10-14 12:27:04 +08:00
dependabot[bot]
bc47959384 chore(deps): bump go.opentelemetry.io/otel/exporters/zipkin (#2511)
Bumps [go.opentelemetry.io/otel/exporters/zipkin](https://github.com/open-telemetry/opentelemetry-go) from 1.10.0 to 1.11.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.10.0...v1.11.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>

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-10-14 11:47:38 +08:00
dependabot[bot]
9f6d926455 chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc (#2510)
Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) from 1.10.0 to 1.11.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.10.0...v1.11.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 23:13:04 +08:00
foliet
f7a4e3a19e chore: fix naming problem (#2500)
When I was looking for how to mock mongo client, I found some naming problems and wanted to fix them.
2022-10-13 22:53:27 +08:00
swliao425
a515a3c735 chore: sqlx's metric name is different from redis (#2505) 2022-10-13 22:52:36 +08:00
dependabot[bot]
6f6f1ae21f chore(deps): bump go.opentelemetry.io/otel/sdk from 1.10.0 to 1.11.0 (#2504)
Bumps [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) from 1.10.0 to 1.11.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.10.0...v1.11.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 22:51:40 +08:00
Kevin Wan
10f94ffcc2 chore: remove unnecessary code (#2499) 2022-10-11 22:56:12 +08:00
sado
f068062b13 token limit support context (#2335)
* token limit support context

* add token limit with ctx

add token limit with ctx

Co-authored-by: sado <liaoyonglin@bilibili.com>
2022-10-11 22:40:00 +08:00
foliet
799c118d95 feat(goctl): better generate the api code of typescript (#2483) 2022-10-11 22:19:22 +08:00
#Suyghur
74cc6b55e8 fix: replace Infof() with Errorf() in DurationInterceptor (#2495) (#2497) 2022-10-11 21:45:31 +08:00
cui fliter
fc59aec2e7 fix a few function names on comments (#2496)
Signed-off-by: cui fliter <imcusg@gmail.com>

Signed-off-by: cui fliter <imcusg@gmail.com>
2022-10-10 22:12:11 +08:00
dependabot[bot]
7868667b4f chore(deps): bump google.golang.org/grpc from 1.49.0 to 1.50.0 (#2487)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.49.0 to 1.50.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.49.0...v1.50.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-07 22:42:24 +08:00
Kevin Wan
773b59106b chore: remove init if possible (#2485) 2022-10-06 23:57:56 +08:00
dependabot[bot]
97f8667b71 chore(deps): bump go.mongodb.org/mongo-driver from 1.10.2 to 1.10.3 (#2484)
Bumps [go.mongodb.org/mongo-driver](https://github.com/mongodb/mongo-go-driver) from 1.10.2 to 1.10.3.
- [Release notes](https://github.com/mongodb/mongo-go-driver/releases)
- [Commits](https://github.com/mongodb/mongo-go-driver/compare/v1.10.2...v1.10.3)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-06 11:04:11 +08:00
foliet
b51339b69b fix(mongo): fix file name generation errors (#2479)
Before this, no matter what style is used, lowercase file names without underscores will be generated.
2022-10-04 18:09:03 +08:00
Kevin Wan
38a73d7fbe fix: etcd reconnecting problem (#2478) 2022-10-02 22:03:56 +08:00
re-dylan
e50689beed fix #2343 (#2349)
Co-authored-by: dylan.wang <dylan.wang@yijinin.com>
2022-10-02 21:46:33 +08:00
Kevin Wan
1bc138bd34 chore: refactor to reduce duplicated code (#2477) 2022-10-01 21:45:53 +08:00
Kevin Wan
4b9066eda6 chore: better shedding algorithm, make sure recover from shedding (#2476)
* backup

* chore: better shedding algorithm, make sure recover from shedding
2022-10-01 20:55:25 +08:00
#Suyghur
0c66e041b5 feat(redis):add timeout method to extend blpop (#2472) 2022-10-01 20:53:54 +08:00
Halo
aa2be0163a fix: add more tests (#2473)
* chore: add string to map in httpx parse method

* feat: add httpx parse stringToMap method test

* fix: add more test
2022-09-30 22:01:39 +08:00
Kevin Wan
ada2941e87 chore: sort methods (#2470) 2022-09-30 14:57:40 +08:00
Kevin Wan
59c0013cd1 feat: add logc package, support AddGlobalFields for both logc and logx. (#2463)
* feat: add logc package

* feat: add logc, add AddGlobalFields for both logc and logx

* chore: add benchmarks

* chore: add more tests

* chore: simplify globalFields in logx

* chore: remove outdated comments
2022-09-29 22:49:41 +08:00
Halo
05737f6519 feat: add string to map in httpx parse method (#2459)
* chore: add string to map in httpx parse method

* feat: add httpx parse stringToMap method test
2022-09-29 22:34:58 +08:00
chen quan
4f6a900fd4 fix(goctl): fix the unit test bug of goctl (#2458) 2022-09-27 23:52:05 +08:00
aV
63cfe60f1a Readme Tweak (#2436)
* Update readme.md

* Update readme.md

* Update readme.md

Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2022-09-25 22:59:55 +08:00
bensonfx
e7acadb15d fix #2435 (#2442)
* feat: add color to debug (#2433)

* fix header and path type ts gen

Co-authored-by: chen quan <chenquan.dev@gmail.com>
2022-09-24 22:28:25 +08:00
chen quan
111e626a73 refactor: adjust http request slow log format (#2440) 2022-09-23 21:20:38 +08:00
Kevin Wan
1a6d7b3ef6 chore: gofumpt (#2439) 2022-09-22 22:40:01 +08:00
chen quan
2e1e4f3574 feat: add color to debug (#2433) 2022-09-21 22:30:06 +08:00
Kevin Wan
22d0a2120a chore: replace fmt.Fprint (#2425) 2022-09-20 23:51:58 +08:00
chen quan
68e15360c2 fix: fix log output (#2424) 2022-09-20 22:45:52 +08:00
jesse.tang
1b344a8851 cleanup: deprecated field and func (#2416)
* cleanup: deprecated field and func

* fmt import order
2022-09-20 22:13:34 +08:00
dawn_zhou
d640544a40 refactor: redis error for prometheus metric label (#2412)
Co-authored-by: dawn.zhou <dawn.zhou@yijinin.com>
2022-09-20 21:13:33 +08:00
MarkJoyMa
e6aa6fc361 feat: add log debug level (#2411) 2022-09-20 07:50:11 +08:00
MarkJoyMa
4c927624b0 fix goctl help message (#2414) 2022-09-19 14:05:46 +08:00
Kevin Wan
0ea92b7280 chore: add more tests (#2410) 2022-09-19 13:52:14 +08:00
anqiansong
2cde970c9e feat(goctl):Add ignore-columns flag (#2407)
* fix #2074,#2100

* format code

* fix #2397

* format code

* Support comma spliter

* format code
2022-09-19 11:49:39 +08:00
Kevin Wan
5061158bd6 chore: add more tests (#2409) 2022-09-18 23:17:21 +08:00
kevin
9138056c01 chore: update go-zero to v1.4.1 2022-09-17 23:07:29 +08:00
Kevin Wan
0b1884b6bd feat: support caller skip in logx (#2401)
* feat: support caller skip in logx

* chore: remove debug prints

* chore: remove debug prints

* chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc (#2402)

Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) from 1.9.0 to 1.10.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.9.0...v1.10.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore: simplify test code

* chore: remove new WithFields in logx, and deprecated old WithFields

* chore: simplify WithDuration

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-17 22:48:24 +08:00
Kevin Wan
1f6688e5c1 chore: refactor the imports (#2406) 2022-09-17 20:06:23 +08:00
dawn_zhou
ae7f1aabdd feat: mysql and redis metric support (#2355)
* feat: mysql and redis metric support

* feat: mysql and redis metric support

* feat: mysql and redis metric support

Co-authored-by: dawn.zhou <dawn.zhou@yijinin.com>
2022-09-17 19:35:30 +08:00
dependabot[bot]
b8664be2bb chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc (#2402)
Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) from 1.9.0 to 1.10.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.9.0...v1.10.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-17 00:14:39 +08:00
dependabot[bot]
6e16a9647e chore(deps): bump go.etcd.io/etcd/client/v3 from 3.5.4 to 3.5.5 (#2395)
Bumps [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) from 3.5.4 to 3.5.5.
- [Release notes](https://github.com/etcd-io/etcd/releases)
- [Changelog](https://github.com/etcd-io/etcd/blob/main/Dockerfile-release.amd64)
- [Commits](https://github.com/etcd-io/etcd/compare/v3.5.4...v3.5.5)

---
updated-dependencies:
- dependency-name: go.etcd.io/etcd/client/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

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-09-16 23:54:05 +08:00
dependabot[bot]
bb0e76be47 chore(deps): bump go.etcd.io/etcd/api/v3 from 3.5.4 to 3.5.5 (#2394)
Bumps [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd) from 3.5.4 to 3.5.5.
- [Release notes](https://github.com/etcd-io/etcd/releases)
- [Changelog](https://github.com/etcd-io/etcd/blob/main/Dockerfile-release.amd64)
- [Commits](https://github.com/etcd-io/etcd/compare/v3.5.4...v3.5.5)

---
updated-dependencies:
- dependency-name: go.etcd.io/etcd/api/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-15 23:07:56 +08:00
dependabot[bot]
27a20e1ed3 chore(deps): bump github.com/jhump/protoreflect from 1.12.0 to 1.13.0 (#2393)
Bumps [github.com/jhump/protoreflect](https://github.com/jhump/protoreflect) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/jhump/protoreflect/releases)
- [Commits](https://github.com/jhump/protoreflect/compare/v1.12.0...v1.13.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-15 22:32:30 +08:00
dependabot[bot]
cbbbee0ace chore(deps): bump go.opentelemetry.io/otel/exporters/jaeger (#2389)
Bumps [go.opentelemetry.io/otel/exporters/jaeger](https://github.com/open-telemetry/opentelemetry-go) from 1.9.0 to 1.10.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.9.0...v1.10.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-15 22:00:07 +08:00
Kevin Wan
e9650d547b chore: refactor (#2388) 2022-09-14 23:46:34 +08:00
dependabot[bot]
60160f56b8 chore(deps): bump go.opentelemetry.io/otel/exporters/zipkin (#2385)
Bumps [go.opentelemetry.io/otel/exporters/zipkin](https://github.com/open-telemetry/opentelemetry-go) from 1.9.0 to 1.10.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.9.0...v1.10.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>

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-09-14 23:46:15 +08:00
genewoo
05c2f313c7 feat: add grpc export (#2379)
Co-authored-by: Gene Wu <gene.wu@cabital.com>
2022-09-14 22:54:52 +08:00
dependabot[bot]
f2a0f78288 chore(deps): bump go.opentelemetry.io/otel/sdk from 1.9.0 to 1.10.0 (#2383)
Bumps [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) from 1.9.0 to 1.10.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.9.0...v1.10.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 18:08:37 +08:00
Kevin Wan
3e96994b7b feat: support targetPort option in goctl kube (#2378) 2022-09-12 20:42:41 +08:00
Kevin Wan
66c2a28e66 fix #2364 (#2377) 2022-09-12 19:29:43 +08:00
Kevin Wan
9672071b5d Update readme-cn.md 2022-09-12 18:31:42 +08:00
anqiansong
9581e8445a fix: issue #2359 (#2368)
* Revert changes

* Unrap nested structure for doc code generation

* Revert changes

* Remove useless code

* Remove useless code

* Format code
2022-09-11 22:56:53 +08:00
dependabot[bot]
6ec8bc6655 chore(deps): bump github.com/lib/pq from 1.10.6 to 1.10.7 (#2373)
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.6 to 1.10.7.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.6...v1.10.7)

---
updated-dependencies:
- dependency-name: github.com/lib/pq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

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-09-11 22:33:37 +08:00
Kevin Wan
d935c83a54 feat: support baggage propagation in httpc (#2375)
* feat: support baggage propagation in httpc

* chore: use go 1.16

* chore: use go 1.16

* chore: use go ^1.16

* chore: remove deprecated
2022-09-10 15:18:52 +08:00
dependabot[bot]
590d784800 chore(deps): bump go.uber.org/goleak from 1.1.12 to 1.2.0 (#2371)
Bumps [go.uber.org/goleak](https://github.com/uber-go/goleak) from 1.1.12 to 1.2.0.
- [Release notes](https://github.com/uber-go/goleak/releases)
- [Changelog](https://github.com/uber-go/goleak/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/goleak/compare/v1.1.12...v1.2.0)

---
updated-dependencies:
- dependency-name: go.uber.org/goleak
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-08 12:07:50 +08:00
dependabot[bot]
784276b360 chore(deps): bump go.mongodb.org/mongo-driver from 1.10.1 to 1.10.2 (#2370) 2022-09-08 07:26:55 +08:00
Kevin Wan
da80662b0f chore: refactor (#2365) 2022-09-07 11:18:52 +08:00
maizige
cfda972d50 fix:trace graceful stop,pre loss trace (#2358) 2022-09-07 10:33:01 +08:00
Archer
6078bf1a04 correct test case (#2340) 2022-09-04 21:14:56 +08:00
anqiansong
ce638d26d9 Hidden java (#2333) 2022-08-30 23:54:36 +08:00
maizige
422f401153 fix:etcd get&watch not atomic (#2321) 2022-08-29 08:35:31 +08:00
Kevin Wan
dfeef5e497 fix: thread-safe in getWriter of logx (#2319) 2022-08-29 08:32:17 +08:00
Archer
8c72136631 make logx#getWriter concurrency-safe (#2233)
* make logx#getWriter concurrency-safe

* make logx#getWriter concurrency-safe
2022-08-28 22:10:50 +08:00
Zlx
9d6c8f67f5 generates nested types in doc (#2201)
Co-authored-by: Link_Zhao <Link_Zhao@epam.com>
2022-08-28 21:51:27 +08:00
anqiansong
f70805ee60 Add strict flag (#2248)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2022-08-28 18:55:52 +08:00
Kevin Wan
a1466e1707 fix: range validation on mapping (#2317) 2022-08-28 17:49:26 +08:00
lowang-bh
1b477bbef9 improve: number range compare left and righ value (#2315)
Co-authored-by: wanglonghui7 <wanglonghui7@jd.com>
2022-08-28 17:17:22 +08:00
Kevin Wan
813625d995 refactor: sequential range over safemap (#2316) 2022-08-28 17:16:31 +08:00
李平平
15a2802f12 safemap add Range method (#2314) 2022-08-28 16:51:45 +08:00
Kevin Wan
5d00dfb962 fix: handle the scenarios that content-length is invalid (#2313) 2022-08-28 15:41:02 +08:00
Kevin Wan
d9620bb072 chore: remove unused packages (#2312) 2022-08-28 14:20:03 +08:00
Kevin Wan
d978563523 fix: more accurate panic message on mapreduce (#2311) 2022-08-27 22:47:25 +08:00
yiGmMk
fb6d7e2fd2 fix #2301,package conflict generated by ddl (#2307)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2022-08-27 21:56:39 +08:00
Kevin Wan
2d60f0c65a fix: logx disable not working in some cases (#2306)
* fix: logx disable not working in some cases

* fix: test fail
2022-08-27 19:24:31 +08:00
maizige
5d4ae201d0 Fix/del server interceptor duplicate copy md 20220827 (#2309)
* fix:grpc server interceptor duplicate copy MD

* modify wrong comments
2022-08-27 18:55:40 +08:00
maizige
05007c86bb fix:duplicate copy MD (#2304) 2022-08-27 12:18:23 +08:00
Kevin Wan
93584c6ca6 chore: refactor gateway (#2303) 2022-08-27 11:39:42 +08:00
dependabot[bot]
22bb7e95fd chore(deps): bump github.com/pelletier/go-toml/v2 from 2.0.3 to 2.0.5 (#2305)
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.3 to 2.0.5.
- [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.3...v2.0.5)

---
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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-27 11:39:07 +08:00
sado
bebf6322ff fix resource manager dead lock (#2302)
Co-authored-by: sado <liaoyonglin@bilibili.com>
2022-08-26 20:07:25 +08:00
Kevin Wan
36678f9023 chore: refactor stat (#2299) 2022-08-25 23:37:32 +08:00
Josh Quintana
90cdd61efc Initialize CPU stat code only if used (#2020)
Co-authored-by: Josh Quintana <josh@highwaybenefits.com>
2022-08-25 22:05:29 +08:00
dependabot[bot]
28166dedd6 chore(deps): bump google.golang.org/grpc from 1.48.0 to 1.49.0 (#2297) 2022-08-25 08:44:09 +08:00
chen quan
0316b6e10e feat(redis): add ZaddFloat & ZaddFloatCtx (#2291) 2022-08-24 21:02:16 +08:00
Kevin Wan
4cb68a034a fix #2163 (#2283) 2022-08-24 20:19:53 +08:00
chen quan
847a396f1c fix(logx): display garbled characters in windows(DOS, Powershell) (#2232)
* fix(logx): display garbled characters in windows(DOS, Powershell)

* Update writer.go
2022-08-23 22:45:11 +08:00
chen quan
c1babdf8b2 doc(readme): add star history (#2275) 2022-08-23 22:42:03 +08:00
MarkJoyMa
040c9e0954 feat: rpc add health check function configuration optional (#2288)
* feat: rpc add health check function configuration optional

* update config field name
2022-08-23 13:44:21 +08:00
Kevin Wan
1c85d39add Update readme-cn.md 2022-08-22 17:12:26 +08:00
Kevin Wan
4cd065f4f4 Update issues.yml 2022-08-19 23:10:16 +08:00
anqiansong
b9c97678bc chore: Update readme (#2280)
* Update readme

* Update readme
2022-08-19 23:08:07 +08:00
Kevin Wan
5208def65a fix #2240 (#2271) 2022-08-18 23:10:04 +08:00
Kevin Wan
3b96dc1598 Update readme-cn.md 2022-08-18 21:25:08 +08:00
dependabot[bot]
fa3f1bc19c chore(deps): bump github.com/pelletier/go-toml/v2 from 2.0.2 to 2.0.3 (#2267)
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.2 to 2.0.3.
- [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.2...v2.0.3)

---
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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-17 22:30:27 +08:00
Kevin Wan
8ed22eafdd fix #2240 (#2263) 2022-08-14 19:49:47 +08:00
Kevin Wan
05dd6bd743 chore: refactor logx (#2262) 2022-08-14 13:58:06 +08:00
dependabot[bot]
9af1a42386 chore(deps): bump github.com/alicebob/miniredis/v2 from 2.22.0 to 2.23.0 (#2260)
Bumps [github.com/alicebob/miniredis/v2](https://github.com/alicebob/miniredis) from 2.22.0 to 2.23.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.22.0...v2.23.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-13 10:34:23 +08:00
Kevin Wan
f3645e420e test: add more tests (#2261) 2022-08-13 10:31:23 +08:00
fyyang
62abac0b7e fix: unsignedTypeMap type error (#2246) 2022-08-11 22:56:00 +08:00
Kevin Wan
6357e27418 fix: test failure, due to go 1.19 compatibility (#2256) 2022-08-11 22:55:12 +08:00
Kevin Wan
1568c3be0e fix: time repr wrapper (#2255) 2022-08-11 22:39:54 +08:00
dependabot[bot]
27e773fa1f chore(deps): bump github.com/prometheus/client_golang (#2244)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.12.2 to 1.13.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.12.2...v1.13.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  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-10 22:16:27 +08:00
dependabot[bot]
d8e17be33e chore(deps): bump github.com/fullstorydev/grpcurl from 1.8.6 to 1.8.7 (#2245)
Bumps [github.com/fullstorydev/grpcurl](https://github.com/fullstorydev/grpcurl) from 1.8.6 to 1.8.7.
- [Release notes](https://github.com/fullstorydev/grpcurl/releases)
- [Changelog](https://github.com/fullstorydev/grpcurl/blob/master/.goreleaser.yml)
- [Commits](https://github.com/fullstorydev/grpcurl/compare/v1.8.6...v1.8.7)

---
updated-dependencies:
- dependency-name: github.com/fullstorydev/grpcurl
  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-08-10 22:09:04 +08:00
Kevin Wan
da5770ee2b chore: release action for goctl (#2239) 2022-08-07 16:49:22 +08:00
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
382 changed files with 13903 additions and 5488 deletions

View File

@@ -11,15 +11,17 @@ jobs:
name: Linux
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.15
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go 1.x
uses: actions/setup-go@v3
with:
go-version: ^1.16
check-latest: true
cache: true
id: go
- name: Get dependencies
run: |
go get -v -t -d ./...
@@ -34,20 +36,23 @@ jobs:
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
- name: Codecov
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v3
test-win:
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: Set up Go 1.x
uses: actions/setup-go@v3
with:
# use 1.16 to guarantee Go 1.16 compatibility
go-version: 1.16
check-latest: true
cache: true
- name: Test
run: |
go mod verify

View File

@@ -9,7 +9,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: tomsun28/issues-translate-action@v2.6
- uses: usthe/issues-translate-action@v2.7
with:
IS_MODIFY_TITLE: true
# not require, default false, . Decide whether to modify the issue title

View File

@@ -7,10 +7,10 @@ jobs:
close-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
- uses: actions/stale@v6
with:
days-before-issue-stale: 30
days-before-issue-close: 14
days-before-issue-stale: 365
days-before-issue-close: 90
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."

View File

@@ -25,4 +25,4 @@ jobs:
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
extra_files: tools/goctl/readme.md tools/goctl/readme-cn.md

1
.gitignore vendored
View File

@@ -22,6 +22,7 @@ go.work.sum
# gitlab ci
.cache
.golangci.yml
# vim auto backup file
*~

View File

@@ -20,16 +20,16 @@ func (b noOpBreaker) Do(req func() error) error {
return req()
}
func (b noOpBreaker) DoWithAcceptable(req func() error, acceptable Acceptable) error {
func (b noOpBreaker) DoWithAcceptable(req func() error, _ Acceptable) error {
return req()
}
func (b noOpBreaker) DoWithFallback(req func() error, fallback func(err error) error) error {
func (b noOpBreaker) DoWithFallback(req func() error, _ func(err error) error) error {
return req()
}
func (b noOpBreaker) DoWithFallbackAcceptable(req func() error, fallback func(err error) error,
acceptable Acceptable) error {
func (b noOpBreaker) DoWithFallbackAcceptable(req func() error, _ func(err error) error,
_ Acceptable) error {
return req()
}
@@ -38,5 +38,5 @@ type nopPromise struct{}
func (p nopPromise) Accept() {
}
func (p nopPromise) Reject(reason string) {
func (p nopPromise) Reject(_ string) {
}

View File

@@ -7,7 +7,7 @@ import (
"encoding/base64"
"encoding/pem"
"errors"
"io/ioutil"
"os"
)
var (
@@ -48,7 +48,7 @@ type (
// NewRsaDecrypter returns a RsaDecrypter with the given file.
func NewRsaDecrypter(file string) (RsaDecrypter, error) {
content, err := ioutil.ReadFile(file)
content, err := os.ReadFile(file)
if err != nil {
return nil, err
}

View File

@@ -26,7 +26,7 @@ type (
// CacheOption defines the method to customize a Cache.
CacheOption func(cache *Cache)
// A Cache object is a in-memory cache.
// A Cache object is an in-memory cache.
Cache struct {
name string
lock sync.Mutex

View File

@@ -68,6 +68,24 @@ func (m *SafeMap) Get(key interface{}) (interface{}, bool) {
return val, ok
}
// Range calls f sequentially for each key and value present in the map.
// If f returns false, range stops the iteration.
func (m *SafeMap) Range(f func(key, val interface{}) bool) {
m.lock.RLock()
defer m.lock.RUnlock()
for k, v := range m.dirtyOld {
if !f(k, v) {
return
}
}
for k, v := range m.dirtyNew {
if !f(k, v) {
return
}
}
}
// Set sets the value into m with the given key.
func (m *SafeMap) Set(key, value interface{}) {
m.lock.Lock()

View File

@@ -1,6 +1,7 @@
package collection
import (
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
@@ -107,3 +108,42 @@ func testSafeMapWithParameters(t *testing.T, size, exception int) {
}
}
}
func TestSafeMap_Range(t *testing.T) {
const (
size = 100000
exception1 = 5
exception2 = 500
)
m := NewSafeMap()
newMap := NewSafeMap()
for i := 0; i < size; i++ {
m.Set(i, i)
}
for i := 0; i < size; i++ {
if i%exception1 == 0 {
m.Del(i)
}
}
for i := size; i < size<<1; i++ {
m.Set(i, i)
}
for i := size; i < size<<1; i++ {
if i%exception2 != 0 {
m.Del(i)
}
}
var count int32
m.Range(func(k, v interface{}) bool {
atomic.AddInt32(&count, 1)
newMap.Set(k, v)
return true
})
assert.Equal(t, int(atomic.LoadInt32(&count)), m.Size())
assert.Equal(t, m.dirtyNew, newMap.dirtyNew)
assert.Equal(t, m.dirtyOld, newMap.dirtyOld)
}

View File

@@ -29,7 +29,7 @@ func NewSet() *Set {
}
}
// NewUnmanagedSet returns a unmanaged Set, which can put values with different types.
// NewUnmanagedSet returns an unmanaged Set, which can put values with different types.
func NewUnmanagedSet() *Set {
return &Set{
data: make(map[interface{}]lang.PlaceholderType),

View File

@@ -2,15 +2,18 @@ package conf
import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
"strings"
"github.com/zeromicro/go-zero/core/jsonx"
"github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/internal/encoding"
)
const distanceBetweenUpperAndLower = 32
var loaders = map[string]func([]byte, interface{}) error{
".json": LoadFromJsonBytes,
".toml": LoadFromTomlBytes,
@@ -20,7 +23,7 @@ var loaders = map[string]func([]byte, interface{}) error{
// Load loads config into v from file, .json, .yaml and .yml are acceptable.
func Load(file string, v interface{}, opts ...Option) error {
content, err := ioutil.ReadFile(file)
content, err := os.ReadFile(file)
if err != nil {
return err
}
@@ -50,7 +53,12 @@ func LoadConfig(file string, v interface{}, opts ...Option) error {
// LoadFromJsonBytes loads config into v from content json bytes.
func LoadFromJsonBytes(content []byte, v interface{}) error {
return mapping.UnmarshalJsonBytes(content, v)
var m map[string]interface{}
if err := jsonx.Unmarshal(content, &m); err != nil {
return err
}
return mapping.UnmarshalJsonMap(toCamelCaseKeyMap(m), v, mapping.WithCanonicalKeyFunc(toCamelCase))
}
// LoadConfigFromJsonBytes loads config into v from content json bytes.
@@ -61,12 +69,22 @@ func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
// LoadFromTomlBytes loads config into v from content toml bytes.
func LoadFromTomlBytes(content []byte, v interface{}) error {
return mapping.UnmarshalTomlBytes(content, v)
b, err := encoding.TomlToJson(content)
if err != nil {
return err
}
return LoadFromJsonBytes(b, v)
}
// LoadFromYamlBytes loads config into v from content yaml bytes.
func LoadFromYamlBytes(content []byte, v interface{}) error {
return mapping.UnmarshalYamlBytes(content, v)
b, err := encoding.YamlToJson(content)
if err != nil {
return err
}
return LoadFromJsonBytes(b, v)
}
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
@@ -81,3 +99,66 @@ func MustLoad(path string, v interface{}, opts ...Option) {
log.Fatalf("error: config file %s, %s", path, err.Error())
}
}
func toCamelCase(s string) string {
var buf strings.Builder
buf.Grow(len(s))
var capNext bool
boundary := true
for _, v := range s {
isCap := v >= 'A' && v <= 'Z'
isLow := v >= 'a' && v <= 'z'
if boundary && (isCap || isLow) {
if capNext {
if isLow {
v -= distanceBetweenUpperAndLower
}
} else {
if isCap {
v += distanceBetweenUpperAndLower
}
}
boundary = false
}
if isCap || isLow {
buf.WriteRune(v)
capNext = false
} else if v == ' ' || v == '\t' {
buf.WriteRune(v)
capNext = false
boundary = true
} else if v == '_' {
capNext = true
boundary = true
} else {
buf.WriteRune(v)
capNext = true
}
}
return buf.String()
}
func toCamelCaseInterface(v interface{}) interface{} {
switch vv := v.(type) {
case map[string]interface{}:
return toCamelCaseKeyMap(vv)
case []interface{}:
var arr []interface{}
for _, vvv := range vv {
arr = append(arr, toCamelCaseInterface(vvv))
}
return arr
default:
return v
}
}
func toCamelCaseKeyMap(m map[string]interface{}) map[string]interface{} {
res := make(map[string]interface{})
for k, v := range m {
res[toCamelCase(k)] = toCamelCaseInterface(v)
}
return res
}

View File

@@ -1,7 +1,6 @@
package conf
import (
"io/ioutil"
"os"
"testing"
@@ -57,6 +56,22 @@ func TestConfigJson(t *testing.T) {
}
}
func TestLoadFromJsonBytesArray(t *testing.T) {
input := []byte(`{"users": [{"name": "foo"}, {"Name": "bar"}]}`)
var val struct {
Users []struct {
Name string
}
}
assert.NoError(t, LoadFromJsonBytes(input, &val))
var expect []string
for _, user := range val.Users {
expect = append(expect, user.Name)
}
assert.EqualValues(t, []string{"foo", "bar"}, expect)
}
func TestConfigToml(t *testing.T) {
text := `a = "foo"
b = 1
@@ -82,6 +97,65 @@ d = "abcd!@#$112"
assert.Equal(t, "abcd!@#$112", val.D)
}
func TestConfigJsonCanonical(t *testing.T) {
text := []byte(`{"a": "foo", "B": "bar"}`)
var val1 struct {
A string `json:"a"`
B string `json:"b"`
}
var val2 struct {
A string
B string
}
assert.NoError(t, LoadFromJsonBytes(text, &val1))
assert.Equal(t, "foo", val1.A)
assert.Equal(t, "bar", val1.B)
assert.NoError(t, LoadFromJsonBytes(text, &val2))
assert.Equal(t, "foo", val2.A)
assert.Equal(t, "bar", val2.B)
}
func TestConfigTomlCanonical(t *testing.T) {
text := []byte(`a = "foo"
B = "bar"`)
var val1 struct {
A string `json:"a"`
B string `json:"b"`
}
var val2 struct {
A string
B string
}
assert.NoError(t, LoadFromTomlBytes(text, &val1))
assert.Equal(t, "foo", val1.A)
assert.Equal(t, "bar", val1.B)
assert.NoError(t, LoadFromTomlBytes(text, &val2))
assert.Equal(t, "foo", val2.A)
assert.Equal(t, "bar", val2.B)
}
func TestConfigYamlCanonical(t *testing.T) {
text := []byte(`a: foo
B: bar`)
var val1 struct {
A string `json:"a"`
B string `json:"b"`
}
var val2 struct {
A string
B string
}
assert.NoError(t, LoadFromYamlBytes(text, &val1))
assert.Equal(t, "foo", val1.A)
assert.Equal(t, "bar", val1.B)
assert.NoError(t, LoadFromYamlBytes(text, &val2))
assert.Equal(t, "foo", val2.A)
assert.Equal(t, "bar", val2.B)
}
func TestConfigTomlEnv(t *testing.T) {
text := `a = "foo"
b = 1
@@ -106,7 +180,6 @@ d = "abcd!@#112"
assert.Equal(t, 1, val.B)
assert.Equal(t, "2", val.C)
assert.Equal(t, "abcd!@#112", val.D)
}
func TestConfigJsonEnv(t *testing.T) {
@@ -145,13 +218,123 @@ func TestConfigJsonEnv(t *testing.T) {
}
}
func TestToCamelCase(t *testing.T) {
tests := []struct {
input string
expect string
}{
{
input: "",
expect: "",
},
{
input: "A",
expect: "a",
},
{
input: "a",
expect: "a",
},
{
input: "hello_world",
expect: "helloWorld",
},
{
input: "Hello_world",
expect: "helloWorld",
},
{
input: "hello_World",
expect: "helloWorld",
},
{
input: "helloWorld",
expect: "helloWorld",
},
{
input: "HelloWorld",
expect: "helloWorld",
},
{
input: "hello World",
expect: "hello world",
},
{
input: "Hello World",
expect: "hello world",
},
{
input: "Hello World",
expect: "hello world",
},
{
input: "Hello World foo_bar",
expect: "hello world fooBar",
},
{
input: "Hello World foo_Bar",
expect: "hello world fooBar",
},
{
input: "Hello World Foo_bar",
expect: "hello world fooBar",
},
{
input: "Hello World Foo_Bar",
expect: "hello world fooBar",
},
{
input: "你好 World Foo_Bar",
expect: "你好 world fooBar",
},
}
for _, test := range tests {
test := test
t.Run(test.input, func(t *testing.T) {
assert.Equal(t, test.expect, toCamelCase(test.input))
})
}
}
func TestLoadFromJsonBytesError(t *testing.T) {
var val struct{}
assert.Error(t, LoadFromJsonBytes([]byte(`hello`), &val))
}
func TestLoadFromTomlBytesError(t *testing.T) {
var val struct{}
assert.Error(t, LoadFromTomlBytes([]byte(`hello`), &val))
}
func TestLoadFromYamlBytesError(t *testing.T) {
var val struct{}
assert.Error(t, LoadFromYamlBytes([]byte(`':hello`), &val))
}
func TestLoadFromYamlBytes(t *testing.T) {
input := []byte(`layer1:
layer2:
layer3: foo`)
var val struct {
Layer1 struct {
Layer2 struct {
Layer3 string
}
}
}
assert.NoError(t, LoadFromYamlBytes(input, &val))
assert.Equal(t, "foo", val.Layer1.Layer2.Layer3)
}
func createTempFile(ext, text string) (string, error) {
tmpfile, err := ioutil.TempFile(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
if err != nil {
return "", err
}
if err := ioutil.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
if err := os.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
return "", err
}

View File

@@ -3,7 +3,7 @@ package internal
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"os"
"sync"
)
@@ -37,7 +37,7 @@ func AddTLS(endpoints []string, certFile, certKeyFile, caFile string, insecureSk
return err
}
caData, err := ioutil.ReadFile(caFile)
caData, err := os.ReadFile(caFile)
if err != nil {
return err
}

View File

@@ -208,7 +208,7 @@ func (c *cluster) handleWatchEvents(key string, events []*clientv3.Event) {
}
}
func (c *cluster) load(cli EtcdClient, key string) {
func (c *cluster) load(cli EtcdClient, key string) int64 {
var resp *clientv3.GetResponse
for {
var err error
@@ -232,6 +232,8 @@ func (c *cluster) load(cli EtcdClient, key string) {
}
c.handleChanges(key, kvs)
return resp.Header.Revision
}
func (c *cluster) monitor(key string, l UpdateListener) error {
@@ -244,9 +246,9 @@ func (c *cluster) monitor(key string, l UpdateListener) error {
return err
}
c.load(cli, key)
rev := c.load(cli, key)
c.watchGroup.Run(func() {
c.watch(cli, key)
c.watch(cli, key, rev)
})
return nil
@@ -278,22 +280,29 @@ func (c *cluster) reload(cli EtcdClient) {
for _, key := range keys {
k := key
c.watchGroup.Run(func() {
c.load(cli, k)
c.watch(cli, k)
rev := c.load(cli, k)
c.watch(cli, k, rev)
})
}
}
func (c *cluster) watch(cli EtcdClient, key string) {
func (c *cluster) watch(cli EtcdClient, key string, rev int64) {
for {
if c.watchStream(cli, key) {
if c.watchStream(cli, key, rev) {
return
}
}
}
func (c *cluster) watchStream(cli EtcdClient, key string) bool {
rch := cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix())
func (c *cluster) watchStream(cli EtcdClient, key string, rev int64) bool {
var rch clientv3.WatchChan
if rev != 0 {
rch = cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix(),
clientv3.WithRev(rev+1))
} else {
rch = cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix())
}
for {
select {
case wresp, ok := <-rch:
@@ -334,6 +343,7 @@ func DialClient(endpoints []string) (EtcdClient, error) {
DialKeepAliveTime: dialKeepAliveTime,
DialKeepAliveTimeout: DialTimeout,
RejectOldCluster: true,
PermitWithoutStream: true,
}
if account, ok := GetAccount(endpoints); ok {
cfg.Username = account.User

View File

@@ -11,6 +11,7 @@ import (
"github.com/zeromicro/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stringx"
"go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/mvccpb"
clientv3 "go.etcd.io/etcd/client/v3"
)
@@ -112,6 +113,7 @@ func TestCluster_Load(t *testing.T) {
restore := setMockClient(cli)
defer restore()
cli.EXPECT().Get(gomock.Any(), "any/", gomock.Any()).Return(&clientv3.GetResponse{
Header: &etcdserverpb.ResponseHeader{},
Kvs: []*mvccpb.KeyValue{
{
Key: []byte("hello"),
@@ -168,7 +170,7 @@ func TestCluster_Watch(t *testing.T) {
listener.EXPECT().OnDelete(gomock.Any()).Do(func(_ interface{}) {
wg.Done()
}).MaxTimes(1)
go c.watch(cli, "any")
go c.watch(cli, "any", 0)
ch <- clientv3.WatchResponse{
Events: []*clientv3.Event{
{
@@ -212,7 +214,7 @@ func TestClusterWatch_RespFailures(t *testing.T) {
ch <- resp
close(c.done)
}()
c.watch(cli, "any")
c.watch(cli, "any", 0)
})
}
}
@@ -232,7 +234,7 @@ func TestClusterWatch_CloseChan(t *testing.T) {
close(ch)
close(c.done)
}()
c.watch(cli, "any")
c.watch(cli, "any", 0)
}
func TestValueOnlyContext(t *testing.T) {

View File

@@ -13,7 +13,7 @@ type (
// SubOption defines the method to customize a Subscriber.
SubOption func(sub *Subscriber)
// A Subscriber is used to subscribe the given key on a etcd cluster.
// A Subscriber is used to subscribe the given key on an etcd cluster.
Subscriber struct {
endpoints []string
exclusive bool

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))
}

15
core/fs/files_test.go Normal file
View File

@@ -0,0 +1,15 @@
package fs
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCloseOnExec(t *testing.T) {
file := os.NewFile(0, os.DevNull)
assert.NotPanics(t, func() {
CloseOnExec(file)
})
}

View File

@@ -1,7 +1,6 @@
package fs
import (
"io/ioutil"
"os"
"github.com/zeromicro/go-zero/core/hash"
@@ -12,12 +11,12 @@ import (
// The file is kept as open, the caller should close the file handle,
// and remove the file by name.
func TempFileWithText(text string) (*os.File, error) {
tmpfile, err := ioutil.TempFile(os.TempDir(), hash.Md5Hex([]byte(text)))
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text)))
if err != nil {
return nil, err
}
if err := ioutil.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
if err := os.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
return nil, err
}

View File

@@ -1,7 +1,7 @@
package fs
import (
"io/ioutil"
"io"
"os"
"testing"
@@ -21,7 +21,7 @@ func TestTempFileWithText(t *testing.T) {
}
defer os.Remove(f.Name())
bs, err := ioutil.ReadAll(f)
bs, err := io.ReadAll(f)
assert.Nil(t, err)
if len(bs) != 4 {
t.Error("TempFileWithText returned wrong file size")
@@ -41,7 +41,7 @@ func TestTempFilenameWithText(t *testing.T) {
}
defer os.Remove(f)
bs, err := ioutil.ReadFile(f)
bs, err := os.ReadFile(f)
assert.Nil(t, err)
if len(bs) != 4 {
t.Error("TempFilenameWithText returned wrong file size")

View File

@@ -328,7 +328,7 @@ func (s Stream) Parallel(fn ParallelFunc, opts ...Option) {
}, opts...).Done()
}
// Reduce is a utility method to let the caller deal with the underlying channel.
// Reduce is an utility method to let the caller deal with the underlying channel.
func (s Stream) Reduce(fn ReduceFunc) (interface{}, error) {
return fn(s.source)
}

View File

@@ -1,7 +1,7 @@
package fx
import (
"io/ioutil"
"io"
"log"
"math/rand"
"reflect"
@@ -238,7 +238,7 @@ func TestLast(t *testing.T) {
func TestMap(t *testing.T) {
runCheckedTest(t, func(t *testing.T) {
log.SetOutput(ioutil.Discard)
log.SetOutput(io.Discard)
tests := []struct {
mapper MapFunc

View File

@@ -7,7 +7,6 @@ import (
"sync"
"github.com/zeromicro/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/mapping"
)
const (
@@ -183,5 +182,5 @@ func innerRepr(node interface{}) string {
}
func repr(node interface{}) string {
return mapping.Repr(node)
return lang.Repr(node)
}

View File

@@ -10,7 +10,7 @@ func (nopCloser) Close() error {
return nil
}
// NopCloser returns a io.WriteCloser that does nothing on calling Close.
// NopCloser returns an io.WriteCloser that does nothing on calling Close.
func NopCloser(w io.Writer) io.WriteCloser {
return nopCloser{w}
}

View File

@@ -4,7 +4,6 @@ import (
"bufio"
"bytes"
"io"
"io/ioutil"
"os"
"strings"
)
@@ -26,7 +25,7 @@ type (
func DupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) {
var buf bytes.Buffer
tee := io.TeeReader(reader, &buf)
return ioutil.NopCloser(tee), ioutil.NopCloser(&buf)
return io.NopCloser(tee), io.NopCloser(&buf)
}
// KeepSpace customizes the reading functions to keep leading and tailing spaces.
@@ -54,7 +53,7 @@ func ReadBytes(reader io.Reader, buf []byte) error {
// ReadText reads content from the given file with leading and tailing spaces trimmed.
func ReadText(filename string) (string, error) {
content, err := ioutil.ReadFile(filename)
content, err := os.ReadFile(filename)
if err != nil {
return "", err
}

View File

@@ -3,7 +3,6 @@ package iox
import (
"bytes"
"io"
"io/ioutil"
"os"
"testing"
"time"
@@ -97,10 +96,10 @@ func TestReadTextLines(t *testing.T) {
func TestDupReadCloser(t *testing.T) {
input := "hello"
reader := ioutil.NopCloser(bytes.NewBufferString(input))
reader := io.NopCloser(bytes.NewBufferString(input))
r1, r2 := DupReadCloser(reader)
verify := func(r io.Reader) {
output, err := ioutil.ReadAll(r)
output, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, input, string(output))
}
@@ -110,7 +109,7 @@ func TestDupReadCloser(t *testing.T) {
}
func TestReadBytes(t *testing.T) {
reader := ioutil.NopCloser(bytes.NewBufferString("helloworld"))
reader := io.NopCloser(bytes.NewBufferString("helloworld"))
buf := make([]byte, 5)
err := ReadBytes(reader, buf)
assert.Nil(t, err)
@@ -118,7 +117,7 @@ func TestReadBytes(t *testing.T) {
}
func TestReadBytesNotEnough(t *testing.T) {
reader := ioutil.NopCloser(bytes.NewBufferString("hell"))
reader := io.NopCloser(bytes.NewBufferString("hell"))
buf := make([]byte, 5)
err := ReadBytes(reader, buf)
assert.Equal(t, io.EOF, err)

View File

@@ -1,7 +1,6 @@
package iox
import (
"io/ioutil"
"os"
"testing"
@@ -13,7 +12,7 @@ func TestCountLines(t *testing.T) {
2
3
4`
file, err := ioutil.TempFile(os.TempDir(), "test-")
file, err := os.CreateTemp(os.TempDir(), "test-")
if err != nil {
t.Fatal(err)
}

View File

@@ -1,5 +1,11 @@
package lang
import (
"fmt"
"reflect"
"strconv"
)
// Placeholder is a placeholder object that can be used globally.
var Placeholder PlaceholderType
@@ -9,3 +15,64 @@ type (
// PlaceholderType represents a placeholder type.
PlaceholderType = struct{}
)
// Repr returns the string representation of v.
func Repr(v interface{}) string {
if v == nil {
return ""
}
// if func (v *Type) String() string, we can't use Elem()
switch vt := v.(type) {
case fmt.Stringer:
return vt.String()
}
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr && !val.IsNil() {
val = val.Elem()
}
return reprOfValue(val)
}
func reprOfValue(val reflect.Value) string {
switch vt := val.Interface().(type) {
case bool:
return strconv.FormatBool(vt)
case error:
return vt.Error()
case float32:
return strconv.FormatFloat(float64(vt), 'f', -1, 32)
case float64:
return strconv.FormatFloat(vt, 'f', -1, 64)
case fmt.Stringer:
return vt.String()
case int:
return strconv.Itoa(vt)
case int8:
return strconv.Itoa(int(vt))
case int16:
return strconv.Itoa(int(vt))
case int32:
return strconv.Itoa(int(vt))
case int64:
return strconv.FormatInt(vt, 10)
case string:
return vt
case uint:
return strconv.FormatUint(uint64(vt), 10)
case uint8:
return strconv.FormatUint(uint64(vt), 10)
case uint16:
return strconv.FormatUint(uint64(vt), 10)
case uint32:
return strconv.FormatUint(uint64(vt), 10)
case uint64:
return strconv.FormatUint(vt, 10)
case []byte:
return string(vt)
default:
return fmt.Sprint(val.Interface())
}
}

131
core/lang/lang_test.go Normal file
View File

@@ -0,0 +1,131 @@
package lang
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRepr(t *testing.T) {
var (
f32 float32 = 1.1
f64 = 2.2
i8 int8 = 1
i16 int16 = 2
i32 int32 = 3
i64 int64 = 4
u8 uint8 = 5
u16 uint16 = 6
u32 uint32 = 7
u64 uint64 = 8
)
tests := []struct {
v interface{}
expect string
}{
{
nil,
"",
},
{
mockStringable{},
"mocked",
},
{
new(mockStringable),
"mocked",
},
{
newMockPtr(),
"mockptr",
},
{
&mockOpacity{
val: 1,
},
"{1}",
},
{
true,
"true",
},
{
false,
"false",
},
{
f32,
"1.1",
},
{
f64,
"2.2",
},
{
i8,
"1",
},
{
i16,
"2",
},
{
i32,
"3",
},
{
i64,
"4",
},
{
u8,
"5",
},
{
u16,
"6",
},
{
u32,
"7",
},
{
u64,
"8",
},
{
[]byte(`abcd`),
"abcd",
},
{
mockOpacity{val: 1},
"{1}",
},
}
for _, test := range tests {
t.Run(test.expect, func(t *testing.T) {
assert.Equal(t, test.expect, Repr(test.v))
})
}
}
type mockStringable struct{}
func (m mockStringable) String() string {
return "mocked"
}
type mockPtr struct{}
func newMockPtr() *mockPtr {
return new(mockPtr)
}
func (m *mockPtr) String() string {
return "mockptr"
}
type mockOpacity struct {
val int
}

View File

@@ -1,6 +1,8 @@
package limit
import (
"context"
"errors"
"fmt"
"strconv"
"sync"
@@ -58,8 +60,8 @@ type TokenLimiter struct {
timestampKey string
rescueLock sync.Mutex
redisAlive uint32
rescueLimiter *xrate.Limiter
monitorStarted bool
rescueLimiter *xrate.Limiter
}
// NewTokenLimiter returns a new TokenLimiter that allows events up to rate and permits
@@ -84,19 +86,31 @@ func (lim *TokenLimiter) Allow() bool {
return lim.AllowN(time.Now(), 1)
}
// AllowCtx is shorthand for AllowNCtx(ctx,time.Now(), 1) with incoming context.
func (lim *TokenLimiter) AllowCtx(ctx context.Context) bool {
return lim.AllowNCtx(ctx, time.Now(), 1)
}
// AllowN reports whether n events may happen at time now.
// Use this method if you intend to drop / skip events that exceed the rate.
// Otherwise, use Reserve or Wait.
func (lim *TokenLimiter) AllowN(now time.Time, n int) bool {
return lim.reserveN(now, n)
return lim.reserveN(context.Background(), now, n)
}
func (lim *TokenLimiter) reserveN(now time.Time, n int) bool {
// AllowNCtx reports whether n events may happen at time now with incoming context.
// Use this method if you intend to drop / skip events that exceed the rate.
// Otherwise, use Reserve or Wait.
func (lim *TokenLimiter) AllowNCtx(ctx context.Context, now time.Time, n int) bool {
return lim.reserveN(ctx, now, n)
}
func (lim *TokenLimiter) reserveN(ctx context.Context, now time.Time, n int) bool {
if atomic.LoadUint32(&lim.redisAlive) == 0 {
return lim.rescueLimiter.AllowN(now, n)
}
resp, err := lim.store.Eval(
resp, err := lim.store.EvalCtx(ctx,
script,
[]string{
lim.tokenKey,
@@ -113,6 +127,10 @@ func (lim *TokenLimiter) reserveN(now time.Time, n int) bool {
if err == redis.Nil {
return false
}
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
logx.Errorf("fail to use rate limiter: %s", err)
return false
}
if err != nil {
logx.Errorf("fail to use rate limiter: %s, use in-process limiter for rescue", err)
lim.startMonitor()

View File

@@ -1,6 +1,7 @@
package limit
import (
"context"
"testing"
"time"
@@ -15,6 +16,30 @@ func init() {
logx.Disable()
}
func TestTokenLimit_WithCtx(t *testing.T) {
s, err := miniredis.Run()
assert.Nil(t, err)
const (
total = 100
rate = 5
burst = 10
)
l := NewTokenLimiter(rate, burst, redis.New(s.Addr()), "tokenlimit")
defer s.Close()
ctx, cancel := context.WithCancel(context.Background())
ok := l.AllowCtx(ctx)
assert.True(t, ok)
cancel()
for i := 0; i < total; i++ {
ok := l.AllowCtx(ctx)
assert.False(t, ok)
assert.False(t, l.monitorStarted)
}
}
func TestTokenLimit_Rescue(t *testing.T) {
s, err := miniredis.Run()
assert.Nil(t, err)

View File

@@ -17,7 +17,7 @@ import (
const (
defaultBuckets = 50
defaultWindow = time.Second * 5
// using 1000m notation, 900m is like 80%, keep it as var for unit test
// using 1000m notation, 900m is like 90%, keep it as var for unit test
defaultCpuThreshold = 900
defaultMinRt = float64(time.Second / time.Millisecond)
// moving average hyperparameter beta for calculating requests on the fly
@@ -70,7 +70,7 @@ type (
flying int64
avgFlying float64
avgFlyingLock syncx.SpinLock
dropTime *syncx.AtomicDuration
overloadTime *syncx.AtomicDuration
droppedRecently *syncx.AtomicBool
passCounter *collection.RollingWindow
rtCounter *collection.RollingWindow
@@ -106,7 +106,7 @@ func NewAdaptiveShedder(opts ...ShedderOption) Shedder {
return &adaptiveShedder{
cpuThreshold: options.cpuThreshold,
windows: int64(time.Second / bucketDuration),
dropTime: syncx.NewAtomicDuration(),
overloadTime: syncx.NewAtomicDuration(),
droppedRecently: syncx.NewAtomicBool(),
passCounter: collection.NewRollingWindow(options.buckets, bucketDuration,
collection.IgnoreCurrentBucket()),
@@ -118,7 +118,6 @@ func NewAdaptiveShedder(opts ...ShedderOption) Shedder {
// Allow implements Shedder.Allow.
func (as *adaptiveShedder) Allow() (Promise, error) {
if as.shouldDrop() {
as.dropTime.Set(timex.Now())
as.droppedRecently.Set(true)
return nil, ErrServiceOverloaded
@@ -215,21 +214,26 @@ func (as *adaptiveShedder) stillHot() bool {
return false
}
dropTime := as.dropTime.Load()
if dropTime == 0 {
overloadTime := as.overloadTime.Load()
if overloadTime == 0 {
return false
}
hot := timex.Since(dropTime) < coolOffDuration
if !hot {
as.droppedRecently.Set(false)
if timex.Since(overloadTime) < coolOffDuration {
return true
}
return hot
as.droppedRecently.Set(false)
return false
}
func (as *adaptiveShedder) systemOverloaded() bool {
return systemOverloadChecker(as.cpuThreshold)
if !systemOverloadChecker(as.cpuThreshold) {
return false
}
as.overloadTime.Set(timex.Now())
return true
}
// WithBuckets customizes the Shedder with given number of buckets.

View File

@@ -13,6 +13,7 @@ import (
"github.com/zeromicro/go-zero/core/mathx"
"github.com/zeromicro/go-zero/core/stat"
"github.com/zeromicro/go-zero/core/syncx"
"github.com/zeromicro/go-zero/core/timex"
)
const (
@@ -136,7 +137,7 @@ func TestAdaptiveShedderShouldDrop(t *testing.T) {
passCounter: passCounter,
rtCounter: rtCounter,
windows: buckets,
dropTime: syncx.NewAtomicDuration(),
overloadTime: syncx.NewAtomicDuration(),
droppedRecently: syncx.NewAtomicBool(),
}
// cpu >= 800, inflight < maxPass
@@ -190,12 +191,15 @@ func TestAdaptiveShedderStillHot(t *testing.T) {
passCounter: passCounter,
rtCounter: rtCounter,
windows: buckets,
dropTime: syncx.NewAtomicDuration(),
overloadTime: syncx.NewAtomicDuration(),
droppedRecently: syncx.ForAtomicBool(true),
}
assert.False(t, shedder.stillHot())
shedder.dropTime.Set(-coolOffDuration * 2)
shedder.overloadTime.Set(-coolOffDuration * 2)
assert.False(t, shedder.stillHot())
shedder.droppedRecently.Set(true)
shedder.overloadTime.Set(timex.Now())
assert.True(t, shedder.stillHot())
}
func BenchmarkAdaptiveShedder_Allow(b *testing.B) {

122
core/logc/logs.go Normal file
View File

@@ -0,0 +1,122 @@
package logc
import (
"context"
"fmt"
"github.com/zeromicro/go-zero/core/logx"
)
type (
LogConf = logx.LogConf
LogField = logx.LogField
)
// AddGlobalFields adds global fields.
func AddGlobalFields(fields ...LogField) {
logx.AddGlobalFields(fields...)
}
// Alert alerts v in alert level, and the message is written to error log.
func Alert(_ context.Context, v string) {
logx.Alert(v)
}
// Close closes the logging.
func Close() error {
return logx.Close()
}
// Error writes v into error log.
func Error(ctx context.Context, v ...interface{}) {
getLogger(ctx).Error(v...)
}
// Errorf writes v with format into error log.
func Errorf(ctx context.Context, format string, v ...interface{}) {
getLogger(ctx).Errorf(fmt.Errorf(format, v...).Error())
}
// Errorv writes v into error log with json content.
// No call stack attached, because not elegant to pack the messages.
func Errorv(ctx context.Context, v interface{}) {
getLogger(ctx).Errorv(v)
}
// Errorw writes msg along with fields into error log.
func Errorw(ctx context.Context, msg string, fields ...LogField) {
getLogger(ctx).Errorw(msg, fields...)
}
// Field returns a LogField for the given key and value.
func Field(key string, value interface{}) LogField {
return logx.Field(key, value)
}
// Info writes v into access log.
func Info(ctx context.Context, v ...interface{}) {
getLogger(ctx).Info(v...)
}
// Infof writes v with format into access log.
func Infof(ctx context.Context, format string, v ...interface{}) {
getLogger(ctx).Infof(format, v...)
}
// Infov writes v into access log with json content.
func Infov(ctx context.Context, v interface{}) {
getLogger(ctx).Infov(v)
}
// Infow writes msg along with fields into access log.
func Infow(ctx context.Context, msg string, fields ...LogField) {
getLogger(ctx).Infow(msg, fields...)
}
// Must checks if err is nil, otherwise logs the error and exits.
func Must(err error) {
logx.Must(err)
}
// MustSetup sets up logging with given config c. It exits on error.
func MustSetup(c logx.LogConf) {
logx.MustSetup(c)
}
// SetLevel sets the logging level. It can be used to suppress some logs.
func SetLevel(level uint32) {
logx.SetLevel(level)
}
// 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 {
return logx.SetUp(c)
}
// Slow writes v into slow log.
func Slow(ctx context.Context, v ...interface{}) {
getLogger(ctx).Slow(v...)
}
// Slowf writes v with format into slow log.
func Slowf(ctx context.Context, format string, v ...interface{}) {
getLogger(ctx).Slowf(format, v...)
}
// Slowv writes v into slow log with json content.
func Slowv(ctx context.Context, v interface{}) {
getLogger(ctx).Slowv(v)
}
// Sloww writes msg along with fields into slow log.
func Sloww(ctx context.Context, msg string, fields ...LogField) {
getLogger(ctx).Sloww(msg, fields...)
}
// getLogger returns the logx.Logger with the given ctx and correct caller.
func getLogger(ctx context.Context) logx.Logger {
return logx.WithContext(ctx).WithCallerSkip(1)
}

218
core/logc/logs_test.go Normal file
View File

@@ -0,0 +1,218 @@
package logc
import (
"bytes"
"context"
"encoding/json"
"fmt"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
)
func TestAddGlobalFields(t *testing.T) {
var buf bytes.Buffer
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
Info(context.Background(), "hello")
buf.Reset()
AddGlobalFields(Field("a", "1"), Field("b", "2"))
AddGlobalFields(Field("c", "3"))
Info(context.Background(), "world")
var m map[string]interface{}
assert.NoError(t, json.Unmarshal(buf.Bytes(), &m))
assert.Equal(t, "1", m["a"])
assert.Equal(t, "2", m["b"])
assert.Equal(t, "3", m["c"])
}
func TestAlert(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
Alert(context.Background(), "foo")
assert.True(t, strings.Contains(buf.String(), "foo"), buf.String())
}
func TestError(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Error(context.Background(), "foo")
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
}
func TestErrorf(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Errorf(context.Background(), "foo %s", "bar")
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
}
func TestErrorv(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Errorv(context.Background(), "foo")
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
}
func TestErrorw(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Errorw(context.Background(), "foo", Field("a", "b"))
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
}
func TestInfo(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Info(context.Background(), "foo")
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
}
func TestInfof(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Infof(context.Background(), "foo %s", "bar")
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
}
func TestInfov(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Infov(context.Background(), "foo")
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
}
func TestInfow(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Infow(context.Background(), "foo", Field("a", "b"))
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
}
func TestMust(t *testing.T) {
assert.NotPanics(t, func() {
Must(nil)
})
assert.NotPanics(t, func() {
MustSetup(LogConf{})
})
}
func TestMisc(t *testing.T) {
SetLevel(logx.DebugLevel)
assert.NoError(t, SetUp(LogConf{}))
assert.NoError(t, Close())
}
func TestSlow(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Slow(context.Background(), "foo")
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
}
func TestSlowf(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Slowf(context.Background(), "foo %s", "bar")
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
}
func TestSlowv(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Slowv(context.Background(), "foo")
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
}
func TestSloww(t *testing.T) {
var buf strings.Builder
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
file, line := getFileLine()
Sloww(context.Background(), "foo", Field("a", "b"))
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
}
func getFileLine() (string, int) {
_, file, line, _ := runtime.Caller(1)
short := file
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
short = file[i+1:]
break
}
}
return short, line
}

View File

@@ -7,8 +7,20 @@ type LogConf struct {
Encoding string `json:",default=json,options=[json,plain]"`
TimeFormat string `json:",optional"`
Path string `json:",default=logs"`
Level string `json:",default=info,options=[info,error,severe]"`
Level string `json:",default=info,options=[debug,info,error,severe]"`
Compress bool `json:",optional"`
KeepDays int `json:",optional"`
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]"`
}

View File

@@ -1,101 +0,0 @@
package logx
import (
"context"
"fmt"
"time"
"github.com/zeromicro/go-zero/core/timex"
)
// WithDuration returns a Logger which logs the given duration.
func WithDuration(d time.Duration) Logger {
return &durationLogger{
Duration: timex.ReprOfDuration(d),
}
}
type durationLogger logEntry
func (l *durationLogger) Error(v ...interface{}) {
l.err(fmt.Sprint(v...))
}
func (l *durationLogger) Errorf(format string, v ...interface{}) {
l.err(fmt.Sprintf(format, v...))
}
func (l *durationLogger) Errorv(v interface{}) {
l.err(v)
}
func (l *durationLogger) Errorw(msg string, fields ...LogField) {
l.err(msg, fields...)
}
func (l *durationLogger) Info(v ...interface{}) {
l.info(fmt.Sprint(v...))
}
func (l *durationLogger) Infof(format string, v ...interface{}) {
l.info(fmt.Sprintf(format, v...))
}
func (l *durationLogger) Infov(v interface{}) {
l.info(v)
}
func (l *durationLogger) Infow(msg string, fields ...LogField) {
l.info(msg, fields...)
}
func (l *durationLogger) Slow(v ...interface{}) {
l.slow(fmt.Sprint(v...))
}
func (l *durationLogger) Slowf(format string, v ...interface{}) {
l.slow(fmt.Sprintf(format, v...))
}
func (l *durationLogger) Slowv(v interface{}) {
l.slow(v)
}
func (l *durationLogger) Sloww(msg string, fields ...LogField) {
l.slow(msg, fields...)
}
func (l *durationLogger) WithContext(ctx context.Context) Logger {
return &traceLogger{
ctx: ctx,
logEntry: logEntry{
Duration: l.Duration,
},
}
}
func (l *durationLogger) WithDuration(duration time.Duration) Logger {
l.Duration = timex.ReprOfDuration(duration)
return l
}
func (l *durationLogger) err(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
fields = append(fields, Field(durationKey, l.Duration))
getWriter().Error(v, fields...)
}
}
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,161 +0,0 @@
package logx
import (
"context"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func TestWithDurationError(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Error("foo")
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationErrorf(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Errorf("foo")
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) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Info("foo")
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) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Infof("foo")
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) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Slow("foo")
assert.True(t, strings.Contains(w.String(), "duration"), w.String())
}
func TestWithDurationSlowf(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).WithDuration(time.Hour).Slowf("foo")
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())
}

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

@@ -0,0 +1,48 @@
package logx
import (
"context"
"sync"
"sync/atomic"
)
var (
fieldsContextKey contextKey
globalFields atomic.Value
globalFieldsLock sync.Mutex
)
type contextKey struct{}
// AddGlobalFields adds global fields.
func AddGlobalFields(fields ...LogField) {
globalFieldsLock.Lock()
defer globalFieldsLock.Unlock()
old := globalFields.Load()
if old == nil {
globalFields.Store(append([]LogField(nil), fields...))
} else {
globalFields.Store(append(old.([]LogField), fields...))
}
}
// ContextWithFields returns a new context with the given fields.
func ContextWithFields(ctx context.Context, fields ...LogField) context.Context {
if val := ctx.Value(fieldsContextKey); val != nil {
if arr, ok := val.([]LogField); ok {
allFields := make([]LogField, 0, len(arr)+len(fields))
allFields = append(allFields, arr...)
allFields = append(allFields, fields...)
return context.WithValue(ctx, fieldsContextKey, allFields)
}
}
return context.WithValue(ctx, fieldsContextKey, fields)
}
// WithFields returns a new logger with the given fields.
// deprecated: use ContextWithFields instead.
func WithFields(ctx context.Context, fields ...LogField) context.Context {
return ContextWithFields(ctx, fields...)
}

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

@@ -0,0 +1,121 @@
package logx
import (
"bytes"
"context"
"encoding/json"
"strconv"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddGlobalFields(t *testing.T) {
var buf bytes.Buffer
writer := NewWriter(&buf)
old := Reset()
SetWriter(writer)
defer SetWriter(old)
Info("hello")
buf.Reset()
AddGlobalFields(Field("a", "1"), Field("b", "2"))
AddGlobalFields(Field("c", "3"))
Info("world")
var m map[string]interface{}
assert.NoError(t, json.Unmarshal(buf.Bytes(), &m))
assert.Equal(t, "1", m["a"])
assert.Equal(t, "2", m["b"])
assert.Equal(t, "3", m["c"])
}
func TestContextWithFields(t *testing.T) {
ctx := ContextWithFields(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 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 = ContextWithFields(ctx, Field("a", 1), Field("b", 2))
ctx = ContextWithFields(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)
}
func TestWithFieldsAppendCopy(t *testing.T) {
const count = 10
ctx := context.Background()
for i := 0; i < count; i++ {
ctx = ContextWithFields(ctx, Field(strconv.Itoa(i), 1))
}
af := Field("foo", 1)
bf := Field("bar", 2)
ctxa := ContextWithFields(ctx, af)
ctxb := ContextWithFields(ctx, bf)
assert.EqualValues(t, af, ctxa.Value(fieldsContextKey).([]LogField)[count])
assert.EqualValues(t, bf, ctxb.Value(fieldsContextKey).([]LogField)[count])
}
func BenchmarkAtomicValue(b *testing.B) {
b.ReportAllocs()
var container atomic.Value
vals := []LogField{
Field("a", "b"),
Field("c", "d"),
Field("e", "f"),
}
container.Store(&vals)
for i := 0; i < b.N; i++ {
val := container.Load()
if val != nil {
_ = *val.(*[]LogField)
}
}
}
func BenchmarkRWMutex(b *testing.B) {
b.ReportAllocs()
var lock sync.RWMutex
vals := []LogField{
Field("a", "b"),
Field("c", "d"),
Field("e", "f"),
}
for i := 0; i < b.N; i++ {
lock.RLock()
_ = vals
lock.RUnlock()
}
}

View File

@@ -7,6 +7,14 @@ import (
// A Logger represents a logger.
type Logger interface {
// Debug logs a message at info level.
Debug(...interface{})
// Debugf logs a message at info level.
Debugf(string, ...interface{})
// Debugv logs a message at info level.
Debugv(interface{})
// Debugw logs a message at info level.
Debugw(string, ...LogField)
// Error logs a message at error level.
Error(...interface{})
// Errorf logs a message at error level.
@@ -31,8 +39,12 @@ type Logger interface {
Slowv(interface{})
// Sloww logs a message at slow level.
Sloww(string, ...LogField)
// WithCallerSkip returns a new logger with the given caller skip.
WithCallerSkip(skip int) Logger
// WithContext returns a new logger with the given context.
WithContext(context.Context) Logger
WithContext(ctx context.Context) Logger
// WithDuration returns a new logger with the given duration.
WithDuration(time.Duration) Logger
WithDuration(d time.Duration) Logger
// WithFields returns a new logger with the given fields.
WithFields(fields ...LogField) Logger
}

View File

@@ -7,42 +7,28 @@ import (
"os"
"path"
"runtime/debug"
"sync"
"sync/atomic"
"time"
"github.com/zeromicro/go-zero/core/sysx"
)
const callerDepth = 5
const callerDepth = 4
var (
timeFormat = "2006-01-02T15:04:05.000Z07:00"
logLevel uint32
encoding uint32 = jsonEncodingType
// use uint32 for atomic operations
disableLog uint32
disableStat uint32
options logOptions
writer = new(atomicWriter)
options logOptions
writer = new(atomicWriter)
setupOnce sync.Once
)
type (
logEntry struct {
Timestamp string `json:"@timestamp"`
Level string `json:"level"`
Duration string `json:"duration,omitempty"`
Caller string `json:"caller,omitempty"`
Content interface{} `json:"content"`
}
logEntryWithFields map[string]interface{}
logOptions struct {
gzipEnabled bool
logStackCooldownMills int
keepDays int
}
// LogField is a key-value pair that will be added to the log entry.
LogField struct {
Key string
@@ -51,6 +37,17 @@ type (
// LogOption defines the method to customize the logging.
LogOption func(options *logOptions)
logEntry map[string]interface{}
logOptions struct {
gzipEnabled bool
logStackCooldownMills int
keepDays int
maxBackups int
maxSize int
rotationRule string
}
)
// Alert alerts v in alert level, and the message is written to error log.
@@ -67,8 +64,29 @@ func Close() error {
return nil
}
// Debug writes v into access log.
func Debug(v ...interface{}) {
writeDebug(fmt.Sprint(v...))
}
// Debugf writes v with format into access log.
func Debugf(format string, v ...interface{}) {
writeDebug(fmt.Sprintf(format, v...))
}
// Debugv writes v into access log with json content.
func Debugv(v interface{}) {
writeDebug(v)
}
// Debugw writes msg along with fields into access log.
func Debugw(msg string, fields ...LogField) {
writeDebug(msg, fields...)
}
// Disable disables the logging.
func Disable() {
atomic.StoreUint32(&disableLog, 1)
writer.Store(nopWriter{})
}
@@ -79,35 +97,35 @@ func DisableStat() {
// Error writes v into error log.
func Error(v ...interface{}) {
errorTextSync(fmt.Sprint(v...))
writeError(fmt.Sprint(v...))
}
// Errorf writes v with format into error log.
func Errorf(format string, v ...interface{}) {
errorTextSync(fmt.Errorf(format, v...).Error())
writeError(fmt.Errorf(format, v...).Error())
}
// ErrorStack writes v along with call stack into error log.
func ErrorStack(v ...interface{}) {
// there is newline in stack string
stackSync(fmt.Sprint(v...))
writeStack(fmt.Sprint(v...))
}
// ErrorStackf writes v along with call stack in format into error log.
func ErrorStackf(format string, v ...interface{}) {
// there is newline in stack string
stackSync(fmt.Sprintf(format, v...))
writeStack(fmt.Sprintf(format, v...))
}
// Errorv writes v into error log with json content.
// No call stack attached, because not elegant to pack the messages.
func Errorv(v interface{}) {
errorAnySync(v)
writeError(v)
}
// Errorw writes msg along with fields into error log.
func Errorw(msg string, fields ...LogField) {
errorFieldsSync(msg, fields...)
writeError(msg, fields...)
}
// Field returns a LogField for the given key and value.
@@ -150,22 +168,22 @@ func Field(key string, value interface{}) LogField {
// Info writes v into access log.
func Info(v ...interface{}) {
infoTextSync(fmt.Sprint(v...))
writeInfo(fmt.Sprint(v...))
}
// Infof writes v with format into access log.
func Infof(format string, v ...interface{}) {
infoTextSync(fmt.Sprintf(format, v...))
writeInfo(fmt.Sprintf(format, v...))
}
// Infov writes v into access log with json content.
func Infov(v interface{}) {
infoAnySync(v)
writeInfo(v)
}
// Infow writes msg along with fields into access log.
func Infow(msg string, fields ...LogField) {
infoFieldsSync(msg, fields...)
writeInfo(msg, fields...)
}
// Must checks if err is nil, otherwise logs the error and exits.
@@ -187,7 +205,6 @@ func MustSetup(c LogConf) {
// Reset clears the writer and resets the log level.
func Reset() Writer {
SetLevel(InfoLevel)
return writer.Swap(nil)
}
@@ -197,9 +214,8 @@ func SetLevel(level uint32) {
}
// SetWriter sets the logging writer. It can be used to customize the logging.
// Call Reset before calling SetWriter again.
func SetWriter(w Writer) {
if writer.Load() == nil {
if atomic.LoadUint32(&disableLog) == 0 {
writer.Store(w)
}
}
@@ -207,70 +223,75 @@ func SetWriter(w Writer) {
// 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 {
setupLogLevel(c)
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
}
if len(c.TimeFormat) > 0 {
timeFormat = c.TimeFormat
}
switch c.Encoding {
case plainEncoding:
atomic.StoreUint32(&encoding, plainEncodingType)
default:
atomic.StoreUint32(&encoding, jsonEncodingType)
}
switch c.Encoding {
case plainEncoding:
atomic.StoreUint32(&encoding, plainEncodingType)
default:
atomic.StoreUint32(&encoding, jsonEncodingType)
}
switch c.Mode {
case fileMode:
return setupWithFiles(c)
case volumeMode:
return setupWithVolume(c)
default:
setupWithConsole()
return nil
}
switch c.Mode {
case fileMode:
err = setupWithFiles(c)
case volumeMode:
err = setupWithVolume(c)
default:
setupWithConsole()
}
})
return
}
// Severe writes v into severe log.
func Severe(v ...interface{}) {
severeSync(fmt.Sprint(v...))
writeSevere(fmt.Sprint(v...))
}
// Severef writes v with format into severe log.
func Severef(format string, v ...interface{}) {
severeSync(fmt.Sprintf(format, v...))
writeSevere(fmt.Sprintf(format, v...))
}
// Slow writes v into slow log.
func Slow(v ...interface{}) {
slowTextSync(fmt.Sprint(v...))
writeSlow(fmt.Sprint(v...))
}
// Slowf writes v with format into slow log.
func Slowf(format string, v ...interface{}) {
slowTextSync(fmt.Sprintf(format, v...))
writeSlow(fmt.Sprintf(format, v...))
}
// Slowv writes v into slow log with json content.
func Slowv(v interface{}) {
slowAnySync(v)
writeSlow(v)
}
// Sloww writes msg along with fields into slow log.
func Sloww(msg string, fields ...LogField) {
slowFieldsSync(msg, fields...)
writeSlow(msg, fields...)
}
// Stat writes v into stat log.
func Stat(v ...interface{}) {
statSync(fmt.Sprint(v...))
writeStat(fmt.Sprint(v...))
}
// Statf writes v with format into stat log.
func Statf(format string, v ...interface{}) {
statSync(fmt.Sprintf(format, v...))
writeStat(fmt.Sprintf(format, v...))
}
// WithCooldownMillis customizes logging on writing call stack interval.
@@ -294,38 +315,50 @@ 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 addCaller(fields ...LogField) []LogField {
return append(fields, Field(callerKey, getCaller(callerDepth)))
}
func createOutput(path string) (io.WriteCloser, error) {
if len(path) == 0 {
return nil, ErrLogPathNotSet
}
return NewLogger(path, DefaultRotateRule(path, backupFileDelimiter, options.keepDays,
options.gzipEnabled), options.gzipEnabled)
}
func errorAnySync(v interface{}) {
if shallLog(ErrorLevel) {
getWriter().Error(v)
}
}
func errorFieldsSync(content string, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Error(content, fields...)
}
}
func errorTextSync(msg string) {
if shallLog(ErrorLevel) {
getWriter().Error(msg)
switch options.rotationRule {
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 getWriter() Writer {
w := writer.Load()
if w == nil {
w = newConsoleWriter()
writer.Store(w)
w = writer.StoreIfNil(newConsoleWriter())
}
return w
@@ -337,26 +370,10 @@ func handleOptions(opts []LogOption) {
}
}
func infoAnySync(val interface{}) {
if shallLog(InfoLevel) {
getWriter().Info(val)
}
}
func infoFieldsSync(content string, fields ...LogField) {
if shallLog(InfoLevel) {
getWriter().Info(content, fields...)
}
}
func infoTextSync(msg string) {
if shallLog(InfoLevel) {
getWriter().Info(msg)
}
}
func setupLogLevel(c LogConf) {
switch c.Level {
case levelDebug:
SetLevel(DebugLevel)
case levelInfo:
SetLevel(InfoLevel)
case levelError:
@@ -389,12 +406,6 @@ func setupWithVolume(c LogConf) error {
return setupWithFiles(c)
}
func severeSync(msg string) {
if shallLog(SevereLevel) {
getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
}
}
func shallLog(level uint32) bool {
return atomic.LoadUint32(&logLevel) <= level
}
@@ -403,32 +414,44 @@ func shallLogStat() bool {
return atomic.LoadUint32(&disableStat) == 0
}
func slowAnySync(v interface{}) {
if shallLog(ErrorLevel) {
getWriter().Slow(v)
func writeDebug(val interface{}, fields ...LogField) {
if shallLog(DebugLevel) {
getWriter().Debug(val, addCaller(fields...)...)
}
}
func slowFieldsSync(content string, fields ...LogField) {
func writeError(val interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Slow(content, fields...)
getWriter().Error(val, addCaller(fields...)...)
}
}
func slowTextSync(msg string) {
if shallLog(ErrorLevel) {
getWriter().Slow(msg)
func writeInfo(val interface{}, fields ...LogField) {
if shallLog(InfoLevel) {
getWriter().Info(val, addCaller(fields...)...)
}
}
func stackSync(msg string) {
func writeSevere(msg string) {
if shallLog(SevereLevel) {
getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
}
}
func writeSlow(val interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Slow(val, addCaller(fields...)...)
}
}
func writeStack(msg string) {
if shallLog(ErrorLevel) {
getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
}
}
func statSync(msg string) {
func writeStat(msg string) {
if shallLogStat() && shallLog(InfoLevel) {
getWriter().Stat(msg)
getWriter().Stat(msg, addCaller()...)
}
}

View File

@@ -4,7 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"log"
"os"
"reflect"
@@ -35,6 +35,12 @@ func (mw *mockWriter) Alert(v interface{}) {
output(&mw.builder, levelAlert, v)
}
func (mw *mockWriter) Debug(v interface{}, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelDebug, v, fields...)
}
func (mw *mockWriter) Error(v interface{}, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
@@ -212,6 +218,46 @@ func TestStructedLogAlert(t *testing.T) {
})
}
func TestStructedLogDebug(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
Debug(v...)
})
}
func TestStructedLogDebugf(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
Debugf(fmt.Sprint(v...))
})
}
func TestStructedLogDebugv(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
Debugv(fmt.Sprint(v...))
})
}
func TestStructedLogDebugw(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
Debugw(fmt.Sprint(v...), Field("foo", time.Second))
})
}
func TestStructedLogError(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
@@ -461,13 +507,13 @@ func TestStructedLogWithDuration(t *testing.T) {
defer writer.Store(old)
WithDuration(time.Second).Info(message)
var entry logEntry
var entry map[string]interface{}
if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
t.Error(err)
}
assert.Equal(t, levelInfo, entry.Level)
assert.Equal(t, message, entry.Content)
assert.Equal(t, "1000.0ms", entry.Duration)
assert.Equal(t, levelInfo, entry[levelKey])
assert.Equal(t, message, entry[contentKey])
assert.Equal(t, "1000.0ms", entry[durationKey])
}
func TestSetLevel(t *testing.T) {
@@ -531,6 +577,7 @@ func TestSetup(t *testing.T) {
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
TimeFormat: timeFormat,
})
MustSetup(LogConf{
ServiceName: "any",
@@ -553,13 +600,23 @@ func TestSetup(t *testing.T) {
Encoding: plainEncoding,
})
defer os.RemoveAll("CD01CB7D-2705-4F3F-889E-86219BF56F10")
assert.NotNil(t, setupWithVolume(LogConf{}))
assert.Nil(t, setupWithVolume(LogConf{
ServiceName: "CD01CB7D-2705-4F3F-889E-86219BF56F10",
}))
assert.Nil(t, setupWithVolume(LogConf{
ServiceName: "CD01CB7D-2705-4F3F-889E-86219BF56F10",
Rotation: sizeRotationRule,
}))
assert.NotNil(t, setupWithFiles(LogConf{}))
assert.Nil(t, setupWithFiles(LogConf{
ServiceName: "any",
Path: os.TempDir(),
Compress: true,
KeepDays: 1,
MaxBackups: 3,
MaxSize: 1024 * 1024,
}))
setupLogLevel(LogConf{
Level: levelInfo,
@@ -583,6 +640,8 @@ func TestDisable(t *testing.T) {
var opt logOptions
WithKeepDays(1)(&opt)
WithGzip()(&opt)
WithMaxBackups(1)(&opt)
WithMaxSize(1024)(&opt)
assert.Nil(t, Close())
assert.Nil(t, Close())
}
@@ -599,12 +658,14 @@ func TestDisableStat(t *testing.T) {
}
func TestSetWriter(t *testing.T) {
atomic.StoreUint32(&disableLog, 0)
Reset()
SetWriter(nopWriter{})
assert.NotNil(t, writer.Load())
assert.True(t, writer.Load() == nopWriter{})
SetWriter(new(mockWriter))
assert.True(t, writer.Load() == nopWriter{})
mocked := new(mockWriter)
SetWriter(mocked)
assert.Equal(t, mocked, writer.Load())
}
func TestWithGzip(t *testing.T) {
@@ -647,7 +708,7 @@ func BenchmarkCopyByteSlice(b *testing.B) {
buf = make([]byte, len(s))
copy(buf, s)
}
fmt.Fprint(ioutil.Discard, buf)
fmt.Fprint(io.Discard, buf)
}
func BenchmarkCopyOnWriteByteSlice(b *testing.B) {
@@ -656,7 +717,7 @@ func BenchmarkCopyOnWriteByteSlice(b *testing.B) {
size := len(s)
buf = s[:size:size]
}
fmt.Fprint(ioutil.Discard, buf)
fmt.Fprint(io.Discard, buf)
}
func BenchmarkCacheByteSlice(b *testing.B) {
@@ -670,7 +731,7 @@ func BenchmarkCacheByteSlice(b *testing.B) {
func BenchmarkLogs(b *testing.B) {
b.ReportAllocs()
log.SetOutput(ioutil.Discard)
log.SetOutput(io.Discard)
for i := 0; i < b.N; i++ {
Info(i)
}
@@ -709,14 +770,16 @@ func put(b []byte) {
func doTestStructedLog(t *testing.T, level string, w *mockWriter, write func(...interface{})) {
const message = "hello there"
write(message)
var entry logEntry
var entry map[string]interface{}
if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
t.Error(err)
}
assert.Equal(t, level, entry.Level)
val, ok := entry.Content.(string)
assert.Equal(t, level, entry[levelKey])
val, ok := entry[contentKey]
assert.True(t, ok)
assert.True(t, strings.Contains(val, message))
assert.True(t, strings.Contains(val.(string), message))
}
func doTestStructedLogConsole(t *testing.T, w *mockWriter, write func(...interface{})) {

View File

@@ -8,15 +8,18 @@
```go
type LogConf struct {
ServiceName string `json:",optional"`
Mode string `json:",default=console,options=[console,file,volume]"`
Encoding string `json:",default=json,options=[json,plain]"`
TimeFormat string `json:",optional"`
Path string `json:",default=logs"`
Level string `json:",default=info,options=[info,error,severe]"`
Compress bool `json:",optional"`
KeepDays int `json:",optional"`
StackCooldownMillis int `json:",default=100"`
ServiceName string `json:",optional"`
Mode string `json:",default=console,options=[console,file,volume]"`
Encoding string `json:",default=json,options=[json,plain]"`
TimeFormat string `json:",optional"`
Path string `json:",default=logs"`
Level string `json:",default=info,options=[info,error,severe]"`
Compress bool `json:",optional"`
KeepDays int `json:",optional"`
StackCooldownMillis int `json:",default=100"`
MaxBackups int `json:",default=0"`
MaxSize int `json:",default=0"`
Rotation string `json:",default=daily,options=[daily,size]"`
}
```
@@ -37,6 +40,12 @@ type LogConf struct {
- `Compress`: 是否压缩日志文件,只在 `file` 模式下工作
- `KeepDays`:日志文件被保留多少天,在给定的天数之后,过期的文件将被自动删除。对 `console` 模式没有影响
- `StackCooldownMillis`:多少毫秒后再次写入堆栈跟踪。用来避免堆栈跟踪日志过多
- `MaxBackups`: 多少个日志文件备份将被保存。0代表所有备份都被保存。当`Rotation`被设置为`size`时才会起作用。注意:`KeepDays`选项的优先级会比`MaxBackups`高,即使`MaxBackups`被设置为0当达到`KeepDays`上限时备份文件同样会被删除。
- `MaxSize`: 当前被写入的日志文件最大可占用多少空间。0代表没有上限。单位为`MB`。当`Rotation`被设置为`size`时才会起作用。
- `Rotation`: 日志轮转策略类型。默认为`daily`(按天轮转)。
- `daily` 按天轮转。
- `size` 按日志大小轮转。
## 打印日志方法

View File

@@ -8,15 +8,18 @@ English | [简体中文](readme-cn.md)
```go
type LogConf struct {
ServiceName string `json:",optional"`
Mode string `json:",default=console,options=[console,file,volume]"`
Encoding string `json:",default=json,options=[json,plain]"`
TimeFormat string `json:",optional"`
Path string `json:",default=logs"`
Level string `json:",default=info,options=[info,error,severe]"`
Compress bool `json:",optional"`
KeepDays int `json:",optional"`
StackCooldownMillis int `json:",default=100"`
ServiceName string `json:",optional"`
Mode string `json:",default=console,options=[console,file,volume]"`
Encoding string `json:",default=json,options=[json,plain]"`
TimeFormat string `json:",optional"`
Path string `json:",default=logs"`
Level string `json:",default=info,options=[info,error,severe]"`
Compress bool `json:",optional"`
KeepDays int `json:",optional"`
StackCooldownMillis int `json:",default=100"`
MaxBackups int `json:",default=0"`
MaxSize int `json:",default=0"`
Rotation string `json:",default=daily,options=[daily,size]"`
}
```
@@ -37,6 +40,11 @@ type LogConf struct {
- `Compress`: whether or not to compress log files, only works with `file` mode.
- `KeepDays`: how many days that the log files are kept, after the given days, the outdated files will be deleted automatically. It has no effect on `console` mode.
- `StackCooldownMillis`: how many milliseconds to rewrite stacktrace again. Its used to avoid stacktrace flooding.
- `MaxBackups`: represents how many backup log files will be kept. 0 means all files will be kept forever. Only take effect when `Rotation` is `size`. NOTE: the level of option `KeepDays` will be higher. Even thougth `MaxBackups` sets 0, log files will still be removed if the `KeepDays` limitation is reached.
- `MaxSize`: represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`. Only take effect when `Rotation` is `size`.
- `Rotation`: represents the type of log rotation rule. Default is `daily`.
- `daily` rotate the logs by day.
- `size` rotate the logs by size of logs.
## Logging methods

181
core/logx/richlogger.go Normal file
View File

@@ -0,0 +1,181 @@
package logx
import (
"context"
"fmt"
"time"
"github.com/zeromicro/go-zero/core/timex"
"github.com/zeromicro/go-zero/internal/trace"
)
// WithCallerSkip returns a Logger with given caller skip.
func WithCallerSkip(skip int) Logger {
if skip <= 0 {
return new(richLogger)
}
return &richLogger{
callerSkip: skip,
}
}
// WithContext sets ctx to log, for keeping tracing information.
func WithContext(ctx context.Context) Logger {
return &richLogger{
ctx: ctx,
}
}
// WithDuration returns a Logger with given duration.
func WithDuration(d time.Duration) Logger {
return &richLogger{
fields: []LogField{Field(durationKey, timex.ReprOfDuration(d))},
}
}
type richLogger struct {
ctx context.Context
callerSkip int
fields []LogField
}
func (l *richLogger) Debug(v ...interface{}) {
l.debug(fmt.Sprint(v...))
}
func (l *richLogger) Debugf(format string, v ...interface{}) {
l.debug(fmt.Sprintf(format, v...))
}
func (l *richLogger) Debugv(v interface{}) {
l.debug(v)
}
func (l *richLogger) Debugw(msg string, fields ...LogField) {
l.debug(msg, fields...)
}
func (l *richLogger) Error(v ...interface{}) {
l.err(fmt.Sprint(v...))
}
func (l *richLogger) Errorf(format string, v ...interface{}) {
l.err(fmt.Sprintf(format, v...))
}
func (l *richLogger) Errorv(v interface{}) {
l.err(fmt.Sprint(v))
}
func (l *richLogger) Errorw(msg string, fields ...LogField) {
l.err(msg, fields...)
}
func (l *richLogger) Info(v ...interface{}) {
l.info(fmt.Sprint(v...))
}
func (l *richLogger) Infof(format string, v ...interface{}) {
l.info(fmt.Sprintf(format, v...))
}
func (l *richLogger) Infov(v interface{}) {
l.info(v)
}
func (l *richLogger) Infow(msg string, fields ...LogField) {
l.info(msg, fields...)
}
func (l *richLogger) Slow(v ...interface{}) {
l.slow(fmt.Sprint(v...))
}
func (l *richLogger) Slowf(format string, v ...interface{}) {
l.slow(fmt.Sprintf(format, v...))
}
func (l *richLogger) Slowv(v interface{}) {
l.slow(v)
}
func (l *richLogger) Sloww(msg string, fields ...LogField) {
l.slow(msg, fields...)
}
func (l *richLogger) WithCallerSkip(skip int) Logger {
if skip <= 0 {
return l
}
l.callerSkip = skip
return l
}
func (l *richLogger) WithContext(ctx context.Context) Logger {
l.ctx = ctx
return l
}
func (l *richLogger) WithDuration(duration time.Duration) Logger {
l.fields = append(l.fields, Field(durationKey, timex.ReprOfDuration(duration)))
return l
}
func (l *richLogger) WithFields(fields ...LogField) Logger {
l.fields = append(l.fields, fields...)
return l
}
func (l *richLogger) buildFields(fields ...LogField) []LogField {
fields = append(l.fields, fields...)
fields = append(fields, Field(callerKey, getCaller(callerDepth+l.callerSkip)))
if l.ctx == nil {
return fields
}
traceID := trace.TraceIDFromContext(l.ctx)
if len(traceID) > 0 {
fields = append(fields, Field(traceKey, traceID))
}
spanID := trace.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 *richLogger) debug(v interface{}, fields ...LogField) {
if shallLog(DebugLevel) {
getWriter().Debug(v, l.buildFields(fields...)...)
}
}
func (l *richLogger) err(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Error(v, l.buildFields(fields...)...)
}
}
func (l *richLogger) info(v interface{}, fields ...LogField) {
if shallLog(InfoLevel) {
getWriter().Info(v, l.buildFields(fields...)...)
}
}
func (l *richLogger) slow(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Slow(v, l.buildFields(fields...)...)
}
}

View File

@@ -3,6 +3,7 @@ package logx
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"sync/atomic"
@@ -29,13 +30,48 @@ func TestTraceLog(t *testing.T) {
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
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 TestTraceDebug(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("foo").Start(context.Background(), "bar")
defer span.End()
l := WithContext(ctx)
SetLevel(DebugLevel)
l.WithDuration(time.Second).Debug(testlog)
assert.True(t, strings.Contains(w.String(), traceKey))
assert.True(t, strings.Contains(w.String(), spanKey))
w.Reset()
l.WithDuration(time.Second).Debugf(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Debugv(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Debugw(testlog, Field("foo", "bar"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
}
func TestTraceError(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
@@ -50,7 +86,7 @@ func TestTraceError(t *testing.T) {
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
var nilCtx context.Context
@@ -67,10 +103,10 @@ func TestTraceError(t *testing.T) {
l.WithDuration(time.Second).Errorv(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorw(testlog, Field("foo", "bar"))
l.WithDuration(time.Second).Errorw(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
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(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceInfo(t *testing.T) {
@@ -87,7 +123,7 @@ func TestTraceInfo(t *testing.T) {
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
SetLevel(InfoLevel)
@@ -101,10 +137,10 @@ func TestTraceInfo(t *testing.T) {
l.WithDuration(time.Second).Infov(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infow(testlog, Field("foo", "bar"))
l.WithDuration(time.Second).Infow(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
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(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceInfoConsole(t *testing.T) {
@@ -124,7 +160,7 @@ func TestTraceInfoConsole(t *testing.T) {
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
l := WithContext(ctx)
@@ -153,7 +189,7 @@ func TestTraceSlow(t *testing.T) {
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
l := WithContext(ctx)
@@ -168,10 +204,10 @@ func TestTraceSlow(t *testing.T) {
l.WithDuration(time.Second).Slowv(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Sloww(testlog, Field("foo", "bar"))
l.WithDuration(time.Second).Sloww(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
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(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceWithoutContext(t *testing.T) {
@@ -192,6 +228,67 @@ func TestTraceWithoutContext(t *testing.T) {
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 := ContextWithFields(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 TestLogWithCallerSkip(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
l := WithCallerSkip(1).WithCallerSkip(0)
p := func(v string) {
l.Info(v)
}
file, line := getFileLine()
p(testlog)
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
w.Reset()
l = WithCallerSkip(0).WithCallerSkip(1)
file, line = getFileLine()
p(testlog)
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
}
func TestLoggerWithFields(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()).WithFields(Field("foo", "bar"))
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))
@@ -217,4 +314,5 @@ func validate(t *testing.T, body string, expectedTrace, expectedSpan bool) {
type mockValue struct {
Trace string `json:"trace"`
Span string `json:"span"`
Foo string `json:"foo"`
}

View File

@@ -9,6 +9,7 @@ import (
"os"
"path"
"path/filepath"
"sort"
"strings"
"sync"
"time"
@@ -19,10 +20,13 @@ import (
const (
dateFormat = "2006-01-02"
fileTimeFormat = time.RFC3339
hoursPerDay = 24
bufferSize = 100
defaultDirMode = 0o755
defaultFileMode = 0o600
gzipExt = ".gz"
megaBytes = 1 << 20
)
// ErrLogFileClosed is an error that indicates the log file is already closed.
@@ -34,7 +38,7 @@ type (
BackupFileName() string
MarkRotated()
OutdatedFiles() []string
ShallRotate() bool
ShallRotate(size int64) bool
}
// A RotateLogger is a Logger that can rotate log files with given rules.
@@ -47,8 +51,9 @@ type (
rule RotateRule
compress bool
// can't use threading.RoutineGroup because of cycle import
waitGroup sync.WaitGroup
closeOnce sync.Once
waitGroup sync.WaitGroup
closeOnce sync.Once
currentSize int64
}
// A DailyRotateRule is a rule to daily rotate the log files.
@@ -59,6 +64,13 @@ type (
days int
gzip bool
}
// SizeLimitRotateRule a rotation rule that make the log file rotated base on size
SizeLimitRotateRule struct {
DailyRotateRule
maxSize int64
maxBackups int
}
)
// DefaultRotateRule is a default log rotating rule, currently DailyRotateRule.
@@ -90,7 +102,7 @@ func (r *DailyRotateRule) OutdatedFiles() []string {
var pattern string
if r.gzip {
pattern = fmt.Sprintf("%s%s*.gz", r.filename, r.delimiter)
pattern = fmt.Sprintf("%s%s*%s", r.filename, r.delimiter, gzipExt)
} else {
pattern = fmt.Sprintf("%s%s*", r.filename, r.delimiter)
}
@@ -103,9 +115,11 @@ func (r *DailyRotateRule) OutdatedFiles() []string {
var buf strings.Builder
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay*r.days)).Format(dateFormat)
fmt.Fprintf(&buf, "%s%s%s", r.filename, r.delimiter, boundary)
buf.WriteString(r.filename)
buf.WriteString(r.delimiter)
buf.WriteString(boundary)
if r.gzip {
buf.WriteString(".gz")
buf.WriteString(gzipExt)
}
boundaryFile := buf.String()
@@ -120,10 +134,100 @@ func (r *DailyRotateRule) OutdatedFiles() []string {
}
// ShallRotate checks if the file should be rotated.
func (r *DailyRotateRule) ShallRotate() bool {
func (r *DailyRotateRule) ShallRotate(_ int64) bool {
return len(r.rotatedTime) > 0 && getNowDate() != r.rotatedTime
}
// NewSizeLimitRotateRule returns the rotation rule with size limit
func NewSizeLimitRotateRule(filename, delimiter string, days, maxSize, maxBackups int, gzip bool) RotateRule {
return &SizeLimitRotateRule{
DailyRotateRule: DailyRotateRule{
rotatedTime: getNowDateInRFC3339Format(),
filename: filename,
delimiter: delimiter,
days: days,
gzip: gzip,
},
maxSize: int64(maxSize) * megaBytes,
maxBackups: maxBackups,
}
}
func (r *SizeLimitRotateRule) BackupFileName() string {
dir := filepath.Dir(r.filename)
prefix, ext := r.parseFilename()
timestamp := getNowDateInRFC3339Format()
return filepath.Join(dir, fmt.Sprintf("%s%s%s%s", prefix, r.delimiter, timestamp, ext))
}
func (r *SizeLimitRotateRule) MarkRotated() {
r.rotatedTime = getNowDateInRFC3339Format()
}
func (r *SizeLimitRotateRule) OutdatedFiles() []string {
dir := filepath.Dir(r.filename)
prefix, ext := r.parseFilename()
var pattern string
if r.gzip {
pattern = fmt.Sprintf("%s%s%s%s*%s%s", dir, string(filepath.Separator),
prefix, r.delimiter, ext, gzipExt)
} else {
pattern = fmt.Sprintf("%s%s%s%s*%s", dir, string(filepath.Separator),
prefix, r.delimiter, ext)
}
files, err := filepath.Glob(pattern)
if err != nil {
Errorf("failed to delete outdated log files, error: %s", err)
return nil
}
sort.Strings(files)
outdated := make(map[string]lang.PlaceholderType)
// test if too many backups
if r.maxBackups > 0 && len(files) > r.maxBackups {
for _, f := range files[:len(files)-r.maxBackups] {
outdated[f] = lang.Placeholder
}
files = files[len(files)-r.maxBackups:]
}
// test if any too old backups
if r.days > 0 {
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay*r.days)).Format(fileTimeFormat)
boundaryFile := filepath.Join(dir, fmt.Sprintf("%s%s%s%s", prefix, r.delimiter, boundary, ext))
if r.gzip {
boundaryFile += gzipExt
}
for _, f := range files {
if f >= boundaryFile {
break
}
outdated[f] = lang.Placeholder
}
}
var result []string
for k := range outdated {
result = append(result, k)
}
return result
}
func (r *SizeLimitRotateRule) ShallRotate(size int64) bool {
return r.maxSize > 0 && r.maxSize < size
}
func (r *SizeLimitRotateRule) parseFilename() (prefix, ext string) {
logName := filepath.Base(r.filename)
ext = filepath.Ext(r.filename)
prefix = logName[:len(logName)-len(ext)]
return
}
// NewLogger returns a RotateLogger with given filename and rule, etc.
func NewLogger(filename string, rule RotateRule, compress bool) (*RotateLogger, error) {
l := &RotateLogger{
@@ -180,7 +284,7 @@ func (l *RotateLogger) getBackupFilename() string {
func (l *RotateLogger) init() error {
l.backup = l.rule.BackupFileName()
if _, err := os.Stat(l.filename); err != nil {
if fileInfo, err := os.Stat(l.filename); err != nil {
basePath := path.Dir(l.filename)
if _, err = os.Stat(basePath); err != nil {
if err = os.MkdirAll(basePath, defaultDirMode); err != nil {
@@ -191,8 +295,11 @@ func (l *RotateLogger) init() error {
if l.fp, err = os.Create(l.filename); err != nil {
return err
}
} else if l.fp, err = os.OpenFile(l.filename, os.O_APPEND|os.O_WRONLY, defaultFileMode); err != nil {
return err
} else {
if l.fp, err = os.OpenFile(l.filename, os.O_APPEND|os.O_WRONLY, defaultFileMode); err != nil {
return err
}
l.currentSize = fileInfo.Size()
}
fs.CloseOnExec(l.fp)
@@ -282,15 +389,17 @@ func (l *RotateLogger) startWorker() {
}
func (l *RotateLogger) write(v []byte) {
if l.rule.ShallRotate() {
if l.rule.ShallRotate(l.currentSize + int64(len(v))) {
if err := l.rotate(); err != nil {
log.Println(err)
} else {
l.rule.MarkRotated()
l.currentSize = 0
}
}
if l.fp != nil {
l.fp.Write(v)
l.currentSize += int64(len(v))
}
}
@@ -308,6 +417,10 @@ func getNowDate() string {
return time.Now().Format(dateFormat)
}
func getNowDateInRFC3339Format() string {
return time.Now().Format(fileTimeFormat)
}
func gzipFile(file string) error {
in, err := os.Open(file)
if err != nil {
@@ -315,7 +428,7 @@ func gzipFile(file string) error {
}
defer in.Close()
out, err := os.Create(fmt.Sprintf("%s.gz", file))
out, err := os.Create(fmt.Sprintf("%s%s", file, gzipExt))
if err != nil {
return err
}

View File

@@ -29,7 +29,34 @@ func TestDailyRotateRuleOutdatedFiles(t *testing.T) {
func TestDailyRotateRuleShallRotate(t *testing.T) {
var rule DailyRotateRule
rule.rotatedTime = time.Now().Add(time.Hour * 24).Format(dateFormat)
assert.True(t, rule.ShallRotate())
assert.True(t, rule.ShallRotate(0))
}
func TestSizeLimitRotateRuleMarkRotated(t *testing.T) {
var rule SizeLimitRotateRule
rule.MarkRotated()
assert.Equal(t, getNowDateInRFC3339Format(), rule.rotatedTime)
}
func TestSizeLimitRotateRuleOutdatedFiles(t *testing.T) {
var rule SizeLimitRotateRule
assert.Empty(t, rule.OutdatedFiles())
rule.days = 1
assert.Empty(t, rule.OutdatedFiles())
rule.gzip = true
assert.Empty(t, rule.OutdatedFiles())
rule.maxBackups = 0
assert.Empty(t, rule.OutdatedFiles())
}
func TestSizeLimitRotateRuleShallRotate(t *testing.T) {
var rule SizeLimitRotateRule
rule.rotatedTime = time.Now().Add(time.Hour * 24).Format(fileTimeFormat)
rule.maxSize = 0
assert.False(t, rule.ShallRotate(0))
rule.maxSize = 100
assert.False(t, rule.ShallRotate(0))
assert.True(t, rule.ShallRotate(101*megaBytes))
}
func TestRotateLoggerClose(t *testing.T) {
@@ -142,3 +169,162 @@ func TestRotateLoggerWrite(t *testing.T) {
func TestLogWriterClose(t *testing.T) {
assert.Nil(t, newLogWriter(nil).Close())
}
func TestRotateLoggerWithSizeLimitRotateRuleClose(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
assert.Nil(t, err)
assert.Nil(t, logger.Close())
}
func TestRotateLoggerGetBackupWithSizeLimitRotateRuleFilename(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
assert.Nil(t, err)
assert.True(t, len(logger.getBackupFilename()) > 0)
logger.backup = ""
assert.True(t, len(logger.getBackupFilename()) > 0)
}
func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFile(t *testing.T) {
old := os.Stdout
os.Stdout = os.NewFile(0, os.DevNull)
defer func() {
os.Stdout = old
}()
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
assert.Nil(t, err)
logger.maybeCompressFile(filename)
_, err = os.Stat(filename)
assert.Nil(t, err)
}
func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFileTrue(t *testing.T) {
old := os.Stdout
os.Stdout = os.NewFile(0, os.DevNull)
defer func() {
os.Stdout = old
}()
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}
logger.maybeCompressFile(filename)
_, err = os.Stat(filename)
assert.NotNil(t, err)
}
func TestRotateLoggerWithSizeLimitRotateRuleRotate(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
assert.Nil(t, err)
if len(filename) > 0 {
defer func() {
os.Remove(logger.getBackupFilename())
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}()
}
err = logger.rotate()
switch v := err.(type) {
case *os.LinkError:
// avoid rename error on docker container
assert.Equal(t, syscall.EXDEV, v.Err)
case *os.PathError:
// ignore remove error for tests,
// files are cleaned in GitHub actions.
assert.Equal(t, "remove", v.Op)
default:
assert.Nil(t, err)
}
}
func TestRotateLoggerWithSizeLimitRotateRuleWrite(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
rule := new(SizeLimitRotateRule)
logger, err := NewLogger(filename, rule, true)
assert.Nil(t, err)
if len(filename) > 0 {
defer func() {
os.Remove(logger.getBackupFilename())
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}()
}
// the following write calls cannot be changed to Write, because of DATA RACE.
logger.write([]byte(`foo`))
rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
logger.write([]byte(`bar`))
logger.Close()
logger.write([]byte(`baz`))
}
func BenchmarkRotateLogger(b *testing.B) {
filename := "./test.log"
filename2 := "./test2.log"
dailyRotateRuleLogger, err1 := NewLogger(
filename,
DefaultRotateRule(
filename,
backupFileDelimiter,
1,
true,
),
true,
)
if err1 != nil {
b.Logf("Failed to new daily rotate rule logger: %v", err1)
b.FailNow()
}
sizeLimitRotateRuleLogger, err2 := NewLogger(
filename2,
NewSizeLimitRotateRule(
filename,
backupFileDelimiter,
1,
100,
10,
true,
),
true,
)
if err2 != nil {
b.Logf("Failed to new size limit rotate rule logger: %v", err1)
b.FailNow()
}
defer func() {
dailyRotateRuleLogger.Close()
sizeLimitRotateRuleLogger.Close()
os.Remove(filename)
os.Remove(filename2)
}()
b.Run("daily rotate rule", func(b *testing.B) {
for i := 0; i < b.N; i++ {
dailyRotateRuleLogger.write([]byte("testing\ntesting\n"))
}
})
b.Run("size limit rotate rule", func(b *testing.B) {
for i := 0; i < b.N; i++ {
sizeLimitRotateRuleLogger.write([]byte("testing\ntesting\n"))
}
})
}

View File

@@ -42,11 +42,18 @@ func captureOutput(f func()) string {
}
func getContent(jsonStr string) string {
var entry logEntry
var entry map[string]interface{}
json.Unmarshal([]byte(jsonStr), &entry)
val, ok := entry.Content.(string)
if ok {
return val
val, ok := entry[contentKey]
if !ok {
return ""
}
return ""
str, ok := val.(string)
if !ok {
return ""
}
return str
}

View File

@@ -1,136 +0,0 @@
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 &traceLogger{
ctx: ctx,
}
}
type traceLogger struct {
logEntry
ctx context.Context
}
func (l *traceLogger) Error(v ...interface{}) {
l.err(fmt.Sprint(v...))
}
func (l *traceLogger) Errorf(format string, v ...interface{}) {
l.err(fmt.Sprintf(format, v...))
}
func (l *traceLogger) Errorv(v interface{}) {
l.err(fmt.Sprint(v))
}
func (l *traceLogger) Errorw(msg string, fields ...LogField) {
l.err(msg, fields...)
}
func (l *traceLogger) Info(v ...interface{}) {
l.info(fmt.Sprint(v...))
}
func (l *traceLogger) Infof(format string, v ...interface{}) {
l.info(fmt.Sprintf(format, v...))
}
func (l *traceLogger) Infov(v interface{}) {
l.info(v)
}
func (l *traceLogger) Infow(msg string, fields ...LogField) {
l.info(msg, fields...)
}
func (l *traceLogger) Slow(v ...interface{}) {
l.slow(fmt.Sprint(v...))
}
func (l *traceLogger) Slowf(format string, v ...interface{}) {
l.slow(fmt.Sprintf(format, v...))
}
func (l *traceLogger) Slowv(v interface{}) {
l.slow(v)
}
func (l *traceLogger) Sloww(msg string, fields ...LogField) {
l.slow(msg, fields...)
}
func (l *traceLogger) WithContext(ctx context.Context) Logger {
if ctx == nil {
return l
}
l.ctx = ctx
return l
}
func (l *traceLogger) WithDuration(duration time.Duration) Logger {
l.Duration = timex.ReprOfDuration(duration)
return l
}
func (l *traceLogger) 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))
}
return fields
}
func (l *traceLogger) err(v interface{}, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Error(v, l.buildFields(fields...)...)
}
}
func (l *traceLogger) info(v interface{}, fields ...LogField) {
if shallLog(InfoLevel) {
getWriter().Info(v, l.buildFields(fields...)...)
}
}
func (l *traceLogger) 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

@@ -3,8 +3,10 @@ package logx
import "errors"
const (
// InfoLevel logs everything
InfoLevel uint32 = iota
// DebugLevel logs everything
DebugLevel uint32 = iota
// InfoLevel does not include debugs
InfoLevel
// ErrorLevel includes errors, slows, stacks
ErrorLevel
// SevereLevel only log severe messages
@@ -15,9 +17,9 @@ const (
jsonEncodingType = iota
plainEncodingType
jsonEncoding = "json"
plainEncoding = "plain"
plainEncodingSep = '\t'
sizeRotationRule = "size"
)
const (
@@ -27,9 +29,8 @@ const (
slowFilename = "slow.log"
statFilename = "stat.log"
consoleMode = "console"
fileMode = "file"
volumeMode = "volume"
fileMode = "file"
volumeMode = "volume"
levelAlert = "alert"
levelInfo = "info"
@@ -38,6 +39,7 @@ const (
levelFatal = "fatal"
levelSlow = "slow"
levelStat = "stat"
levelDebug = "debug"
backupFileDelimiter = "-"
flags = 0x0

View File

@@ -1,16 +1,16 @@
package logx
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"os"
"path"
"strings"
"sync"
"sync/atomic"
fatihcolor "github.com/fatih/color"
"github.com/zeromicro/go-zero/core/color"
)
@@ -18,6 +18,7 @@ type (
Writer interface {
Alert(v interface{})
Close() error
Debug(v interface{}, fields ...LogField)
Error(v interface{}, fields ...LogField)
Info(v interface{}, fields ...LogField)
Severe(v interface{})
@@ -63,21 +64,32 @@ func (w *atomicWriter) Load() Writer {
func (w *atomicWriter) Store(v Writer) {
w.lock.Lock()
defer w.lock.Unlock()
w.writer = v
w.lock.Unlock()
}
func (w *atomicWriter) StoreIfNil(v Writer) Writer {
w.lock.Lock()
defer w.lock.Unlock()
if w.writer == nil {
w.writer = v
}
return w.writer
}
func (w *atomicWriter) Swap(v Writer) Writer {
w.lock.Lock()
defer w.lock.Unlock()
old := w.writer
w.writer = v
w.lock.Unlock()
return old
}
func newConsoleWriter() Writer {
outLog := newLogWriter(log.New(os.Stdout, "", flags))
errLog := newLogWriter(log.New(os.Stderr, "", flags))
outLog := newLogWriter(log.New(fatihcolor.Output, "", flags))
errLog := newLogWriter(log.New(fatihcolor.Error, "", flags))
return &concreteWriter{
infoLog: outLog,
errorLog: errLog,
@@ -109,6 +121,14 @@ func newFileWriter(c LogConf) (Writer, error) {
if c.KeepDays > 0 {
opts = append(opts, WithKeepDays(c.KeepDays))
}
if c.MaxBackups > 0 {
opts = append(opts, WithMaxBackups(c.MaxBackups))
}
if c.MaxSize > 0 {
opts = append(opts, WithMaxSize(c.MaxSize))
}
opts = append(opts, WithRotation(c.Rotation))
accessFile := path.Join(c.Path, accessFilename)
errorFile := path.Join(c.Path, errorFilename)
@@ -175,6 +195,10 @@ func (w *concreteWriter) Close() error {
return w.statLog.Close()
}
func (w *concreteWriter) Debug(v interface{}, fields ...LogField) {
output(w.infoLog, levelDebug, v, fields...)
}
func (w *concreteWriter) Error(v interface{}, fields ...LogField) {
output(w.errorLog, levelError, v, fields...)
}
@@ -208,6 +232,9 @@ func (n nopWriter) Close() error {
return nil
}
func (n nopWriter) Debug(_ interface{}, _ ...LogField) {
}
func (n nopWriter) Error(_ interface{}, _ ...LogField) {
}
@@ -236,14 +263,23 @@ func buildFields(fields ...LogField) []string {
return items
}
func combineGlobalFields(fields []LogField) []LogField {
globals := globalFields.Load()
if globals == nil {
return fields
}
return append(globals.([]LogField), fields...)
}
func output(writer io.Writer, level string, val interface{}, fields ...LogField) {
fields = append(fields, Field(callerKey, getCaller(callerDepth)))
fields = combineGlobalFields(fields)
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainAny(writer, level, val, buildFields(fields...)...)
default:
entry := make(logEntryWithFields)
entry := make(logEntry)
for _, field := range fields {
entry[field.Key] = field.Value
}
@@ -267,6 +303,8 @@ func wrapLevelWithColor(level string) string {
colour = color.FgBlue
case levelSlow:
colour = color.FgYellow
case levelDebug:
colour = color.FgYellow
case levelStat:
colour = color.FgGreen
}
@@ -299,34 +337,12 @@ func writePlainAny(writer io.Writer, level string, val interface{}, fields ...st
case fmt.Stringer:
writePlainText(writer, level, v.String(), fields...)
default:
var buf strings.Builder
buf.WriteString(getTimestamp())
buf.WriteByte(plainEncodingSep)
buf.WriteString(level)
buf.WriteByte(plainEncodingSep)
if err := json.NewEncoder(&buf).Encode(val); err != nil {
log.Println(err.Error())
return
}
for _, item := range fields {
buf.WriteByte(plainEncodingSep)
buf.WriteString(item)
}
buf.WriteByte('\n')
if writer == nil {
log.Println(buf.String())
return
}
if _, err := fmt.Fprint(writer, buf.String()); err != nil {
log.Println(err.Error())
}
writePlainValue(writer, level, v, fields...)
}
}
func writePlainText(writer io.Writer, level, msg string, fields ...string) {
var buf strings.Builder
var buf bytes.Buffer
buf.WriteString(getTimestamp())
buf.WriteByte(plainEncodingSep)
buf.WriteString(level)
@@ -342,7 +358,33 @@ func writePlainText(writer io.Writer, level, msg string, fields ...string) {
return
}
if _, err := fmt.Fprint(writer, buf.String()); err != nil {
if _, err := writer.Write(buf.Bytes()); err != nil {
log.Println(err.Error())
}
}
func writePlainValue(writer io.Writer, level string, val interface{}, fields ...string) {
var buf bytes.Buffer
buf.WriteString(getTimestamp())
buf.WriteByte(plainEncodingSep)
buf.WriteString(level)
buf.WriteByte(plainEncodingSep)
if err := json.NewEncoder(&buf).Encode(val); err != nil {
log.Println(err.Error())
return
}
for _, item := range fields {
buf.WriteByte(plainEncodingSep)
buf.WriteString(item)
}
buf.WriteByte('\n')
if writer == nil {
log.Println(buf.String())
return
}
if _, err := writer.Write(buf.Bytes()); err != nil {
log.Println(err.Error())
}
}

View File

@@ -16,6 +16,9 @@ func TestNewWriter(t *testing.T) {
w := NewWriter(&buf)
w.Info(literal)
assert.Contains(t, buf.String(), literal)
buf.Reset()
w.Debug(literal)
assert.Contains(t, buf.String(), literal)
}
func TestConsoleWriter(t *testing.T) {
@@ -97,6 +100,7 @@ func TestNopWriter(t *testing.T) {
assert.NotPanics(t, func() {
var w nopWriter
w.Alert("foo")
w.Debug("foo")
w.Error("foo")
w.Info("foo")
w.Severe("foo")
@@ -123,6 +127,12 @@ func TestWritePlainAny(t *testing.T) {
writePlainAny(nil, levelInfo, "foo")
assert.Contains(t, buf.String(), "foo")
buf.Reset()
writePlainAny(nil, levelDebug, make(chan int))
assert.Contains(t, buf.String(), "unsupported type")
writePlainAny(nil, levelDebug, 100)
assert.Contains(t, buf.String(), "100")
buf.Reset()
writePlainAny(nil, levelError, make(chan int))
assert.Contains(t, buf.String(), "unsupported type")

View File

@@ -8,10 +8,12 @@ type (
// use context and OptionalDep option to determine the value of Optional
// nothing to do with context.Context
fieldOptionsWithContext struct {
Inherit bool
FromString bool
Optional bool
Options []string
Default string
EnvVar string
Range *numberRange
}
@@ -40,6 +42,10 @@ func (o *fieldOptionsWithContext) getDefault() (string, bool) {
return o.Default, len(o.Default) > 0
}
func (o *fieldOptionsWithContext) inherit() bool {
return o != nil && o.Inherit
}
func (o *fieldOptionsWithContext) optional() bool {
return o != nil && o.Optional
}
@@ -101,5 +107,6 @@ func (o *fieldOptions) toOptionsWithContext(key string, m Valuer, fullName strin
Optional: optional,
Options: o.Options,
Default: o.Default,
EnvVar: o.EnvVar,
}, nil
}

View File

@@ -11,18 +11,26 @@ const jsonTagKey = "json"
var jsonUnmarshaler = NewUnmarshaler(jsonTagKey)
// UnmarshalJsonBytes unmarshals content into v.
func UnmarshalJsonBytes(content []byte, v interface{}) error {
return unmarshalJsonBytes(content, v, jsonUnmarshaler)
func UnmarshalJsonBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
return unmarshalJsonBytes(content, v, getJsonUnmarshaler(opts...))
}
// UnmarshalJsonMap unmarshals content from m into v.
func UnmarshalJsonMap(m map[string]interface{}, v interface{}) error {
return jsonUnmarshaler.Unmarshal(m, v)
func UnmarshalJsonMap(m map[string]interface{}, v interface{}, opts ...UnmarshalOption) error {
return getJsonUnmarshaler(opts...).Unmarshal(m, v)
}
// UnmarshalJsonReader unmarshals content from reader into v.
func UnmarshalJsonReader(reader io.Reader, v interface{}) error {
return unmarshalJsonReader(reader, v, jsonUnmarshaler)
func UnmarshalJsonReader(reader io.Reader, v interface{}, opts ...UnmarshalOption) error {
return unmarshalJsonReader(reader, v, getJsonUnmarshaler(opts...))
}
func getJsonUnmarshaler(opts ...UnmarshalOption) *Unmarshaler {
if len(opts) > 0 {
return NewUnmarshaler(jsonTagKey, opts...)
}
return jsonUnmarshaler
}
func unmarshalJsonBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {

View File

@@ -900,7 +900,9 @@ func TestUnmarshalMap(t *testing.T) {
Any string `json:",optional"`
}
err := UnmarshalJsonMap(m, &v)
err := UnmarshalJsonMap(m, &v, WithCanonicalKeyFunc(func(s string) string {
return s
}))
assert.Nil(t, err)
assert.True(t, len(v.Any) == 0)
})

View File

@@ -261,6 +261,78 @@ func TestMarshal_RangeOut(t *testing.T) {
}
}
func TestMarshal_RangeIllegal(t *testing.T) {
tests := []interface{}{
struct {
Int int `json:"int,range=[3:1]"`
}{
Int: 2,
},
struct {
Int int `json:"int,range=(3:1]"`
}{
Int: 2,
},
}
for _, test := range tests {
_, err := Marshal(test)
assert.Equal(t, err, errNumberRange)
}
}
func TestMarshal_RangeLeftEqualsToRight(t *testing.T) {
tests := []struct {
name string
value interface{}
err error
}{
{
name: "left inclusive, right inclusive",
value: struct {
Int int `json:"int,range=[2:2]"`
}{
Int: 2,
},
},
{
name: "left inclusive, right exclusive",
value: struct {
Int int `json:"int,range=[2:2)"`
}{
Int: 2,
},
err: errNumberRange,
},
{
name: "left exclusive, right inclusive",
value: struct {
Int int `json:"int,range=(2:2]"`
}{
Int: 2,
},
err: errNumberRange,
},
{
name: "left exclusive, right exclusive",
value: struct {
Int int `json:"int,range=(2:2)"`
}{
Int: 2,
},
err: errNumberRange,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
_, err := Marshal(test.value)
assert.Equal(t, test.err, err)
})
}
}
func TestMarshal_FromString(t *testing.T) {
v := struct {
Age int `json:"age,string"`

View File

@@ -1,29 +1,27 @@
package mapping
import (
"bytes"
"encoding/json"
"io"
"github.com/pelletier/go-toml/v2"
"github.com/zeromicro/go-zero/internal/encoding"
)
// UnmarshalTomlBytes unmarshals TOML bytes into the given v.
func UnmarshalTomlBytes(content []byte, v interface{}) error {
return UnmarshalTomlReader(bytes.NewReader(content), v)
func UnmarshalTomlBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
b, err := encoding.TomlToJson(content)
if err != nil {
return err
}
return UnmarshalJsonBytes(b, v, opts...)
}
// UnmarshalTomlReader unmarshals TOML from the given io.Reader into the given v.
func UnmarshalTomlReader(r io.Reader, v interface{}) error {
var val interface{}
if err := toml.NewDecoder(r).Decode(&val); err != nil {
func UnmarshalTomlReader(r io.Reader, v interface{}, opts ...UnmarshalOption) error {
b, err := io.ReadAll(r)
if err != nil {
return err
}
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(val); err != nil {
return err
}
return UnmarshalJsonReader(&buf, v)
return UnmarshalTomlBytes(b, v, opts...)
}

View File

@@ -1,6 +1,7 @@
package mapping
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -18,7 +19,7 @@ d = "abcd!@#$112"
C string `json:"c"`
D string `json:"d"`
}
assert.Nil(t, UnmarshalTomlBytes([]byte(input), &val))
assert.NoError(t, UnmarshalTomlReader(strings.NewReader(input), &val))
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C)
@@ -37,5 +38,12 @@ d = "abcd!@#$112"
C string `json:"c"`
D string `json:"d"`
}
assert.NotNil(t, UnmarshalTomlBytes([]byte(input), &val))
assert.Error(t, UnmarshalTomlReader(strings.NewReader(input), &val))
}
func TestUnmarshalTomlBadReader(t *testing.T) {
var val struct {
A string `json:"a"`
}
assert.Error(t, UnmarshalTomlReader(new(badReader), &val))
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -10,11 +10,14 @@ import (
"strings"
"sync"
"github.com/zeromicro/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/stringx"
)
const (
defaultOption = "default"
envOption = "env"
inheritOption = "inherit"
stringOption = "string"
optionalOption = "optional"
optionsOption = "options"
@@ -62,22 +65,7 @@ func Deref(t reflect.Type) reflect.Type {
// Repr returns the string representation of v.
func Repr(v interface{}) string {
if v == nil {
return ""
}
// if func (v *Type) String() string, we can't use Elem()
switch vt := v.(type) {
case fmt.Stringer:
return vt.String()
}
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr && !val.IsNil() {
val = val.Elem()
}
return reprOfValue(val)
return lang.Repr(v)
}
// ValidatePtr validates v if it's a valid pointer.
@@ -94,7 +82,14 @@ func ValidatePtr(v *reflect.Value) error {
func convertType(kind reflect.Kind, str string) (interface{}, error) {
switch kind {
case reflect.Bool:
return str == "1" || strings.ToLower(str) == "true", nil
switch strings.ToLower(str) {
case "1", "true":
return true, nil
case "0", "false":
return false, nil
default:
return false, errTypeMismatch
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
intValue, err := strconv.ParseInt(str, 10, 64)
if err != nil {
@@ -215,8 +210,8 @@ func isRightInclude(b byte) (bool, error) {
}
}
func maybeNewValue(field reflect.StructField, value reflect.Value) {
if field.Type.Kind() == reflect.Ptr && value.IsNil() {
func maybeNewValue(fieldType reflect.Type, value reflect.Value) {
if fieldType.Kind() == reflect.Ptr && value.IsNil() {
value.Set(reflect.New(value.Type().Elem()))
}
}
@@ -311,6 +306,20 @@ func parseNumberRange(str string) (*numberRange, error) {
right = math.MaxFloat64
}
if left > right {
return nil, errNumberRange
}
// [2:2] valid
// [2:2) invalid
// (2:2] invalid
// (2:2) invalid
if left == right {
if !leftInclude || !rightInclude {
return nil, errNumberRange
}
}
return &numberRange{
left: left,
leftInclude: leftInclude,
@@ -321,6 +330,8 @@ func parseNumberRange(str string) (*numberRange, error) {
func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
switch {
case option == inheritOption:
fieldOpts.Inherit = true
case option == stringOption:
fieldOpts.FromString = true
case strings.HasPrefix(option, optionalOption):
@@ -337,26 +348,33 @@ func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
case option == optionalOption:
fieldOpts.Optional = true
case strings.HasPrefix(option, optionsOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return fmt.Errorf("field %s has wrong options", fieldName)
val, err := parseProperty(fieldName, optionsOption, option)
if err != nil {
return err
}
fieldOpts.Options = parseOptions(segs[1])
fieldOpts.Options = parseOptions(val)
case strings.HasPrefix(option, defaultOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return fmt.Errorf("field %s has wrong default option", fieldName)
val, err := parseProperty(fieldName, defaultOption, option)
if err != nil {
return err
}
fieldOpts.Default = strings.TrimSpace(segs[1])
fieldOpts.Default = val
case strings.HasPrefix(option, envOption):
val, err := parseProperty(fieldName, envOption, option)
if err != nil {
return err
}
fieldOpts.EnvVar = val
case strings.HasPrefix(option, rangeOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return fmt.Errorf("field %s has wrong range", fieldName)
val, err := parseProperty(fieldName, rangeOption, option)
if err != nil {
return err
}
nr, err := parseNumberRange(segs[1])
nr, err := parseNumberRange(val)
if err != nil {
return err
}
@@ -381,6 +399,15 @@ func parseOptions(val string) []string {
return strings.Split(val, optionSeparator)
}
func parseProperty(field, tag, val string) (string, error) {
segs := strings.Split(val, equalToken)
if len(segs) != 2 {
return "", fmt.Errorf("field %s has wrong %s", field, tag)
}
return strings.TrimSpace(segs[1]), nil
}
func parseSegments(val string) []string {
var segments []string
var escaped, grouped bool
@@ -430,47 +457,6 @@ func parseSegments(val string) []string {
return segments
}
func reprOfValue(val reflect.Value) string {
switch vt := val.Interface().(type) {
case bool:
return strconv.FormatBool(vt)
case error:
return vt.Error()
case float32:
return strconv.FormatFloat(float64(vt), 'f', -1, 32)
case float64:
return strconv.FormatFloat(vt, 'f', -1, 64)
case fmt.Stringer:
return vt.String()
case int:
return strconv.Itoa(vt)
case int8:
return strconv.Itoa(int(vt))
case int16:
return strconv.Itoa(int(vt))
case int32:
return strconv.Itoa(int(vt))
case int64:
return strconv.FormatInt(vt, 10)
case string:
return vt
case uint:
return strconv.FormatUint(uint64(vt), 10)
case uint8:
return strconv.FormatUint(uint64(vt), 10)
case uint16:
return strconv.FormatUint(uint64(vt), 10)
case uint32:
return strconv.FormatUint(uint64(vt), 10)
case uint64:
return strconv.FormatUint(vt, 10)
case []byte:
return string(vt)
default:
return fmt.Sprint(val.Interface())
}
}
func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v interface{}) error {
switch kind {
case reflect.Bool:

View File

@@ -296,127 +296,3 @@ func TestSetValueFormatErrors(t *testing.T) {
})
}
}
func TestRepr(t *testing.T) {
var (
f32 float32 = 1.1
f64 = 2.2
i8 int8 = 1
i16 int16 = 2
i32 int32 = 3
i64 int64 = 4
u8 uint8 = 5
u16 uint16 = 6
u32 uint32 = 7
u64 uint64 = 8
)
tests := []struct {
v interface{}
expect string
}{
{
nil,
"",
},
{
mockStringable{},
"mocked",
},
{
new(mockStringable),
"mocked",
},
{
newMockPtr(),
"mockptr",
},
{
&mockOpacity{
val: 1,
},
"{1}",
},
{
true,
"true",
},
{
false,
"false",
},
{
f32,
"1.1",
},
{
f64,
"2.2",
},
{
i8,
"1",
},
{
i16,
"2",
},
{
i32,
"3",
},
{
i64,
"4",
},
{
u8,
"5",
},
{
u16,
"6",
},
{
u32,
"7",
},
{
u64,
"8",
},
{
[]byte(`abcd`),
"abcd",
},
{
mockOpacity{val: 1},
"{1}",
},
}
for _, test := range tests {
t.Run(test.expect, func(t *testing.T) {
assert.Equal(t, test.expect, Repr(test.v))
})
}
}
type mockStringable struct{}
func (m mockStringable) String() string {
return "mocked"
}
type mockPtr struct{}
func newMockPtr() *mockPtr {
return new(mockPtr)
}
func (m *mockPtr) String() string {
return "mockptr"
}
type mockOpacity struct {
val int
}

View File

@@ -7,12 +7,106 @@ type (
Value(key string) (interface{}, bool)
}
// A MapValuer is a map that can use Value method to get values with given keys.
MapValuer map[string]interface{}
// A valuerWithParent defines a node that has a parent node.
valuerWithParent interface {
Valuer
// Parent get the parent valuer for current node.
Parent() valuerWithParent
}
// A node is a map that can use Value method to get values with given keys.
node struct {
current Valuer
parent valuerWithParent
}
// A valueWithParent is used to wrap the value with its parent.
valueWithParent struct {
value interface{}
parent valuerWithParent
}
// mapValuer is a type for map to meet the Valuer interface.
mapValuer map[string]interface{}
// simpleValuer is a type to get value from current node.
simpleValuer node
// recursiveValuer is a type to get the value recursively from current and parent nodes.
recursiveValuer node
)
// Value gets the value associated with the given key from mv.
func (mv MapValuer) Value(key string) (interface{}, bool) {
// Value gets the value assciated with the given key from mv.
func (mv mapValuer) Value(key string) (interface{}, bool) {
v, ok := mv[key]
return v, ok
}
// Value gets the value associated with the given key from sv.
func (sv simpleValuer) Value(key string) (interface{}, bool) {
v, ok := sv.current.Value(key)
return v, ok
}
// Parent get the parent valuer from sv.
func (sv simpleValuer) Parent() valuerWithParent {
if sv.parent == nil {
return nil
}
return recursiveValuer{
current: sv.parent,
parent: sv.parent.Parent(),
}
}
// Value gets the value associated with the given key from rv,
// and it will inherit the value from parent nodes.
func (rv recursiveValuer) Value(key string) (interface{}, bool) {
val, ok := rv.current.Value(key)
if !ok {
if parent := rv.Parent(); parent != nil {
return parent.Value(key)
}
return nil, false
}
vm, ok := val.(map[string]interface{})
if !ok {
return val, true
}
parent := rv.Parent()
if parent == nil {
return val, true
}
pv, ok := parent.Value(key)
if !ok {
return val, true
}
pm, ok := pv.(map[string]interface{})
if !ok {
return val, true
}
for k, v := range pm {
if _, ok := vm[k]; !ok {
vm[k] = v
}
}
return vm, true
}
// Parent get the parent valuer from rv.
func (rv recursiveValuer) Parent() valuerWithParent {
if rv.parent == nil {
return nil
}
return recursiveValuer{
current: rv.parent,
parent: rv.parent.Parent(),
}
}

View File

@@ -0,0 +1,33 @@
package mapping
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMapValuerWithInherit_Value(t *testing.T) {
input := map[string]interface{}{
"discovery": map[string]interface{}{
"host": "localhost",
"port": 8080,
},
"component": map[string]interface{}{
"name": "test",
},
}
valuer := recursiveValuer{
current: mapValuer(input["component"].(map[string]interface{})),
parent: simpleValuer{
current: mapValuer(input),
},
}
val, ok := valuer.Value("discovery")
assert.True(t, ok)
m, ok := val.(map[string]interface{})
assert.True(t, ok)
assert.Equal(t, "localhost", m["host"])
assert.Equal(t, 8080, m["port"])
}

View File

@@ -1,101 +1,27 @@
package mapping
import (
"encoding/json"
"errors"
"io"
"gopkg.in/yaml.v2"
)
// To make .json & .yaml consistent, we just use json as the tag key.
const yamlTagKey = "json"
var (
// ErrUnsupportedType is an error that indicates the config format is not supported.
ErrUnsupportedType = errors.New("only map-like configs are supported")
yamlUnmarshaler = NewUnmarshaler(yamlTagKey)
"github.com/zeromicro/go-zero/internal/encoding"
)
// UnmarshalYamlBytes unmarshals content into v.
func UnmarshalYamlBytes(content []byte, v interface{}) error {
return unmarshalYamlBytes(content, v, yamlUnmarshaler)
func UnmarshalYamlBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
b, err := encoding.YamlToJson(content)
if err != nil {
return err
}
return UnmarshalJsonBytes(b, v, opts...)
}
// UnmarshalYamlReader unmarshals content from reader into v.
func UnmarshalYamlReader(reader io.Reader, v interface{}) error {
return unmarshalYamlReader(reader, v, yamlUnmarshaler)
}
func cleanupInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
res := make(map[string]interface{})
for k, v := range in {
res[Repr(k)] = cleanupMapValue(v)
}
return res
}
func cleanupInterfaceNumber(in interface{}) json.Number {
return json.Number(Repr(in))
}
func cleanupInterfaceSlice(in []interface{}) []interface{} {
res := make([]interface{}, len(in))
for i, v := range in {
res[i] = cleanupMapValue(v)
}
return res
}
func cleanupMapValue(v interface{}) interface{} {
switch v := v.(type) {
case []interface{}:
return cleanupInterfaceSlice(v)
case map[interface{}]interface{}:
return cleanupInterfaceMap(v)
case bool, string:
return v
case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64:
return cleanupInterfaceNumber(v)
default:
return Repr(v)
}
}
func unmarshal(unmarshaler *Unmarshaler, o interface{}, v interface{}) error {
if m, ok := o.(map[string]interface{}); ok {
return unmarshaler.Unmarshal(m, v)
}
return ErrUnsupportedType
}
func unmarshalYamlBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {
var o interface{}
if err := yamlUnmarshal(content, &o); err != nil {
func UnmarshalYamlReader(reader io.Reader, v interface{}, opts ...UnmarshalOption) error {
b, err := io.ReadAll(reader)
if err != nil {
return err
}
return unmarshal(unmarshaler, o, v)
}
func unmarshalYamlReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error {
var res interface{}
if err := yaml.NewDecoder(reader).Decode(&res); err != nil {
return err
}
return unmarshal(unmarshaler, cleanupMapValue(res), v)
}
// yamlUnmarshal YAML to map[string]interface{} instead of map[interface{}]interface{}.
func yamlUnmarshal(in []byte, out interface{}) error {
var res interface{}
if err := yaml.Unmarshal(in, &res); err != nil {
return err
}
*out.(*interface{}) = cleanupMapValue(res)
return nil
return UnmarshalYamlBytes(b, v, opts...)
}

View File

@@ -934,9 +934,8 @@ func TestUnmarshalYamlReaderError(t *testing.T) {
err := UnmarshalYamlReader(reader, &v)
assert.NotNil(t, err)
reader = strings.NewReader("chenquan")
err = UnmarshalYamlReader(reader, &v)
assert.ErrorIs(t, err, ErrUnsupportedType)
reader = strings.NewReader("foo")
assert.Error(t, UnmarshalYamlReader(reader, &v))
}
func TestUnmarshalYamlBadReader(t *testing.T) {
@@ -1012,6 +1011,13 @@ func TestUnmarshalYamlMapRune(t *testing.T) {
assert.Equal(t, rune(3), v.Machine["node3"])
}
func TestUnmarshalYamlBadInput(t *testing.T) {
var v struct {
Any string
}
assert.Error(t, UnmarshalYamlBytes([]byte("':foo"), &v))
}
type badReader struct{}
func (b *badReader) Read(_ []byte) (n int, err error) {

View File

@@ -6,14 +6,14 @@ import (
"time"
)
// A Unstable is used to generate random value around the mean value base on given deviation.
// An Unstable is used to generate random value around the mean value base on given deviation.
type Unstable struct {
deviation float64
r *rand.Rand
lock *sync.Mutex
}
// NewUnstable returns a Unstable.
// NewUnstable returns an Unstable.
func NewUnstable(deviation float64) Unstable {
if deviation < 0 {
deviation = 0

View File

@@ -3,6 +3,7 @@ package metric
import (
prom "github.com/prometheus/client_golang/prometheus"
"github.com/zeromicro/go-zero/core/proc"
"github.com/zeromicro/go-zero/core/prometheus"
)
type (
@@ -47,10 +48,18 @@ func NewCounterVec(cfg *CounterVecOpts) CounterVec {
}
func (cv *promCounterVec) Inc(labels ...string) {
if !prometheus.Enabled() {
return
}
cv.counter.WithLabelValues(labels...).Inc()
}
func (cv *promCounterVec) Add(v float64, labels ...string) {
if !prometheus.Enabled() {
return
}
cv.counter.WithLabelValues(labels...).Add(v)
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/prometheus"
)
func TestNewCounterVec(t *testing.T) {
@@ -21,6 +22,7 @@ func TestNewCounterVec(t *testing.T) {
}
func TestCounterIncr(t *testing.T) {
startAgent()
counterVec := NewCounterVec(&CounterVecOpts{
Namespace: "http_client",
Subsystem: "call",
@@ -37,6 +39,7 @@ func TestCounterIncr(t *testing.T) {
}
func TestCounterAdd(t *testing.T) {
startAgent()
counterVec := NewCounterVec(&CounterVecOpts{
Namespace: "rpc_server",
Subsystem: "requests",
@@ -51,3 +54,11 @@ func TestCounterAdd(t *testing.T) {
r := testutil.ToFloat64(cv.counter)
assert.Equal(t, float64(33), r)
}
func startAgent() {
prometheus.StartAgent(prometheus.Config{
Host: "127.0.0.1",
Port: 9101,
Path: "/metrics",
})
}

View File

@@ -3,6 +3,7 @@ package metric
import (
prom "github.com/prometheus/client_golang/prometheus"
"github.com/zeromicro/go-zero/core/proc"
"github.com/zeromicro/go-zero/core/prometheus"
)
type (
@@ -50,14 +51,26 @@ func NewGaugeVec(cfg *GaugeVecOpts) GaugeVec {
}
func (gv *promGaugeVec) Inc(labels ...string) {
if !prometheus.Enabled() {
return
}
gv.gauge.WithLabelValues(labels...).Inc()
}
func (gv *promGaugeVec) Add(v float64, labels ...string) {
if !prometheus.Enabled() {
return
}
gv.gauge.WithLabelValues(labels...).Add(v)
}
func (gv *promGaugeVec) Set(v float64, labels ...string) {
if !prometheus.Enabled() {
return
}
gv.gauge.WithLabelValues(labels...).Set(v)
}

View File

@@ -21,6 +21,7 @@ func TestNewGaugeVec(t *testing.T) {
}
func TestGaugeInc(t *testing.T) {
startAgent()
gaugeVec := NewGaugeVec(&GaugeVecOpts{
Namespace: "rpc_client2",
Subsystem: "requests",
@@ -37,6 +38,7 @@ func TestGaugeInc(t *testing.T) {
}
func TestGaugeAdd(t *testing.T) {
startAgent()
gaugeVec := NewGaugeVec(&GaugeVecOpts{
Namespace: "rpc_client",
Subsystem: "request",
@@ -53,6 +55,7 @@ func TestGaugeAdd(t *testing.T) {
}
func TestGaugeSet(t *testing.T) {
startAgent()
gaugeVec := NewGaugeVec(&GaugeVecOpts{
Namespace: "http_client",
Subsystem: "request",

View File

@@ -3,6 +3,7 @@ package metric
import (
prom "github.com/prometheus/client_golang/prometheus"
"github.com/zeromicro/go-zero/core/proc"
"github.com/zeromicro/go-zero/core/prometheus"
)
type (
@@ -53,6 +54,10 @@ func NewHistogramVec(cfg *HistogramVecOpts) HistogramVec {
}
func (hv *promHistogramVec) Observe(v int64, labels ...string) {
if !prometheus.Enabled() {
return
}
hv.histogram.WithLabelValues(labels...).Observe(float64(v))
}

View File

@@ -21,6 +21,7 @@ func TestNewHistogramVec(t *testing.T) {
}
func TestHistogramObserve(t *testing.T) {
startAgent()
histogramVec := NewHistogramVec(&HistogramVecOpts{
Name: "counts",
Help: "rpc server requests duration(ms).",

View File

@@ -145,7 +145,7 @@ func MapReduceChan(source <-chan interface{}, mapper MapperFunc, reducer Reducer
return mapReduceWithPanicChan(source, panicChan, mapper, reducer, opts...)
}
// MapReduceChan maps all elements from source, and reduce the output elements with given reducer.
// mapReduceWithPanicChan maps all elements from source, and reduce the output elements with given reducer.
func mapReduceWithPanicChan(source <-chan interface{}, panicChan *onceChan, mapper MapperFunc,
reducer ReducerFunc, opts ...Option) (interface{}, error) {
options := buildOptions(opts...)
@@ -212,6 +212,8 @@ func mapReduceWithPanicChan(source <-chan interface{}, panicChan *onceChan, mapp
cancel(context.DeadlineExceeded)
return nil, context.DeadlineExceeded
case v := <-panicChan.channel:
// drain output here, otherwise for loop panic in defer
drain(output)
panic(v)
case v, ok := <-output:
if err := retErr.Load(); err != nil {

View File

@@ -19,7 +19,7 @@ func FuzzMapReduce(f *testing.F) {
rand.Seed(time.Now().UnixNano())
f.Add(uint(10), uint(runtime.NumCPU()))
f.Fuzz(func(t *testing.T, num uint, workers uint) {
f.Fuzz(func(t *testing.T, num, workers uint) {
n := int64(num)%5000 + 5000
genPanic := rand.Intn(100) == 0
mapperPanic := rand.Intn(100) == 0

View File

@@ -3,7 +3,7 @@ package mr
import (
"context"
"errors"
"io/ioutil"
"io"
"log"
"runtime"
"sync/atomic"
@@ -17,7 +17,7 @@ import (
var errDummy = errors.New("dummy")
func init() {
log.SetOutput(ioutil.Discard)
log.SetOutput(io.Discard)
}
func TestFinish(t *testing.T) {

View File

@@ -4,7 +4,8 @@ import "github.com/zeromicro/go-zero/core/logx"
// Recover is used with defer to do cleanup on panics.
// Use it like:
// defer Recover(func() {})
//
// defer Recover(func() {})
func Recover(cleanups ...func()) {
for _, cleanup := range cleanups {
cleanup()

View File

@@ -5,9 +5,11 @@ import (
"github.com/zeromicro/go-zero/core/load"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/proc"
"github.com/zeromicro/go-zero/core/prometheus"
"github.com/zeromicro/go-zero/core/stat"
"github.com/zeromicro/go-zero/core/trace"
"github.com/zeromicro/go-zero/internal/devserver"
)
const (
@@ -27,10 +29,12 @@ const (
type ServiceConf struct {
Name string
Log logx.LogConf
Mode string `json:",default=pro,options=dev|test|rt|pre|pro"`
MetricsUrl string `json:",optional"`
Mode string `json:",default=pro,options=dev|test|rt|pre|pro"`
MetricsUrl string `json:",optional"`
// Deprecated: please use DevServer
Prometheus prometheus.Config `json:",optional"`
Telemetry trace.Config `json:",optional"`
DevServer devserver.Config `json:",optional"`
}
// MustSetUp sets up the service, exits on error.
@@ -56,10 +60,14 @@ func (sc ServiceConf) SetUp() error {
sc.Telemetry.Name = sc.Name
}
trace.StartAgent(sc.Telemetry)
proc.AddShutdownListener(func() {
trace.StopAgent()
})
if len(sc.MetricsUrl) > 0 {
stat.SetReportWriter(stat.NewRemoteWriter(sc.MetricsUrl))
}
devserver.StartAgent(sc.DevServer)
return nil
}

View File

@@ -46,14 +46,14 @@ func Report(msg string) {
if fn != nil {
reported := lessExecutor.DoOrDiscard(func() {
var builder strings.Builder
fmt.Fprintf(&builder, "%s\n", time.Now().Format(timeFormat))
builder.WriteString(fmt.Sprintln(time.Now().Format(timeFormat)))
if len(clusterName) > 0 {
fmt.Fprintf(&builder, "cluster: %s\n", clusterName)
builder.WriteString(fmt.Sprintf("cluster: %s\n", clusterName))
}
fmt.Fprintf(&builder, "host: %s\n", sysx.Hostname())
builder.WriteString(fmt.Sprintf("host: %s\n", sysx.Hostname()))
dp := atomic.SwapInt32(&dropped, 0)
if dp > 0 {
fmt.Fprintf(&builder, "dropped: %d\n", dp)
builder.WriteString(fmt.Sprintf("dropped: %d\n", dp))
}
builder.WriteString(strings.TrimSpace(msg))
fn(builder.String())

View File

@@ -4,6 +4,7 @@
package stat
import (
"os"
"strconv"
"sync/atomic"
"testing"
@@ -12,6 +13,9 @@ import (
)
func TestReport(t *testing.T) {
os.Setenv(clusterNameKey, "test-cluster")
defer os.Unsetenv(clusterNameKey)
var count int32
SetReporter(func(s string) {
atomic.AddInt32(&count, 1)

View File

@@ -258,7 +258,7 @@ func parseUints(val string) ([]uint64, error) {
return sets, nil
}
// runningInUserNS detects whether we are currently running in a user namespace.
// runningInUserNS detects whether we are currently running in an user namespace.
func runningInUserNS() bool {
nsOnce.Do(func() {
file, err := os.Open("/proc/self/uid_map")

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/zeromicro/go-zero/core/iox"
@@ -20,10 +21,11 @@ var (
preTotal uint64
quota float64
cores uint64
initOnce sync.Once
)
// if /proc not present, ignore the cpu calculation, like wsl linux
func init() {
func initialize() {
cpus, err := cpuSets()
if err != nil {
logx.Error(err)
@@ -31,13 +33,7 @@ func init() {
}
cores = uint64(len(cpus))
sets, err := cpuSets()
if err != nil {
logx.Error(err)
return
}
quota = float64(len(sets))
quota = float64(len(cpus))
cq, err := cpuQuota()
if err == nil {
if cq != -1 {
@@ -69,10 +65,13 @@ func init() {
// RefreshCpu refreshes cpu usage and returns.
func RefreshCpu() uint64 {
initOnce.Do(initialize)
total, err := totalCpuUsage()
if err != nil {
return 0
}
system, err := systemCpuUsage()
if err != nil {
return 0

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"math"
"math/rand"
"sync"
"time"
@@ -130,7 +131,7 @@ func (c cacheNode) SetWithExpireCtx(ctx context.Context, key string, val interfa
return err
}
return c.rds.SetexCtx(ctx, key, string(data), int(expire.Seconds()))
return c.rds.SetexCtx(ctx, key, string(data), int(math.Ceil(expire.Seconds())))
}
// String returns a string that represents the cacheNode.
@@ -275,5 +276,6 @@ func (c cacheNode) processCache(ctx context.Context, key, data string, v interfa
}
func (c cacheNode) setCacheWithNotFound(ctx context.Context, key string) error {
return c.rds.SetexCtx(ctx, key, notFoundPlaceholder, int(c.aroundDuration(c.notFoundExpiry).Seconds()))
seconds := int(math.Ceil(c.aroundDuration(c.notFoundExpiry).Seconds()))
return c.rds.SetexCtx(ctx, key, notFoundPlaceholder, seconds)
}

View File

@@ -34,14 +34,14 @@ func newOptions(opts ...Option) Options {
return o
}
// WithExpiry returns a func to customize a Options with given expiry.
// WithExpiry returns a func to customize an Options with given expiry.
func WithExpiry(expiry time.Duration) Option {
return func(o *Options) {
o.Expiry = expiry
}
}
// WithNotFoundExpiry returns a func to customize a Options with given not found expiry.
// WithNotFoundExpiry returns a func to customize an Options with given not found expiry.
func WithNotFoundExpiry(expiry time.Duration) Option {
return func(o *Options) {
o.NotFoundExpiry = expiry

View File

@@ -110,7 +110,9 @@ type (
Ttl(key string) (int, error)
TtlCtx(ctx context.Context, key string) (int, error)
Zadd(key string, score int64, value string) (bool, error)
ZaddFloat(key string, score float64, value string) (bool, error)
ZaddCtx(ctx context.Context, key string, score int64, value string) (bool, error)
ZaddFloatCtx(ctx context.Context, key string, score float64, value string) (bool, error)
Zadds(key string, ps ...redis.Pair) (int64, error)
ZaddsCtx(ctx context.Context, key string, ps ...redis.Pair) (int64, error)
Zcard(key string) (int, error)
@@ -787,13 +789,21 @@ func (cs clusterStore) Zadd(key string, score int64, value string) (bool, error)
return cs.ZaddCtx(context.Background(), key, score, value)
}
func (cs clusterStore) ZaddFloat(key string, score float64, value string) (bool, error) {
return cs.ZaddFloatCtx(context.Background(), key, score, value)
}
func (cs clusterStore) ZaddCtx(ctx context.Context, key string, score int64, value string) (bool, error) {
return cs.ZaddFloatCtx(ctx, key, float64(score), value)
}
func (cs clusterStore) ZaddFloatCtx(ctx context.Context, key string, score float64, value string) (bool, error) {
node, err := cs.getRedis(key)
if err != nil {
return false, err
}
return node.ZaddCtx(ctx, key, score, value)
return node.ZaddFloatCtx(ctx, key, score, value)
}
func (cs clusterStore) Zadds(key string, ps ...redis.Pair) (int64, error) {

View File

@@ -22,7 +22,7 @@ func TestRedis_Decr(t *testing.T) {
_, err := store.Decr("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
val, err := client.Decr("a")
assert.Nil(t, err)
assert.Equal(t, int64(-1), val)
@@ -37,7 +37,7 @@ func TestRedis_DecrBy(t *testing.T) {
_, err := store.Incrby("a", 2)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
val, err := client.Decrby("a", 2)
assert.Nil(t, err)
assert.Equal(t, int64(-2), val)
@@ -52,7 +52,7 @@ func TestRedis_Exists(t *testing.T) {
_, err := store.Exists("foo")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
ok, err := client.Exists("a")
assert.Nil(t, err)
assert.False(t, ok)
@@ -68,7 +68,7 @@ func TestRedis_Eval(t *testing.T) {
_, err := store.Eval(`redis.call("EXISTS", KEYS[1])`, "key1")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
_, err := client.Eval(`redis.call("EXISTS", KEYS[1])`, "notexist")
assert.Equal(t, redis.Nil, err)
err = client.Set("key1", "value1")
@@ -88,7 +88,7 @@ func TestRedis_Hgetall(t *testing.T) {
_, err = store.Hgetall("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
vals, err := client.Hgetall("a")
@@ -105,7 +105,7 @@ func TestRedis_Hvals(t *testing.T) {
_, err := store.Hvals("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
vals, err := client.Hvals("a")
@@ -119,7 +119,7 @@ func TestRedis_Hsetnx(t *testing.T) {
_, err := store.Hsetnx("a", "dd", "ddd")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
ok, err := client.Hsetnx("a", "bb", "ccc")
@@ -141,7 +141,7 @@ func TestRedis_HdelHlen(t *testing.T) {
_, err = store.Hlen("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
num, err := client.Hlen("a")
@@ -161,7 +161,7 @@ func TestRedis_HIncrBy(t *testing.T) {
_, err := store.Hincrby("key", "field", 3)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
val, err := client.Hincrby("key", "field", 2)
assert.Nil(t, err)
assert.Equal(t, 2, val)
@@ -176,7 +176,7 @@ func TestRedis_Hkeys(t *testing.T) {
_, err := store.Hkeys("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
vals, err := client.Hkeys("a")
@@ -190,7 +190,7 @@ func TestRedis_Hmget(t *testing.T) {
_, err := store.Hmget("a", "aa", "bb")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
vals, err := client.Hmget("a", "aa", "bb")
@@ -209,7 +209,7 @@ func TestRedis_Hmset(t *testing.T) {
})
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
assert.Nil(t, client.Hmset("a", map[string]string{
"aa": "aaa",
"bb": "bbb",
@@ -225,7 +225,7 @@ func TestRedis_Incr(t *testing.T) {
_, err := store.Incr("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
val, err := client.Incr("a")
assert.Nil(t, err)
assert.Equal(t, int64(1), val)
@@ -240,7 +240,7 @@ func TestRedis_IncrBy(t *testing.T) {
_, err := store.Incrby("a", 2)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
val, err := client.Incrby("a", 2)
assert.Nil(t, err)
assert.Equal(t, int64(2), val)
@@ -267,7 +267,7 @@ func TestRedis_List(t *testing.T) {
_, err = store.Lindex("key", 0)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
val, err := client.Lpush("key", "value1", "value2")
assert.Nil(t, err)
assert.Equal(t, 2, val)
@@ -316,7 +316,7 @@ func TestRedis_Persist(t *testing.T) {
err = store.Expireat("key", time.Now().Unix()+5)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
ok, err := client.Persist("key")
assert.Nil(t, err)
assert.False(t, ok)
@@ -348,7 +348,7 @@ func TestRedis_Sscan(t *testing.T) {
_, err = store.Del(key)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
var list []string
for i := 0; i < 1550; i++ {
list = append(list, stringx.Randn(i))
@@ -390,7 +390,7 @@ func TestRedis_Set(t *testing.T) {
_, err = store.Spop("key")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
num, err := client.Sadd("key", 1, 2, 3, 4)
assert.Nil(t, err)
assert.Equal(t, 4, num)
@@ -434,7 +434,7 @@ func TestRedis_SetGetDel(t *testing.T) {
_, err = store.Del("hello")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
err := client.Set("hello", "world")
assert.Nil(t, err)
val, err := client.Get("hello")
@@ -457,7 +457,7 @@ func TestRedis_SetExNx(t *testing.T) {
_, err = store.SetnxEx("newhello", "newworld", 5)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
err := client.Setex("hello", "world", 5)
assert.Nil(t, err)
ok, err := client.Setnx("hello", "newworld")
@@ -495,7 +495,7 @@ func TestRedis_Getset(t *testing.T) {
_, err := store.GetSet("hello", "world")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
val, err := client.GetSet("hello", "world")
assert.Nil(t, err)
assert.Equal(t, "", val)
@@ -524,7 +524,7 @@ func TestRedis_SetGetDelHashField(t *testing.T) {
_, err = store.Hdel("key", "field")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
runOnCluster(func(client Store) {
err := client.Hset("key", "field", "value")
assert.Nil(t, err)
val, err := client.Hget("key", "field")
@@ -587,8 +587,8 @@ func TestRedis_SortedSet(t *testing.T) {
})
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
ok, err := client.Zadd("key", 1, "value1")
runOnCluster(func(client Store) {
ok, err := client.ZaddFloat("key", 1, "value1")
assert.Nil(t, err)
assert.True(t, ok)
ok, err = client.Zadd("key", 2, "value1")
@@ -724,7 +724,7 @@ func TestRedis_HyperLogLog(t *testing.T) {
_, err = store.Pfcount("key")
assert.NotNil(t, err)
runOnCluster(t, func(cluster Store) {
runOnCluster(func(cluster Store) {
ok, err := cluster.Pfadd("key", "value")
assert.Nil(t, err)
assert.True(t, ok)
@@ -734,7 +734,7 @@ func TestRedis_HyperLogLog(t *testing.T) {
})
}
func runOnCluster(t *testing.T, fn func(cluster Store)) {
func runOnCluster(fn func(cluster Store)) {
s1.FlushAll()
s2.FlushAll()

View File

@@ -83,12 +83,12 @@ type (
// FindOneAndReplace returns at most one document that matches the filter. If the filter
// matches multiple documents, FindOneAndReplace returns the first document in the
// collection that matches the filter.
FindOneAndReplace(ctx context.Context, filter interface{}, replacement interface{},
FindOneAndReplace(ctx context.Context, filter, replacement interface{},
opts ...*mopt.FindOneAndReplaceOptions) (*mongo.SingleResult, error)
// FindOneAndUpdate returns at most one document that matches the filter. If the filter
// matches multiple documents, FindOneAndUpdate returns the first document in the
// collection that matches the filter.
FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{},
FindOneAndUpdate(ctx context.Context, filter, update interface{},
opts ...*mopt.FindOneAndUpdateOptions) (*mongo.SingleResult, error)
// Indexes returns the index view for this collection.
Indexes() mongo.IndexView
@@ -99,16 +99,16 @@ type (
InsertOne(ctx context.Context, document interface{}, opts ...*mopt.InsertOneOptions) (
*mongo.InsertOneResult, error)
// ReplaceOne replaces at most one document that matches the filter.
ReplaceOne(ctx context.Context, filter interface{}, replacement interface{},
ReplaceOne(ctx context.Context, filter, replacement interface{},
opts ...*mopt.ReplaceOptions) (*mongo.UpdateResult, error)
// UpdateByID updates a single document matching the provided filter.
UpdateByID(ctx context.Context, id interface{}, update interface{},
UpdateByID(ctx context.Context, id, update interface{},
opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error)
// UpdateMany updates the provided documents.
UpdateMany(ctx context.Context, filter interface{}, update interface{},
UpdateMany(ctx context.Context, filter, update interface{},
opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error)
// UpdateOne updates a single document matching the provided filter.
UpdateOne(ctx context.Context, filter interface{}, update interface{},
UpdateOne(ctx context.Context, filter, update interface{},
opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error)
// Watch returns a change stream cursor used to receive notifications of changes to the collection.
Watch(ctx context.Context, pipeline interface{}, opts ...*mopt.ChangeStreamOptions) (
@@ -359,7 +359,7 @@ func (c *decoratedCollection) FindOneAndReplace(ctx context.Context, filter inte
return
}
func (c *decoratedCollection) FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{},
func (c *decoratedCollection) FindOneAndUpdate(ctx context.Context, filter, update interface{},
opts ...*mopt.FindOneAndUpdateOptions) (res *mongo.SingleResult, err error) {
ctx, span := startSpan(ctx, findOneAndUpdate)
defer func() {
@@ -420,7 +420,7 @@ func (c *decoratedCollection) InsertOne(ctx context.Context, document interface{
return
}
func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter interface{}, replacement interface{},
func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter, replacement interface{},
opts ...*mopt.ReplaceOptions) (res *mongo.UpdateResult, err error) {
ctx, span := startSpan(ctx, replaceOne)
defer func() {
@@ -440,7 +440,7 @@ func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter interface{}
return
}
func (c *decoratedCollection) UpdateByID(ctx context.Context, id interface{}, update interface{},
func (c *decoratedCollection) UpdateByID(ctx context.Context, id, update interface{},
opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
ctx, span := startSpan(ctx, updateByID)
defer func() {
@@ -460,7 +460,7 @@ func (c *decoratedCollection) UpdateByID(ctx context.Context, id interface{}, up
return
}
func (c *decoratedCollection) UpdateMany(ctx context.Context, filter interface{}, update interface{},
func (c *decoratedCollection) UpdateMany(ctx context.Context, filter, update interface{},
opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
ctx, span := startSpan(ctx, updateMany)
defer func() {
@@ -480,7 +480,7 @@ func (c *decoratedCollection) UpdateMany(ctx context.Context, filter interface{}
return
}
func (c *decoratedCollection) UpdateOne(ctx context.Context, filter interface{}, update interface{},
func (c *decoratedCollection) UpdateOne(ctx context.Context, filter, update interface{},
opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
ctx, span := startSpan(ctx, updateOne)
defer func() {

View File

@@ -20,10 +20,6 @@ import (
var errDummy = errors.New("dummy")
func init() {
logx.Disable()
}
func TestKeepPromise_accept(t *testing.T) {
p := new(mockPromise)
kp := keepablePromise{
@@ -110,14 +106,14 @@ func TestCollection_BulkWrite(t *testing.T) {
}
mt.AddMockResponses(mtest.CreateSuccessResponse(bson.D{{Key: "ok", Value: 1}}...))
res, err := c.BulkWrite(context.Background(), []mongo.WriteModel{
mongo.NewInsertOneModel().SetDocument(bson.D{{Key: "foo", Value: 1}})},
)
mongo.NewInsertOneModel().SetDocument(bson.D{{Key: "foo", Value: 1}}),
})
assert.Nil(t, err)
assert.NotNil(t, res)
c.brk = new(dropBreaker)
_, err = c.BulkWrite(context.Background(), []mongo.WriteModel{
mongo.NewInsertOneModel().SetDocument(bson.D{{Key: "foo", Value: 1}})},
)
mongo.NewInsertOneModel().SetDocument(bson.D{{Key: "foo", Value: 1}}),
})
assert.Equal(t, errDummy, err)
})
}
@@ -208,7 +204,7 @@ func TestCollection_EstimatedDocumentCount(t *testing.T) {
})
}
func TestCollectionFind(t *testing.T) {
func TestCollection_Find(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
defer mt.Close()
@@ -256,7 +252,7 @@ func TestCollectionFind(t *testing.T) {
})
}
func TestCollectionFindOne(t *testing.T) {
func TestCollection_FindOne(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
defer mt.Close()
@@ -440,7 +436,7 @@ func TestCollection_InsertMany(t *testing.T) {
})
}
func TestCollection_Remove(t *testing.T) {
func TestCollection_DeleteOne(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
defer mt.Close()
@@ -460,7 +456,7 @@ func TestCollection_Remove(t *testing.T) {
})
}
func TestCollectionRemoveAll(t *testing.T) {
func TestCollection_DeleteMany(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
defer mt.Close()
@@ -569,7 +565,7 @@ func TestCollection_UpdateMany(t *testing.T) {
})
}
func Test_DecoratedCollectionLogDuration(t *testing.T) {
func TestDecoratedCollection_LogDuration(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
defer mt.Close()
c := decoratedCollection{

View File

@@ -159,7 +159,7 @@ func (m *Model) FindOneAndDelete(ctx context.Context, v, filter interface{},
}
// FindOneAndReplace finds a single document and replaces it.
func (m *Model) FindOneAndReplace(ctx context.Context, v, filter interface{}, replacement interface{},
func (m *Model) FindOneAndReplace(ctx context.Context, v, filter, replacement interface{},
opts ...*mopt.FindOneAndReplaceOptions) error {
res, err := m.Collection.FindOneAndReplace(ctx, filter, replacement, opts...)
if err != nil {
@@ -170,7 +170,7 @@ func (m *Model) FindOneAndReplace(ctx context.Context, v, filter interface{}, re
}
// FindOneAndUpdate finds a single document and updates it.
func (m *Model) FindOneAndUpdate(ctx context.Context, v, filter interface{}, update interface{},
func (m *Model) FindOneAndUpdate(ctx context.Context, v, filter, update interface{},
opts ...*mopt.FindOneAndUpdateOptions) error {
res, err := m.Collection.FindOneAndUpdate(ctx, filter, update, opts...)
if err != nil {

View File

@@ -192,7 +192,7 @@ func (mm *Model) InsertOneNoCache(ctx context.Context, document interface{},
}
// ReplaceOne replaces a single document in the collection, and remove the cache.
func (mm *Model) ReplaceOne(ctx context.Context, key string, filter interface{}, replacement interface{},
func (mm *Model) ReplaceOne(ctx context.Context, key string, filter, replacement interface{},
opts ...*mopt.ReplaceOptions) (*mongo.UpdateResult, error) {
res, err := mm.Model.ReplaceOne(ctx, filter, replacement, opts...)
if err != nil {
@@ -207,7 +207,7 @@ func (mm *Model) ReplaceOne(ctx context.Context, key string, filter interface{},
}
// ReplaceOneNoCache replaces a single document in the collection.
func (mm *Model) ReplaceOneNoCache(ctx context.Context, filter interface{}, replacement interface{},
func (mm *Model) ReplaceOneNoCache(ctx context.Context, filter, replacement interface{},
opts ...*mopt.ReplaceOptions) (*mongo.UpdateResult, error) {
return mm.Model.ReplaceOne(ctx, filter, replacement, opts...)
}
@@ -218,7 +218,7 @@ func (mm *Model) SetCache(key string, v interface{}) error {
}
// UpdateByID updates the document with given id with update, and remove the cache.
func (mm *Model) UpdateByID(ctx context.Context, key string, id interface{}, update interface{},
func (mm *Model) UpdateByID(ctx context.Context, key string, id, update interface{},
opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
res, err := mm.Model.UpdateByID(ctx, id, update, opts...)
if err != nil {
@@ -233,13 +233,13 @@ func (mm *Model) UpdateByID(ctx context.Context, key string, id interface{}, upd
}
// UpdateByIDNoCache updates the document with given id with update.
func (mm *Model) UpdateByIDNoCache(ctx context.Context, id interface{}, update interface{},
func (mm *Model) UpdateByIDNoCache(ctx context.Context, id, update interface{},
opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
return mm.Model.UpdateByID(ctx, id, update, opts...)
}
// UpdateMany updates the documents that match filter with update, and remove the cache.
func (mm *Model) UpdateMany(ctx context.Context, keys []string, filter interface{}, update interface{},
func (mm *Model) UpdateMany(ctx context.Context, keys []string, filter, update interface{},
opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
res, err := mm.Model.UpdateMany(ctx, filter, update, opts...)
if err != nil {
@@ -254,13 +254,13 @@ func (mm *Model) UpdateMany(ctx context.Context, keys []string, filter interface
}
// UpdateManyNoCache updates the documents that match filter with update.
func (mm *Model) UpdateManyNoCache(ctx context.Context, filter interface{}, update interface{},
func (mm *Model) UpdateManyNoCache(ctx context.Context, filter, update interface{},
opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
return mm.Model.UpdateMany(ctx, filter, update, opts...)
}
// UpdateOne updates the first document that matches filter with update, and remove the cache.
func (mm *Model) UpdateOne(ctx context.Context, key string, filter interface{}, update interface{},
func (mm *Model) UpdateOne(ctx context.Context, key string, filter, update interface{},
opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
res, err := mm.Model.UpdateOne(ctx, filter, update, opts...)
if err != nil {
@@ -275,7 +275,7 @@ func (mm *Model) UpdateOne(ctx context.Context, key string, filter interface{},
}
// UpdateOneNoCache updates the first document that matches filter with update.
func (mm *Model) UpdateOneNoCache(ctx context.Context, filter interface{}, update interface{},
func (mm *Model) UpdateOneNoCache(ctx context.Context, filter, update interface{},
opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
return mm.Model.UpdateOne(ctx, filter, update, opts...)
}

View File

@@ -17,10 +17,6 @@ import (
var errDummy = errors.New("dummy")
func init() {
logx.Disable()
}
func TestKeepPromise_accept(t *testing.T) {
p := new(mockPromise)
kp := keepablePromise{

View File

@@ -3,7 +3,7 @@ package mongoc
import (
"encoding/json"
"errors"
"io/ioutil"
"io"
"log"
"os"
"runtime"
@@ -117,7 +117,7 @@ func TestStat(t *testing.T) {
func TestStatCacheFails(t *testing.T) {
resetStats()
log.SetOutput(ioutil.Discard)
log.SetOutput(io.Discard)
defer log.SetOutput(os.Stdout)
r := redis.New("localhost:59999")

View File

@@ -2,10 +2,13 @@ package redis
import (
"context"
"io"
"net"
"strings"
"time"
red "github.com/go-redis/redis/v8"
"github.com/zeromicro/go-zero/core/breaker"
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mapping"
@@ -53,7 +56,12 @@ func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
duration := timex.Since(start)
if duration > slowThreshold.Load() {
logDuration(ctx, cmd, duration)
logDuration(ctx, []red.Cmder{cmd}, duration)
}
metricReqDur.Observe(int64(duration/time.Millisecond), cmd.Name())
if msg := formatError(err); len(msg) > 0 {
metricReqErr.Inc(cmd.Name(), msg)
}
return nil
@@ -95,19 +103,53 @@ func (h hook) AfterProcessPipeline(ctx context.Context, cmds []red.Cmder) error
duration := timex.Since(start)
if duration > slowThreshold.Load()*time.Duration(len(cmds)) {
logDuration(ctx, cmds[0], duration)
logDuration(ctx, cmds, duration)
}
metricReqDur.Observe(int64(duration/time.Millisecond), "Pipeline")
if msg := formatError(batchError.Err()); len(msg) > 0 {
metricReqErr.Inc("Pipeline", msg)
}
return nil
}
func logDuration(ctx context.Context, cmd red.Cmder, duration time.Duration) {
func formatError(err error) string {
if err == nil || err == red.Nil {
return ""
}
opErr, ok := err.(*net.OpError)
if ok && opErr.Timeout() {
return "timeout"
}
switch err {
case io.EOF:
return "eof"
case context.DeadlineExceeded:
return "context deadline"
case breaker.ErrServiceUnavailable:
return "breaker"
default:
return "unexpected error"
}
}
func logDuration(ctx context.Context, cmds []red.Cmder, duration time.Duration) {
var buf strings.Builder
for i, arg := range cmd.Args() {
if i > 0 {
buf.WriteByte(' ')
for k, cmd := range cmds {
if k > 0 {
buf.WriteByte('\n')
}
buf.WriteString(mapping.Repr(arg))
var build strings.Builder
for i, arg := range cmd.Args() {
if i > 0 {
build.WriteByte(' ')
}
build.WriteString(mapping.Repr(arg))
}
buf.WriteString(build.String())
}
logx.WithContext(ctx).WithDuration(duration).Slowf("[REDIS] slowcall on executing: %s", buf.String())
}

View File

@@ -21,6 +21,7 @@ func TestHookProcessCase1(t *testing.T) {
Batcher: "jaeger",
Sampler: 1.0,
})
defer ztrace.StopAgent()
writer := log.Writer()
var buf strings.Builder
@@ -44,6 +45,7 @@ func TestHookProcessCase2(t *testing.T) {
Batcher: "jaeger",
Sampler: 1.0,
})
defer ztrace.StopAgent()
w, restore := injectLog()
defer restore()
@@ -89,10 +91,10 @@ func TestHookProcessPipelineCase1(t *testing.T) {
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{red.NewCmd(context.Background())})
if err != nil {
t.Fatal(err)
}
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{
red.NewCmd(context.Background()),
})
assert.NoError(t, err)
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
@@ -108,14 +110,15 @@ func TestHookProcessPipelineCase2(t *testing.T) {
Batcher: "jaeger",
Sampler: 1.0,
})
defer ztrace.StopAgent()
w, restore := injectLog()
defer restore()
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{red.NewCmd(context.Background())})
if err != nil {
t.Fatal(err)
}
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{
red.NewCmd(context.Background()),
})
assert.NoError(t, err)
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
time.Sleep(slowThreshold.Load() + time.Millisecond)
@@ -156,10 +159,28 @@ func TestHookProcessPipelineCase5(t *testing.T) {
defer log.SetOutput(writer)
ctx := context.WithValue(context.Background(), startTimeKey, "foo")
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{red.NewCmd(context.Background())}))
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
red.NewCmd(context.Background()),
}))
assert.True(t, buf.Len() == 0)
}
func TestLogDuration(t *testing.T) {
w, restore := injectLog()
defer restore()
logDuration(context.Background(), []red.Cmder{
red.NewCmd(context.Background(), "get", "foo"),
}, 1*time.Second)
assert.True(t, strings.Contains(w.String(), "get foo"))
logDuration(context.Background(), []red.Cmder{
red.NewCmd(context.Background(), "get", "foo"),
red.NewCmd(context.Background(), "set", "bar", 0),
}, 1*time.Second)
assert.True(t, strings.Contains(w.String(), `get foo\nset bar 0`))
}
func injectLog() (r *strings.Builder, restore func()) {
var buf strings.Builder
w := logx.NewWriter(&buf)

View File

@@ -0,0 +1,23 @@
package redis
import "github.com/zeromicro/go-zero/core/metric"
const namespace = "redis_client"
var (
metricReqDur = metric.NewHistogramVec(&metric.HistogramVecOpts{
Namespace: namespace,
Subsystem: "requests",
Name: "duration_ms",
Help: "redis client requests duration(ms).",
Labels: []string{"command"},
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000, 2500},
})
metricReqErr = metric.NewCounterVec(&metric.CounterVecOpts{
Namespace: namespace,
Subsystem: "requests",
Name: "error_total",
Help: "redis client requests error count.",
Labels: []string{"command", "error"},
})
)

View File

@@ -226,20 +226,7 @@ func (s *Redis) Blpop(node RedisNode, key string) (string, error) {
// BlpopCtx uses passed in redis connection to execute blocking queries.
// Doesn't benefit from pooling redis connections of blocking queries
func (s *Redis) BlpopCtx(ctx context.Context, node RedisNode, key string) (string, error) {
if node == nil {
return "", ErrNilNode
}
vals, err := node.BLPop(ctx, blockingQueryTimeout, key).Result()
if err != nil {
return "", err
}
if len(vals) < 2 {
return "", fmt.Errorf("no value on key: %s", key)
}
return vals[1], nil
return s.BlpopWithTimeoutCtx(ctx, node, blockingQueryTimeout, key)
}
// BlpopEx uses passed in redis connection to execute blpop command.
@@ -267,6 +254,32 @@ func (s *Redis) BlpopExCtx(ctx context.Context, node RedisNode, key string) (str
return vals[1], true, nil
}
// BlpopWithTimeout uses passed in redis connection to execute blpop command.
// Control blocking query timeout
func (s *Redis) BlpopWithTimeout(node RedisNode, timeout time.Duration, key string) (string, error) {
return s.BlpopWithTimeoutCtx(context.Background(), node, timeout, key)
}
// BlpopWithTimeoutCtx uses passed in redis connection to execute blpop command.
// Control blocking query timeout
func (s *Redis) BlpopWithTimeoutCtx(ctx context.Context, node RedisNode, timeout time.Duration,
key string) (string, error) {
if node == nil {
return "", ErrNilNode
}
vals, err := node.BLPop(ctx, timeout, key).Result()
if err != nil {
return "", err
}
if len(vals) < 2 {
return "", fmt.Errorf("no value on key: %s", key)
}
return vals[1], nil
}
// Decr is the implementation of redis decr command.
func (s *Redis) Decr(key string) (int64, error) {
return s.DecrCtx(context.Background(), key)
@@ -1836,8 +1849,19 @@ func (s *Redis) Zadd(key string, score int64, value string) (bool, error) {
return s.ZaddCtx(context.Background(), key, score, value)
}
// ZaddFloat is the implementation of redis zadd command.
func (s *Redis) ZaddFloat(key string, score float64, value string) (bool, error) {
return s.ZaddFloatCtx(context.Background(), key, score, value)
}
// ZaddCtx is the implementation of redis zadd command.
func (s *Redis) ZaddCtx(ctx context.Context, key string, score int64, value string) (
val bool, err error) {
return s.ZaddFloatCtx(ctx, key, float64(score), value)
}
// ZaddFloatCtx is the implementation of redis zadd command.
func (s *Redis) ZaddFloatCtx(ctx context.Context, key string, score float64, value string) (
val bool, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
@@ -1846,7 +1870,7 @@ func (s *Redis) ZaddCtx(ctx context.Context, key string, score int64, value stri
}
v, err := conn.ZAdd(ctx, key, &red.Z{
Score: float64(score),
Score: score,
Member: value,
}).Result()
if err != nil {

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