Compare commits

..

125 Commits
master ... v3

Author SHA1 Message Date
Василий Толстов 3eebfb5b11 Обновить options.go 2024-05-10 08:12:10 +03:00
Василий Толстов fa1427014c close #343
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-05-09 19:16:12 +03:00
Василий Толстов 62074965ee close #329
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-05-09 16:41:22 +03:00
Василий Толстов 9c8fbb2202 broker: add Event Context() method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-05-05 16:22:06 +03:00
Василий Толстов 7c0a5f5e2a add abilit to skip span recording
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-05-04 19:31:35 +03:00
Василий Толстов b08f5321b0 tracer: allow to skip span recording
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-05-04 19:18:12 +03:00
Василий Толстов cc0f24e012 add ability to skip endpoints for tracer and meter
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-05-04 19:05:07 +03:00
Василий Толстов 307a08f50c add more checks
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-05-04 15:31:08 +03:00
Василий Толстов edc93e8c37 util/reflect: update StructFieldNameByTag
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-05-04 14:43:46 +03:00
Василий Толстов 391813c260 util/reflect: add StructFieldNameByTag
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-05-04 14:34:41 +03:00
Василий Толстов 1a1459dd0e util/reflect: fix StructFieldByTag
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-05-04 13:16:31 +03:00
Василий Толстов 4e99680c30 server: add missing hook definitions
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-23 07:39:08 +03:00
Василий Толстов 92a3a547b8 Merge pull request 'server/noop: cleanup' (#342) from server-noop into v3
Reviewed-on: #342
2024-04-23 07:30:20 +03:00
Василий Толстов 849c462037 server/noop: cleanup
pr / test (pull_request) Successful in 1m38s Details
lint / lint (pull_request) Successful in 10m35s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-23 07:28:58 +03:00
Василий Толстов 54a55c83e2 Merge pull request 'add client tracing' (#341) from traceclient into v3
Reviewed-on: #341
2024-04-22 23:44:54 +03:00
Василий Толстов 781dee03db add client tracing
pr / test (pull_request) Successful in 1m36s Details
lint / lint (pull_request) Successful in 10m37s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-22 23:39:21 +03:00
Василий Толстов 26dd2eb405 Merge pull request 'replace wrappers with hooks' (#339) from hooks into v3
Reviewed-on: #339
2024-04-22 08:50:53 +03:00
Василий Толстов 3a21069b86 remote stale test
pr / test (pull_request) Successful in 2m55s Details
lint / lint (pull_request) Successful in 11m55s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-22 08:50:33 +03:00
Василий Толстов add3ce478c replace wrappers with hooks
pr / test (pull_request) Failing after 2m59s Details
lint / lint (pull_request) Successful in 11m36s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-22 08:47:50 +03:00
Кирилл Горбунов c3de003e4a #335 caller skip count. (#337)
Co-authored-by: Gorbunov Kirill Andreevich <kgorbunov@mtsbank.ru>
Reviewed-on: #337
Co-authored-by: Кирилл Горбунов <kirya_gorbunov_2015@mail.ru>
Co-committed-by: Кирилл Горбунов <kirya_gorbunov_2015@mail.ru>
2024-04-15 13:30:48 +03:00
Василий Толстов 7b7cf18a65 semconv: add cache metric names
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-14 16:42:36 +03:00
Василий Толстов 1bcf71c189 util/xpool: package pool
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-14 00:16:55 +03:00
Василий Толстов c320d8e518 store/options: extend options to holds name and timeout
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-13 12:52:06 +03:00
Василий Толстов b5f8316b57 semconv: fix broker group lag metric name
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-13 02:38:58 +03:00
Василий Толстов d7ddd912a8 Merge pull request 'semconv: add broker group lag' (#336) from brokerlag into v3
Reviewed-on: #336
2024-04-13 02:07:53 +03:00
Василий Толстов c020d90cb4 semconv: add broker group lag
pr / test (pull_request) Failing after 1m39s Details
lint / lint (pull_request) Successful in 10m49s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-13 02:06:51 +03:00
Василий Толстов db47b62159 Merge pull request 'add options in broker' (#334) from devstigneev/micro:v3 into v3
Reviewed-on: #334
2024-04-08 23:12:59 +03:00
Денис Евстигнеев 8254456c8b rename path to sync
lint / lint (pull_request) Has been cancelled Details
pr / test (pull_request) Has been cancelled Details
2024-04-07 21:16:50 +03:00
Денис Евстигнеев c2808679c3 add options in broker
lint / lint (pull_request) Has been cancelled Details
pr / test (pull_request) Has been cancelled Details
2024-04-07 20:53:01 +03:00
Василий Толстов f418235c16 Merge pull request 'cluster: initial import' (#332) from cluster into v3
Reviewed-on: #332
2024-04-06 23:29:04 +03:00
Василий Толстов 67ba7b3753 cluster: initial import
pr / test (pull_request) Failing after 1m36s Details
lint / lint (pull_request) Successful in 10m48s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-06 23:28:01 +03:00
Василий Толстов e48d7cadf9 Merge pull request 'add semconv package' (#331) from semconv into v3
Reviewed-on: #331
2024-04-06 22:04:47 +03:00
Василий Толстов c906186011 add semconv package
pr / test (pull_request) Failing after 1m39s Details
lint / lint (pull_request) Successful in 10m24s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-06 22:03:56 +03:00
Василий Толстов dc0ff91b83 Merge pull request 'util/reflect: detect json.Unmarshaler' (#328) from utilsort into v3
Reviewed-on: #328
2024-04-02 08:52:11 +03:00
Василий Толстов e739c2d438 util/reflect: detect json.Unmarshaler
lint / lint (pull_request) Has been cancelled Details
pr / test (pull_request) Failing after 2m3s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-02 08:51:06 +03:00
Василий Толстов bf4a036652 Merge pull request 'move sort.Uniq to dedicated package' (#327) from utilsort into v3
Reviewed-on: #327
2024-03-27 11:25:50 +03:00
Василий Толстов f83a29eb67 move sort.Uniq to dedicated package
lint / lint (pull_request) Has been cancelled Details
pr / test (pull_request) Has been cancelled Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-27 11:24:28 +03:00
Василий Толстов aef7f53d88 Merge pull request 'tracer: append labels' (#326) from tracerfix into v3
Reviewed-on: #326
2024-03-17 00:18:23 +03:00
Василий Толстов 02c8e4fb7f tracer: append labels
pr / test (pull_request) Successful in 1m35s Details
lint / lint (pull_request) Successful in 10m38s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-17 00:17:10 +03:00
Василий Толстов f5693bd940 Merge pull request 'v3 update WaitGroup Options' (#325) from devstigneev/micro:v3 into v3
Reviewed-on: #325
2024-03-13 11:03:29 +03:00
Денис Евстигнеев 701afb7bea sort imports
lint / lint (pull_request) Has been cancelled Details
pr / test (pull_request) Has been cancelled Details
2024-03-13 10:51:03 +03:00
Денис Евстигнеев 019b407e74 update WaitOptions 2024-03-13 10:49:58 +03:00
Василий Толстов f29a346434 Merge pull request 'tracer: add Context init to NewOptions' (#323) from tracerctx into v3
Reviewed-on: #323
2024-03-11 01:13:01 +03:00
Василий Толстов 27db1876c0 tracer: add Context init to NewOptions
pr / test (pull_request) Successful in 1m30s Details
lint / lint (pull_request) Successful in 10m33s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-11 01:12:20 +03:00
Василий Толстов f66ac9736b metadata: allow to exclude some keys in Copy func (#321)
Reviewed-on: #321
Co-authored-by: Vasiliy Tolstov <v.tolstov@unistack.org>
Co-committed-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-09 23:50:40 +03:00
Василий Толстов ed7972a1fa Merge pull request 'sync/waitgroup: backport from master' (#320) from waitgroup into v3
Reviewed-on: #320
2024-03-09 23:37:39 +03:00
Василий Толстов 2cc004b01c sync/waitgroup: backport from master
pr / test (pull_request) Successful in 1m40s Details
lint / lint (pull_request) Successful in 10m42s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-09 23:36:39 +03:00
Василий Толстов df951e5daf Merge pull request 'logger/slog: fix slog' (#317) from slogfix2 into v3
Reviewed-on: #317
2024-03-07 08:22:37 +03:00
Василий Толстов 5bec0cef03 logger/slog: fix slog
pr / test (pull_request) Successful in 1m24s Details
lint / lint (pull_request) Successful in 10m24s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-07 08:19:14 +03:00
Василий Толстов 34940b68d7 Merge pull request 'logger/slog: fix race condition' (#316) from slogfix into v3
Reviewed-on: #316
2024-03-07 07:45:07 +03:00
Василий Толстов 1c57127128 logger/slog: fix race condition
pr / test (pull_request) Successful in 1m34s Details
lint / lint (pull_request) Successful in 10m36s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-07 07:43:52 +03:00
Василий Толстов a4dd1a494c Merge pull request 'logger: add TimeFunc option' (#315) from logger-timefunc into v3
Reviewed-on: #315
2024-03-07 00:02:53 +03:00
Василий Толстов 60e5e42167 logger: add TimeFunc option
pr / test (pull_request) Successful in 1m36s Details
lint / lint (pull_request) Successful in 10m45s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-07 00:02:00 +03:00
Василий Толстов b519b61fff Merge pull request 'fixup interfaces' (#314) from iface-v3 into v3
Reviewed-on: #314
2024-03-06 18:49:03 +03:00
Василий Толстов f62b26eda3 fixup interfaces
pr / test (pull_request) Failing after 1m30s Details
lint / lint (pull_request) Successful in 10m37s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-06 18:45:32 +03:00
Василий Толстов 13eda451da Merge pull request 'fixup deps' (#313) from deps into v3
Reviewed-on: #313
2024-03-06 16:46:33 +03:00
Василий Толстов 89cad06121 fixup deps
pr / test (pull_request) Failing after 1m35s Details
lint / lint (pull_request) Successful in 10m44s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-06 16:45:19 +03:00
Василий Толстов 0bebf3d59f Merge pull request 'tracer and logger improvements' (#312) from tracer-logger into v3
Reviewed-on: #312
2024-03-06 00:57:01 +03:00
Василий Толстов 01e05e8df6 tracer and logger improvements
pr / test (pull_request) Failing after 1m27s Details
lint / lint (pull_request) Successful in 10m33s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-06 00:53:20 +03:00
Василий Толстов 2b69a4f51c Merge pull request 'logger/slog: backport default logger keys from master' (#311) from v3-logger into v3
Reviewed-on: #311
2024-03-05 01:54:17 +03:00
Василий Толстов 4af2b077dd logger/slog: backport default logger keys from master
pr / test (pull_request) Successful in 1m45s Details
lint / lint (pull_request) Successful in 10m43s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 23:58:31 +03:00
Василий Толстов de4418189d Merge pull request 'add missing option' (#309) from logger-stacktrace into v3
Reviewed-on: #309
2024-03-04 23:04:50 +03:00
Василий Толстов 2c44550897 add missing option
pr / test (pull_request) Successful in 1m46s Details
lint / lint (pull_request) Successful in 10m49s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 23:03:55 +03:00
Василий Толстов 99b8a3c950 Merge pull request 'logger/slog: add stacktrace support' (#308) from logger-stacktrace into v3
Reviewed-on: #308
2024-03-04 23:00:35 +03:00
Василий Толстов 4c7e1607d4 logger/slog: add stacktrace support
pr / test (pull_request) Failing after 1m28s Details
lint / lint (pull_request) Successful in 10m40s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 22:54:11 +03:00
Василий Толстов 897be419b4 Merge pull request 'broker noop implementation' (#307) from noops into v3
Reviewed-on: #307
2024-03-04 01:15:16 +03:00
Василий Толстов 81b9a4341f logger: extend interface, fix tests
pr / test (pull_request) Successful in 1m35s Details
lint / lint (pull_request) Successful in 10m40s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 01:09:08 +03:00
Василий Толстов d3bb2f7236 broker/noop: add initial implementation
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 01:05:40 +03:00
Василий Толстов 97fd62cb21 Merge pull request 'register/noop: add noop register' (#306) from register-noop into v3
Reviewed-on: #306
2024-03-01 21:40:01 +03:00
Василий Толстов 3cd8bc33d6 fixup test
pr / test (pull_request) Failing after 1m31s Details
lint / lint (pull_request) Successful in 10m44s Details
2024-03-01 21:39:31 +03:00
Василий Толстов f6f67af8d0 register/noop: add noop register
pr / test (pull_request) Failing after 1m34s Details
lint / lint (pull_request) Successful in 11m0s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-29 23:58:11 +03:00
Василий Толстов 2d5acaca2f Merge pull request 'server: add GracefulTimeout option' (#304) from graceful into v3
Reviewed-on: #304
2024-02-29 23:24:43 +03:00
Василий Толстов 0674df3d9f update workflow
pr / test (pull_request) Failing after 1m40s Details
lint / lint (pull_request) Successful in 11m5s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-29 23:23:51 +03:00
Василий Толстов 2c282825ce fixup
pr / test (pull_request) Failing after 1m38s Details
lint / lint (pull_request) Failing after 1m47s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-29 23:17:36 +03:00
Василий Толстов e87ff942bb bump gomod
lint / lint (pull_request) Failing after 1m40s Details
pr / test (pull_request) Failing after 1m44s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-29 23:03:39 +03:00
Василий Толстов 0459ea0613 fixup
lint / lint (pull_request) Failing after 1m38s Details
pr / test (pull_request) Failing after 1m38s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-29 22:40:05 +03:00
Денис Евстигнеев d44a75d074 add gracefultimeout in server 2024-02-29 22:35:55 +03:00
Кирилл Горбунов ccf92eb84d As for interface casting
Co-authored-by: Gorbunov Kirill Andreevich <kgorbunov@mtsbank.ru>
Reviewed-on: #299
Co-authored-by: Кирилл Горбунов <kirya_gorbunov_2015@mail.ru>
Co-committed-by: Кирилл Горбунов <kirya_gorbunov_2015@mail.ru>
2024-02-27 23:35:49 +03:00
Василий Толстов 6baf1f2744 Merge pull request 'logger/slog: fixup race condition' (#292) from log into v3
Reviewed-on: #292
2024-02-22 08:58:40 +03:00
Василий Толстов 8e2eafde9c logger/slog: fixup race condition
lint / lint (pull_request) Has been cancelled Details
pr / test (pull_request) Has been cancelled Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-22 08:57:21 +03:00
Василий Толстов c2b97b0f20 fixup logger/slog
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-15 10:01:41 +03:00
Василий Толстов 1db017d966 Merge pull request 'logger/slog: fixup old format' (#291) from fixupslog into v3
Reviewed-on: #291
2024-02-08 08:44:23 +03:00
Василий Толстов debf8cb03d logger/slog: fixup old format
lint / lint (pull_request) Has been cancelled Details
pr / test (pull_request) Has been cancelled Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-08 08:43:53 +03:00
Василий Толстов 1dc9c1891f Merge pull request 'logger/slog: initial import' (#290) from slog into v3
Reviewed-on: #290
2024-02-08 08:18:57 +03:00
Василий Толстов 930859a537 logger/slog: initial import
lint / lint (pull_request) Has been cancelled Details
pr / test (pull_request) Has been cancelled Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-08 08:17:53 +03:00
Василий Толстов 3141f1ed8b Merge pull request 'config: add conditions' (#286) from cond-config into v3
Reviewed-on: #286
2024-01-15 00:46:37 +03:00
Василий Толстов 47943cfb05 config: add conditions
lint / lint (pull_request) Successful in 1m28s Details
pr / test (pull_request) Failing after 1m5s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-01-15 00:46:00 +03:00
Василий Толстов ed4e9d54b1 Merge pull request 'client/noop: fixup md' (#285) from noopfix into v3
Reviewed-on: #285
2023-12-21 00:14:54 +03:00
Василий Толстов b4b8583594 client/noop: fixup md
lint / lint (pull_request) Failing after 1m28s Details
pr / test (pull_request) Failing after 2m45s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-12-21 00:13:08 +03:00
Василий Толстов fb43e8c58c Merge pull request 'client/noop: fix metadata overwrite' (#284) from noopfix into v3
Reviewed-on: #284
2023-12-21 00:07:22 +03:00
Василий Толстов 8863c10ef4 client/noop: fix metadata overwrite
lint / lint (pull_request) Failing after 1m29s Details
pr / test (pull_request) Failing after 2m36s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-12-21 00:06:56 +03:00
Василий Толстов 8058095bcc Merge pull request 'copy incoming content-type' (#283) from ct into v3
Reviewed-on: #283
2023-12-20 09:35:33 +03:00
Василий Толстов 092f5d96b1 copy incoming content-type
lint / lint (pull_request) Failing after 1m28s Details
pr / test (pull_request) Failing after 2m33s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-12-20 09:35:01 +03:00
Василий Толстов 84552513f7 Merge pull request 'fixup multiple client handling' (#280) from multiple into v3
Reviewed-on: #280
2023-11-13 08:20:52 +03:00
Василий Толстов 80a2db264e fixup multiple client handling
lint / lint (pull_request) Failing after 1m29s Details
pr / test (pull_request) Failing after 2m35s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-11-13 08:19:44 +03:00
Василий Толстов 0be09c8b3e Merge pull request 'database: add FormatDSN' (#278) from database-newv3 into v3
Reviewed-on: #278
2023-11-02 01:35:25 +03:00
Василий Толстов 047f479e1b database: add FormatDSN
lint / lint (pull_request) Failing after 1m27s Details
pr / test (pull_request) Failing after 2m39s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-11-02 01:32:26 +03:00
Василий Толстов 8f757c953e Merge pull request 'database: initial import for dsn parsing' (#276) from databasev3 into v3
Reviewed-on: #276
2023-11-01 23:44:17 +03:00
Василий Толстов 5f1c673a24 database: initial import for dsn parsing
lint / lint (pull_request) Failing after 1m28s Details
pr / test (pull_request) Failing after 2m36s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-11-01 23:42:48 +03:00
Василий Толстов 6794ea9871 Merge pull request 'client/noop: fix MessageMetadata option' (#274) from client-noop-metadata into v3
Reviewed-on: #274
2023-10-26 03:07:12 +03:00
Василий Толстов 089e7b6812 client/noop: fix MessageMetadata option
lint / lint (pull_request) Successful in 1m18s Details
pr / test (pull_request) Successful in 1m1s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-26 03:06:39 +03:00
Василий Толстов 1c703f0f0c Merge pull request 'errors: add IsRetrayable func' (#273) from errors into v3
Reviewed-on: #273
2023-10-25 10:24:58 +03:00
Василий Толстов d167c8c67c cleanup
lint / lint (pull_request) Successful in 1m7s Details
pr / test (pull_request) Successful in 1m2s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-25 02:36:52 +03:00
Василий Толстов df4f96a2d8 errors: add IsRetrayable func
lint / lint (pull_request) Successful in 1m18s Details
pr / test (pull_request) Successful in 1m3s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-23 02:35:10 +03:00
Василий Толстов fac3b20bd4 Merge pull request 'util/reflect: add Equal func with ability to skip some fields' (#244) from util-reflect into v3
Reviewed-on: #244
2023-09-12 11:45:26 +03:00
Василий Толстов 7c6bd98498 util/reflect: add Equal func with ability to skip some fields
pr / test (pull_request) Successful in 1m4s Details
lint / lint (pull_request) Successful in 1m10s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-12 10:31:45 +03:00
Василий Толстов 23e1174f25 Merge pull request 'tracer: improve' (#241) from tracing into v3
Reviewed-on: #241
2023-09-08 13:40:51 +03:00
Василий Толстов 52bed214cf tracer: improve
lint / lint (pull_request) Failing after 1m31s Details
pr / test (pull_request) Failing after 2m44s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-08 13:40:01 +03:00
Василий Толстов 64c4f5f47e Merge pull request 'tracer: tweaks for span tags and naming' (#239) from tracing into v3
Reviewed-on: #239
2023-09-01 14:58:15 +03:00
Василий Толстов 036c612137 tracer: tweaks for span tags and naming
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-01 14:58:15 +03:00
Василий Толстов ca80e3ecf2 Merge pull request 'tracer: improve tracing info' (#238) from tracing into v3
Reviewed-on: #238
2023-09-01 08:41:46 +03:00
Василий Толстов 18e7bb41ca tracer: improve tracing info
lint / lint (pull_request) Failing after 1m29s Details
pr / test (pull_request) Failing after 2m37s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-01 08:41:23 +03:00
Василий Толстов 8e72fb1c35 Merge pull request 'add util/test' (#235) from util-test into v3
Reviewed-on: #235
2023-08-07 18:35:31 +03:00
Василий Толстов 17f21a03f4 add util/test
lint / lint (pull_request) Failing after 1m28s Details
pr / test (pull_request) Failing after 2m35s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-08-07 18:33:23 +03:00
Василий Толстов a076d43a26 add util/test
lint / lint (pull_request) Failing after 1m31s Details
pr / test (pull_request) Failing after 2m33s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-08-07 18:32:29 +03:00
Василий Толстов de6efaee0b Merge pull request 'config/default: add micro:generate uuid/id' (#232) from config-default-gen into v3
Reviewed-on: #232
2023-07-13 20:27:13 +03:00
Василий Толстов 9e0e657003 config/default: add micro:generate uuid/id
lint / lint (pull_request) Failing after 1m28s Details
pr / test (pull_request) Failing after 2m35s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-07-13 20:26:47 +03:00
Василий Толстов be5f9ab77f Merge pull request 'tracer: add Flush method' (#225) from traceimp into v3
Reviewed-on: #225
2023-07-04 00:26:33 +03:00
Василий Толстов 144dca0cae tracer: add Flush method
pr / test (pull_request) Failing after 2m42s Details
lint / lint (pull_request) Failing after 1m29s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-07-04 00:25:41 +03:00
Василий Толстов 75173560e3 Merge pull request 'util/time: ParseDuration fix' (#222) from timefix into v3
Reviewed-on: #222
2023-05-29 14:04:41 +03:00
Василий Толстов 9b3bccd1f1 util/time: ParseDuration fix
lint / lint (pull_request) Successful in 1m0s Details
pr / test (pull_request) Successful in 58s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-05-29 14:02:06 +03:00
Василий Толстов ce125b77c1 Merge pull request 'util/time: fix duration parsing' (#219) from timefeature into v3
Reviewed-on: #219
2023-05-27 23:55:51 +03:00
Василий Толстов 2ee8d4ed46 util/time: fix duration parsing
lint / lint (pull_request) Successful in 59s Details
pr / test (pull_request) Failing after 1m0s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-05-27 23:55:08 +03:00
Василий Толстов f58781d076 Merge pull request 'server/noop: fix graceful unsubscribe' (#218) from unsubfix into v3
Reviewed-on: #218
2023-05-25 23:19:26 +03:00
Василий Толстов e1af4aa3a4 server/noop: fix graceful unsubscribe
pr / test (pull_request) Successful in 1m2s Details
lint / lint (pull_request) Successful in 59s Details
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-05-25 23:18:47 +03:00
194 changed files with 8628 additions and 2916 deletions

View File

@ -1,3 +0,0 @@
branches:
- master
- v3

View File

@ -1,24 +0,0 @@
on:
push:
branches:
- 'main'
- 'master'
- 'v3'
schedule:
#- cron: '* * * * *'
- cron: '@hourly'
jobs:
autoupdate:
runs-on: ubuntu-latest
steps:
- name: setup-go
uses: https://gitea.com/actions/setup-go@v3
with:
go-version: 1.21
- name: checkout
uses: https://gitea.com/actions/checkout@v3
- name: get pkgdashcli
run: GOPROXY=direct GONOSUMDB="git.unistack.org/*" GONOPROXY="git.unistack.org/*" GOBIN=/bin go install git.unistack.org/unistack-org/pkgdash/cmd/pkgdashcli@latest
- name: pkgdashcli check
run: /bin/pkgdashcli check

View File

@ -1,30 +0,0 @@
name: Go
on:
push:
branches: [ master, v3 ]
pull_request:
branches: [ master, v3 ]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup
uses: actions/setup-go@v4
with:
go-version: stable
- name: coverage
run: go test -v -coverprofile coverage.out ./...
- name: badge
uses: ncruces/go-coverage-report@main
with:
coverage-file: coverage.out
reuse-go: true
amend: true

View File

@ -10,15 +10,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: setup-go
uses: https://gitea.com/actions/setup-go@v3
uses: actions/setup-go@v3
with:
go-version: 1.21
- name: checkout
uses: https://gitea.com/actions/checkout@v3
uses: actions/checkout@v3
- name: deps
run: go get -v -d ./...
- name: lint
uses: https://github.com/golangci/golangci-lint-action@v3.4.0
continue-on-error: true
with:
version: v1.52
version: v1.52

View File

@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: https://gitea.com/actions/checkout@v3
uses: actions/checkout@v3
- name: setup-go
uses: https://gitea.com/actions/setup-go@v3
uses: actions/setup-go@v3
with:
go-version: 1.21
- name: deps
@ -20,4 +20,4 @@ jobs:
- name: test
env:
INTEGRATION_TESTS: yes
run: go test -v -mod readonly -race -coverprofile=coverage.txt -covermode=atomic ./...
run: go test -mod readonly -v ./...

View File

@ -1,5 +1,6 @@
run:
concurrency: 4
deadline: 5m
issues-exit-code: 1
tests: true
@ -12,13 +13,15 @@ linters-settings:
linters:
enable:
- govet
- deadcode
- errcheck
- govet
- ineffassign
- staticcheck
- structcheck
- typecheck
- unused
- spancheck
- varcheck
- bodyclose
- gci
- goconst
@ -38,5 +41,4 @@ linters:
- prealloc
- unconvert
- unparam
- unused
disable-all: false

View File

@ -1,4 +1,4 @@
# Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/go.unistack.org/micro/v4?tab=overview) [![Status](https://github.com/unistack-org/micro/workflows/build/badge.svg?branch=master)](https://github.com/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Amaster+event%3Apush) [![Lint](https://goreportcard.com/badge/go.unistack.org/micro/v4)](https://goreportcard.com/report/go.unistack.org/micro/v4) [![Coverage](https://codecov.io/gh/unistack-org/micro/branch/v4/graph/badge.svg?token=OZPO2LP7VS)](https://codecov.io/gh/unistack-org/micro)
# Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [![Status](https://github.com/unistack-org/micro/workflows/build/badge.svg?branch=master)](https://github.com/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Amaster+event%3Apush) [![Lint](https://goreportcard.com/badge/go.unistack.org/micro/v3)](https://goreportcard.com/report/go.unistack.org/micro/v3) [![Coverage](https://codecov.io/gh/unistack-org/micro/branch/v3/graph/badge.svg?token=OZPO2LP7VS)](https://codecov.io/gh/unistack-org/micro)
Micro is a standard library for microservices.

View File

@ -1,13 +1,13 @@
// Package broker is an interface used for asynchronous messaging
package broker // import "go.unistack.org/micro/v4/broker"
package broker // import "go.unistack.org/micro/v3/broker"
import (
"context"
"errors"
"time"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata"
)
// DefaultBroker default memory broker
@ -18,8 +18,6 @@ var (
ErrNotConnected = errors.New("broker not connected")
// ErrDisconnected returns when broker disconnected
ErrDisconnected = errors.New("broker disconnected")
// ErrInvalidMessage returns when message has nvalid format
ErrInvalidMessage = errors.New("broker message has invalid format")
// DefaultGracefulTimeout
DefaultGracefulTimeout = 5 * time.Second
)
@ -29,7 +27,7 @@ type Broker interface {
// Name returns broker instance name
Name() string
// Init initilize broker
Init(opts ...options.Option) error
Init(opts ...Option) error
// Options returns broker options
Options() Options
// Address return configured address
@ -38,29 +36,85 @@ type Broker interface {
Connect(ctx context.Context) error
// Disconnect disconnect from broker
Disconnect(ctx context.Context) error
// Publish message, msg can be single broker.Message or []broker.Message
Publish(ctx context.Context, msg interface{}, opts ...options.Option) error
// Publish message to broker topic
Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error
// Subscribe subscribes to topic message via handler
Subscribe(ctx context.Context, topic string, handler interface{}, opts ...options.Option) (Subscriber, error)
Subscribe(ctx context.Context, topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)
// BatchPublish messages to broker with multiple topics
BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error
// BatchSubscribe subscribes to topic messages via handler
BatchSubscribe(ctx context.Context, topic string, h BatchHandler, opts ...SubscribeOption) (Subscriber, error)
// String type of broker
String() string
}
// Message is given to a subscription handler for processing
type Message interface {
// Context for the message
type (
FuncPublish func(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error
HookPublish func(next FuncPublish) FuncPublish
FuncBatchPublish func(ctx context.Context, msgs []*Message, opts ...PublishOption) error
HookBatchPublish func(next FuncBatchPublish) FuncBatchPublish
FuncSubscribe func(ctx context.Context, topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)
HookSubscribe func(next FuncSubscribe) FuncSubscribe
FuncBatchSubscribe func(ctx context.Context, topic string, h BatchHandler, opts ...SubscribeOption) (Subscriber, error)
HookBatchSubscribe func(next FuncBatchSubscribe) FuncBatchSubscribe
)
// Handler is used to process messages via a subscription of a topic.
type Handler func(Event) error
// Events contains multiple events
type Events []Event
// Ack try to ack all events and return
func (evs Events) Ack() error {
var err error
for _, ev := range evs {
if err = ev.Ack(); err != nil {
return err
}
}
return nil
}
// SetError sets error on event
func (evs Events) SetError(err error) {
for _, ev := range evs {
ev.SetError(err)
}
}
// BatchHandler is used to process messages in batches via a subscription of a topic.
type BatchHandler func(Events) error
// Event is given to a subscription handler for processing
type Event interface {
// Context return context.Context for event
Context() context.Context
// Topic
// Topic returns event topic
Topic() string
// Header returns message headers
Header() metadata.Metadata
// Body returns broker message may be []byte slice or some go struct
Body() interface{}
// Message returns broker message
Message() *Message
// Ack acknowledge message
Ack() error
// Error returns message error (like decoding errors or some other)
// In this case Body contains raw []byte from broker
Error() error
// SetError set event processing error
SetError(err error)
}
// Message is used to transfer data
type Message struct {
// Header contains message metadata
Header metadata.Metadata
// Body contains message body
Body codec.RawMessage
}
// NewMessage create broker message with topic filled
func NewMessage(topic string) *Message {
m := &Message{Header: metadata.New(2)}
m.Header.Set(metadata.HeaderTopic, topic)
return m
}
// Subscriber is a convenience return type for the Subscribe method
@ -72,9 +126,3 @@ type Subscriber interface {
// Unsubscribe from topic
Unsubscribe(ctx context.Context) error
}
// MessageHandler func signature for single message processing
type MessageHandler func(Message) error
// MessagesHandler func signature for batch message processing
type MessagesHandler func([]Message) error

View File

@ -22,3 +22,33 @@ func NewContext(ctx context.Context, s Broker) context.Context {
}
return context.WithValue(ctx, brokerKey{}, s)
}
// SetSubscribeOption returns a function to setup a context with given value
func SetSubscribeOption(k, v interface{}) SubscribeOption {
return func(o *SubscribeOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// SetPublishOption returns a function to setup a context with given value
func SetPublishOption(k, v interface{}) PublishOption {
return func(o *PublishOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@ -37,3 +37,36 @@ func TestNewNilContext(t *testing.T) {
t.Fatal("NewContext not works")
}
}
func TestSetSubscribeOption(t *testing.T) {
type key struct{}
o := SetSubscribeOption(key{}, "test")
opts := &SubscribeOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetSubscribeOption not works")
}
}
func TestSetPublishOption(t *testing.T) {
type key struct{}
o := SetPublishOption(key{}, "test")
opts := &PublishOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetPublishOption not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@ -1,334 +0,0 @@
package broker
import (
"context"
"fmt"
"sync"
"time"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/semconv"
maddr "go.unistack.org/micro/v4/util/addr"
"go.unistack.org/micro/v4/util/id"
mnet "go.unistack.org/micro/v4/util/net"
"go.unistack.org/micro/v4/util/rand"
)
type MemoryBroker struct {
subscribers map[string][]*memorySubscriber
addr string
opts Options
sync.RWMutex
connected bool
}
func (m *MemoryBroker) Options() Options {
return m.opts
}
func (m *MemoryBroker) Address() string {
return m.addr
}
func (m *MemoryBroker) Connect(ctx context.Context) error {
m.Lock()
defer m.Unlock()
if m.connected {
return nil
}
// use 127.0.0.1 to avoid scan of all network interfaces
addr, err := maddr.Extract("127.0.0.1")
if err != nil {
return err
}
var rng rand.Rand
i := rng.Intn(20000)
// set addr with port
addr = mnet.HostPort(addr, 10000+i)
m.addr = addr
m.connected = true
return nil
}
func (m *MemoryBroker) Disconnect(ctx context.Context) error {
m.Lock()
defer m.Unlock()
select {
case <-ctx.Done():
return ctx.Err()
default:
if m.connected {
m.connected = false
}
}
return nil
}
func (m *MemoryBroker) Init(opts ...options.Option) error {
var err error
for _, o := range opts {
if err = o(&m.opts); err != nil {
return err
}
}
return nil
}
func (m *MemoryBroker) Publish(ctx context.Context, message interface{}, opts ...options.Option) error {
m.RLock()
if !m.connected {
m.RUnlock()
return ErrNotConnected
}
m.RUnlock()
var err error
select {
case <-ctx.Done():
return ctx.Err()
default:
options := NewPublishOptions(opts...)
var msgs []Message
switch v := message.(type) {
case []Message:
msgs = v
case Message:
msgs = append(msgs, v)
default:
return ErrInvalidMessage
}
msgTopicMap := make(map[string][]*memoryMessage)
for _, msg := range msgs {
p := &memoryMessage{opts: options}
p.topic, _ = msg.Header().Get(metadata.HeaderTopic)
if v, ok := msg.Body().(*codec.Frame); ok {
p.body = msg.Body()
} else if len(m.opts.Codecs) == 0 {
p.body = msg.Body()
} else {
cf, ok := m.opts.Codecs[options.ContentType]
if !ok {
return fmt.Errorf("%s: %s", codec.ErrUnknownContentType, options.ContentType)
}
p.body, err = cf.Marshal(v)
if err != nil {
return err
}
}
msgTopicMap[p.topic] = append(msgTopicMap[p.topic], p)
}
eh := m.opts.ErrorHandler
for t, ms := range msgTopicMap {
ts := time.Now()
m.opts.Meter.Counter(semconv.PublishMessageInflight, "endpoint", t).Add(len(ms))
m.opts.Meter.Counter(semconv.SubscribeMessageInflight, "endpoint", t).Add(len(ms))
m.RLock()
subs, ok := m.subscribers[t]
m.RUnlock()
if !ok {
m.opts.Meter.Counter(semconv.PublishMessageTotal, "endpoint", t, "status", "failure").Add(len(ms))
m.opts.Meter.Counter(semconv.PublishMessageInflight, "endpoint", t).Add(-len(ms))
m.opts.Meter.Counter(semconv.SubscribeMessageInflight, "endpoint", t).Add(-len(ms))
continue
}
m.opts.Meter.Counter(semconv.PublishMessageTotal, "endpoint", t, "status", "success").Add(len(ms))
for _, sub := range subs {
if sub.opts.ErrorHandler != nil {
eh = sub.opts.ErrorHandler
}
switch mh := sub.handler.(type) {
case MessagesHandler:
mhs := make([]Message, 0, len(ms))
for _, m := range ms {
mhs = append(mhs, m)
}
if err = mh(mhs); err != nil {
m.opts.Meter.Counter(semconv.SubscribeMessageTotal, "endpoint", t, "status", "failure").Add(len(ms))
if eh != nil {
switch meh := eh.(type) {
case MessagesHandler:
_ = meh(mhs)
case MessageHandler:
for _, me := range mhs {
_ = meh(me)
}
}
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
}
case MessageHandler:
for _, p := range ms {
if err = mh(p); err != nil {
m.opts.Meter.Counter(semconv.SubscribeMessageTotal, "endpoint", t, "status", "failure").Inc()
if eh != nil {
switch meh := eh.(type) {
case MessageHandler:
_ = meh(p)
case MessagesHandler:
_ = meh([]Message{p})
}
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
} else {
if sub.opts.AutoAck {
if err = p.Ack(); err != nil {
m.opts.Logger.Error(m.opts.Context, "ack failed: "+err.Error())
m.opts.Meter.Counter(semconv.SubscribeMessageTotal, "endpoint", t, "status", "failure").Inc()
} else {
m.opts.Meter.Counter(semconv.SubscribeMessageTotal, "endpoint", t, "status", "success").Inc()
}
} else {
m.opts.Meter.Counter(semconv.SubscribeMessageTotal, "endpoint", t, "status", "success").Inc()
}
}
m.opts.Meter.Counter(semconv.PublishMessageInflight, "endpoint", t).Add(-1)
m.opts.Meter.Counter(semconv.SubscribeMessageInflight, "endpoint", t).Add(-1)
}
}
}
te := time.Since(ts)
m.opts.Meter.Summary(semconv.PublishMessageLatencyMicroseconds, "endpoint", t).Update(te.Seconds())
m.opts.Meter.Histogram(semconv.PublishMessageDurationSeconds, "endpoint", t).Update(te.Seconds())
m.opts.Meter.Summary(semconv.SubscribeMessageLatencyMicroseconds, "endpoint", t).Update(te.Seconds())
m.opts.Meter.Histogram(semconv.SubscribeMessageDurationSeconds, "endpoint", t).Update(te.Seconds())
}
}
return nil
}
func (m *MemoryBroker) Subscribe(ctx context.Context, topic string, handler interface{}, opts ...options.Option) (Subscriber, error) {
m.RLock()
if !m.connected {
m.RUnlock()
return nil, ErrNotConnected
}
m.RUnlock()
sid, err := id.New()
if err != nil {
return nil, err
}
options := NewSubscribeOptions(opts...)
sub := &memorySubscriber{
exit: make(chan bool, 1),
id: sid,
topic: topic,
handler: handler,
opts: options,
ctx: ctx,
}
m.Lock()
m.subscribers[topic] = append(m.subscribers[topic], sub)
m.Unlock()
go func() {
<-sub.exit
m.Lock()
newSubscribers := make([]*memorySubscriber, 0, len(m.subscribers)-1)
for _, sb := range m.subscribers[topic] {
if sb.id == sub.id {
continue
}
newSubscribers = append(newSubscribers, sb)
}
m.subscribers[topic] = newSubscribers
m.Unlock()
}()
return sub, nil
}
func (m *MemoryBroker) String() string {
return "memory"
}
func (m *MemoryBroker) Name() string {
return m.opts.Name
}
type memoryMessage struct {
err error
body interface{}
topic string
header metadata.Metadata
opts PublishOptions
ctx context.Context
}
func (m *memoryMessage) Topic() string {
return m.topic
}
func (m *memoryMessage) Header() metadata.Metadata {
return m.header
}
func (m *memoryMessage) Body() interface{} {
return m.body
}
func (m *memoryMessage) Ack() error {
return nil
}
func (m *memoryMessage) Error() error {
return m.err
}
func (m *memoryMessage) Context() context.Context {
return m.ctx
}
type memorySubscriber struct {
ctx context.Context
exit chan bool
handler interface{}
id string
topic string
opts SubscribeOptions
}
func (m *memorySubscriber) Options() SubscribeOptions {
return m.opts
}
func (m *memorySubscriber) Topic() string {
return m.topic
}
func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
m.exit <- true
return nil
}
// NewBroker return new memory broker
func NewBroker(opts ...options.Option) *MemoryBroker {
return &MemoryBroker{
opts: NewOptions(opts...),
subscribers: make(map[string][]*memorySubscriber),
}
}

395
broker/memory/memory.go Normal file
View File

@ -0,0 +1,395 @@
package broker
import (
"context"
"sync"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/options"
maddr "go.unistack.org/micro/v3/util/addr"
"go.unistack.org/micro/v3/util/id"
mnet "go.unistack.org/micro/v3/util/net"
"go.unistack.org/micro/v3/util/rand"
)
type memoryBroker struct {
funcPublish broker.FuncPublish
funcBatchPublish broker.FuncBatchPublish
funcSubscribe broker.FuncSubscribe
funcBatchSubscribe broker.FuncBatchSubscribe
subscribers map[string][]*memorySubscriber
addr string
opts broker.Options
sync.RWMutex
connected bool
}
type memoryEvent struct {
err error
message interface{}
topic string
opts broker.Options
}
type memorySubscriber struct {
ctx context.Context
exit chan bool
handler broker.Handler
batchhandler broker.BatchHandler
id string
topic string
opts broker.SubscribeOptions
}
func (m *memoryBroker) Options() broker.Options {
return m.opts
}
func (m *memoryBroker) Address() string {
return m.addr
}
func (m *memoryBroker) Connect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
m.Lock()
defer m.Unlock()
if m.connected {
return nil
}
// use 127.0.0.1 to avoid scan of all network interfaces
addr, err := maddr.Extract("127.0.0.1")
if err != nil {
return err
}
var rng rand.Rand
i := rng.Intn(20000)
// set addr with port
addr = mnet.HostPort(addr, 10000+i)
m.addr = addr
m.connected = true
return nil
}
func (m *memoryBroker) Disconnect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
m.Lock()
defer m.Unlock()
if !m.connected {
return nil
}
m.connected = false
return nil
}
func (m *memoryBroker) Init(opts ...broker.Option) error {
for _, o := range opts {
o(&m.opts)
}
m.funcPublish = m.fnPublish
m.funcBatchPublish = m.fnBatchPublish
m.funcSubscribe = m.fnSubscribe
m.funcBatchSubscribe = m.fnBatchSubscribe
m.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case broker.HookPublish:
m.funcPublish = h(m.funcPublish)
case broker.HookBatchPublish:
m.funcBatchPublish = h(m.funcBatchPublish)
case broker.HookSubscribe:
m.funcSubscribe = h(m.funcSubscribe)
case broker.HookBatchSubscribe:
m.funcBatchSubscribe = h(m.funcBatchSubscribe)
}
})
return nil
}
func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *broker.Message, opts ...broker.PublishOption) error {
return m.funcPublish(ctx, topic, msg, opts...)
}
func (m *memoryBroker) fnPublish(ctx context.Context, topic string, msg *broker.Message, opts ...broker.PublishOption) error {
msg.Header.Set(metadata.HeaderTopic, topic)
return m.publish(ctx, []*broker.Message{msg}, opts...)
}
func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
return m.funcBatchPublish(ctx, msgs, opts...)
}
func (m *memoryBroker) fnBatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
return m.publish(ctx, msgs, opts...)
}
func (m *memoryBroker) publish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
m.RLock()
if !m.connected {
m.RUnlock()
return broker.ErrNotConnected
}
m.RUnlock()
var err error
select {
case <-ctx.Done():
return ctx.Err()
default:
options := broker.NewPublishOptions(opts...)
msgTopicMap := make(map[string]broker.Events)
for _, v := range msgs {
p := &memoryEvent{opts: m.opts}
if m.opts.Codec == nil || options.BodyOnly {
p.topic, _ = v.Header.Get(metadata.HeaderTopic)
p.message = v.Body
} else {
p.topic, _ = v.Header.Get(metadata.HeaderTopic)
p.message, err = m.opts.Codec.Marshal(v)
if err != nil {
return err
}
}
msgTopicMap[p.topic] = append(msgTopicMap[p.topic], p)
}
beh := m.opts.BatchErrorHandler
eh := m.opts.ErrorHandler
for t, ms := range msgTopicMap {
m.RLock()
subs, ok := m.subscribers[t]
m.RUnlock()
if !ok {
continue
}
for _, sub := range subs {
if sub.opts.BatchErrorHandler != nil {
beh = sub.opts.BatchErrorHandler
}
if sub.opts.ErrorHandler != nil {
eh = sub.opts.ErrorHandler
}
switch {
// batch processing
case sub.batchhandler != nil:
if err = sub.batchhandler(ms); err != nil {
ms.SetError(err)
if beh != nil {
_ = beh(ms)
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
} else if sub.opts.AutoAck {
if err = ms.Ack(); err != nil {
m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
}
}
// single processing
case sub.handler != nil:
for _, p := range ms {
if err = sub.handler(p); err != nil {
p.SetError(err)
if eh != nil {
_ = eh(p)
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
} else if sub.opts.AutoAck {
if err = p.Ack(); err != nil {
m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
}
}
}
}
}
}
}
return nil
}
func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return m.funcBatchSubscribe(ctx, topic, handler, opts...)
}
func (m *memoryBroker) fnBatchSubscribe(ctx context.Context, topic string, handler broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
m.RLock()
if !m.connected {
m.RUnlock()
return nil, broker.ErrNotConnected
}
m.RUnlock()
sid, err := id.New()
if err != nil {
return nil, err
}
options := broker.NewSubscribeOptions(opts...)
sub := &memorySubscriber{
exit: make(chan bool, 1),
id: sid,
topic: topic,
batchhandler: handler,
opts: options,
ctx: ctx,
}
m.Lock()
m.subscribers[topic] = append(m.subscribers[topic], sub)
m.Unlock()
go func() {
<-sub.exit
m.Lock()
newSubscribers := make([]*memorySubscriber, 0, len(m.subscribers)-1)
for _, sb := range m.subscribers[topic] {
if sb.id == sub.id {
continue
}
newSubscribers = append(newSubscribers, sb)
}
m.subscribers[topic] = newSubscribers
m.Unlock()
}()
return sub, nil
}
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return m.funcSubscribe(ctx, topic, handler, opts...)
}
func (m *memoryBroker) fnSubscribe(ctx context.Context, topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
m.RLock()
if !m.connected {
m.RUnlock()
return nil, broker.ErrNotConnected
}
m.RUnlock()
sid, err := id.New()
if err != nil {
return nil, err
}
options := broker.NewSubscribeOptions(opts...)
sub := &memorySubscriber{
exit: make(chan bool, 1),
id: sid,
topic: topic,
handler: handler,
opts: options,
ctx: ctx,
}
m.Lock()
m.subscribers[topic] = append(m.subscribers[topic], sub)
m.Unlock()
go func() {
<-sub.exit
m.Lock()
newSubscribers := make([]*memorySubscriber, 0, len(m.subscribers)-1)
for _, sb := range m.subscribers[topic] {
if sb.id == sub.id {
continue
}
newSubscribers = append(newSubscribers, sb)
}
m.subscribers[topic] = newSubscribers
m.Unlock()
}()
return sub, nil
}
func (m *memoryBroker) String() string {
return "memory"
}
func (m *memoryBroker) Name() string {
return m.opts.Name
}
func (m *memoryEvent) Topic() string {
return m.topic
}
func (m *memoryEvent) Message() *broker.Message {
switch v := m.message.(type) {
case *broker.Message:
return v
case []byte:
msg := &broker.Message{}
if err := m.opts.Codec.Unmarshal(v, msg); err != nil {
if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, "[memory]: failed to unmarshal: %v", err)
}
return nil
}
return msg
}
return nil
}
func (m *memoryEvent) Ack() error {
return nil
}
func (m *memoryEvent) Error() error {
return m.err
}
func (m *memoryEvent) SetError(err error) {
m.err = err
}
func (m *memorySubscriber) Options() broker.SubscribeOptions {
return m.opts
}
func (m *memorySubscriber) Topic() string {
return m.topic
}
func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
m.exit <- true
return nil
}
// NewBroker return new memory broker
func NewBroker(opts ...broker.Option) broker.Broker {
return &memoryBroker{
opts: broker.NewOptions(opts...),
subscribers: make(map[string][]*memorySubscriber),
}
}

View File

@ -5,13 +5,18 @@ import (
"fmt"
"testing"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/metadata"
)
func TestMemoryBatchBroker(t *testing.T) {
b := NewBroker()
ctx := context.Background()
if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error %v", err)
}
if err := b.Connect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err)
}
@ -19,35 +24,29 @@ func TestMemoryBatchBroker(t *testing.T) {
topic := "test"
count := 10
fn := func(evts []Message) error {
var err error
for _, evt := range evts {
if err = evt.Ack(); err != nil {
return err
}
}
return nil
fn := func(evts broker.Events) error {
return evts.Ack()
}
sub, err := b.Subscribe(ctx, topic, fn)
sub, err := b.BatchSubscribe(ctx, topic, fn)
if err != nil {
t.Fatalf("Unexpected error subscribing %v", err)
}
msgs := make([]Message, 0, count)
msgs := make([]*broker.Message, 0, count)
for i := 0; i < count; i++ {
message := &memoryMessage{
header: metadata.Metadata{
metadata.HeaderTopic: []string{topic},
"foo": []string{"bar"},
"id": []string{fmt.Sprintf("%d", i)},
message := &broker.Message{
Header: map[string]string{
metadata.HeaderTopic: topic,
"foo": "bar",
"id": fmt.Sprintf("%d", i),
},
body: []byte(`"hello world"`),
Body: []byte(`"hello world"`),
}
msgs = append(msgs, message)
}
if err := b.Publish(ctx, msgs); err != nil {
if err := b.BatchPublish(ctx, msgs); err != nil {
t.Fatalf("Unexpected error publishing %v", err)
}
@ -64,6 +63,10 @@ func TestMemoryBroker(t *testing.T) {
b := NewBroker()
ctx := context.Background()
if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error %v", err)
}
if err := b.Connect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err)
}
@ -71,8 +74,8 @@ func TestMemoryBroker(t *testing.T) {
topic := "test"
count := 10
fn := func(p Message) error {
return p.Ack()
fn := func(p broker.Event) error {
return nil
}
sub, err := b.Subscribe(ctx, topic, fn)
@ -80,20 +83,24 @@ func TestMemoryBroker(t *testing.T) {
t.Fatalf("Unexpected error subscribing %v", err)
}
msgs := make([]Message, 0, count)
msgs := make([]*broker.Message, 0, count)
for i := 0; i < count; i++ {
message := &memoryMessage{
header: metadata.Metadata{
metadata.HeaderTopic: []string{topic},
"foo": []string{"bar"},
"id": []string{fmt.Sprintf("%d", i)},
message := &broker.Message{
Header: map[string]string{
metadata.HeaderTopic: topic,
"foo": "bar",
"id": fmt.Sprintf("%d", i),
},
body: []byte(`"hello world"`),
Body: []byte(`"hello world"`),
}
msgs = append(msgs, message)
if err := b.Publish(ctx, topic, message); err != nil {
t.Fatalf("Unexpected error publishing %d err: %v", i, err)
}
}
if err := b.Publish(ctx, msgs); err != nil {
if err := b.BatchPublish(ctx, msgs); err != nil {
t.Fatalf("Unexpected error publishing %v", err)
}

128
broker/noop.go Normal file
View File

@ -0,0 +1,128 @@
package broker
import (
"context"
"strings"
"go.unistack.org/micro/v3/options"
)
type NoopBroker struct {
funcPublish FuncPublish
funcBatchPublish FuncBatchPublish
funcSubscribe FuncSubscribe
funcBatchSubscribe FuncBatchSubscribe
opts Options
}
func NewBroker(opts ...Option) *NoopBroker {
b := &NoopBroker{opts: NewOptions(opts...)}
b.funcPublish = b.fnPublish
b.funcBatchPublish = b.fnBatchPublish
b.funcSubscribe = b.fnSubscribe
b.funcBatchSubscribe = b.fnBatchSubscribe
return b
}
func (b *NoopBroker) Name() string {
return b.opts.Name
}
func (b *NoopBroker) String() string {
return "noop"
}
func (b *NoopBroker) Options() Options {
return b.opts
}
func (b *NoopBroker) Init(opts ...Option) error {
for _, opt := range opts {
opt(&b.opts)
}
b.funcPublish = b.fnPublish
b.funcBatchPublish = b.fnBatchPublish
b.funcSubscribe = b.fnSubscribe
b.funcBatchSubscribe = b.fnBatchSubscribe
b.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookPublish:
b.funcPublish = h(b.funcPublish)
case HookBatchPublish:
b.funcBatchPublish = h(b.funcBatchPublish)
case HookSubscribe:
b.funcSubscribe = h(b.funcSubscribe)
case HookBatchSubscribe:
b.funcBatchSubscribe = h(b.funcBatchSubscribe)
}
})
return nil
}
func (b *NoopBroker) Connect(_ context.Context) error {
return nil
}
func (b *NoopBroker) Disconnect(_ context.Context) error {
return nil
}
func (b *NoopBroker) Address() string {
return strings.Join(b.opts.Addrs, ",")
}
func (b *NoopBroker) fnBatchPublish(_ context.Context, _ []*Message, _ ...PublishOption) error {
return nil
}
func (b *NoopBroker) BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
return b.funcBatchPublish(ctx, msgs, opts...)
}
func (b *NoopBroker) fnPublish(_ context.Context, _ string, _ *Message, _ ...PublishOption) error {
return nil
}
func (b *NoopBroker) Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error {
return b.funcPublish(ctx, topic, msg, opts...)
}
type NoopSubscriber struct {
ctx context.Context
topic string
handler Handler
batchHandler BatchHandler
opts SubscribeOptions
}
func (b *NoopBroker) fnBatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) {
return &NoopSubscriber{ctx: ctx, topic: topic, opts: NewSubscribeOptions(opts...), batchHandler: handler}, nil
}
func (b *NoopBroker) BatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) {
return b.funcBatchSubscribe(ctx, topic, handler, opts...)
}
func (b *NoopBroker) fnSubscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
return &NoopSubscriber{ctx: ctx, topic: topic, opts: NewSubscribeOptions(opts...), handler: handler}, nil
}
func (b *NoopBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
return b.funcSubscribe(ctx, topic, handler, opts...)
}
func (s *NoopSubscriber) Options() SubscribeOptions {
return s.opts
}
func (s *NoopSubscriber) Topic() string {
return s.topic
}
func (s *NoopSubscriber) Unsubscribe(_ context.Context) error {
return nil
}

35
broker/noop_test.go Normal file
View File

@ -0,0 +1,35 @@
package broker
import (
"context"
"testing"
)
type testHook struct {
f bool
}
func (t *testHook) Publish1(fn FuncPublish) FuncPublish {
return func(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error {
t.f = true
return fn(ctx, topic, msg, opts...)
}
}
func TestNoopHook(t *testing.T) {
h := &testHook{}
b := NewBroker(Hooks(HookPublish(h.Publish1)))
if err := b.Init(); err != nil {
t.Fatal(err)
}
if err := b.Publish(context.TODO(), "", nil); err != nil {
t.Fatal(err)
}
if !h.f {
t.Fatal("hook not works")
}
}

View File

@ -5,14 +5,13 @@ import (
"crypto/tls"
"time"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/register"
"go.unistack.org/micro/v4/sync"
"go.unistack.org/micro/v4/tracer"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/sync"
"go.unistack.org/micro/v3/tracer"
)
// Options struct
@ -21,8 +20,8 @@ type Options struct {
Tracer tracer.Tracer
// Register can be used for clustering
Register register.Register
// Codecs holds the codec for marshal/unmarshal
Codecs map[string]codec.Codec
// Codec holds the codec for marshal/unmarshal
Codec codec.Codec
// Logger used for logging
Logger logger.Logger
// Meter used for metrics
@ -31,51 +30,57 @@ type Options struct {
Context context.Context
// TLSConfig holds tls.TLSConfig options
TLSConfig *tls.Config
// ErrorHandler used when broker have error while processing message
ErrorHandler interface{}
// ErrorHandler used when broker can't unmarshal incoming message
ErrorHandler Handler
// BatchErrorHandler used when broker can't unmashal incoming messages
BatchErrorHandler BatchHandler
// Name holds the broker name
Name string
// Address holds the broker address
Address []string
// Addrs holds the broker address
Addrs []string
// Wait waits for a collection of goroutines to finish
Wait *sync.WaitGroup
// GracefulTimeout contains time to wait to finish in flight requests
GracefulTimeout time.Duration
// Hooks can be run before broker Publish/BatchPublish and
// Subscribe/BatchSubscribe methods
Hooks options.Hooks
}
// NewOptions create new Options
func NewOptions(opts ...options.Option) Options {
newOpts := Options{
func NewOptions(opts ...Option) Options {
options := Options{
Register: register.DefaultRegister,
Logger: logger.DefaultLogger,
Context: context.Background(),
Meter: meter.DefaultMeter,
Codecs: make(map[string]codec.Codec),
Codec: codec.DefaultCodec,
Tracer: tracer.DefaultTracer,
GracefulTimeout: DefaultGracefulTimeout,
}
for _, o := range opts {
o(&newOpts)
o(&options)
}
return options
}
// Context sets the context option
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
return newOpts
}
// PublishOptions struct
type PublishOptions struct {
// Context holds external options
Context context.Context
// Message metadata usually passed as message headers
Metadata metadata.Metadata
// Content-Type of message for marshal
ContentType string
// Topic destination
Topic string
// BodyOnly flag says the message contains raw body bytes
BodyOnly bool
}
// NewPublishOptions creates PublishOptions struct
func NewPublishOptions(opts ...options.Option) PublishOptions {
func NewPublishOptions(opts ...PublishOption) PublishOptions {
options := PublishOptions{
Context: context.Background(),
}
@ -85,21 +90,16 @@ func NewPublishOptions(opts ...options.Option) PublishOptions {
return options
}
// PublishTopic pass topic for messages
func PublishTopic(t string) options.Option {
return func(src interface{}) error {
return options.Set(src, t, ".Topic")
}
}
// SubscribeOptions struct
type SubscribeOptions struct {
// Context holds external options
Context context.Context
// ErrorHandler used when broker have error while processing message
ErrorHandler interface{}
// QueueGroup holds consumer group
QueueGroup string
// ErrorHandler used when broker can't unmarshal incoming message
ErrorHandler Handler
// BatchErrorHandler used when broker can't unmashal incoming messages
BatchErrorHandler BatchHandler
// Group holds consumer group
Group string
// AutoAck flag specifies auto ack of incoming message when no error happens
AutoAck bool
// BodyOnly flag specifies that message contains only body bytes without header
@ -110,16 +110,186 @@ type SubscribeOptions struct {
BatchWait time.Duration
}
// ErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func ErrorHandler(h interface{}) options.Option {
return func(src interface{}) error {
return options.Set(src, h, ".ErrorHandler")
// Option func
type Option func(*Options)
// PublishOption func
type PublishOption func(*PublishOptions)
// PublishBodyOnly publish only body of the message
func PublishBodyOnly(b bool) PublishOption {
return func(o *PublishOptions) {
o.BodyOnly = b
}
}
// PublishContext sets the context
func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) {
o.Context = ctx
}
}
// Addrs sets the host addresses to be used by the broker
func Addrs(addrs ...string) Option {
return func(o *Options) {
o.Addrs = addrs
}
}
// Codec sets the codec used for encoding/decoding used where
// a broker does not support headers
func Codec(c codec.Codec) Option {
return func(o *Options) {
o.Codec = c
}
}
// ErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func ErrorHandler(h Handler) Option {
return func(o *Options) {
o.ErrorHandler = h
}
}
// BatchErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func BatchErrorHandler(h BatchHandler) Option {
return func(o *Options) {
o.BatchErrorHandler = h
}
}
// SubscribeErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func SubscribeErrorHandler(h Handler) SubscribeOption {
return func(o *SubscribeOptions) {
o.ErrorHandler = h
}
}
// SubscribeBatchErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func SubscribeBatchErrorHandler(h BatchHandler) SubscribeOption {
return func(o *SubscribeOptions) {
o.BatchErrorHandler = h
}
}
// Queue sets the subscribers queue
// Deprecated
func Queue(name string) SubscribeOption {
return func(o *SubscribeOptions) {
o.Group = name
}
}
// SubscribeGroup sets the name of the queue to share messages on
func SubscribeGroup(name string) SubscribeOption {
return func(o *SubscribeOptions) {
o.Group = name
}
}
// Register sets register option
func Register(r register.Register) Option {
return func(o *Options) {
o.Register = r
}
}
// TLSConfig sets the TLS Config
func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
o.TLSConfig = t
}
}
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Tracer to be used for tracing
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Meter sets the meter
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Name sets the name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}
// SubscribeContext set context
func SubscribeContext(ctx context.Context) SubscribeOption {
return func(o *SubscribeOptions) {
o.Context = ctx
}
}
// DisableAutoAck disables auto ack
// Deprecated
func DisableAutoAck() SubscribeOption {
return func(o *SubscribeOptions) {
o.AutoAck = false
}
}
// SubscribeAutoAck contol auto acking of messages
// after they have been handled.
func SubscribeAutoAck(b bool) SubscribeOption {
return func(o *SubscribeOptions) {
o.AutoAck = b
}
}
// SubscribeBodyOnly consumes only body of the message
func SubscribeBodyOnly(b bool) SubscribeOption {
return func(o *SubscribeOptions) {
o.BodyOnly = b
}
}
// SubscribeBatchSize specifies max batch size
func SubscribeBatchSize(n int) SubscribeOption {
return func(o *SubscribeOptions) {
o.BatchSize = n
}
}
// SubscribeBatchWait specifies max batch wait time
func SubscribeBatchWait(td time.Duration) SubscribeOption {
return func(o *SubscribeOptions) {
o.BatchWait = td
}
}
// SubscribeOption func
type SubscribeOption func(*SubscribeOptions)
// NewSubscribeOptions creates new SubscribeOptions
func NewSubscribeOptions(opts ...options.Option) SubscribeOptions {
func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
options := SubscribeOptions{
AutoAck: true,
Context: context.Background(),
@ -129,39 +299,3 @@ func NewSubscribeOptions(opts ...options.Option) SubscribeOptions {
}
return options
}
// SubscribeAutoAck contol auto acking of messages
// after they have been handled.
func SubscribeAutoAck(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".AutoAck")
}
}
// BodyOnly transfer only body without
func BodyOnly(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".BodyOnly")
}
}
// SubscribeBatchSize specifies max batch size
func SubscribeBatchSize(n int) options.Option {
return func(src interface{}) error {
return options.Set(src, n, ".BatchSize")
}
}
// SubscribeBatchWait specifies max batch wait time
func SubscribeBatchWait(td time.Duration) options.Option {
return func(src interface{}) error {
return options.Set(src, td, ".BatchWait")
}
}
// SubscribeQueueGroup sets the shared queue name distributed messages across subscribers
func SubscribeQueueGroup(n string) options.Option {
return func(src interface{}) error {
return options.Set(src, n, ".QueueGroup")
}
}

View File

@ -5,7 +5,7 @@ import (
"math"
"time"
"go.unistack.org/micro/v4/util/backoff"
"go.unistack.org/micro/v3/util/backoff"
)
// BackoffFunc is the backoff call func

View File

@ -1,12 +1,12 @@
// Package client is an interface for an RPC client
package client // import "go.unistack.org/micro/v4/client"
package client // import "go.unistack.org/micro/v3/client"
import (
"context"
"time"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata"
)
var (
@ -22,8 +22,6 @@ var (
DefaultRetries = 0
// DefaultRequestTimeout is the default request timeout
DefaultRequestTimeout = time.Second * 5
// DefaultDialTimeout the default dial timeout
DefaultDialTimeout = time.Second * 5
// DefaultPoolSize sets the connection pool size
DefaultPoolSize = 100
// DefaultPoolTTL sets the connection pool ttl
@ -35,14 +33,36 @@ var (
// It also supports bidirectional streaming of requests.
type Client interface {
Name() string
Init(opts ...options.Option) error
Init(opts ...Option) error
Options() Options
NewRequest(service string, endpoint string, req interface{}, opts ...options.Option) Request
Call(ctx context.Context, req Request, rsp interface{}, opts ...options.Option) error
Stream(ctx context.Context, req Request, opts ...options.Option) (Stream, error)
NewMessage(topic string, msg interface{}, opts ...MessageOption) Message
NewRequest(service string, endpoint string, req interface{}, opts ...RequestOption) Request
Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
Publish(ctx context.Context, msg Message, opts ...PublishOption) error
BatchPublish(ctx context.Context, msg []Message, opts ...PublishOption) error
String() string
}
type (
FuncCall func(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
HookCall func(next FuncCall) FuncCall
FuncStream func(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
HookStream func(next FuncStream) FuncStream
FuncPublish func(ctx context.Context, msg Message, opts ...PublishOption) error
HookPublish func(next FuncPublish) FuncPublish
FuncBatchPublish func(ctx context.Context, msg []Message, opts ...PublishOption) error
HookBatchPublish func(next FuncBatchPublish) FuncBatchPublish
)
// Message is the interface for publishing asynchronously
type Message interface {
Topic() string
Payload() interface{}
ContentType() string
Metadata() metadata.Metadata
}
// Request is the interface for a synchronous request used by Call or Stream
type Request interface {
// The service to call
@ -59,22 +79,16 @@ type Request interface {
Codec() codec.Codec
// indicates whether the request will be a streaming one rather than unary
Stream() bool
// Header data
// Header() metadata.Metadata
}
// Response is the response received from a service
type Response interface {
// Read the response
Codec() codec.Codec
// The content type
// ContentType() string
// Header data
// Header() metadata.Metadata
Header() metadata.Metadata
// Read the undecoded response
Read() ([]byte, error)
// The unencoded request body
// Body() interface{}
}
// Stream is the interface for a bidirectional synchronous stream
@ -100,3 +114,18 @@ type Stream interface {
// CloseSend closes the send direction of the stream
CloseSend() error
}
// Option used by the Client
type Option func(*Options)
// CallOption used by Call or Stream
type CallOption func(*CallOptions)
// PublishOption used by Publish
type PublishOption func(*PublishOptions)
// MessageOption used by NewMessage
type MessageOption func(*MessageOptions)
// RequestOption used by NewRequest
type RequestOption func(*RequestOptions)

View File

@ -2,24 +2,22 @@ package client
import (
"context"
"go.unistack.org/micro/v4/options"
)
type clientCallOptions struct {
Client
opts []options.Option
opts []CallOption
}
func (s *clientCallOptions) Call(ctx context.Context, req Request, rsp interface{}, opts ...options.Option) error {
func (s *clientCallOptions) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error {
return s.Client.Call(ctx, req, rsp, append(s.opts, opts...)...)
}
func (s *clientCallOptions) Stream(ctx context.Context, req Request, opts ...options.Option) (Stream, error) {
func (s *clientCallOptions) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) {
return s.Client.Stream(ctx, req, append(s.opts, opts...)...)
}
// NewClientCallOptions add CallOption to every call
func NewClientCallOptions(c Client, opts ...options.Option) Client {
func NewClientCallOptions(c Client, opts ...CallOption) Client {
return &clientCallOptions{c, opts}
}

View File

@ -1,28 +0,0 @@
package client
import (
"context"
"testing"
"time"
"go.unistack.org/micro/v4/options"
)
func TestNewClientCallOptions(t *testing.T) {
var flag bool
w := func(fn CallFunc) CallFunc {
flag = true
return fn
}
c := NewClientCallOptions(NewClient(),
options.Address("127.0.0.1"),
WithCallWrapper(w),
RequestTimeout(1*time.Millisecond),
Retries(0),
Backoff(BackoffInterval(10*time.Millisecond, 100*time.Millisecond)),
)
_ = c.Call(context.TODO(), c.NewRequest("service", "endpoint", nil), nil)
if !flag {
t.Fatalf("NewClientCallOptions not works")
}
}

View File

@ -22,3 +22,33 @@ func NewContext(ctx context.Context, c Client) context.Context {
}
return context.WithValue(ctx, clientKey{}, c)
}
// SetPublishOption returns a function to setup a context with given value
func SetPublishOption(k, v interface{}) PublishOption {
return func(o *PublishOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// SetCallOption returns a function to setup a context with given value
func SetCallOption(k, v interface{}) CallOption {
return func(o *CallOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@ -38,3 +38,36 @@ func TestNewNilContext(t *testing.T) {
t.Fatal("NewContext not works")
}
}
func TestSetPublishOption(t *testing.T) {
type key struct{}
o := SetPublishOption(key{}, "test")
opts := &PublishOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetPublishOption not works")
}
}
func TestSetCallOption(t *testing.T) {
type key struct{}
o := SetCallOption(key{}, "test")
opts := &CallOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetCallOption not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@ -4,8 +4,8 @@ import (
"context"
"sort"
"go.unistack.org/micro/v4/errors"
"go.unistack.org/micro/v4/router"
"go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/router"
)
// LookupFunc is used to lookup routes for a service

View File

@ -3,14 +3,18 @@ package client
import (
"context"
"fmt"
"os"
"strconv"
"time"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/errors"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/selector"
"go.unistack.org/micro/v4/semconv"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/selector"
"go.unistack.org/micro/v3/semconv"
"go.unistack.org/micro/v3/tracer"
)
// DefaultCodecs will be used to encode/decode data
@ -19,7 +23,17 @@ var DefaultCodecs = map[string]codec.Codec{
}
type noopClient struct {
opts Options
funcPublish FuncPublish
funcBatchPublish FuncBatchPublish
funcCall FuncCall
funcStream FuncStream
opts Options
}
type noopMessage struct {
topic string
payload interface{}
opts MessageOptions
}
type noopRequest struct {
@ -33,13 +47,15 @@ type noopRequest struct {
}
// NewClient returns new noop client
func NewClient(opts ...options.Option) Client {
nc := &noopClient{opts: NewOptions(opts...)}
// wrap in reverse
func NewClient(opts ...Option) Client {
n := &noopClient{opts: NewOptions(opts...)}
c := Client(nc)
n.funcCall = n.fnCall
n.funcStream = n.fnStream
n.funcPublish = n.fnPublish
n.funcBatchPublish = n.fnBatchPublish
return c
return n
}
func (n *noopClient) Name() string {
@ -91,10 +107,13 @@ func (n *noopResponse) Read() ([]byte, error) {
return nil, nil
}
type noopStream struct{}
type noopStream struct {
err error
ctx context.Context
}
func (n *noopStream) Context() context.Context {
return context.Background()
return n.ctx
}
func (n *noopStream) Request() Request {
@ -122,15 +141,37 @@ func (n *noopStream) RecvMsg(interface{}) error {
}
func (n *noopStream) Error() error {
return nil
return n.err
}
func (n *noopStream) Close() error {
return nil
if sp, ok := tracer.SpanFromContext(n.ctx); ok && sp != nil {
if n.err != nil {
sp.SetStatus(tracer.SpanStatusError, n.err.Error())
}
sp.Finish()
}
return n.err
}
func (n *noopStream) CloseSend() error {
return nil
return n.err
}
func (n *noopMessage) Topic() string {
return n.topic
}
func (n *noopMessage) Payload() interface{} {
return n.payload
}
func (n *noopMessage) ContentType() string {
return n.opts.ContentType
}
func (n *noopMessage) Metadata() metadata.Metadata {
return n.opts.Metadata
}
func (n *noopClient) newCodec(contentType string) (codec.Codec, error) {
@ -143,10 +184,29 @@ func (n *noopClient) newCodec(contentType string) (codec.Codec, error) {
return nil, codec.ErrUnknownContentType
}
func (n *noopClient) Init(opts ...options.Option) error {
func (n *noopClient) Init(opts ...Option) error {
for _, o := range opts {
o(&n.opts)
}
n.funcCall = n.fnCall
n.funcStream = n.fnStream
n.funcPublish = n.fnPublish
n.funcBatchPublish = n.fnBatchPublish
n.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookCall:
n.funcCall = h(n.funcCall)
case HookStream:
n.funcStream = h(n.funcStream)
case HookPublish:
n.funcPublish = h(n.funcPublish)
case HookBatchPublish:
n.funcBatchPublish = h(n.funcBatchPublish)
}
})
return nil
}
@ -158,7 +218,32 @@ func (n *noopClient) String() string {
return "noop"
}
func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...options.Option) error {
func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error {
ts := time.Now()
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Inc()
var sp tracer.Span
ctx, sp = n.opts.Tracer.Start(ctx, req.Endpoint()+" rpc-client",
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels("endpoint", req.Endpoint()),
)
err := n.funcCall(ctx, req, rsp, opts...)
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Dec()
te := time.Since(ts)
n.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", req.Endpoint()).Update(te.Seconds())
n.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", req.Endpoint()).Update(te.Seconds())
if me := errors.FromError(err); me == nil {
sp.Finish()
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "success", "code", strconv.Itoa(int(200))).Inc()
} else {
sp.SetStatus(tracer.SpanStatusError, err.Error())
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "failure", "code", strconv.Itoa(int(me.Code))).Inc()
}
return err
}
func (n *noopClient) fnCall(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error {
// make a copy of call opts
callOpts := n.opts.CallOptions
for _, opt := range opts {
@ -175,7 +260,7 @@ func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opt
} else {
// got a deadline so no need to setup context
// but we need to set the timeout we pass along
opt := RequestTimeout(time.Until(d))
opt := WithRequestTimeout(time.Until(d))
opt(&callOpts)
}
@ -187,11 +272,8 @@ func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opt
}
// make copy of call method
hcall := n.call
// wrap the call in reverse
for i := len(callOpts.CallWrappers); i > 0; i-- {
hcall = callOpts.CallWrappers[i-1](hcall)
hcall := func(ctx context.Context, addr string, req Request, rsp interface{}, opts CallOptions) error {
return nil
}
// use the router passed as a call option, or fallback to the rpc clients router
@ -260,9 +342,6 @@ func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opt
ch := make(chan error, callOpts.Retries)
var gerr error
ts := time.Now()
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", endpoint).Inc()
for i := 0; i <= callOpts.Retries; i++ {
go func() {
ch <- call(i)
@ -290,28 +369,44 @@ func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opt
}
}
if gerr != nil {
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", endpoint, "status", "failure").Inc()
} else {
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", endpoint, "status", "success").Inc()
}
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", endpoint).Dec()
te := time.Since(ts)
n.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", endpoint).Update(te.Seconds())
n.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", endpoint).Update(te.Seconds())
return gerr
}
func (n *noopClient) call(ctx context.Context, addr string, req Request, rsp interface{}, opts CallOptions) error {
return nil
}
func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...options.Option) Request {
func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request {
return &noopRequest{service: service, endpoint: endpoint}
}
func (n *noopClient) Stream(ctx context.Context, req Request, opts ...options.Option) (Stream, error) {
func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message {
options := NewMessageOptions(append([]MessageOption{MessageContentType(n.opts.ContentType)}, opts...)...)
return &noopMessage{topic: topic, payload: msg, opts: options}
}
func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) {
ts := time.Now()
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Inc()
var sp tracer.Span
ctx, sp = n.opts.Tracer.Start(ctx, req.Endpoint()+" rpc-client",
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels("endpoint", req.Endpoint()),
)
stream, err := n.funcStream(ctx, req, opts...)
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Dec()
te := time.Since(ts)
n.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", req.Endpoint()).Update(te.Seconds())
n.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", req.Endpoint()).Update(te.Seconds())
if me := errors.FromError(err); me == nil {
sp.Finish()
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "success", "code", strconv.Itoa(int(200))).Inc()
} else {
sp.SetStatus(tracer.SpanStatusError, err.Error())
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "failure", "code", strconv.Itoa(int(me.Code))).Inc()
}
return stream, err
}
func (n *noopClient) fnStream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) {
var err error
// make a copy of call opts
@ -330,7 +425,7 @@ func (n *noopClient) Stream(ctx context.Context, req Request, opts ...options.Op
} else {
// got a deadline so no need to setup context
// but we need to set the timeout we pass along
o := StreamTimeout(time.Until(d))
o := WithStreamTimeout(time.Until(d))
o(&callOpts)
}
@ -397,15 +492,7 @@ func (n *noopClient) Stream(ctx context.Context, req Request, opts ...options.Op
node := next()
// ts := time.Now()
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", endpoint).Inc()
stream, cerr := n.stream(ctx, node, req, callOpts)
if cerr != nil {
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", endpoint, "status", "failure").Inc()
} else {
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", endpoint, "status", "success").Inc()
}
// record the result of the call to inform future routing decisions
if verr := n.opts.Selector.Record(node, cerr); verr != nil {
@ -459,6 +546,92 @@ func (n *noopClient) Stream(ctx context.Context, req Request, opts ...options.Op
return nil, grr
}
func (n *noopClient) stream(ctx context.Context, addr string, req Request, opts CallOptions) (*noopStream, error) {
return &noopStream{}, nil
func (n *noopClient) stream(ctx context.Context, addr string, req Request, opts CallOptions) (Stream, error) {
return &noopStream{ctx: ctx}, nil
}
func (n *noopClient) BatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
return n.funcBatchPublish(ctx, ps, opts...)
}
func (n *noopClient) fnBatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
return n.publish(ctx, ps, opts...)
}
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error {
return n.funcPublish(ctx, p, opts...)
}
func (n *noopClient) fnPublish(ctx context.Context, p Message, opts ...PublishOption) error {
return n.publish(ctx, []Message{p}, opts...)
}
func (n *noopClient) publish(ctx context.Context, ps []Message, opts ...PublishOption) error {
options := NewPublishOptions(opts...)
msgs := make([]*broker.Message, 0, len(ps))
// get proxy
exchange := ""
if v, ok := os.LookupEnv("MICRO_PROXY"); ok {
exchange = v
}
// get the exchange
if len(options.Exchange) > 0 {
exchange = options.Exchange
}
omd, ok := metadata.FromOutgoingContext(ctx)
if !ok {
omd = metadata.New(0)
}
for _, p := range ps {
md := metadata.Copy(omd)
md[metadata.HeaderContentType] = p.ContentType()
topic := p.Topic()
if len(exchange) > 0 {
topic = exchange
}
md[metadata.HeaderTopic] = topic
iter := p.Metadata().Iterator()
var k, v string
for iter.Next(&k, &v) {
md.Set(k, v)
}
var body []byte
// passed in raw data
if d, ok := p.Payload().(*codec.Frame); ok {
body = d.Data
} else {
// use codec for payload
cf, err := n.newCodec(p.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// set the body
b, err := cf.Marshal(p.Payload())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
body = b
}
msgs = append(msgs, &broker.Message{Header: md, Body: body})
}
if len(msgs) == 1 {
return n.opts.Broker.Publish(ctx, msgs[0].Header[metadata.HeaderTopic], msgs[0],
broker.PublishContext(options.Context),
broker.PublishBodyOnly(options.BodyOnly),
)
}
return n.opts.Broker.BatchPublish(ctx, msgs,
broker.PublishContext(options.Context),
broker.PublishBodyOnly(options.BodyOnly),
)
}

35
client/noop_test.go Normal file
View File

@ -0,0 +1,35 @@
package client
import (
"context"
"testing"
)
type testHook struct {
f bool
}
func (t *testHook) Publish(fn FuncPublish) FuncPublish {
return func(ctx context.Context, msg Message, opts ...PublishOption) error {
t.f = true
return fn(ctx, msg, opts...)
}
}
func TestNoopHook(t *testing.T) {
h := &testHook{}
c := NewClient(Hooks(HookPublish(h.Publish)))
if err := c.Init(); err != nil {
t.Fatal(err)
}
if err := c.Publish(context.TODO(), c.NewMessage("", nil, MessageContentType("application/octet-stream"))); err != nil {
t.Fatal(err)
}
if !h.f {
t.Fatal("hook not works")
}
}

View File

@ -6,25 +6,32 @@ import (
"net"
"time"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/router"
"go.unistack.org/micro/v4/selector"
"go.unistack.org/micro/v4/selector/random"
"go.unistack.org/micro/v4/tracer"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/router"
"go.unistack.org/micro/v3/selector"
"go.unistack.org/micro/v3/selector/random"
"go.unistack.org/micro/v3/tracer"
)
// Options holds client options
type Options struct {
// Transport used for transfer messages
Transport transport.Transport
// Selector used to select needed address
Selector selector.Selector
// Logger used to log messages
Logger logger.Logger
// Tracer used for tracing
Tracer tracer.Tracer
// Broker used to publish messages
Broker broker.Broker
// Meter used for metrics
Meter meter.Meter
// Context is used for external options
@ -43,6 +50,8 @@ type Options struct {
ContentType string
// Name is the client name
Name string
// Wrappers contains wrappers
Wrappers []Wrapper
// CallOptions contains default CallOptions
CallOptions CallOptions
// PoolSize connection pool size
@ -51,12 +60,13 @@ type Options struct {
PoolTTL time.Duration
// ContextDialer used to connect
ContextDialer func(context.Context, string) (net.Conn, error)
// Hooks may contains Client func wrapper
// Hooks can be run before broker Publish/BatchPublish and
// Subscribe/BatchSubscribe methods
Hooks options.Hooks
}
// NewCallOptions creates new call options struct
func NewCallOptions(opts ...options.Option) CallOptions {
func NewCallOptions(opts ...CallOption) CallOptions {
options := CallOptions{}
for _, o := range opts {
o(&options)
@ -86,8 +96,6 @@ type CallOptions struct {
Address []string
// SelectOptions selector options
SelectOptions []selector.SelectOption
// CallWrappers call wrappers
CallWrappers []CallWrapper
// StreamTimeout stream timeout
StreamTimeout time.Duration
// RequestTimeout request timeout
@ -105,14 +113,58 @@ type CallOptions struct {
}
// ContextDialer pass ContextDialer to client
func ContextDialer(fn func(context.Context, string) (net.Conn, error)) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".ContextDialer")
func ContextDialer(fn func(context.Context, string) (net.Conn, error)) Option {
return func(o *Options) {
o.ContextDialer = fn
}
}
// Context pass context to client
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// NewPublishOptions create new PublishOptions struct from option
func NewPublishOptions(opts ...PublishOption) PublishOptions {
options := PublishOptions{}
for _, o := range opts {
o(&options)
}
return options
}
// PublishOptions holds publish options
type PublishOptions struct {
// Context used for external options
Context context.Context
// Exchange topic exchange name
Exchange string
// BodyOnly will publish only message body
BodyOnly bool
}
// NewMessageOptions creates message options struct
func NewMessageOptions(opts ...MessageOption) MessageOptions {
options := MessageOptions{Metadata: metadata.New(1)}
for _, o := range opts {
o(&options)
}
return options
}
// MessageOptions holds client message options
type MessageOptions struct {
// Metadata additional metadata
Metadata metadata.Metadata
// ContentType specify content-type of message
// deprecated
ContentType string
}
// NewRequestOptions creates new RequestOptions struct
func NewRequestOptions(opts ...options.Option) RequestOptions {
func NewRequestOptions(opts ...RequestOption) RequestOptions {
options := RequestOptions{}
for _, o := range opts {
o(&options)
@ -131,27 +183,29 @@ type RequestOptions struct {
}
// NewOptions creates new options struct
func NewOptions(opts ...options.Option) Options {
func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),
ContentType: DefaultContentType,
Codecs: make(map[string]codec.Codec),
Codecs: DefaultCodecs,
CallOptions: CallOptions{
Context: context.Background(),
Backoff: DefaultBackoff,
Retry: DefaultRetry,
Retries: DefaultRetries,
RequestTimeout: DefaultRequestTimeout,
DialTimeout: DefaultDialTimeout,
DialTimeout: transport.DefaultDialTimeout,
},
Lookup: LookupRoute,
PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(),
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Router: router.DefaultRouter,
Lookup: LookupRoute,
PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(),
Logger: logger.DefaultLogger,
Broker: broker.DefaultBroker,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Router: router.DefaultRouter,
Transport: transport.DefaultTransport,
}
for _, o := range opts {
@ -161,131 +215,367 @@ func NewOptions(opts ...options.Option) Options {
return options
}
// Broker to be used for pub/sub
func Broker(b broker.Broker) Option {
return func(o *Options) {
o.Broker = b
}
}
// Tracer to be used for tracing
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Logger to be used for log mesages
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Meter to be used for metrics
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Codec to be used to encode/decode requests for a given content type
func Codec(contentType string, c codec.Codec) Option {
return func(o *Options) {
o.Codecs[contentType] = c
}
}
// ContentType used by default if not specified
func ContentType(ct string) Option {
return func(o *Options) {
o.ContentType = ct
}
}
// Proxy sets the proxy address
func Proxy(addr string) options.Option {
return func(src interface{}) error {
return options.Set(src, addr, ".Proxy")
func Proxy(addr string) Option {
return func(o *Options) {
o.Proxy = addr
}
}
// PoolSize sets the connection pool size
func PoolSize(d int) options.Option {
return func(src interface{}) error {
return options.Set(src, d, ".PoolSize")
func PoolSize(d int) Option {
return func(o *Options) {
o.PoolSize = d
}
}
// PoolTTL sets the connection pool ttl
func PoolTTL(td time.Duration) options.Option {
return func(src interface{}) error {
return options.Set(src, td, ".PoolTTL")
func PoolTTL(d time.Duration) Option {
return func(o *Options) {
o.PoolTTL = d
}
}
// Transport to use for communication e.g http, rabbitmq, etc
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
// Register sets the routers register
func Register(r register.Register) Option {
return func(o *Options) {
if o.Router != nil {
_ = o.Router.Init(router.Register(r))
}
}
}
// Router is used to lookup routes for a service
func Router(r router.Router) Option {
return func(o *Options) {
o.Router = r
}
}
// Selector is used to select a route
func Selector(s selector.Selector) options.Option {
return func(src interface{}) error {
return options.Set(src, s, ".Selector")
func Selector(s selector.Selector) Option {
return func(o *Options) {
o.Selector = s
}
}
// Backoff is used to set the backoff function used when retrying Calls
func Backoff(fn BackoffFunc) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".Backoff")
func Backoff(fn BackoffFunc) Option {
return func(o *Options) {
o.CallOptions.Backoff = fn
}
}
// Name sets the client name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Lookup sets the lookup function to use for resolving service names
func Lookup(fn LookupFunc) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".Lookup")
func Lookup(l LookupFunc) Option {
return func(o *Options) {
o.Lookup = l
}
}
// WithCallWrapper sets the retry function to be used when re-trying.
func WithCallWrapper(fn CallWrapper) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".CallWrappers")
// TLSConfig specifies a *tls.Config
func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
// set the internal tls
o.TLSConfig = t
// set the default transport if one is not
// already set. Required for Init call below.
// set the transport tls
_ = o.Transport.Init(
transport.TLSConfig(t),
)
}
}
// Retries sets the retry count when making the request.
func Retries(n int) options.Option {
return func(src interface{}) error {
return options.Set(src, n, ".Retries")
func Retries(i int) Option {
return func(o *Options) {
o.CallOptions.Retries = i
}
}
// Retry sets the retry function to be used when re-trying.
func Retry(fn RetryFunc) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".Retry")
func Retry(fn RetryFunc) Option {
return func(o *Options) {
o.CallOptions.Retry = fn
}
}
// RequestTimeout is the request timeout.
func RequestTimeout(td time.Duration) options.Option {
return func(src interface{}) error {
return options.Set(src, td, ".RequestTimeout")
func RequestTimeout(d time.Duration) Option {
return func(o *Options) {
o.CallOptions.RequestTimeout = d
}
}
// StreamTimeout sets the stream timeout
func StreamTimeout(td time.Duration) options.Option {
return func(src interface{}) error {
return options.Set(src, td, ".StreamTimeout")
func StreamTimeout(d time.Duration) Option {
return func(o *Options) {
o.CallOptions.StreamTimeout = d
}
}
// DialTimeout sets the dial timeout
func DialTimeout(td time.Duration) options.Option {
return func(src interface{}) error {
return options.Set(src, td, ".DialTimeout")
func DialTimeout(d time.Duration) Option {
return func(o *Options) {
o.CallOptions.DialTimeout = d
}
}
// WithExchange sets the exchange to route a message through
// Deprecated
func WithExchange(e string) PublishOption {
return func(o *PublishOptions) {
o.Exchange = e
}
}
// PublishExchange sets the exchange to route a message through
func PublishExchange(e string) PublishOption {
return func(o *PublishOptions) {
o.Exchange = e
}
}
// WithBodyOnly publish only message body
// DERECATED
func WithBodyOnly(b bool) PublishOption {
return func(o *PublishOptions) {
o.BodyOnly = b
}
}
// PublishBodyOnly publish only message body
func PublishBodyOnly(b bool) PublishOption {
return func(o *PublishOptions) {
o.BodyOnly = b
}
}
// PublishContext sets the context in publish options
func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) {
o.Context = ctx
}
}
// WithContextDialer pass ContextDialer to client call
func WithContextDialer(fn func(context.Context, string) (net.Conn, error)) CallOption {
return func(o *CallOptions) {
o.ContextDialer = fn
}
}
// WithContentType specifies call content type
func WithContentType(ct string) CallOption {
return func(o *CallOptions) {
o.ContentType = ct
}
}
// WithAddress sets the remote addresses to use rather than using service discovery
func WithAddress(a ...string) CallOption {
return func(o *CallOptions) {
o.Address = a
}
}
// WithBackoff is a CallOption which overrides that which
// set in Options.CallOptions
func WithBackoff(fn BackoffFunc) CallOption {
return func(o *CallOptions) {
o.Backoff = fn
}
}
// WithRetry is a CallOption which overrides that which
// set in Options.CallOptions
func WithRetry(fn RetryFunc) CallOption {
return func(o *CallOptions) {
o.Retry = fn
}
}
// WithRetries is a CallOption which overrides that which
// set in Options.CallOptions
func WithRetries(i int) CallOption {
return func(o *CallOptions) {
o.Retries = i
}
}
// WithResponseMetadata is a CallOption which adds metadata.Metadata to Options.CallOptions
func ResponseMetadata(md *metadata.Metadata) options.Option {
return func(src interface{}) error {
return options.Set(src, md, ".ResponseMetadata")
func WithResponseMetadata(md *metadata.Metadata) CallOption {
return func(o *CallOptions) {
o.ResponseMetadata = md
}
}
// WithRequestMetadata is a CallOption which adds metadata.Metadata to Options.CallOptions
func RequestMetadata(md metadata.Metadata) options.Option {
return func(src interface{}) error {
return options.Set(src, metadata.Copy(md), ".RequestMetadata")
func WithRequestMetadata(md metadata.Metadata) CallOption {
return func(o *CallOptions) {
o.RequestMetadata = md
}
}
// AuthToken is a CallOption which overrides the
// WithRequestTimeout is a CallOption which overrides that which
// set in Options.CallOptions
func WithRequestTimeout(d time.Duration) CallOption {
return func(o *CallOptions) {
o.RequestTimeout = d
}
}
// WithStreamTimeout sets the stream timeout
func WithStreamTimeout(d time.Duration) CallOption {
return func(o *CallOptions) {
o.StreamTimeout = d
}
}
// WithDialTimeout is a CallOption which overrides that which
// set in Options.CallOptions
func WithDialTimeout(d time.Duration) CallOption {
return func(o *CallOptions) {
o.DialTimeout = d
}
}
// WithAuthToken is a CallOption which overrides the
// authorization header with the services own auth token
func AuthToken(t string) options.Option {
return func(src interface{}) error {
return options.Set(src, t, ".AuthToken")
func WithAuthToken(t string) CallOption {
return func(o *CallOptions) {
o.AuthToken = t
}
}
// Network is a CallOption which sets the network attribute
func Network(n string) options.Option {
return func(src interface{}) error {
return options.Set(src, n, ".Network")
// WithNetwork is a CallOption which sets the network attribute
func WithNetwork(n string) CallOption {
return func(o *CallOptions) {
o.Network = n
}
}
// WithRouter sets the router to use for this call
func WithRouter(r router.Router) CallOption {
return func(o *CallOptions) {
o.Router = r
}
}
// WithSelector sets the selector to use for this call
func WithSelector(s selector.Selector) CallOption {
return func(o *CallOptions) {
o.Selector = s
}
}
/*
// WithSelectOptions sets the options to pass to the selector for this call
func WithSelectOptions(sops ...selector.SelectOption) options.Option {
func WithSelectOptions(sops ...selector.SelectOption) CallOption {
return func(o *CallOptions) {
o.SelectOptions = sops
}
}
*/
// StreamingRequest specifies that request is streaming
func StreamingRequest(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".Stream")
// WithMessageContentType sets the message content type
// Deprecated
func WithMessageContentType(ct string) MessageOption {
return func(o *MessageOptions) {
o.Metadata.Set(metadata.HeaderContentType, ct)
o.ContentType = ct
}
}
// MessageContentType sets the message content type
func MessageContentType(ct string) MessageOption {
return func(o *MessageOptions) {
o.Metadata.Set(metadata.HeaderContentType, ct)
o.ContentType = ct
}
}
// MessageMetadata sets the message metadata
func MessageMetadata(k, v string) MessageOption {
return func(o *MessageOptions) {
o.Metadata.Set(k, v)
}
}
// StreamingRequest specifies that request is streaming
func StreamingRequest(b bool) RequestOption {
return func(o *RequestOptions) {
o.Stream = b
}
}
// RequestContentType specifies request content type
func RequestContentType(ct string) RequestOption {
return func(o *RequestOptions) {
o.ContentType = ct
}
}
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}

View File

@ -3,7 +3,7 @@ package client
import (
"context"
"go.unistack.org/micro/v4/errors"
"go.unistack.org/micro/v3/errors"
)
// RetryFunc that returning either false or a non-nil error will result in the call not being retried

View File

@ -5,7 +5,7 @@ import (
"fmt"
"testing"
"go.unistack.org/micro/v4/errors"
"go.unistack.org/micro/v3/errors"
)
func TestRetryAlways(t *testing.T) {

View File

@ -1,7 +1,7 @@
package client
import (
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v3/codec"
)
type testRequest struct {

41
cluster/cluster.go Normal file
View File

@ -0,0 +1,41 @@
package cluster
import (
"context"
"go.unistack.org/micro/v3/metadata"
)
// Message sent to member in cluster
type Message interface {
// Header returns message headers
Header() metadata.Metadata
// Body returns broker message may be []byte slice or some go struct or interface
Body() interface{}
}
type Node interface {
// Name returns node name
Name() string
// Address returns node address
Address() string
// Metadata returns node metadata
Metadata() metadata.Metadata
}
// Cluster interface used for cluster communication across nodes
type Cluster interface {
// Join is used to take an existing members and performing state sync
Join(ctx context.Context, addr ...string) error
// Leave broadcast a leave message and stop listeners
Leave(ctx context.Context) error
// Ping is used to probe live status of the node
Ping(ctx context.Context, node Node, payload []byte) error
// Members returns the cluster members
Members() ([]Node, error)
// Broadcast send message for all members in cluster, if filter is not nil, nodes may be filtered
// by key/value pairs
Broadcast(ctx context.Context, msg Message, filter ...string) error
// Unicast send message to single member in cluster
Unicast(ctx context.Context, node Node, msg Message) error
}

View File

@ -1,11 +1,11 @@
// Package codec is an interface for encoding messages
package codec // import "go.unistack.org/micro/v4/codec"
package codec // import "go.unistack.org/micro/v3/codec"
import (
"errors"
"io"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v3/metadata"
)
// Message types

View File

@ -1,4 +1,4 @@
// Copyright 2021-2023 Unistack LLC
// Copyright 2021 Unistack LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -17,7 +17,7 @@ syntax = "proto3";
package micro.codec;
option cc_enable_arenas = true;
option go_package = "go.unistack.org/micro/v4/codec;codec";
option go_package = "go.unistack.org/micro/v3/codec;codec";
option java_multiple_files = true;
option java_outer_classname = "MicroCodec";
option java_package = "micro.codec";

View File

@ -3,9 +3,9 @@ package codec
import (
"context"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/tracer"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/tracer"
)
// Option func

View File

@ -1,13 +1,11 @@
// Package config is an interface for dynamic configuration.
package config // import "go.unistack.org/micro/v4/config"
package config // import "go.unistack.org/micro/v3/config"
import (
"context"
"errors"
"reflect"
"time"
"go.unistack.org/micro/v4/options"
)
type Validator interface {
@ -15,7 +13,7 @@ type Validator interface {
}
// DefaultConfig default config
var DefaultConfig = NewConfig()
var DefaultConfig Config = NewConfig()
// DefaultWatcherMinInterval default min interval for poll changes
var DefaultWatcherMinInterval = 5 * time.Second
@ -39,19 +37,26 @@ type Config interface {
// Name returns name of config
Name() string
// Init the config
Init(opts ...options.Option) error
Init(opts ...Option) error
// Options in the config
Options() Options
// Load config from sources
Load(context.Context, ...options.Option) error
Load(context.Context, ...LoadOption) error
// Save config to sources
Save(context.Context, ...options.Option) error
Save(context.Context, ...SaveOption) error
// Watch a config for changes
Watch(context.Context, ...options.Option) (Watcher, error)
Watch(context.Context, ...WatchOption) (Watcher, error)
// String returns config type name
String() string
}
type (
FuncLoad func(ctx context.Context, opts ...LoadOption) error
HookLoad func(next FuncLoad) FuncLoad
FuncSave func(ctx context.Context, opts ...SaveOption) error
HookSave func(next FuncSave) FuncSave
)
// Watcher is the config watcher
type Watcher interface {
// Next blocks until update happens or error returned
@ -61,7 +66,7 @@ type Watcher interface {
}
// Load loads config from config sources
func Load(ctx context.Context, cs []Config, opts ...options.Option) error {
func Load(ctx context.Context, cs []Config, opts ...LoadOption) error {
var err error
for _, c := range cs {
if err = c.Init(); err != nil {
@ -133,7 +138,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" BeforeLoad error "+err.Error())
c.Options().Logger.Errorf(ctx, "%s BeforeLoad err: %v", c.String(), err)
if !c.Options().AllowFail {
return err
}
@ -148,7 +153,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" AfterLoad error "+err.Error())
c.Options().Logger.Errorf(ctx, "%s AfterLoad err: %v", c.String(), err)
if !c.Options().AllowFail {
return err
}
@ -163,7 +168,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" BeforeSave error "+err.Error())
c.Options().Logger.Errorf(ctx, "%s BeforeSave err: %v", c.String(), err)
if !c.Options().AllowFail {
return err
}
@ -178,7 +183,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" AfterSave error "+err.Error())
c.Options().Logger.Errorf(ctx, "%s AfterSave err: %v", c.String(), err)
if !c.Options().AllowFail {
return err
}
@ -193,7 +198,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" BeforeInit error "+err.Error())
c.Options().Logger.Errorf(ctx, "%s BeforeInit err: %v", c.String(), err)
if !c.Options().AllowFail {
return err
}
@ -208,7 +213,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" AfterInit error "+err.Error())
c.Options().Logger.Errorf(ctx, "%s AfterInit err: %v", c.String(), err)
if !c.Options().AllowFail {
return err
}

View File

@ -22,3 +22,43 @@ func NewContext(ctx context.Context, c Config) context.Context {
}
return context.WithValue(ctx, configKey{}, c)
}
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// SetSaveOption returns a function to setup a context with given value
func SetSaveOption(k, v interface{}) SaveOption {
return func(o *SaveOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// SetLoadOption returns a function to setup a context with given value
func SetLoadOption(k, v interface{}) LoadOption {
return func(o *LoadOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// SetWatchOption returns a function to setup a context with given value
func SetWatchOption(k, v interface{}) WatchOption {
return func(o *WatchOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@ -40,3 +40,47 @@ func TestNewContext(t *testing.T) {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}
func TestSetSaveOption(t *testing.T) {
type key struct{}
o := SetSaveOption(key{}, "test")
opts := &SaveOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetSaveOption not works")
}
}
func TestSetLoadOption(t *testing.T) {
type key struct{}
o := SetLoadOption(key{}, "test")
opts := &LoadOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetLoadOption not works")
}
}
func TestSetWatchOption(t *testing.T) {
type key struct{}
o := SetWatchOption(key{}, "test")
opts := &WatchOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetWatchOption not works")
}
}

View File

@ -9,21 +9,23 @@ import (
"dario.cat/mergo"
"github.com/google/uuid"
"go.unistack.org/micro/v4/options"
mid "go.unistack.org/micro/v4/util/id"
rutil "go.unistack.org/micro/v4/util/reflect"
mtime "go.unistack.org/micro/v4/util/time"
"go.unistack.org/micro/v3/options"
mid "go.unistack.org/micro/v3/util/id"
rutil "go.unistack.org/micro/v3/util/reflect"
mtime "go.unistack.org/micro/v3/util/time"
)
type defaultConfig struct {
opts Options
funcLoad FuncLoad
funcSave FuncSave
opts Options
}
func (c *defaultConfig) Options() Options {
return c.opts
}
func (c *defaultConfig) Init(opts ...options.Option) error {
func (c *defaultConfig) Init(opts ...Option) error {
for _, o := range opts {
o(&c.opts)
}
@ -32,6 +34,18 @@ func (c *defaultConfig) Init(opts ...options.Option) error {
return err
}
c.funcLoad = c.fnLoad
c.funcSave = c.fnSave
c.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookLoad:
c.funcLoad = h(c.funcLoad)
case HookSave:
c.funcSave = h(c.funcSave)
}
})
if err := DefaultAfterInit(c.opts.Context, c); err != nil && !c.opts.AllowFail {
return err
}
@ -39,12 +53,18 @@ func (c *defaultConfig) Init(opts ...options.Option) error {
return nil
}
func (c *defaultConfig) Load(ctx context.Context, opts ...options.Option) error {
func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
return c.funcLoad(ctx, opts...)
}
func (c *defaultConfig) fnLoad(ctx context.Context, opts ...LoadOption) error {
var err error
if c.opts.SkipLoad != nil && c.opts.SkipLoad(ctx, c) {
return nil
}
if err := DefaultBeforeLoad(ctx, c); err != nil && !c.opts.AllowFail {
if err = DefaultBeforeLoad(ctx, c); err != nil && !c.opts.AllowFail {
return err
}
@ -190,7 +210,7 @@ func fillValue(value reflect.Value, val string) error {
return err
}
value.Set(reflect.ValueOf(v))
case value.Type().String() == "time.Duration" && value.Type().PkgPath() == "go.unistack.org/micro/v4/util/time":
case value.Type().String() == "time.Duration" && value.Type().PkgPath() == "go.unistack.org/micro/v3/util/time":
v, err := mtime.ParseDuration(val)
if err != nil {
return err
@ -234,6 +254,7 @@ func fillValue(value reflect.Value, val string) error {
}
value.Set(reflect.ValueOf(v))
}
return nil
}
@ -296,7 +317,11 @@ func fillValues(valueOf reflect.Value, tname string) error {
return nil
}
func (c *defaultConfig) Save(ctx context.Context, _ ...options.Option) error {
func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error {
return c.funcSave(ctx, opts...)
}
func (c *defaultConfig) fnSave(ctx context.Context, opts ...SaveOption) error {
if c.opts.SkipSave != nil && c.opts.SkipSave(ctx, c) {
return nil
}
@ -320,15 +345,19 @@ func (c *defaultConfig) Name() string {
return c.opts.Name
}
func (c *defaultConfig) Watch(ctx context.Context, opts ...options.Option) (Watcher, error) {
func (c *defaultConfig) Watch(_ context.Context, _ ...WatchOption) (Watcher, error) {
return nil, ErrWatcherNotImplemented
}
// NewConfig returns new default config source
func NewConfig(opts ...options.Option) Config {
func NewConfig(opts ...Option) Config {
options := NewOptions(opts...)
if len(options.StructTag) == 0 {
options.StructTag = "default"
}
return &defaultConfig{opts: options}
c := &defaultConfig{opts: options}
c.funcLoad = c.fnLoad
c.funcSave = c.fnSave
return c
}

View File

@ -6,9 +6,9 @@ import (
"testing"
"time"
"go.unistack.org/micro/v4/config"
mid "go.unistack.org/micro/v4/util/id"
mtime "go.unistack.org/micro/v4/util/time"
"go.unistack.org/micro/v3/config"
mid "go.unistack.org/micro/v3/util/id"
mtime "go.unistack.org/micro/v3/util/time"
)
type cfg struct {
@ -41,6 +41,35 @@ func (c *cfgStructValue) Validate() error {
return nil
}
type testHook struct {
f bool
}
func (t *testHook) Load(fn config.FuncLoad) config.FuncLoad {
return func(ctx context.Context, opts ...config.LoadOption) error {
t.f = true
return fn(ctx, opts...)
}
}
func TestHook(t *testing.T) {
h := &testHook{}
c := config.NewConfig(config.Struct(h), config.Hooks(config.HookLoad(h.Load)))
if err := c.Init(); err != nil {
t.Fatal(err)
}
if err := c.Load(context.TODO()); err != nil {
t.Fatal(err)
}
if !h.f {
t.Fatal("hook not works")
}
}
func TestDefault(t *testing.T) {
ctx := context.Background()
conf := &cfg{IntValue: 10}

View File

@ -4,11 +4,11 @@ import (
"context"
"time"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/tracer"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/tracer"
)
// Options hold the config options
@ -47,10 +47,15 @@ type Options struct {
SkipLoad func(context.Context, Config) bool
// SkipSave runs only if condition returns true
SkipSave func(context.Context, Config) bool
// Hooks can be run before/after config Save/Load
Hooks options.Hooks
}
// Option function signature
type Option func(o *Options)
// NewOptions new options struct with filed values
func NewOptions(opts ...options.Option) Options {
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
@ -64,6 +69,9 @@ func NewOptions(opts ...options.Option) Options {
return options
}
// LoadOption function signature
type LoadOption func(o *LoadOptions)
// LoadOptions struct
type LoadOptions struct {
Struct interface{}
@ -73,7 +81,7 @@ type LoadOptions struct {
}
// NewLoadOptions create LoadOptions struct with provided opts
func NewLoadOptions(opts ...options.Option) LoadOptions {
func NewLoadOptions(opts ...LoadOption) LoadOptions {
options := LoadOptions{}
for _, o := range opts {
o(&options)
@ -82,27 +90,44 @@ func NewLoadOptions(opts ...options.Option) LoadOptions {
}
// LoadOverride override values when load
func LoadOverride(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".Override")
func LoadOverride(b bool) LoadOption {
return func(o *LoadOptions) {
o.Override = b
}
}
// LoadAppend override values when load
func LoadAppend(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".Append")
func LoadAppend(b bool) LoadOption {
return func(o *LoadOptions) {
o.Append = b
}
}
// LoadStruct override struct for loading
func LoadStruct(src interface{}) LoadOption {
return func(o *LoadOptions) {
o.Struct = src
}
}
// SaveOption function signature
type SaveOption func(o *SaveOptions)
// SaveOptions struct
type SaveOptions struct {
Struct interface{}
Context context.Context
}
// SaveStruct override struct for save to config
func SaveStruct(src interface{}) SaveOption {
return func(o *SaveOptions) {
o.Struct = src
}
}
// NewSaveOptions fill SaveOptions struct
func NewSaveOptions(opts ...options.Option) SaveOptions {
func NewSaveOptions(opts ...SaveOption) SaveOptions {
options := SaveOptions{}
for _, o := range opts {
o(&options)
@ -111,65 +136,100 @@ func NewSaveOptions(opts ...options.Option) SaveOptions {
}
// AllowFail allows config source to fail
func AllowFail(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".AllowFail")
func AllowFail(b bool) Option {
return func(o *Options) {
o.AllowFail = b
}
}
// BeforeInit run funcs before config Init
func BeforeInit(fn ...func(context.Context, Config) error) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".BeforeInit")
func BeforeInit(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.BeforeInit = fn
}
}
// AfterInit run funcs after config Init
func AfterInit(fn ...func(context.Context, Config) error) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".AfterInit")
func AfterInit(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.AfterInit = fn
}
}
// BeforeLoad run funcs before config load
func BeforeLoad(fn ...func(context.Context, Config) error) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".BeforeLoad")
func BeforeLoad(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.BeforeLoad = fn
}
}
// AfterLoad run funcs after config load
func AfterLoad(fn ...func(context.Context, Config) error) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".AfterLoad")
func AfterLoad(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.AfterLoad = fn
}
}
// BeforeSave run funcs before save
func BeforeSave(fn ...func(context.Context, Config) error) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".BeforeSave")
func BeforeSave(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.BeforeSave = fn
}
}
// AfterSave run fncs after save
func AfterSave(fn ...func(context.Context, Config) error) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".AfterSave")
func AfterSave(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.AfterSave = fn
}
}
// Context pass context
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Codec sets the source codec
func Codec(c codec.Codec) Option {
return func(o *Options) {
o.Codec = c
}
}
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Tracer to be used for tracing
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Struct used as config
func Struct(v interface{}) options.Option {
return func(src interface{}) error {
return options.Set(src, v, ".Struct")
func Struct(v interface{}) Option {
return func(o *Options) {
o.Struct = v
}
}
// StructTag sets the struct tag that used for filling
func StructTag(name string) options.Option {
return func(src interface{}) error {
return options.Set(src, name, ".StructTag")
func StructTag(name string) Option {
return func(o *Options) {
o.StructTag = name
}
}
// Name sets the name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
@ -187,8 +247,11 @@ type WatchOptions struct {
Coalesce bool
}
// WatchOption func signature
type WatchOption func(*WatchOptions)
// NewWatchOptions create WatchOptions struct with provided opts
func NewWatchOptions(opts ...options.Option) WatchOptions {
func NewWatchOptions(opts ...WatchOption) WatchOptions {
options := WatchOptions{
Context: context.Background(),
MinInterval: DefaultWatcherMinInterval,
@ -200,20 +263,38 @@ func NewWatchOptions(opts ...options.Option) WatchOptions {
return options
}
// Coalesce controls watch event combining
func WatchCoalesce(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".Coalesce")
// WatchContext pass context
func WatchContext(ctx context.Context) WatchOption {
return func(o *WatchOptions) {
o.Context = ctx
}
}
// WatchCoalesce controls watch event combining
func WatchCoalesce(b bool) WatchOption {
return func(o *WatchOptions) {
o.Coalesce = b
}
}
// WatchInterval specifies min and max time.Duration for pulling changes
func WatchInterval(min, max time.Duration) options.Option {
return func(src interface{}) error {
var err error
if err = options.Set(src, min, ".MinInterval"); err == nil {
err = options.Set(src, max, ".MaxInterval")
}
return err
func WatchInterval(min, max time.Duration) WatchOption {
return func(o *WatchOptions) {
o.MinInterval = min
o.MaxInterval = max
}
}
// WatchStruct overrides struct for fill
func WatchStruct(src interface{}) WatchOption {
return func(o *WatchOptions) {
o.Struct = src
}
}
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}

View File

@ -75,80 +75,78 @@ func ParseDSN(dsn string) (*Config, error) {
// Find last '/' that goes before dbname
foundSlash := false
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] != '/' {
continue
}
if dsn[i] == '/' {
foundSlash = true
var j, k int
foundSlash = true
var j, k int
// left part is empty if i <= 0
if i > 0 {
// Find the first ':' in dsn
for j = i; j >= 0; j-- {
if dsn[j] == ':' {
cfg.Scheme = dsn[0:j]
// left part is empty if i <= 0
if i > 0 {
// Find the first ':' in dsn
for j = i; j >= 0; j-- {
if dsn[j] == ':' {
cfg.Scheme = dsn[0:j]
}
}
}
// [username[:password]@][host]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the second ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
if cfg.Scheme == dsn[:k] {
continue
// [username[:password]@][host]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the second ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
if cfg.Scheme == dsn[:k] {
continue
}
var err error
cfg.Password, err = url.PathUnescape(dsn[k+1 : j])
if err != nil {
return nil, err
}
break
}
var err error
cfg.Password, err = url.PathUnescape(dsn[k+1 : j])
if err != nil {
return nil, err
}
break
}
cfg.Username = dsn[len(cfg.Scheme)+3 : k]
break
}
cfg.Username = dsn[len(cfg.Scheme)+3 : k]
}
for k = j + 1; k < i; k++ {
if dsn[k] == ':' {
cfg.Host = dsn[j+1 : k]
cfg.Port = dsn[k+1 : i]
break
}
}
}
// dbname[?param1=value1&...&paramN=valueN]
// Find the first '?' in dsn[i+1:]
for j = i + 1; j < len(dsn); j++ {
if dsn[j] == '?' {
parts := strings.Split(dsn[j+1:], "&")
cfg.Params = make([]string, 0, len(parts)*2)
for _, p := range parts {
k, v, found := strings.Cut(p, "=")
if !found {
continue
}
cfg.Params = append(cfg.Params, k, v)
}
break
}
}
for k = j + 1; k < i; k++ {
if dsn[k] == ':' {
cfg.Host = dsn[j+1 : k]
cfg.Port = dsn[k+1 : i]
break
}
var err error
dbname := dsn[i+1 : j]
if cfg.Database, err = url.PathUnescape(dbname); err != nil {
return nil, fmt.Errorf("invalid dbname %q: %w", dbname, err)
}
break
}
// dbname[?param1=value1&...&paramN=valueN]
// Find the first '?' in dsn[i+1:]
for j = i + 1; j < len(dsn); j++ {
if dsn[j] == '?' {
parts := strings.Split(dsn[j+1:], "&")
cfg.Params = make([]string, 0, len(parts)*2)
for _, p := range parts {
k, v, found := strings.Cut(p, "=")
if !found {
continue
}
cfg.Params = append(cfg.Params, k, v)
}
break
}
}
var err error
dbname := dsn[i+1 : j]
if cfg.Database, err = url.PathUnescape(dbname); err != nil {
return nil, fmt.Errorf("invalid dbname %q: %w", dbname, err)
}
break
}
if !foundSlash && len(dsn) > 0 {

View File

@ -1,14 +1,20 @@
// Package errors provides a way to return detailed information
// for an RPC request error. The error is normally JSON encoded.
package errors // import "go.unistack.org/micro/v4/errors"
package errors // import "go.unistack.org/micro/v3/errors"
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var (
@ -256,6 +262,10 @@ func CodeIn(err interface{}, codes ...int32) bool {
// FromError try to convert go error to *Error
func FromError(err error) *Error {
if err == nil {
return nil
}
if verr, ok := err.(*Error); ok && verr != nil {
return verr
}
@ -340,3 +350,135 @@ func addslashes(str string) string {
}
return buf.String()
}
type retryableError struct {
err error
}
// Retryable returns error that can be retried later
func Retryable(err error) error {
return &retryableError{err: err}
}
type IsRetryableFunc func(error) bool
var (
RetrayableOracleErrors = []IsRetryableFunc{
func(err error) bool {
errmsg := err.Error()
switch {
case strings.Contains(errmsg, `ORA-`):
return true
case strings.Contains(errmsg, `can not assign`):
return true
case strings.Contains(errmsg, `can't assign`):
return true
}
return false
},
}
RetrayablePostgresErrors = []IsRetryableFunc{
func(err error) bool {
errmsg := err.Error()
switch {
case strings.Contains(errmsg, `number of field descriptions must equal number of`):
return true
case strings.Contains(errmsg, `not a pointer`):
return true
case strings.Contains(errmsg, `values, but dst struct has only`):
return true
case strings.Contains(errmsg, `struct doesn't have corresponding row field`):
return true
case strings.Contains(errmsg, `cannot find field`):
return true
case strings.Contains(errmsg, `cannot scan`) || strings.Contains(errmsg, `cannot convert`):
return true
case strings.Contains(errmsg, `failed to connect to`):
return true
}
return false
},
}
RetryableMicroErrors = []IsRetryableFunc{
func(err error) bool {
switch verr := err.(type) {
case *Error:
switch verr.Code {
case 401, 403, 408, 500, 501, 502, 503, 504:
return true
default:
return false
}
case *retryableError:
return true
}
return false
},
}
RetryableGoErrors = []IsRetryableFunc{
func(err error) bool {
switch verr := err.(type) {
case interface{ SafeToRetry() bool }:
return verr.SafeToRetry()
case interface{ Timeout() bool }:
return verr.Timeout()
}
switch {
case errors.Is(err, io.EOF), errors.Is(err, io.ErrUnexpectedEOF):
return true
case errors.Is(err, context.DeadlineExceeded):
return true
case errors.Is(err, io.ErrClosedPipe), errors.Is(err, io.ErrShortBuffer), errors.Is(err, io.ErrShortWrite):
return true
}
return false
},
}
RetryableGrpcErrors = []IsRetryableFunc{
func(err error) bool {
st, ok := status.FromError(err)
if !ok {
return false
}
switch st.Code() {
case codes.Unavailable, codes.ResourceExhausted:
return true
case codes.DeadlineExceeded:
return true
case codes.Internal:
switch {
case strings.Contains(st.Message(), `transport: received the unexpected content-type "text/html; charset=UTF-8"`):
return true
case strings.Contains(st.Message(), io.ErrUnexpectedEOF.Error()):
return true
case strings.Contains(st.Message(), `stream terminated by RST_STREAM with error code: INTERNAL_ERROR`):
return true
}
}
return false
},
}
)
// Unwrap provides error wrapping
func (e *retryableError) Unwrap() error {
return e.err
}
// Error returns the error string
func (e *retryableError) Error() string {
if e.err == nil {
return ""
}
return e.err.Error()
}
// IsRetryable checks error for ability to retry later
func IsRetryable(err error, fns ...IsRetryableFunc) bool {
for _, fn := range fns {
if ok := fn(err); ok {
return true
}
}
return false
}

View File

@ -1,4 +1,4 @@
// Copyright 2021-2023 Unistack LLC
// Copyright 2021 Unistack LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -17,7 +17,7 @@ syntax = "proto3";
package micro.errors;
option cc_enable_arenas = true;
option go_package = "go.unistack.org/micro/v4/errors;errors";
option go_package = "go.unistack.org/micro/v3/errors;errors";
option java_multiple_files = true;
option java_outer_classname = "MicroErrors";
option java_package = "micro.errors";

View File

@ -8,6 +8,13 @@ import (
"testing"
)
func TestIsRetrayable(t *testing.T) {
err := fmt.Errorf("ORA-")
if !IsRetryable(err, RetrayableOracleErrors...) {
t.Fatalf("IsRetrayable not works")
}
}
func TestMarshalJSON(t *testing.T) {
e := InternalServerError("id", "err: %v", fmt.Errorf("err: %v", `xxx: "UNIX_TIMESTAMP": invalid identifier`))
_, err := json.Marshal(e)

27
event.go Normal file
View File

@ -0,0 +1,27 @@
package micro
import (
"context"
"go.unistack.org/micro/v3/client"
)
// Event is used to publish messages to a topic
type Event interface {
// Publish publishes a message to the event topic
Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error
}
type event struct {
c client.Client
topic string
}
// NewEvent creates a new event publisher
func NewEvent(topic string, c client.Client) Event {
return &event{c, topic}
}
func (e *event) Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error {
return e.c.Publish(ctx, e.c.NewMessage(e.topic, msg), opts...)
}

View File

@ -22,3 +22,13 @@ func NewContext(ctx context.Context, f Flow) context.Context {
}
return context.WithValue(ctx, flowKey{}, f)
}
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@ -40,3 +40,14 @@ func TestNewContext(t *testing.T) {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@ -6,14 +6,12 @@ import (
"sync"
"github.com/silas/dag"
"go.unistack.org/micro/v4/client"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/options"
moptions "go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/store"
"go.unistack.org/micro/v4/util/id"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/util/id"
)
type microFlow struct {
@ -165,7 +163,7 @@ func (w *microWorkflow) Resume(ctx context.Context, id string) error {
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())})
}
func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...options.Option) (string, error) {
func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error) {
w.Lock()
if !w.init {
if err := w.g.Validate(); err != nil {
@ -190,7 +188,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...optio
steps, err := w.getSteps(options.Start, options.Reverse)
if err != nil {
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
w.opts.Logger.Error(w.opts.Context, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
return "", err
}
@ -202,19 +200,19 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...optio
nctx, cancel := context.WithCancel(ctx)
defer cancel()
nopts := make([]moptions.Option, 0, len(opts)+5)
nopts := make([]ExecuteOption, 0, len(opts)+5)
nopts = append(nopts,
moptions.Client(w.opts.Client),
moptions.Tracer(w.opts.Tracer),
moptions.Logger(w.opts.Logger),
moptions.Meter(w.opts.Meter),
ExecuteClient(w.opts.Client),
ExecuteTracer(w.opts.Tracer),
ExecuteLogger(w.opts.Logger),
ExecuteMeter(w.opts.Meter),
)
nopts = append(nopts, opts...)
done := make(chan struct{})
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
w.opts.Logger.Error(w.opts.Context, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
return eid, werr
}
for idx := range steps {
@ -239,7 +237,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...optio
return
}
if w.opts.Logger.V(logger.TraceLevel) {
w.opts.Logger.Trace(nctx, fmt.Sprintf("step will be executed %v", steps[idx][nidx]))
w.opts.Logger.Tracef(nctx, "will be executed %v", steps[idx][nidx])
}
cstep := steps[idx][nidx]
// nolint: nestif
@ -259,21 +257,21 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...optio
if serr != nil {
step.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
cherr <- serr
return
}
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil {
w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
@ -292,16 +290,16 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...optio
if serr != nil {
cstep.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
cherr <- serr
return
}
if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil {
w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
@ -319,7 +317,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...optio
return eid, nil
}
w.opts.Logger.Trace(ctx, "wait for finish or error")
logger.Tracef(ctx, "wait for finish or error")
select {
case <-nctx.Done():
err = nctx.Err()
@ -335,15 +333,15 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...optio
switch {
case nctx.Err() != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusAborted.String())}); werr != nil {
w.opts.Logger.Error(w.opts.Context, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
case err == nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Error(w.opts.Context, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
case err != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil {
w.opts.Logger.Error(w.opts.Context, "store write error", "error", werr.Error())
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
}
@ -351,7 +349,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...optio
}
// NewFlow create new flow
func NewFlow(opts ...options.Option) Flow {
func NewFlow(opts ...Option) Flow {
options := NewOptions(opts...)
return &microFlow{opts: options}
}
@ -360,7 +358,7 @@ func (f *microFlow) Options() Options {
return f.opts
}
func (f *microFlow) Init(opts ...options.Option) error {
func (f *microFlow) Init(opts ...Option) error {
for _, o := range opts {
o(&f.opts)
}
@ -489,17 +487,17 @@ func (s *microCallStep) SetStatus(status Status) {
s.status = status
}
func (s *microCallStep) Execute(ctx context.Context, req *Message, opts ...options.Option) (*Message, error) {
func (s *microCallStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) {
options := NewExecuteOptions(opts...)
if options.Client == nil {
return nil, ErrMissingClient
}
rsp := &codec.Frame{}
copts := []moptions.Option{client.Retries(0)}
copts := []client.CallOption{client.WithRetries(0)}
if options.Timeout > 0 {
copts = append(copts,
client.RequestTimeout(options.Timeout),
client.DialTimeout(options.Timeout))
client.WithRequestTimeout(options.Timeout),
client.WithDialTimeout(options.Timeout))
}
nctx := metadata.NewOutgoingContext(ctx, req.Header)
err := options.Client.Call(nctx, options.Client.NewRequest(s.service, s.method, &codec.Frame{Data: req.Body}), rsp, copts...)
@ -572,18 +570,18 @@ func (s *microPublishStep) SetStatus(status Status) {
s.status = status
}
func (s *microPublishStep) Execute(ctx context.Context, req *Message, opts ...options.Option) (*Message, error) {
func (s *microPublishStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) {
return nil, nil
}
// NewCallStep create new step with client.Call
func NewCallStep(service string, name string, method string, opts ...options.Option) Step {
func NewCallStep(service string, name string, method string, opts ...StepOption) Step {
options := NewStepOptions(opts...)
return &microCallStep{service: service, method: name + "." + method, opts: options}
}
// NewPublishStep create new step with client.Publish
func NewPublishStep(topic string, opts ...options.Option) Step {
func NewPublishStep(topic string, opts ...StepOption) Step {
options := NewStepOptions(opts...)
return &microPublishStep{topic: topic, opts: options}
}

View File

@ -1,5 +1,5 @@
// Package flow is an interface used for saga pattern microservice workflow
package flow // import "go.unistack.org/micro/v4/flow"
package flow // import "go.unistack.org/micro/v3/flow"
import (
"context"
@ -7,8 +7,7 @@ import (
"sync"
"sync/atomic"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v3/metadata"
)
var (
@ -52,7 +51,7 @@ type Step interface {
// Endpoint returns rpc endpoint service_name.service_method or broker topic
Endpoint() string
// Execute step run
Execute(ctx context.Context, req *Message, opts ...options.Option) (*Message, error)
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error)
// Requires returns dependent steps
Requires() []string
// Options returns step options
@ -119,7 +118,7 @@ type Workflow interface {
// ID returns id of the workflow
ID() string
// Execute workflow with args, return execution id and error
Execute(ctx context.Context, req *Message, opts ...options.Option) (string, error)
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error)
// RemoveSteps remove steps from workflow
RemoveSteps(steps ...Step) error
// AppendSteps append steps to workflow
@ -141,7 +140,7 @@ type Flow interface {
// Options returns options
Options() Options
// Init initialize
Init(...options.Option) error
Init(...Option) error
// WorkflowCreate creates new workflow with specific id and steps
WorkflowCreate(ctx context.Context, id string, steps ...Step) (Workflow, error)
// WorkflowSave saves workflow

View File

@ -4,14 +4,16 @@ import (
"context"
"time"
"go.unistack.org/micro/v4/client"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/store"
"go.unistack.org/micro/v4/tracer"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/tracer"
)
// Option func
type Option func(*Options)
// Options server struct
type Options struct {
// Context holds the external options and can be used for flow shutdown
@ -29,7 +31,7 @@ type Options struct {
}
// NewOptions returns new options struct with default or passed values
func NewOptions(opts ...options.Option) Options {
func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),
Logger: logger.DefaultLogger,
@ -45,12 +47,66 @@ func NewOptions(opts ...options.Option) Options {
return options
}
// Logger sets the logger option
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Meter sets the meter option
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Client to use for sync/async communication
func Client(c client.Client) Option {
return func(o *Options) {
o.Client = c
}
}
// Context specifies a context for the service.
// Can be used to signal shutdown of the flow
// or can be used for extra option values.
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Tracer mechanism for distributed tracking
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Store used for intermediate results
func Store(s store.Store) Option {
return func(o *Options) {
o.Store = s
}
}
// WorkflowOption func signature
type WorkflowOption func(*WorkflowOptions)
// WorkflowOptions holds workflow options
type WorkflowOptions struct {
Context context.Context
ID string
}
// WorkflowID set workflow id
func WorkflowID(id string) WorkflowOption {
return func(o *WorkflowOptions) {
o.ID = id
}
}
// ExecuteOptions holds execute options
type ExecuteOptions struct {
// Client holds the client.Client
@ -73,22 +129,67 @@ type ExecuteOptions struct {
Async bool
}
// Reverse says that dag must be run in reverse order
func Reverse(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".Reverse")
// ExecuteOption func signature
type ExecuteOption func(*ExecuteOptions)
// ExecuteClient pass client.Client to ExecuteOption
func ExecuteClient(c client.Client) ExecuteOption {
return func(o *ExecuteOptions) {
o.Client = c
}
}
// Async says that caller does not wait for execution complete
func Async(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".Async")
// ExecuteTracer pass tracer.Tracer to ExecuteOption
func ExecuteTracer(t tracer.Tracer) ExecuteOption {
return func(o *ExecuteOptions) {
o.Tracer = t
}
}
// ExecuteLogger pass logger.Logger to ExecuteOption
func ExecuteLogger(l logger.Logger) ExecuteOption {
return func(o *ExecuteOptions) {
o.Logger = l
}
}
// ExecuteMeter pass meter.Meter to ExecuteOption
func ExecuteMeter(m meter.Meter) ExecuteOption {
return func(o *ExecuteOptions) {
o.Meter = m
}
}
// ExecuteContext pass context.Context ot ExecuteOption
func ExecuteContext(ctx context.Context) ExecuteOption {
return func(o *ExecuteOptions) {
o.Context = ctx
}
}
// ExecuteReverse says that dag must be run in reverse order
func ExecuteReverse(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Reverse = b
}
}
// ExecuteTimeout pass timeout time.Duration for execution
func ExecuteTimeout(td time.Duration) ExecuteOption {
return func(o *ExecuteOptions) {
o.Timeout = td
}
}
// ExecuteAsync says that caller does not wait for execution complete
func ExecuteAsync(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Async = b
}
}
// NewExecuteOptions create new ExecuteOptions struct
func NewExecuteOptions(opts ...options.Option) ExecuteOptions {
func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
options := ExecuteOptions{
Client: client.DefaultClient,
Logger: logger.DefaultLogger,
@ -110,8 +211,11 @@ type StepOptions struct {
Requires []string
}
// StepOption func signature
type StepOption func(*StepOptions)
// NewStepOptions create new StepOptions struct
func NewStepOptions(opts ...options.Option) StepOptions {
func NewStepOptions(opts ...StepOption) StepOptions {
options := StepOptions{
Context: context.Background(),
}
@ -121,23 +225,23 @@ func NewStepOptions(opts ...options.Option) StepOptions {
return options
}
// Requires specifies required steps
func Requires(steps ...string) options.Option {
return func(src interface{}) error {
return options.Set(src, steps, ".Requires")
// StepID sets the step id for dag
func StepID(id string) StepOption {
return func(o *StepOptions) {
o.ID = id
}
}
// Fallback set the step to run on error
func Fallback(step string) options.Option {
return func(src interface{}) error {
return options.Set(src, step, ".Fallback")
// StepRequires specifies required steps
func StepRequires(steps ...string) StepOption {
return func(o *StepOptions) {
o.Requires = steps
}
}
// ID sets the step ID
func StepID(id string) options.Option {
return func(src interface{}) error {
return options.Set(src, id, ".ID")
// StepFallback set the step to run on error
func StepFallback(step string) StepOption {
return func(o *StepOptions) {
o.Fallback = step
}
}

View File

@ -1,4 +1,4 @@
package fsm // import "go.unistack.org/micro/v4/fsm"
package fsm // import "go.unistack.org/micro/v3/fsm"
import (
"context"

View File

@ -5,7 +5,7 @@ import (
"fmt"
"testing"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v3/logger"
)
func TestFSMStart(t *testing.T) {
@ -17,7 +17,7 @@ func TestFSMStart(t *testing.T) {
wrapper := func(next StateFunc) StateFunc {
return func(sctx context.Context, s State, opts ...StateOption) (State, error) {
sctx = logger.NewContext(sctx, logger.Attrs("state", s.Name()))
sctx = logger.NewContext(sctx, logger.Fields("state", s.Name()))
return next(sctx, s, opts...)
}
}

14
go.mod
View File

@ -1,20 +1,20 @@
module go.unistack.org/micro/v4
module go.unistack.org/micro/v3
go 1.20
require (
dario.cat/mergo v1.0.0
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/google/uuid v1.6.0
github.com/google/uuid v1.3.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
golang.org/x/sync v0.6.0
golang.org/x/sys v0.16.0
google.golang.org/grpc v1.62.1
google.golang.org/protobuf v1.32.0
golang.org/x/sync v0.3.0
google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.31.0
)
require (
github.com/golang/protobuf v1.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
golang.org/x/net v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect
)

30
go.sum
View File

@ -6,28 +6,28 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 h1:G/FZtUu7a6NTWl3KUHMV9jkLAh/Rvtf03NWMHaEDl+E=
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -4,6 +4,17 @@ import "context"
type loggerKey struct{}
// MustContext returns logger from passed context or DefaultLogger if empty
func MustContext(ctx context.Context) Logger {
if ctx == nil {
return DefaultLogger
}
if l, ok := ctx.Value(loggerKey{}).(Logger); ok && l != nil {
return l
}
return DefaultLogger
}
// FromContext returns logger from passed context
func FromContext(ctx context.Context) (Logger, bool) {
if ctx == nil {
@ -20,3 +31,13 @@ func NewContext(ctx context.Context, l Logger) context.Context {
}
return context.WithValue(ctx, loggerKey{}, l)
}
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@ -40,3 +40,14 @@ func TestNewContext(t *testing.T) {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@ -1,10 +1,8 @@
// Package logger provides a log interface
package logger
package logger // import "go.unistack.org/micro/v3/logger"
import (
"context"
"go.unistack.org/micro/v4/options"
)
type ContextAttrFunc func(ctx context.Context) []interface{}
@ -13,7 +11,7 @@ var DefaultContextAttrFuncs []ContextAttrFunc
var (
// DefaultLogger variable
DefaultLogger = NewLogger()
DefaultLogger Logger = NewLogger()
// DefaultLevel used by logger
DefaultLevel = InfoLevel
// DefaultCallerSkipCount used by logger
@ -23,78 +21,155 @@ var (
// Logger is a generic logging interface
type Logger interface {
// Init initialises options
Init(opts ...options.Option) error
Init(opts ...Option) error
// Clone create logger copy with new options
Clone(opts ...options.Option) Logger
Clone(opts ...Option) Logger
// V compare provided verbosity level with current log level
V(level Level) bool
// Level sets the log level for logger
Level(level Level)
// The Logger options
Options() Options
// Attrs set attrs to always be logged with keyval pairs
Attrs(attrs ...interface{}) Logger
// Fields set fields to always be logged with keyval pairs
Fields(fields ...interface{}) Logger
// Info level message
Info(ctx context.Context, msg string, attrs ...interface{})
// Tracef level message
Trace(ctx context.Context, msg string, attrs ...interface{})
Info(ctx context.Context, args ...interface{})
// Trace level message
Trace(ctx context.Context, args ...interface{})
// Debug level message
Debug(ctx context.Context, msg string, attrs ...interface{})
Debug(ctx context.Context, args ...interface{})
// Warn level message
Warn(ctx context.Context, msg string, attrs ...interface{})
Warn(ctx context.Context, args ...interface{})
// Error level message
Error(ctx context.Context, msg string, attrs ...interface{})
Error(ctx context.Context, args ...interface{})
// Fatal level message
Fatal(ctx context.Context, msg string, attrs ...interface{})
Fatal(ctx context.Context, args ...interface{})
// Infof level message
Infof(ctx context.Context, msg string, args ...interface{})
// Tracef level message
Tracef(ctx context.Context, msg string, args ...interface{})
// Debug level message
Debugf(ctx context.Context, msg string, args ...interface{})
// Warn level message
Warnf(ctx context.Context, msg string, args ...interface{})
// Error level message
Errorf(ctx context.Context, msg string, args ...interface{})
// Fatal level message
Fatalf(ctx context.Context, msg string, args ...interface{})
// Log logs message with needed level
Log(ctx context.Context, level Level, msg string, attrs ...interface{})
// String returns the type name of logger
String() string
// String returns the name of logger
Log(ctx context.Context, level Level, args ...interface{})
// Logf logs message with needed level
Logf(ctx context.Context, level Level, msg string, args ...interface{})
// Name returns broker instance name
Name() string
// String returns the type of logger
String() string
}
// Info writes formatted msg to default logger on info level
func Info(ctx context.Context, msg string, attrs ...interface{}) {
DefaultLogger.Info(ctx, msg, attrs...)
// Field contains keyval pair
type Field interface{}
// Info writes msg to default logger on info level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Info(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Info(ctx, args...)
}
// Error writes formatted msg to default logger on error level
func Error(ctx context.Context, msg string, attrs ...interface{}) {
DefaultLogger.Error(ctx, msg, attrs...)
// Error writes msg to default logger on error level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Error(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Error(ctx, args...)
}
// Debug writes formatted msg to default logger on debug level
func Debug(ctx context.Context, msg string, attrs ...interface{}) {
DefaultLogger.Debug(ctx, msg, attrs...)
// Debug writes msg to default logger on debug level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Debug(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Debug(ctx, args...)
}
// Warn writes formatted msg to default logger on warn level
func Warn(ctx context.Context, msg string, attrs ...interface{}) {
DefaultLogger.Warn(ctx, msg, attrs...)
// Warn writes msg to default logger on warn level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Warn(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Warn(ctx, args...)
}
// Trace writes formatted msg to default logger on trace level
func Trace(ctx context.Context, msg string, attrs ...interface{}) {
DefaultLogger.Trace(ctx, msg, attrs...)
// Trace writes msg to default logger on trace level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Trace(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Trace(ctx, args...)
}
// Fatal writes formatted msg to default logger on fatal level
func Fatal(ctx context.Context, msg string, attrs ...interface{}) {
DefaultLogger.Fatal(ctx, msg, attrs...)
// Fatal writes msg to default logger on fatal level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Fatal(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Fatal(ctx, args...)
}
// Infof writes formatted msg to default logger on info level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Infof(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Infof(ctx, msg, args...)
}
// Errorf writes formatted msg to default logger on error level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Errorf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Errorf(ctx, msg, args...)
}
// Debugf writes formatted msg to default logger on debug level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Debugf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Debugf(ctx, msg, args...)
}
// Warnf writes formatted msg to default logger on warn level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Warnf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Warnf(ctx, msg, args...)
}
// Tracef writes formatted msg to default logger on trace level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Tracef(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Tracef(ctx, msg, args...)
}
// Fatalf writes formatted msg to default logger on fatal level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Fatalf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Fatalf(ctx, msg, args...)
}
// V returns true if passed level enabled in default logger
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func V(level Level) bool {
return DefaultLogger.V(level)
}
// Init initialize default logger
func Init(opts ...options.Option) error {
// Init initialize logger
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Init(opts ...Option) error {
return DefaultLogger.Init(opts...)
}
// Attrs create default logger with specific attrs
func Attrs(attrs ...interface{}) Logger {
return DefaultLogger.Attrs(attrs...)
// Fields create logger with specific fields
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Fields(fields ...interface{}) Logger {
return DefaultLogger.Fields(fields...)
}

View File

@ -2,34 +2,36 @@ package logger
import (
"context"
"go.unistack.org/micro/v4/options"
)
type noopLogger struct {
opts Options
}
func NewLogger(opts ...options.Option) Logger {
func NewLogger(opts ...Option) Logger {
options := NewOptions(opts...)
return &noopLogger{opts: options}
}
func (l *noopLogger) V(lvl Level) bool {
func (l *noopLogger) V(_ Level) bool {
return false
}
func (l *noopLogger) Level(lvl Level) {
func (l *noopLogger) Level(_ Level) {
}
func (l *noopLogger) Init(opts ...options.Option) error {
func (l *noopLogger) Name() string {
return l.opts.Name
}
func (l *noopLogger) Init(opts ...Option) error {
for _, o := range opts {
o(&l.opts)
}
return nil
}
func (l *noopLogger) Clone(opts ...options.Option) Logger {
func (l *noopLogger) Clone(opts ...Option) Logger {
nl := &noopLogger{opts: l.opts}
for _, o := range opts {
o(&nl.opts)
@ -37,7 +39,7 @@ func (l *noopLogger) Clone(opts ...options.Option) Logger {
return nl
}
func (l *noopLogger) Attrs(attrs ...interface{}) Logger {
func (l *noopLogger) Fields(_ ...interface{}) Logger {
return l
}
@ -45,31 +47,48 @@ func (l *noopLogger) Options() Options {
return l.opts
}
func (l *noopLogger) Name() string {
return l.opts.Name
}
func (l *noopLogger) String() string {
return "noop"
}
func (l *noopLogger) Log(ctx context.Context, lvl Level, msg string, attrs ...interface{}) {
func (l *noopLogger) Log(ctx context.Context, lvl Level, attrs ...interface{}) {
}
func (l *noopLogger) Info(ctx context.Context, msg string, attrs ...interface{}) {
func (l *noopLogger) Info(ctx context.Context, attrs ...interface{}) {
}
func (l *noopLogger) Debug(ctx context.Context, msg string, attrs ...interface{}) {
func (l *noopLogger) Debug(ctx context.Context, attrs ...interface{}) {
}
func (l *noopLogger) Error(ctx context.Context, msg string, attrs ...interface{}) {
func (l *noopLogger) Error(ctx context.Context, attrs ...interface{}) {
}
func (l *noopLogger) Trace(ctx context.Context, msg string, attrs ...interface{}) {
func (l *noopLogger) Trace(ctx context.Context, attrs ...interface{}) {
}
func (l *noopLogger) Warn(ctx context.Context, msg string, attrs ...interface{}) {
func (l *noopLogger) Warn(ctx context.Context, attrs ...interface{}) {
}
func (l *noopLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
func (l *noopLogger) Fatal(ctx context.Context, attrs ...interface{}) {
}
func (l *noopLogger) Logf(ctx context.Context, lvl Level, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Infof(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Debugf(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Errorf(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Tracef(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Warnf(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Fatalf(ctx context.Context, msg string, attrs ...interface{}) {
}

View File

@ -5,239 +5,192 @@ import (
"io"
"log/slog"
"os"
"reflect"
"time"
"go.unistack.org/micro/v4/options"
rutil "go.unistack.org/micro/v4/util/reflect"
"go.unistack.org/micro/v3/meter"
)
// Option func signature
type Option func(*Options)
// Options holds logger options
type Options struct {
// Out holds the output writer
Out io.Writer
// Context holds exernal options
Context context.Context
// TimeFunc used to obtain current time
TimeFunc func() time.Time
// TimeKey is the key used for the time of the log call
TimeKey string
// Name holds the logger name
Name string
// Fields holds additional metadata
Fields []interface{}
// CallerSkipCount number of frmaes to skip
CallerSkipCount int
// ContextAttrFuncs contains funcs that executed before log func on context
ContextAttrFuncs []ContextAttrFunc
// TimeKey is the key used for the time of the log call
TimeKey string
// LevelKey is the key used for the level of the log call
LevelKey string
// ErroreKey is the key used for the error of the log call
ErrorKey string
// MessageKey is the key used for the message of the log call
MessageKey string
// ErrorKey is the key used for the error info
ErrorKey string
// SourceKey is the key used for the source file and line of the log call
SourceKey string
// StacktraceKey is the key used for the stacktrace
StacktraceKey string
// Attrs holds additional attributes
Attrs []interface{}
// ContextAttrFuncs contains funcs that executed before log func on context
ContextAttrFuncs []ContextAttrFunc
// CallerSkipCount number of frmaes to skip
CallerSkipCount int
// The logging level the logger should log
Level Level
// AddStacktrace controls writing of stacktaces on error
AddStacktrace bool
// AddSource enabled writing source file and position in log
AddSource bool
// The logging level the logger should log
Level Level
// TimeFunc used to obtain current time
TimeFunc func() time.Time
// Meter used to count logs for specific level
Meter meter.Meter
}
// NewOptions creates new options struct
func NewOptions(opts ...options.Option) Options {
func NewOptions(opts ...Option) Options {
options := Options{
Level: DefaultLevel,
Attrs: make([]interface{}, 0, 6),
Fields: make([]interface{}, 0, 6),
Out: os.Stderr,
CallerSkipCount: DefaultCallerSkipCount,
Context: context.Background(),
ContextAttrFuncs: DefaultContextAttrFuncs,
AddSource: true,
TimeFunc: time.Now,
Meter: meter.DefaultMeter,
}
_ = WithMicroKeys()(&options)
WithMicroKeys()(&options)
for _, o := range opts {
_ = o(&options)
o(&options)
}
return options
}
// WithContextAttrFuncs appends default funcs for the context arrts filler
func WithContextAttrFuncs(fncs ...ContextAttrFunc) options.Option {
return func(src interface{}) error {
v, err := options.Get(src, ".ContextAttrFuncs")
if err != nil {
return err
} else if rutil.IsZero(v) {
v = reflect.MakeSlice(reflect.TypeOf(v), 0, len(fncs)).Interface()
}
cv := reflect.ValueOf(v)
for _, l := range fncs {
cv = reflect.Append(cv, reflect.ValueOf(l))
}
return options.Set(src, cv.Interface(), ".ContextAttrFuncs")
func WithContextAttrFuncs(fncs ...ContextAttrFunc) Option {
return func(o *Options) {
o.ContextAttrFuncs = append(o.ContextAttrFuncs, fncs...)
}
}
// WithAttrs set default fields for the logger
func WithAttrs(attrs ...interface{}) options.Option {
return func(src interface{}) error {
return options.Set(src, attrs, ".Attrs")
// WithFields set default fields for the logger
func WithFields(fields ...interface{}) Option {
return func(o *Options) {
o.Fields = fields
}
}
// WithLevel set default level for the logger
func WithLevel(lvl Level) options.Option {
return func(src interface{}) error {
return options.Set(src, lvl, ".Level")
func WithLevel(level Level) Option {
return func(o *Options) {
o.Level = level
}
}
// WithOutput set default output writer for the logger
func WithOutput(out io.Writer) options.Option {
return func(src interface{}) error {
return options.Set(src, out, ".Out")
func WithOutput(out io.Writer) Option {
return func(o *Options) {
o.Out = out
}
}
// WithCallerSkipCount set frame count to skip
func WithCallerSkipCount(c int) options.Option {
return func(src interface{}) error {
return options.Set(src, c, ".CallerSkipCount")
}
}
func WithZapKeys() options.Option {
return func(src interface{}) error {
var err error
if err = options.Set(src, "@timestamp", ".TimeKey"); err != nil {
return err
}
if err = options.Set(src, "level", ".LevelKey"); err != nil {
return err
}
if err = options.Set(src, "msg", ".MessageKey"); err != nil {
return err
}
if err = options.Set(src, "caller", ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
if err = options.Set(src, "error", ".ErrorKey"); err != nil {
return err
}
return nil
}
}
func WithZerologKeys() options.Option {
return func(src interface{}) error {
var err error
if err = options.Set(src, "time", ".TimeKey"); err != nil {
return err
}
if err = options.Set(src, "level", ".LevelKey"); err != nil {
return err
}
if err = options.Set(src, "message", ".MessageKey"); err != nil {
return err
}
if err = options.Set(src, "caller", ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
if err = options.Set(src, "error", ".ErrorKey"); err != nil {
return err
}
return nil
}
}
func WithSlogKeys() options.Option {
return func(src interface{}) error {
var err error
if err = options.Set(src, slog.TimeKey, ".TimeKey"); err != nil {
return err
}
if err = options.Set(src, slog.LevelKey, ".LevelKey"); err != nil {
return err
}
if err = options.Set(src, slog.MessageKey, ".MessageKey"); err != nil {
return err
}
if err = options.Set(src, slog.SourceKey, ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
if err = options.Set(src, "error", ".ErrorKey"); err != nil {
return err
}
return nil
}
}
func WithMicroKeys() options.Option {
return func(src interface{}) error {
var err error
if err = options.Set(src, "timestamp", ".TimeKey"); err != nil {
return err
}
if err = options.Set(src, "level", ".LevelKey"); err != nil {
return err
}
if err = options.Set(src, "msg", ".MessageKey"); err != nil {
return err
}
if err = options.Set(src, "caller", ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
if err = options.Set(src, "error", ".ErrorKey"); err != nil {
return err
}
return nil
}
}
// WithAddCallerSkipCount add skip count for copy logger
func WithAddCallerSkipCount(n int) options.Option {
return func(src interface{}) error {
c, err := options.Get(src, ".CallerSkipCount")
if err != nil {
return err
}
if err = options.Set(src, c.(int)+n, ".CallerSkipCount"); err != nil {
return err
}
return nil
}
}
// WithAddStacktrace controls writing stacktrace on error
func WithAddStacktrace(v bool) options.Option {
return func(src interface{}) error {
return options.Set(src, v, ".AddStacktrace")
// WitAddStacktrace controls writing stacktrace on error
func WithAddStacktrace(v bool) Option {
return func(o *Options) {
o.AddStacktrace = v
}
}
// WitAddSource controls writing source file and pos in log
func WithAddSource(v bool) options.Option {
return func(src interface{}) error {
return options.Set(src, v, ".AddSource")
func WithAddSource(v bool) Option {
return func(o *Options) {
o.AddSource = v
}
}
// WithCallerSkipCount set frame count to skip
func WithCallerSkipCount(c int) Option {
return func(o *Options) {
o.CallerSkipCount = c
}
}
// WithContext set context
func WithContext(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// WithName sets the name
func WithName(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// WithTimeFunc sets the func to obtain current time
func WithTimeFunc(fn func() time.Time) Option {
return func(o *Options) {
o.TimeFunc = fn
}
}
func WithZapKeys() Option {
return func(o *Options) {
o.TimeKey = "@timestamp"
o.LevelKey = "level"
o.MessageKey = "msg"
o.SourceKey = "caller"
o.StacktraceKey = "stacktrace"
o.ErrorKey = "error"
}
}
func WithZerologKeys() Option {
return func(o *Options) {
o.TimeKey = "time"
o.LevelKey = "level"
o.MessageKey = "message"
o.SourceKey = "caller"
o.StacktraceKey = "stacktrace"
o.ErrorKey = "error"
}
}
func WithSlogKeys() Option {
return func(o *Options) {
o.TimeKey = slog.TimeKey
o.LevelKey = slog.LevelKey
o.MessageKey = slog.MessageKey
o.SourceKey = slog.SourceKey
o.StacktraceKey = "stacktrace"
o.ErrorKey = "error"
}
}
func WithMicroKeys() Option {
return func(o *Options) {
o.TimeKey = "timestamp"
o.LevelKey = "level"
o.MessageKey = "msg"
o.SourceKey = "caller"
o.StacktraceKey = "stacktrace"
o.ErrorKey = "error"
}
}
// WithAddCallerSkipCount add skip count for copy logger
func WithAddCallerSkipCount(n int) Option {
return func(o *Options) {
o.CallerSkipCount += n
}
}

View File

@ -2,6 +2,7 @@ package slog
import (
"context"
"fmt"
"log/slog"
"os"
"regexp"
@ -9,9 +10,9 @@ import (
"strconv"
"sync"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/tracer"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/semconv"
"go.unistack.org/micro/v3/tracer"
)
var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`)
@ -67,13 +68,13 @@ type slogLogger struct {
mu sync.RWMutex
}
func (s *slogLogger) Clone(opts ...options.Option) logger.Logger {
func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger {
s.mu.RLock()
options := s.opts
s.mu.RUnlock()
for _, o := range opts {
_ = o(&options)
o(&options)
}
l := &slogLogger{
@ -87,7 +88,7 @@ func (s *slogLogger) Clone(opts ...options.Option) logger.Logger {
AddSource: l.opts.AddSource,
}
l.leveler.Set(loggerToSlogLevel(l.opts.Level))
l.handler = slog.New(slog.NewJSONHandler(options.Out, handleOpt)).With(options.Attrs...).Handler()
l.handler = slog.New(slog.NewJSONHandler(options.Out, handleOpt)).With(options.Fields...).Handler()
return l
}
@ -104,7 +105,7 @@ func (s *slogLogger) Options() logger.Options {
return s.opts
}
func (s *slogLogger) Attrs(attrs ...interface{}) logger.Logger {
func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger {
s.mu.RLock()
level := s.leveler.Level()
options := s.opts
@ -120,12 +121,12 @@ func (s *slogLogger) Attrs(attrs ...interface{}) logger.Logger {
AddSource: l.opts.AddSource,
}
l.handler = slog.New(slog.NewJSONHandler(s.opts.Out, handleOpt)).With(attrs...).Handler()
l.handler = slog.New(slog.NewJSONHandler(l.opts.Out, handleOpt)).With(attrs...).Handler()
return l
}
func (s *slogLogger) Init(opts ...options.Option) error {
func (s *slogLogger) Init(opts ...logger.Option) error {
s.mu.Lock()
if len(s.opts.ContextAttrFuncs) == 0 {
@ -133,9 +134,7 @@ func (s *slogLogger) Init(opts ...options.Option) error {
}
for _, o := range opts {
if err := o(&s.opts); err != nil {
return err
}
o(&s.opts)
}
s.leveler = new(slog.LevelVar)
@ -145,22 +144,24 @@ func (s *slogLogger) Init(opts ...options.Option) error {
AddSource: s.opts.AddSource,
}
s.leveler.Set(loggerToSlogLevel(s.opts.Level))
s.handler = slog.New(slog.NewJSONHandler(s.opts.Out, handleOpt)).With(s.opts.Attrs...).Handler()
s.handler = slog.New(slog.NewJSONHandler(s.opts.Out, handleOpt)).With(s.opts.Fields...).Handler()
s.mu.Unlock()
return nil
}
func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, msg string, attrs ...interface{}) {
func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", lvl.String()).Inc()
if !s.V(lvl) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), loggerToSlogLevel(lvl), msg, pcs[0])
r := slog.NewRecord(s.opts.TimeFunc(), loggerToSlogLevel(lvl), fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
@ -176,7 +177,7 @@ func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, msg string, attr
}
}
}
r.Add(attrs...)
r.Add(attrs[1:]...)
r.Attrs(func(a slog.Attr) bool {
if a.Key == s.opts.ErrorKey {
if span, ok := tracer.SpanFromContext(ctx); ok {
@ -189,7 +190,70 @@ func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, msg string, attr
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Info(ctx context.Context, msg string, attrs ...interface{}) {
func (s *slogLogger) Logf(ctx context.Context, lvl logger.Level, msg string, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", lvl.String()).Inc()
if !s.V(lvl) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), loggerToSlogLevel(lvl), msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, (slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1])))
}
}
}
r.Add(attrs[1:]...)
r.Attrs(func(a slog.Attr) bool {
if a.Key == s.opts.ErrorKey {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, a.Value.String())
return false
}
}
return true
})
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Info(ctx context.Context, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.InfoLevel.String()).Inc()
if !s.V(logger.InfoLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelInfo, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Infof(ctx context.Context, msg string, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.InfoLevel.String()).Inc()
if !s.V(logger.InfoLevel) {
return
}
@ -199,6 +263,7 @@ func (s *slogLogger) Info(ctx context.Context, msg string, attrs ...interface{})
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
@ -209,7 +274,30 @@ func (s *slogLogger) Info(ctx context.Context, msg string, attrs ...interface{})
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Debug(ctx context.Context, msg string, attrs ...interface{}) {
func (s *slogLogger) Debug(ctx context.Context, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.DebugLevel.String()).Inc()
if !s.V(logger.DebugLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelDebug, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Debugf(ctx context.Context, msg string, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.DebugLevel.String()).Inc()
if !s.V(logger.DebugLevel) {
return
}
@ -219,6 +307,7 @@ func (s *slogLogger) Debug(ctx context.Context, msg string, attrs ...interface{}
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
@ -229,7 +318,30 @@ func (s *slogLogger) Debug(ctx context.Context, msg string, attrs ...interface{}
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Trace(ctx context.Context, msg string, attrs ...interface{}) {
func (s *slogLogger) Trace(ctx context.Context, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.TraceLevel.String()).Inc()
if !s.V(logger.TraceLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelDebug-1, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Tracef(ctx context.Context, msg string, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.TraceLevel.String()).Inc()
if !s.V(logger.TraceLevel) {
return
}
@ -239,26 +351,29 @@ func (s *slogLogger) Trace(ctx context.Context, msg string, attrs ...interface{}
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs...)
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}) {
func (s *slogLogger) Error(ctx context.Context, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.ErrorLevel.String()).Inc()
if !s.V(logger.ErrorLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelError, msg, pcs[0])
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelError, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
@ -270,7 +385,47 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1]))
attrs = append(attrs, slog.String("stacktrace", traceLines[len(traceLines)-1]))
}
}
}
r.Add(attrs[1:]...)
r.Attrs(func(a slog.Attr) bool {
if a.Key == s.opts.ErrorKey {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, a.Value.String())
return false
}
}
return true
})
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Errorf(ctx context.Context, msg string, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.ErrorLevel.String()).Inc()
if !s.V(logger.ErrorLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelError, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
if s.opts.AddStacktrace {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, slog.String("stacktrace", traceLines[len(traceLines)-1]))
}
}
}
@ -287,7 +442,31 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
func (s *slogLogger) Fatal(ctx context.Context, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.FatalLevel.String()).Inc()
if !s.V(logger.FatalLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelError+1, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
os.Exit(1)
}
func (s *slogLogger) Fatalf(ctx context.Context, msg string, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.FatalLevel.String()).Inc()
if !s.V(logger.FatalLevel) {
return
}
@ -297,6 +476,7 @@ func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
@ -308,7 +488,30 @@ func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}
os.Exit(1)
}
func (s *slogLogger) Warn(ctx context.Context, msg string, attrs ...interface{}) {
func (s *slogLogger) Warn(ctx context.Context, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.WarnLevel.String()).Inc()
if !s.V(logger.WarnLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelWarn, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Warnf(ctx context.Context, msg string, attrs ...interface{}) {
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", logger.WarnLevel.String()).Inc()
if !s.V(logger.WarnLevel) {
return
}
@ -318,13 +521,14 @@ func (s *slogLogger) Warn(ctx context.Context, msg string, attrs ...interface{})
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs...)
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
}
@ -336,11 +540,12 @@ func (s *slogLogger) String() string {
return "slog"
}
func NewLogger(opts ...options.Option) logger.Logger {
l := &slogLogger{
func NewLogger(opts ...logger.Option) logger.Logger {
s := &slogLogger{
opts: logger.NewOptions(opts...),
}
return l
return s
}
func loggerToSlogLevel(level logger.Level) slog.Level {

View File

@ -7,7 +7,7 @@ import (
"log"
"testing"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v3/logger"
)
func TestError(t *testing.T) {
@ -17,10 +17,31 @@ func TestError(t *testing.T) {
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Error(ctx, "msg", fmt.Errorf("message"))
l.Error(ctx, "message", fmt.Errorf("error message"))
if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) {
t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestErrorf(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf), logger.WithAddStacktrace(true))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Errorf(ctx, "message", fmt.Errorf("error message"))
if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) {
t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestContext(t *testing.T) {
@ -31,7 +52,7 @@ func TestContext(t *testing.T) {
t.Fatal(err)
}
nl, ok := logger.FromContext(logger.NewContext(ctx, l.Attrs("key", "val")))
nl, ok := logger.FromContext(logger.NewContext(ctx, l.Fields("key", "val")))
if !ok {
t.Fatal("context without logger")
}
@ -41,7 +62,7 @@ func TestContext(t *testing.T) {
}
}
func TestAttrs(t *testing.T) {
func TestFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
@ -49,7 +70,7 @@ func TestAttrs(t *testing.T) {
t.Fatal(err)
}
nl := l.Attrs("key", "val")
nl := l.Fields("key", "val")
nl.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
@ -65,7 +86,7 @@ func TestFromContextWithFields(t *testing.T) {
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Attrs("key", "val")
nl := l.Fields("key", "val")
ctx = logger.NewContext(ctx, nl)
@ -137,7 +158,7 @@ func TestLogger(t *testing.T) {
}
l.Trace(ctx, "trace_msg1")
l.Warn(ctx, "warn_msg1")
l.Attrs("error", "test").Info(ctx, "error message")
l.Fields("error", "test").Info(ctx, "error message")
l.Warn(ctx, "first second")
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"trace"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"trace_msg1"`))) {

View File

@ -12,7 +12,7 @@ type stdLogger struct {
// NewStdLogger returns new *log.Logger baked by logger.Logger implementation
func NewStdLogger(l Logger, level Level) *log.Logger {
return log.New(&stdLogger{l: l.Clone(WithCallerSkipCount(l.Options().CallerSkipCount + 1)), level: level}, "" /* prefix */, 0 /* flags */)
return log.New(&stdLogger{l: l, level: level}, "" /* prefix */, 0 /* flags */)
}
func (sl *stdLogger) Write(p []byte) (int, error) {

View File

@ -8,7 +8,7 @@ import (
"strconv"
"strings"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v3/codec"
)
const sf = "0-+# "
@ -56,9 +56,9 @@ type Wrapper struct {
s fmt.State
pointers map[uintptr]int
opts *Options
takeMap map[int]bool
depth int
ignoreNextType bool
takeMap map[int]bool
protoWrapperType bool
sqlWrapperType bool
}

View File

@ -5,7 +5,7 @@ import (
"strings"
"testing"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v3/codec"
)
func TestUnwrap(t *testing.T) {

399
logger/wrapper/wrapper.go Normal file
View File

@ -0,0 +1,399 @@
// Package wrapper provides wrapper for Logger
package wrapper
import (
"context"
"fmt"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/server"
)
var (
// DefaultClientCallObserver called by wrapper in client Call
DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []client.CallOption, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultClientStreamObserver called by wrapper in client Stream
DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []client.CallOption, stream client.Stream, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultClientPublishObserver called by wrapper in client Publish
DefaultClientPublishObserver = func(ctx context.Context, msg client.Message, opts []client.PublishOption, err error) []string {
labels := []string{"endpoint", msg.Topic()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultServerHandlerObserver called by wrapper in server Handler
DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultServerSubscriberObserver called by wrapper in server Subscriber
DefaultServerSubscriberObserver = func(ctx context.Context, msg server.Message, err error) []string {
labels := []string{"endpoint", msg.Topic()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultClientCallFuncObserver called by wrapper in client CallFunc
DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultSkipEndpoints wrapper not called for this endpoints
DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"}
)
type lWrapper struct {
client.Client
serverHandler server.HandlerFunc
serverSubscriber server.SubscriberFunc
clientCallFunc client.CallFunc
opts Options
}
type (
// ClientCallObserver func signature
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
// ClientStreamObserver func signature
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
// ClientPublishObserver func signature
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
// ClientCallFuncObserver func signature
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
// ServerHandlerObserver func signature
ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
// ServerSubscriberObserver func signature
ServerSubscriberObserver func(context.Context, server.Message, error) []string
)
// Options struct for wrapper
type Options struct {
// Logger that used for log
Logger logger.Logger
// ServerHandlerObservers funcs
ServerHandlerObservers []ServerHandlerObserver
// ServerSubscriberObservers funcs
ServerSubscriberObservers []ServerSubscriberObserver
// ClientCallObservers funcs
ClientCallObservers []ClientCallObserver
// ClientStreamObservers funcs
ClientStreamObservers []ClientStreamObserver
// ClientPublishObservers funcs
ClientPublishObservers []ClientPublishObserver
// ClientCallFuncObservers funcs
ClientCallFuncObservers []ClientCallFuncObserver
// SkipEndpoints
SkipEndpoints []string
// Level for logger
Level logger.Level
// Enabled flag
Enabled bool
}
// Option func signature
type Option func(*Options)
// NewOptions creates Options from Option slice
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
Level: logger.TraceLevel,
ClientCallObservers: []ClientCallObserver{DefaultClientCallObserver},
ClientStreamObservers: []ClientStreamObserver{DefaultClientStreamObserver},
ClientPublishObservers: []ClientPublishObserver{DefaultClientPublishObserver},
ClientCallFuncObservers: []ClientCallFuncObserver{DefaultClientCallFuncObserver},
ServerHandlerObservers: []ServerHandlerObserver{DefaultServerHandlerObserver},
ServerSubscriberObservers: []ServerSubscriberObserver{DefaultServerSubscriberObserver},
SkipEndpoints: DefaultSkipEndpoints,
}
for _, o := range opts {
o(&options)
}
return options
}
// WithEnabled enable/diable flag
func WithEnabled(b bool) Option {
return func(o *Options) {
o.Enabled = b
}
}
// WithLevel log level
func WithLevel(l logger.Level) Option {
return func(o *Options) {
o.Level = l
}
}
// WithLogger logger
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// WithClientCallObservers funcs
func WithClientCallObservers(ob ...ClientCallObserver) Option {
return func(o *Options) {
o.ClientCallObservers = ob
}
}
// WithClientStreamObservers funcs
func WithClientStreamObservers(ob ...ClientStreamObserver) Option {
return func(o *Options) {
o.ClientStreamObservers = ob
}
}
// WithClientPublishObservers funcs
func WithClientPublishObservers(ob ...ClientPublishObserver) Option {
return func(o *Options) {
o.ClientPublishObservers = ob
}
}
// WithClientCallFuncObservers funcs
func WithClientCallFuncObservers(ob ...ClientCallFuncObserver) Option {
return func(o *Options) {
o.ClientCallFuncObservers = ob
}
}
// WithServerHandlerObservers funcs
func WithServerHandlerObservers(ob ...ServerHandlerObserver) Option {
return func(o *Options) {
o.ServerHandlerObservers = ob
}
}
// WithServerSubscriberObservers funcs
func WithServerSubscriberObservers(ob ...ServerSubscriberObserver) Option {
return func(o *Options) {
o.ServerSubscriberObservers = ob
}
}
// SkipEndpoins
func SkipEndpoints(eps ...string) Option {
return func(o *Options) {
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
}
}
func (l *lWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
err := l.Client.Call(ctx, req, rsp, opts...)
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
var labels []string
for _, o := range l.opts.ClientCallObservers {
labels = append(labels, o(ctx, req, rsp, opts, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
func (l *lWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
stream, err := l.Client.Stream(ctx, req, opts...)
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return stream, err
}
}
if !l.opts.Enabled {
return stream, err
}
var labels []string
for _, o := range l.opts.ClientStreamObservers {
labels = append(labels, o(ctx, req, opts, stream, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return stream, err
}
func (l *lWrapper) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error {
err := l.Client.Publish(ctx, msg, opts...)
endpoint := msg.Topic()
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
var labels []string
for _, o := range l.opts.ClientPublishObservers {
labels = append(labels, o(ctx, msg, opts, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
func (l *lWrapper) ServerHandler(ctx context.Context, req server.Request, rsp interface{}) error {
err := l.serverHandler(ctx, req, rsp)
endpoint := req.Endpoint()
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
var labels []string
for _, o := range l.opts.ServerHandlerObservers {
labels = append(labels, o(ctx, req, rsp, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
func (l *lWrapper) ServerSubscriber(ctx context.Context, msg server.Message) error {
err := l.serverSubscriber(ctx, msg)
endpoint := msg.Topic()
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
var labels []string
for _, o := range l.opts.ServerSubscriberObservers {
labels = append(labels, o(ctx, msg, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
// NewClientWrapper accepts an open options and returns a Client Wrapper
func NewClientWrapper(opts ...Option) client.Wrapper {
return func(c client.Client) client.Client {
options := NewOptions()
for _, o := range opts {
o(&options)
}
return &lWrapper{opts: options, Client: c}
}
}
// NewClientCallWrapper accepts an options and returns a Call Wrapper
func NewClientCallWrapper(opts ...Option) client.CallWrapper {
return func(h client.CallFunc) client.CallFunc {
options := NewOptions()
for _, o := range opts {
o(&options)
}
l := &lWrapper{opts: options, clientCallFunc: h}
return l.ClientCallFunc
}
}
func (l *lWrapper) ClientCallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
err := l.clientCallFunc(ctx, addr, req, rsp, opts)
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
var labels []string
for _, o := range l.opts.ClientCallFuncObservers {
labels = append(labels, o(ctx, addr, req, rsp, opts, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
// NewServerHandlerWrapper accepts an options and returns a Handler Wrapper
func NewServerHandlerWrapper(opts ...Option) server.HandlerWrapper {
return func(h server.HandlerFunc) server.HandlerFunc {
options := NewOptions()
for _, o := range opts {
o(&options)
}
l := &lWrapper{opts: options, serverHandler: h}
return l.ServerHandler
}
}
// NewServerSubscriberWrapper accepts an options and returns a Subscriber Wrapper
func NewServerSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
return func(h server.SubscriberFunc) server.SubscriberFunc {
options := NewOptions()
for _, o := range opts {
o(&options)
}
l := &lWrapper{opts: options, serverSubscriber: h}
return l.ServerSubscriber
}
}

View File

@ -17,11 +17,11 @@ func FromIncomingContext(ctx context.Context) (Metadata, bool) {
if ctx == nil {
return nil, false
}
md, ok := ctx.Value(mdIncomingKey{}).(Metadata)
if !ok || md == nil {
md, ok := ctx.Value(mdIncomingKey{}).(*rawMetadata)
if !ok || md.md == nil {
return nil, false
}
return md, ok
return md.md, ok
}
// FromOutgoingContext returns metadata from outgoing ctx
@ -30,11 +30,11 @@ func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
if ctx == nil {
return nil, false
}
md, ok := ctx.Value(mdOutgoingKey{}).(Metadata)
if !ok || md == nil {
md, ok := ctx.Value(mdOutgoingKey{}).(*rawMetadata)
if !ok || md.md == nil {
return nil, false
}
return md, ok
return md.md, ok
}
// FromContext returns metadata from the given context
@ -43,11 +43,11 @@ func FromContext(ctx context.Context) (Metadata, bool) {
if ctx == nil {
return nil, false
}
md, ok := ctx.Value(mdKey{}).(Metadata)
if !ok || md == nil {
md, ok := ctx.Value(mdKey{}).(*rawMetadata)
if !ok || md.md == nil {
return nil, false
}
return md, ok
return md.md, ok
}
// NewContext creates a new context with the given metadata
@ -55,16 +55,45 @@ func NewContext(ctx context.Context, md Metadata) context.Context {
if ctx == nil {
ctx = context.Background()
}
ctx = context.WithValue(ctx, mdKey{}, md)
ctx = context.WithValue(ctx, mdKey{}, &rawMetadata{md})
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{})
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{})
return ctx
}
// SetOutgoingContext modify outgoing context with given metadata
func SetOutgoingContext(ctx context.Context, md Metadata) bool {
if ctx == nil {
return false
}
if omd, ok := ctx.Value(mdOutgoingKey{}).(*rawMetadata); ok {
omd.md = md
return true
}
return false
}
// SetIncomingContext modify incoming context with given metadata
func SetIncomingContext(ctx context.Context, md Metadata) bool {
if ctx == nil {
return false
}
if omd, ok := ctx.Value(mdIncomingKey{}).(*rawMetadata); ok {
omd.md = md
return true
}
return false
}
// NewIncomingContext creates a new context with incoming metadata attached
func NewIncomingContext(ctx context.Context, md Metadata) context.Context {
if ctx == nil {
ctx = context.Background()
}
ctx = context.WithValue(ctx, mdIncomingKey{}, md)
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{md})
if v, ok := ctx.Value(mdOutgoingKey{}).(*rawMetadata); !ok || v == nil {
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{})
}
return ctx
}
@ -73,28 +102,41 @@ func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
if ctx == nil {
ctx = context.Background()
}
ctx = context.WithValue(ctx, mdOutgoingKey{}, md)
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{md})
if v, ok := ctx.Value(mdIncomingKey{}).(*rawMetadata); !ok || v == nil {
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{})
}
return ctx
}
// AppendOutgoingContext apends new md to context
func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
md := Pairs(kv...)
md, ok := Pairs(kv...)
if !ok {
return ctx
}
omd, ok := FromOutgoingContext(ctx)
if !ok {
return NewOutgoingContext(ctx, md)
}
nmd := Merge(omd, md, true)
return NewOutgoingContext(ctx, nmd)
for k, v := range md {
omd.Set(k, v)
}
return NewOutgoingContext(ctx, omd)
}
// AppendIncomingContext apends new md to context
func AppendIncomingContext(ctx context.Context, kv ...string) context.Context {
md := Pairs(kv...)
md, ok := Pairs(kv...)
if !ok {
return ctx
}
omd, ok := FromIncomingContext(ctx)
if !ok {
return NewIncomingContext(ctx, md)
}
nmd := Merge(omd, md, true)
return NewIncomingContext(ctx, nmd)
for k, v := range md {
omd.Set(k, v)
}
return NewIncomingContext(ctx, omd)
}

View File

@ -24,7 +24,7 @@ func TestNewNilContext(t *testing.T) {
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), mdKey{}, New(0))
ctx := context.WithValue(context.TODO(), mdKey{}, &rawMetadata{New(0)})
c, ok := FromContext(ctx)
if c == nil || !ok {
@ -42,7 +42,7 @@ func TestNewContext(t *testing.T) {
}
func TestFromIncomingContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), mdIncomingKey{}, New(0))
ctx := context.WithValue(context.TODO(), mdIncomingKey{}, &rawMetadata{New(0)})
c, ok := FromIncomingContext(ctx)
if c == nil || !ok {
@ -51,7 +51,7 @@ func TestFromIncomingContext(t *testing.T) {
}
func TestFromOutgoingContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), mdOutgoingKey{}, New(0))
ctx := context.WithValue(context.TODO(), mdOutgoingKey{}, &rawMetadata{New(0)})
c, ok := FromOutgoingContext(ctx)
if c == nil || !ok {
@ -59,6 +59,36 @@ func TestFromOutgoingContext(t *testing.T) {
}
}
func TestSetIncomingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := context.WithValue(context.TODO(), mdIncomingKey{}, &rawMetadata{})
if !SetIncomingContext(ctx, md) {
t.Fatal("SetIncomingContext not works")
}
md, ok := FromIncomingContext(ctx)
if md == nil || !ok {
t.Fatal("SetIncomingContext not works")
} else if v, ok := md.Get("key"); !ok || v != "val" {
t.Fatal("SetIncomingContext not works")
}
}
func TestSetOutgoingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := context.WithValue(context.TODO(), mdOutgoingKey{}, &rawMetadata{})
if !SetOutgoingContext(ctx, md) {
t.Fatal("SetOutgoingContext not works")
}
md, ok := FromOutgoingContext(ctx)
if md == nil || !ok {
t.Fatal("SetOutgoingContext not works")
} else if v, ok := md.Get("key"); !ok || v != "val" {
t.Fatal("SetOutgoingContext not works")
}
}
func TestNewIncomingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")

View File

@ -1,9 +1,9 @@
// Package metadata is a way of defining message headers
package metadata
package metadata // import "go.unistack.org/micro/v3/metadata"
import (
"net/textproto"
"strings"
"sort"
)
var (
@ -24,7 +24,47 @@ var (
)
// Metadata is our way of representing request headers internally.
type Metadata map[string][]string
// They're used at the RPC level and translate back and forth
// from Transport headers.
type Metadata map[string]string
type rawMetadata struct {
md Metadata
}
// defaultMetadataSize used when need to init new Metadata
var defaultMetadataSize = 2
// Iterator used to iterate over metadata with order
type Iterator struct {
md Metadata
keys []string
cur int
cnt int
}
// Next advance iterator to next element
func (iter *Iterator) Next(k, v *string) bool {
if iter.cur+1 > iter.cnt {
return false
}
*k = iter.keys[iter.cur]
*v = iter.md[*k]
iter.cur++
return true
}
// Iterator returns the itarator for metadata in sorted order
func (md Metadata) Iterator() *Iterator {
iter := &Iterator{md: md, cnt: len(md)}
iter.keys = make([]string, 0, iter.cnt)
for k := range md {
iter.keys = append(iter.keys, k)
}
sort.Strings(iter.keys)
return iter
}
// Get returns value from metadata by key
func (md Metadata) Get(key string) (string, bool) {
@ -34,7 +74,7 @@ func (md Metadata) Get(key string) (string, bool) {
// slow path
val, ok = md[textproto.CanonicalMIMEHeaderKey(key)]
}
return strings.Join(val, ","), ok
return val, ok
}
// Set is used to store value in metadata
@ -43,19 +83,10 @@ func (md Metadata) Set(kv ...string) {
kv = kv[:len(kv)-1]
}
for idx := 0; idx < len(kv); idx += 2 {
md[textproto.CanonicalMIMEHeaderKey(kv[idx])] = []string{kv[idx+1]}
md[textproto.CanonicalMIMEHeaderKey(kv[idx])] = kv[idx+1]
}
}
// Append is used to append value in metadata
func (md Metadata) Append(k string, v ...string) {
if len(v) == 0 {
return
}
k = textproto.CanonicalMIMEHeaderKey(k)
md[k] = append(md[k], v...)
}
// Del is used to remove value from metadata
func (md Metadata) Del(keys ...string) {
for _, key := range keys {
@ -68,9 +99,9 @@ func (md Metadata) Del(keys ...string) {
// Copy makes a copy of the metadata
func Copy(md Metadata, exclude ...string) Metadata {
nmd := make(Metadata, len(md))
for k, v := range md {
nmd[k] = v
nmd := New(len(md))
for key, val := range md {
nmd.Set(key, val)
}
nmd.Del(exclude...)
return nmd
@ -79,40 +110,35 @@ func Copy(md Metadata, exclude ...string) Metadata {
// New return new sized metadata
func New(size int) Metadata {
if size == 0 {
size = 2
size = defaultMetadataSize
}
return make(Metadata, size)
}
// Merge merges metadata to existing metadata, overwriting if specified
func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata {
var ok bool
nmd := Copy(omd)
for key, val := range mmd {
nval, ok := nmd[key]
_, ok = nmd[key]
switch {
case ok && overwrite:
nmd[key] = nval
continue
case ok && !overwrite:
continue
case !ok:
for _, v := range val {
if v != "" {
nval = append(nval, v)
}
}
nmd[key] = nval
case val != "":
nmd.Set(key, val)
case ok && val == "":
nmd.Del(key)
}
}
return nmd
}
// Pairs from which metadata created
func Pairs(kv ...string) Metadata {
func Pairs(kv ...string) (Metadata, bool) {
if len(kv)%2 == 1 {
kv = kv[:len(kv)-1]
return nil, false
}
md := make(Metadata, len(kv)/2)
md := New(len(kv) / 2)
md.Set(kv...)
return md
return md, true
}

View File

@ -33,52 +33,30 @@ func TestAppend(t *testing.T) {
}
func TestPairs(t *testing.T) {
md := Pairs("key1", "val1", "key2", "val2")
if _, ok := md.Get("key1"); !ok {
md, ok := Pairs("key1", "val1", "key2", "val2")
if !ok {
t.Fatal("odd number of kv")
}
if _, ok = md.Get("key1"); !ok {
t.Fatal("key1 not found")
}
}
func testIncomingCtx(ctx context.Context) {
if md, ok := FromIncomingContext(ctx); ok && md != nil {
md.Set("Key1", "Val1_new")
md.Set("Key3", "Val3")
}
func testCtx(ctx context.Context) {
md := New(2)
md.Set("Key1", "Val1_new")
md.Set("Key3", "Val3")
SetOutgoingContext(ctx, md)
}
func testOutgoingCtx(ctx context.Context) {
if md, ok := FromOutgoingContext(ctx); ok && md != nil {
md.Set("Key1", "Val1_new")
md.Set("Key3", "Val3")
}
}
func TestIncoming(t *testing.T) {
func TestPassing(t *testing.T) {
ctx := context.TODO()
md1 := New(2)
md1.Set("Key1", "Val1")
md1.Set("Key2", "Val2")
ctx = NewIncomingContext(ctx, md1)
testIncomingCtx(ctx)
md, ok := FromIncomingContext(ctx)
if !ok {
t.Fatalf("missing metadata from incoming context")
}
if v, ok := md.Get("Key1"); !ok || v != "Val1_new" {
t.Fatalf("invalid metadata value %#+v", md)
}
}
func TestOutgoing(t *testing.T) {
ctx := context.TODO()
md1 := New(2)
md1.Set("Key1", "Val1")
md1.Set("Key2", "Val2")
ctx = NewOutgoingContext(ctx, md1)
testOutgoingCtx(ctx)
testCtx(ctx)
md, ok := FromOutgoingContext(ctx)
if !ok {
t.Fatalf("missing metadata from outgoing context")
@ -90,10 +68,10 @@ func TestOutgoing(t *testing.T) {
func TestMerge(t *testing.T) {
omd := Metadata{
"key1": []string{"val1"},
"key1": "val1",
}
mmd := Metadata{
"key2": []string{"val2"},
"key2": "val2",
}
nmd := Merge(omd, mmd, true)
@ -102,6 +80,21 @@ func TestMerge(t *testing.T) {
}
}
func TestIterator(t *testing.T) {
md := Metadata{
"1Last": "last",
"2First": "first",
"3Second": "second",
}
iter := md.Iterator()
var k, v string
for iter.Next(&k, &v) {
// fmt.Printf("k: %s, v: %s\n", k, v)
}
}
func TestMedataCanonicalKey(t *testing.T) {
md := New(1)
md.Set("x-request-id", "12345")
@ -141,7 +134,10 @@ func TestMetadataSet(t *testing.T) {
}
func TestMetadataDelete(t *testing.T) {
md := Pairs("Foo", "bar", "Baz", "empty")
md := Metadata{
"Foo": "bar",
"Baz": "empty",
}
md.Del("Baz")
_, ok := md.Get("Baz")
@ -160,19 +156,24 @@ func TestNilContext(t *testing.T) {
}
func TestMetadataCopy(t *testing.T) {
md := Pairs("Foo", "bar", "Bar", "baz")
md := Metadata{
"Foo": "bar",
"Bar": "baz",
}
cp := Copy(md)
for k, v := range md {
if cv := cp[k]; len(cv) != len(v) {
if cv := cp[k]; cv != v {
t.Fatalf("Got %s:%s for %s:%s", k, cv, k, v)
}
}
}
func TestMetadataContext(t *testing.T) {
md := Pairs("Foo", "bar")
md := Metadata{
"Foo": "bar",
}
ctx := NewContext(context.TODO(), md)
@ -181,7 +182,7 @@ func TestMetadataContext(t *testing.T) {
t.Errorf("Unexpected error retrieving metadata, got %t", ok)
}
if len(emd["Foo"]) != len(md["Foo"]) {
if emd["Foo"] != md["Foo"] {
t.Errorf("Expected key: %s val: %s, got key: %s val: %s", "Foo", md["Foo"], "Foo", emd["Foo"])
}
@ -196,7 +197,7 @@ func TestCopy(t *testing.T) {
nmd := Copy(md, "key2")
if len(nmd) != 1 {
t.Fatal("Copy exclude not works")
} else if nmd["Key1"][0] != "val1" {
} else if nmd["Key1"] != "val1" {
t.Fatal("Copy exclude not works")
}
}

View File

@ -22,3 +22,13 @@ func NewContext(ctx context.Context, c Meter) context.Context {
}
return context.WithValue(ctx, meterKey{}, c)
}
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@ -40,3 +40,14 @@ func TestNewContext(t *testing.T) {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@ -1,5 +1,5 @@
// Package meter is for instrumentation
package meter // import "go.unistack.org/micro/v4/meter"
package meter
import (
"io"
@ -7,13 +7,11 @@ import (
"strconv"
"strings"
"time"
"go.unistack.org/micro/v4/options"
)
var (
// DefaultMeter is the default meter
DefaultMeter = NewMeter()
DefaultMeter Meter = NewMeter()
// DefaultAddress data will be made available on this host:port
DefaultAddress = ":9090"
// DefaultPath the meter endpoint where the Meter data will be made available
@ -26,6 +24,13 @@ var (
DefaultSummaryQuantiles = []float64{0.5, 0.9, 0.97, 0.99, 1}
// DefaultSummaryWindow is the default window for summary
DefaultSummaryWindow = 5 * time.Minute
// DefaultSkipEndpoints is the slice of endpoint that must not be metered
DefaultSkipEndpoints = []string{
"MeterService.Metrics",
"HealthService.Live",
"HealthService.Ready",
"HealthService.Version",
}
)
// Meter is an interface for collecting and instrumenting metrics
@ -33,9 +38,9 @@ type Meter interface {
// Name returns meter name
Name() string
// Init initialize meter
Init(opts ...options.Option) error
Init(opts ...Option) error
// Clone create meter copy with new options
Clone(opts ...options.Option) Meter
Clone(opts ...Option) Meter
// Counter get or create counter
Counter(name string, labels ...string) Counter
// FloatCounter get or create float counter
@ -43,7 +48,7 @@ type Meter interface {
// Gauge get or create gauge
Gauge(name string, fn func() float64, labels ...string) Gauge
// Set create new meter metrics set
Set(opts ...options.Option) Meter
Set(opts ...Option) Meter
// Histogram get or create histogram
Histogram(name string, labels ...string) Histogram
// Summary get or create summary
@ -51,7 +56,7 @@ type Meter interface {
// SummaryExt get or create summary with spcified quantiles and window time
SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary
// Write writes metrics to io.Writer
Write(w io.Writer, opts ...options.Option) error
Write(w io.Writer, opts ...Option) error
// Options returns meter options
Options() Options
// String return meter type

View File

@ -3,8 +3,6 @@ package meter
import (
"io"
"time"
"go.unistack.org/micro/v4/options"
)
// NoopMeter is an noop implementation of Meter
@ -13,12 +11,12 @@ type noopMeter struct {
}
// NewMeter returns a configured noop reporter:
func NewMeter(opts ...options.Option) Meter {
func NewMeter(opts ...Option) Meter {
return &noopMeter{opts: NewOptions(opts...)}
}
// Clone return old meter with new options
func (r *noopMeter) Clone(opts ...options.Option) Meter {
func (r *noopMeter) Clone(opts ...Option) Meter {
options := r.opts
for _, o := range opts {
o(&options)
@ -31,7 +29,7 @@ func (r *noopMeter) Name() string {
}
// Init initialize options
func (r *noopMeter) Init(opts ...options.Option) error {
func (r *noopMeter) Init(opts ...Option) error {
for _, o := range opts {
o(&r.opts)
}
@ -69,7 +67,7 @@ func (r *noopMeter) Histogram(name string, labels ...string) Histogram {
}
// Set implements the Meter interface
func (r *noopMeter) Set(opts ...options.Option) Meter {
func (r *noopMeter) Set(opts ...Option) Meter {
m := &noopMeter{opts: r.opts}
for _, o := range opts {
@ -79,7 +77,7 @@ func (r *noopMeter) Set(opts ...options.Option) Meter {
return m
}
func (r *noopMeter) Write(_ io.Writer, _ ...options.Option) error {
func (r *noopMeter) Write(w io.Writer, opts ...Option) error {
return nil
}

View File

@ -2,17 +2,13 @@ package meter
import (
"context"
"reflect"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/options"
rutil "go.unistack.org/micro/v4/util/reflect"
)
// Option powers the configuration for metrics implementations:
type Option func(*Options)
// Options for metrics implementations
type Options struct {
// Logger used for logging
Logger logger.Logger
// Context holds external options
Context context.Context
// Name holds the meter name
@ -34,12 +30,11 @@ type Options struct {
}
// NewOptions prepares a set of options:
func NewOptions(opt ...options.Option) Options {
func NewOptions(opt ...Option) Options {
opts := Options{
Address: DefaultAddress,
Path: DefaultPath,
Context: context.Background(),
Logger: logger.DefaultLogger,
MetricPrefix: DefaultMetricPrefix,
LabelPrefix: DefaultLabelPrefix,
}
@ -52,23 +47,37 @@ func NewOptions(opt ...options.Option) Options {
}
// LabelPrefix sets the labels prefix
func LabelPrefix(pref string) options.Option {
return func(src interface{}) error {
return options.Set(src, pref, ".LabelPrefix")
func LabelPrefix(pref string) Option {
return func(o *Options) {
o.LabelPrefix = pref
}
}
// MetricPrefix sets the metric prefix
func MetricPrefix(pref string) options.Option {
return func(src interface{}) error {
return options.Set(src, pref, ".MetricPrefix")
func MetricPrefix(pref string) Option {
return func(o *Options) {
o.MetricPrefix = pref
}
}
// Context sets the metrics context
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Path used to serve metrics over HTTP
func Path(path string) options.Option {
return func(src interface{}) error {
return options.Set(src, path, ".Path")
func Path(value string) Option {
return func(o *Options) {
o.Path = value
}
}
// Address is the listen address to serve metrics
func Address(value string) Option {
return func(o *Options) {
o.Address = value
}
}
@ -82,33 +91,29 @@ func TimingObjectives(value map[float64]float64) Option {
*/
// Labels sets the meter labels
func Labels(ls ...string) options.Option {
return func(src interface{}) error {
v, err := options.Get(src, ".Labels")
if err != nil {
return err
} else if rutil.IsZero(v) {
v = reflect.MakeSlice(reflect.TypeOf(v), 0, len(ls)).Interface()
}
cv := reflect.ValueOf(v)
for _, l := range ls {
reflect.Append(cv, reflect.ValueOf(l))
}
err = options.Set(src, cv, ".Labels")
return err
func Labels(ls ...string) Option {
return func(o *Options) {
o.Labels = append(o.Labels, ls...)
}
}
// Name sets the name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// WriteProcessMetrics enable process metrics output for write
func WriteProcessMetrics(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".WriteProcessMetrics")
func WriteProcessMetrics(b bool) Option {
return func(o *Options) {
o.WriteProcessMetrics = b
}
}
// WriteFDMetrics enable fd metrics output for write
func WriteFDMetrics(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".WriteFDMetrics")
func WriteFDMetrics(b bool) Option {
return func(o *Options) {
o.WriteFDMetrics = b
}
}

View File

@ -1,18 +1,49 @@
package wrapper
package wrapper // import "go.unistack.org/micro/v3/meter/wrapper"
import (
"context"
"fmt"
"time"
"go.unistack.org/micro/v4/client"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/semconv"
"go.unistack.org/micro/v4/server"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/server"
)
var (
// ClientRequestDurationSeconds specifies meter metric name
ClientRequestDurationSeconds = "client_request_duration_seconds"
// ClientRequestLatencyMicroseconds specifies meter metric name
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
// ClientRequestTotal specifies meter metric name
ClientRequestTotal = "client_request_total"
// ClientRequestInflight specifies meter metric name
ClientRequestInflight = "client_request_inflight"
// ServerRequestDurationSeconds specifies meter metric name
ServerRequestDurationSeconds = "server_request_duration_seconds"
// ServerRequestLatencyMicroseconds specifies meter metric name
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
// ServerRequestTotal specifies meter metric name
ServerRequestTotal = "server_request_total"
// ServerRequestInflight specifies meter metric name
ServerRequestInflight = "server_request_inflight"
// PublishMessageDurationSeconds specifies meter metric name
PublishMessageDurationSeconds = "publish_message_duration_seconds"
// PublishMessageLatencyMicroseconds specifies meter metric name
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
// PublishMessageTotal specifies meter metric name
PublishMessageTotal = "publish_message_total"
// PublishMessageInflight specifies meter metric name
PublishMessageInflight = "publish_message_inflight"
// SubscribeMessageDurationSeconds specifies meter metric name
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
// SubscribeMessageLatencyMicroseconds specifies meter metric name
SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds"
// SubscribeMessageTotal specifies meter metric name
SubscribeMessageTotal = "subscribe_message_total"
// SubscribeMessageInflight specifies meter metric name
SubscribeMessageInflight = "subscribe_message_inflight"
labelSuccess = "success"
labelFailure = "failure"
labelStatus = "status"
@ -25,7 +56,7 @@ var (
// Options struct
type Options struct {
Meter meter.Meter
lopts []options.Option
lopts []meter.Option
SkipEndpoints []string
}
@ -36,7 +67,7 @@ type Option func(*Options)
func NewOptions(opts ...Option) Options {
options := Options{
Meter: meter.DefaultMeter,
lopts: make([]options.Option, 0, 5),
lopts: make([]meter.Option, 0, 5),
SkipEndpoints: DefaultSkipEndpoints,
}
for _, o := range opts {
@ -119,26 +150,26 @@ func (w *wrapper) CallFunc(ctx context.Context, addr string, req client.Request,
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(semconv.ClientRequestInflight, labels...).Inc()
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
err := w.callFunc(ctx, addr, req, rsp, opts)
te := time.Since(ts)
w.opts.Meter.Counter(semconv.ClientRequestInflight, labels...).Dec()
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
w.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, labels...).Update(te.Seconds())
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(semconv.ClientRequestTotal, labels...).Inc()
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err
}
func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...options.Option) error {
func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
@ -149,26 +180,26 @@ func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{},
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(semconv.ClientRequestInflight, labels...).Inc()
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
err := w.Client.Call(ctx, req, rsp, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(semconv.ClientRequestInflight, labels...).Dec()
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
w.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, labels...).Update(te.Seconds())
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(semconv.ClientRequestTotal, labels...).Inc()
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err
}
func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...options.Option) (client.Stream, error) {
func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
@ -179,25 +210,59 @@ func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...option
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(semconv.ClientRequestInflight, labels...).Inc()
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
stream, err := w.Client.Stream(ctx, req, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(semconv.ClientRequestInflight, labels...).Dec()
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
w.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, labels...).Update(te.Seconds())
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(semconv.ClientRequestTotal, labels...).Inc()
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return stream, err
}
func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
endpoint := p.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Inc()
ts := time.Now()
err := w.Client.Publish(ctx, p, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Dec()
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(PublishMessageDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(PublishMessageTotal, labels...).Inc()
return err
}
// NewHandlerWrapper create new server handler wrapper
// deprecated
func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.HandlerFunc
}
// NewServerHandlerWrapper create new server handler wrapper
func NewServerHandlerWrapper(opts ...Option) server.HandlerWrapper {
handler := &wrapper{
@ -218,21 +283,64 @@ func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(semconv.ServerRequestInflight, labels...).Inc()
w.opts.Meter.Counter(ServerRequestInflight, labels...).Inc()
ts := time.Now()
err := fn(ctx, req, rsp)
te := time.Since(ts)
w.opts.Meter.Counter(semconv.ServerRequestInflight, labels...).Dec()
w.opts.Meter.Counter(ServerRequestInflight, labels...).Dec()
w.opts.Meter.Summary(semconv.ServerRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(semconv.ServerRequestDurationSeconds, labels...).Update(te.Seconds())
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ServerRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(semconv.ServerRequestTotal, labels...).Inc()
w.opts.Meter.Counter(ServerRequestTotal, labels...).Inc()
return err
}
}
// NewSubscriberWrapper create server subscribe wrapper
// deprecated
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.SubscriberFunc
}
func NewServerSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.SubscriberFunc
}
func (w *wrapper) SubscriberFunc(fn server.SubscriberFunc) server.SubscriberFunc {
return func(ctx context.Context, msg server.Message) error {
endpoint := msg.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Inc()
ts := time.Now()
err := fn(ctx, msg)
te := time.Since(ts)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Dec()
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(SubscribeMessageTotal, labels...).Inc()
return err
}

View File

@ -3,21 +3,21 @@ package micro
import (
"reflect"
"go.unistack.org/micro/v4/broker"
"go.unistack.org/micro/v4/client"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/flow"
"go.unistack.org/micro/v4/fsm"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/register"
"go.unistack.org/micro/v4/resolver"
"go.unistack.org/micro/v4/router"
"go.unistack.org/micro/v4/selector"
"go.unistack.org/micro/v4/server"
"go.unistack.org/micro/v4/store"
"go.unistack.org/micro/v4/sync"
"go.unistack.org/micro/v4/tracer"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/flow"
"go.unistack.org/micro/v3/fsm"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/resolver"
"go.unistack.org/micro/v3/router"
"go.unistack.org/micro/v3/selector"
"go.unistack.org/micro/v3/server"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/sync"
"go.unistack.org/micro/v3/tracer"
)
func As(b any, target any) bool {
@ -65,8 +65,6 @@ func As(b any, target any) bool {
break
case targetType.Implements(routerType):
break
case targetType.Implements(tracerType):
break
default:
return false
}
@ -78,21 +76,19 @@ func As(b any, target any) bool {
return false
}
var (
brokerType = reflect.TypeOf((*broker.Broker)(nil)).Elem()
loggerType = reflect.TypeOf((*logger.Logger)(nil)).Elem()
clientType = reflect.TypeOf((*client.Client)(nil)).Elem()
serverType = reflect.TypeOf((*server.Server)(nil)).Elem()
codecType = reflect.TypeOf((*codec.Codec)(nil)).Elem()
flowType = reflect.TypeOf((*flow.Flow)(nil)).Elem()
fsmType = reflect.TypeOf((*fsm.FSM)(nil)).Elem()
meterType = reflect.TypeOf((*meter.Meter)(nil)).Elem()
registerType = reflect.TypeOf((*register.Register)(nil)).Elem()
resolverType = reflect.TypeOf((*resolver.Resolver)(nil)).Elem()
routerType = reflect.TypeOf((*router.Router)(nil)).Elem()
selectorType = reflect.TypeOf((*selector.Selector)(nil)).Elem()
storeType = reflect.TypeOf((*store.Store)(nil)).Elem()
syncType = reflect.TypeOf((*sync.Sync)(nil)).Elem()
tracerType = reflect.TypeOf((*tracer.Tracer)(nil)).Elem()
serviceType = reflect.TypeOf((*Service)(nil)).Elem()
)
var brokerType = reflect.TypeOf((*broker.Broker)(nil)).Elem()
var loggerType = reflect.TypeOf((*logger.Logger)(nil)).Elem()
var clientType = reflect.TypeOf((*client.Client)(nil)).Elem()
var serverType = reflect.TypeOf((*server.Server)(nil)).Elem()
var codecType = reflect.TypeOf((*codec.Codec)(nil)).Elem()
var flowType = reflect.TypeOf((*flow.Flow)(nil)).Elem()
var fsmType = reflect.TypeOf((*fsm.FSM)(nil)).Elem()
var meterType = reflect.TypeOf((*meter.Meter)(nil)).Elem()
var registerType = reflect.TypeOf((*register.Register)(nil)).Elem()
var resolverType = reflect.TypeOf((*resolver.Resolver)(nil)).Elem()
var routerType = reflect.TypeOf((*router.Router)(nil)).Elem()
var selectorType = reflect.TypeOf((*selector.Selector)(nil)).Elem()
var storeType = reflect.TypeOf((*store.Store)(nil)).Elem()
var syncType = reflect.TypeOf((*sync.Sync)(nil)).Elem()
var tracerType = reflect.TypeOf((*tracer.Tracer)(nil)).Elem()
var serviceType = reflect.TypeOf((*Service)(nil)).Elem()

View File

@ -6,9 +6,8 @@ import (
"reflect"
"testing"
"go.unistack.org/micro/v4/broker"
"go.unistack.org/micro/v4/fsm"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/fsm"
)
func TestAs(t *testing.T) {
@ -65,8 +64,9 @@ type bro struct {
name string
}
func (p *bro) Name() string { return p.name }
func (p *bro) Init(opts ...options.Option) error { return nil }
func (p *bro) Name() string { return p.name }
func (p *bro) Init(opts ...broker.Option) error { return nil }
// Options returns broker options
func (p *bro) Options() broker.Options { return broker.Options{} }
@ -81,10 +81,22 @@ func (p *bro) Connect(ctx context.Context) error { return nil }
func (p *bro) Disconnect(ctx context.Context) error { return nil }
// Publish message, msg can be single broker.Message or []broker.Message
func (p *bro) Publish(ctx context.Context, msg interface{}, opts ...options.Option) error { return nil }
func (p *bro) Publish(ctx context.Context, topic string, msg *broker.Message, opts ...broker.PublishOption) error {
return nil
}
// BatchPublish messages to broker with multiple topics
func (p *bro) BatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
return nil
}
// BatchSubscribe subscribes to topic messages via handler
func (p *bro) BatchSubscribe(ctx context.Context, topic string, h broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return nil, nil
}
// Subscribe subscribes to topic message via handler
func (p *bro) Subscribe(ctx context.Context, topic string, handler interface{}, opts ...options.Option) (broker.Subscriber, error) {
func (p *bro) Subscribe(ctx context.Context, topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return nil, nil
}

View File

@ -1,4 +1,4 @@
package mtls // import "go.unistack.org/micro/v4/mtls"
package mtls // import "go.unistack.org/micro/v3/mtls"
import (
"bytes"

55
network/network.go Normal file
View File

@ -0,0 +1,55 @@
// Package network is for creating internetworks
package network // import "go.unistack.org/micro/v3/network"
import (
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/server"
)
// Error is network node errors
type Error interface {
// Count is current count of errors
Count() int
// Msg is last error message
Msg() string
}
// Status is node status
type Status interface {
// Error reports error status
Error() Error
}
// Node is network node
type Node interface {
// Id is node id
Id() string
// Address is node bind address
Address() string
// Peers returns node peers
Peers() []Node
// Network is the network node is in
Network() Network
// Status returns node status
Status() Status
}
// Network is micro network
type Network interface {
// Node is network node
Node
// Initialise options
Init(...Option) error
// Options returns the network options
Options() Options
// Name of the network
Name() string
// Connect starts the resolver and tunnel server
Connect() error
// Close stops the tunnel and resolving
Close() error
// Client is micro client
Client() client.Client
// Server is micro server
Server() server.Server
}

135
network/options.go Normal file
View File

@ -0,0 +1,135 @@
package network
import (
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/network/tunnel"
"go.unistack.org/micro/v3/proxy"
"go.unistack.org/micro/v3/router"
"go.unistack.org/micro/v3/tracer"
"go.unistack.org/micro/v3/util/id"
)
// Option func
type Option func(*Options)
// Options configure network
type Options struct {
// Router used for routing
Router router.Router
// Proxy holds the proxy
Proxy proxy.Proxy
// Logger used for logging
Logger logger.Logger
// Meter used for metrics
Meter meter.Meter
// Tracer used for tracing
Tracer tracer.Tracer
// Tunnel used for transfer data
Tunnel tunnel.Tunnel
// ID of the node
ID string
// Name of the network
Name string
// Address to bind to
Address string
// Advertise sets the address to advertise
Advertise string
// Nodes is a list of nodes to connect to
Nodes []string
}
// ID sets the id of the network node
func ID(id string) Option {
return func(o *Options) {
o.ID = id
}
}
// Name sets the network name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Address sets the network address
func Address(a string) Option {
return func(o *Options) {
o.Address = a
}
}
// Advertise sets the address to advertise
func Advertise(a string) Option {
return func(o *Options) {
o.Advertise = a
}
}
// Nodes is a list of nodes to connect to
func Nodes(n ...string) Option {
return func(o *Options) {
o.Nodes = n
}
}
// Tunnel sets the network tunnel
func Tunnel(t tunnel.Tunnel) Option {
return func(o *Options) {
o.Tunnel = t
}
}
// Router sets the network router
func Router(r router.Router) Option {
return func(o *Options) {
o.Router = r
}
}
// Proxy sets the network proxy
func Proxy(p proxy.Proxy) Option {
return func(o *Options) {
o.Proxy = p
}
}
// Logger sets the network logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Meter sets the meter
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Tracer to be used for tracing
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// NewOptions returns network default options
func NewOptions(opts ...Option) Options {
options := Options{
ID: id.Must(),
Name: "go.micro",
Address: ":0",
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
}
for _, o := range opts {
o(&options)
}
return options
}

View File

@ -0,0 +1,34 @@
package transport
import (
"context"
)
type transportKey struct{}
// FromContext get transport from context
func FromContext(ctx context.Context) (Transport, bool) {
if ctx == nil {
return nil, false
}
c, ok := ctx.Value(transportKey{}).(Transport)
return c, ok
}
// NewContext put transport in context
func NewContext(ctx context.Context, c Transport) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, transportKey{}, c)
}
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

258
network/transport/memory.go Normal file
View File

@ -0,0 +1,258 @@
package transport
import (
"context"
"errors"
"fmt"
"net"
"sync"
"time"
maddr "go.unistack.org/micro/v3/util/addr"
mnet "go.unistack.org/micro/v3/util/net"
"go.unistack.org/micro/v3/util/rand"
)
type memorySocket struct {
ctx context.Context
recv chan *Message
exit chan bool
lexit chan bool
send chan *Message
local string
remote string
timeout time.Duration
sync.RWMutex
}
type memoryClient struct {
*memorySocket
opts DialOptions
}
type memoryListener struct {
lopts ListenOptions
ctx context.Context
exit chan bool
conn chan *memorySocket
addr string
topts Options
sync.RWMutex
}
type memoryTransport struct {
listeners map[string]*memoryListener
opts Options
sync.RWMutex
}
func (ms *memorySocket) Recv(m *Message) error {
ms.RLock()
defer ms.RUnlock()
ctx := ms.ctx
if ms.timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ms.ctx, ms.timeout)
defer cancel()
}
select {
case <-ctx.Done():
return ctx.Err()
case <-ms.exit:
return errors.New("connection closed")
case <-ms.lexit:
return errors.New("server connection closed")
case cm := <-ms.recv:
*m = *cm
}
return nil
}
func (ms *memorySocket) Local() string {
return ms.local
}
func (ms *memorySocket) Remote() string {
return ms.remote
}
func (ms *memorySocket) Send(m *Message) error {
ms.RLock()
defer ms.RUnlock()
ctx := ms.ctx
if ms.timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ms.ctx, ms.timeout)
defer cancel()
}
select {
case <-ctx.Done():
return ctx.Err()
case <-ms.exit:
return errors.New("connection closed")
case <-ms.lexit:
return errors.New("server connection closed")
case ms.send <- m:
}
return nil
}
func (ms *memorySocket) Close() error {
ms.Lock()
defer ms.Unlock()
select {
case <-ms.exit:
return nil
default:
close(ms.exit)
}
return nil
}
func (m *memoryListener) Addr() string {
return m.addr
}
func (m *memoryListener) Close() error {
m.Lock()
defer m.Unlock()
select {
case <-m.exit:
return nil
default:
close(m.exit)
}
return nil
}
func (m *memoryListener) Accept(fn func(Socket)) error {
for {
select {
case <-m.exit:
return nil
case c := <-m.conn:
go fn(&memorySocket{
lexit: c.lexit,
exit: c.exit,
send: c.recv,
recv: c.send,
local: c.Remote(),
remote: c.Local(),
timeout: m.topts.Timeout,
ctx: m.topts.Context,
})
}
}
}
func (m *memoryTransport) Dial(ctx context.Context, addr string, opts ...DialOption) (Client, error) {
m.RLock()
defer m.RUnlock()
listener, ok := m.listeners[addr]
if !ok {
return nil, errors.New("could not dial " + addr)
}
options := NewDialOptions(opts...)
client := &memoryClient{
&memorySocket{
send: make(chan *Message),
recv: make(chan *Message),
exit: make(chan bool),
lexit: listener.exit,
local: addr,
remote: addr,
timeout: m.opts.Timeout,
ctx: m.opts.Context,
},
options,
}
// pseudo connect
select {
case <-listener.exit:
return nil, errors.New("connection error")
case listener.conn <- client.memorySocket:
}
return client, nil
}
func (m *memoryTransport) Listen(ctx context.Context, addr string, opts ...ListenOption) (Listener, error) {
m.Lock()
defer m.Unlock()
options := NewListenOptions(opts...)
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
addr, err = maddr.Extract(host)
if err != nil {
return nil, err
}
// if zero port then randomly assign one
if len(port) > 0 && port == "0" {
var rng rand.Rand
i := rng.Intn(20000)
port = fmt.Sprintf("%d", 10000+i)
}
// set addr with port
addr = mnet.HostPort(addr, port)
if _, ok := m.listeners[addr]; ok {
return nil, errors.New("already listening on " + addr)
}
listener := &memoryListener{
lopts: options,
topts: m.opts,
addr: addr,
conn: make(chan *memorySocket),
exit: make(chan bool),
ctx: m.opts.Context,
}
m.listeners[addr] = listener
return listener, nil
}
func (m *memoryTransport) Init(opts ...Option) error {
for _, o := range opts {
o(&m.opts)
}
return nil
}
func (m *memoryTransport) Options() Options {
return m.opts
}
func (m *memoryTransport) String() string {
return "memory"
}
func (m *memoryTransport) Name() string {
return m.opts.Name
}
// NewTransport returns new memory transport with options
func NewTransport(opts ...Option) Transport {
options := NewOptions(opts...)
return &memoryTransport{
opts: options,
listeners: make(map[string]*memoryListener),
}
}

View File

@ -0,0 +1,100 @@
package transport
import (
"context"
"os"
"testing"
)
func TestMemoryTransport(t *testing.T) {
tr := NewTransport()
ctx := context.Background()
// bind / listen
l, err := tr.Listen(ctx, "127.0.0.1:8080")
if err != nil {
t.Fatalf("Unexpected error listening %v", err)
}
defer l.Close()
cherr := make(chan error, 1)
// accept
go func() {
if nerr := l.Accept(func(sock Socket) {
for {
var m Message
if rerr := sock.Recv(&m); rerr != nil {
cherr <- rerr
return
}
if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
t.Logf("Server Received %s", string(m.Body))
}
if cerr := sock.Send(&Message{
Body: []byte(`pong`),
}); cerr != nil {
cherr <- cerr
return
}
}
}); nerr != nil {
cherr <- err
}
}()
// dial
c, err := tr.Dial(ctx, "127.0.0.1:8080")
if err != nil {
t.Fatalf("Unexpected error dialing %v", err)
}
defer c.Close()
select {
case err := <-cherr:
t.Fatal(err)
default:
// send <=> receive
for i := 0; i < 3; i++ {
if err := c.Send(&Message{
Body: []byte(`ping`),
}); err != nil {
return
}
var m Message
if err := c.Recv(&m); err != nil {
return
}
if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
t.Logf("Client Received %s", string(m.Body))
}
}
}
}
func TestListener(t *testing.T) {
tr := NewTransport()
ctx := context.Background()
// bind / listen on random port
l, err := tr.Listen(ctx, ":0")
if err != nil {
t.Fatalf("Unexpected error listening %v", err)
}
defer l.Close()
// try again
l2, err := tr.Listen(ctx, ":0")
if err != nil {
t.Fatalf("Unexpected error listening %v", err)
}
defer l2.Close()
// now make sure it still fails
l3, err := tr.Listen(ctx, ":8080")
if err != nil {
t.Fatalf("Unexpected error listening %v", err)
}
defer l3.Close()
if _, err := tr.Listen(ctx, ":8080"); err == nil {
t.Fatal("Expected error binding to :8080 got nil")
}
}

View File

@ -0,0 +1,175 @@
package transport
import (
"context"
"crypto/tls"
"time"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/tracer"
)
// Options struct holds the transport options
type Options struct {
// Meter used for metrics
Meter meter.Meter
// Tracer used for tracing
Tracer tracer.Tracer
// Codec used for marshal/unmarshal messages
Codec codec.Codec
// Logger used for logging
Logger logger.Logger
// Context holds external options
Context context.Context
// TLSConfig holds tls.TLSConfig options
TLSConfig *tls.Config
// Name holds the transport name
Name string
// Addrs holds the transport addrs
Addrs []string
// Timeout holds the timeout
Timeout time.Duration
}
// NewOptions returns new options
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// DialOptions struct
type DialOptions struct {
// Context holds the external options
Context context.Context
// Timeout holds the timeout
Timeout time.Duration
// Stream flag
Stream bool
}
// NewDialOptions returns new DialOptions
func NewDialOptions(opts ...DialOption) DialOptions {
options := DialOptions{
Timeout: DefaultDialTimeout,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// ListenOptions struct
type ListenOptions struct {
// TODO: add tls options when listening
// Currently set in global options
// Context holds the external options
Context context.Context
// TLSConfig holds the *tls.Config options
TLSConfig *tls.Config
}
// NewListenOptions returns new ListenOptions
func NewListenOptions(opts ...ListenOption) ListenOptions {
options := ListenOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// Addrs to use for transport
func Addrs(addrs ...string) Option {
return func(o *Options) {
o.Addrs = addrs
}
}
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Meter sets the meter
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Context sets the context
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Codec sets the codec used for encoding where the transport
// does not support message headers
func Codec(c codec.Codec) Option {
return func(o *Options) {
o.Codec = c
}
}
// Timeout sets the timeout for Send/Recv execution
func Timeout(t time.Duration) Option {
return func(o *Options) {
o.Timeout = t
}
}
// TLSConfig to be used for the transport.
func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
o.TLSConfig = t
}
}
// WithStream indicates whether this is a streaming connection
func WithStream() DialOption {
return func(o *DialOptions) {
o.Stream = true
}
}
// WithTimeout used when dialling the remote side
func WithTimeout(d time.Duration) DialOption {
return func(o *DialOptions) {
o.Timeout = d
}
}
// Tracer to be used for tracing
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Name sets the name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}

View File

@ -0,0 +1,63 @@
// Package transport is an interface for synchronous connection based communication
package transport // import "go.unistack.org/micro/v3/network/transport"
import (
"context"
"time"
"go.unistack.org/micro/v3/metadata"
)
var (
// DefaultTransport is the global default transport
DefaultTransport Transport = NewTransport()
// DefaultDialTimeout the default dial timeout
DefaultDialTimeout = time.Second * 5
)
// Transport is an interface which is used for communication between
// services. It uses connection based socket send/recv semantics and
// has various implementations; http, grpc, quic.
type Transport interface {
Init(...Option) error
Options() Options
Dial(ctx context.Context, addr string, opts ...DialOption) (Client, error)
Listen(ctx context.Context, addr string, opts ...ListenOption) (Listener, error)
String() string
}
// Message is used to transfer data
type Message struct {
Header metadata.Metadata
Body []byte
}
// Socket bastraction interface
type Socket interface {
Recv(*Message) error
Send(*Message) error
Close() error
Local() string
Remote() string
}
// Client is the socket owner
type Client interface {
Socket
}
// Listener is the interface for stream oriented messaging
type Listener interface {
Addr() string
Close() error
Accept(func(Socket)) error
}
// Option is the option signature
type Option func(*Options)
// DialOption is the option signature
type DialOption func(*DialOptions)
// ListenOption is the option signature
type ListenOption func(*ListenOptions)

View File

@ -0,0 +1,356 @@
// Package broker is a tunnel broker
package broker // import "go.unistack.org/micro/v3/network/tunnel/broker"
import (
"context"
"fmt"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/network/tunnel"
)
type tunBroker struct {
tunnel tunnel.Tunnel
opts broker.Options
}
type tunSubscriber struct {
listener tunnel.Listener
handler broker.Handler
closed chan bool
topic string
opts broker.SubscribeOptions
}
type tunBatchSubscriber struct {
listener tunnel.Listener
handler broker.BatchHandler
closed chan bool
topic string
opts broker.SubscribeOptions
}
type tunEvent struct {
err error
message *broker.Message
topic string
}
// used to access tunnel from options context
type (
tunnelKey struct{}
tunnelAddr struct{}
)
func (t *tunBroker) Init(opts ...broker.Option) error {
for _, o := range opts {
o(&t.opts)
}
return nil
}
func (t *tunBroker) Name() string {
return t.opts.Name
}
func (t *tunBroker) Options() broker.Options {
return t.opts
}
func (t *tunBroker) Address() string {
return t.tunnel.Address()
}
func (t *tunBroker) Connect(ctx context.Context) error {
return t.tunnel.Connect(ctx)
}
func (t *tunBroker) Disconnect(ctx context.Context) error {
return t.tunnel.Close(ctx)
}
func (t *tunBroker) BatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
// TODO: this is probably inefficient, we might want to just maintain an open connection
// it may be easier to add broadcast to the tunnel
topicMap := make(map[string]tunnel.Session)
var err error
for _, msg := range msgs {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
c, ok := topicMap[topic]
if !ok {
c, err = t.tunnel.Dial(ctx, topic, tunnel.DialMode(tunnel.Multicast))
if err != nil {
return err
}
defer c.Close()
topicMap[topic] = c
}
if err = c.Send(&transport.Message{
Header: msg.Header,
Body: msg.Body,
}); err != nil {
// msg.SetError(err)
return err
}
}
return nil
}
func (t *tunBroker) Publish(ctx context.Context, topic string, m *broker.Message, opts ...broker.PublishOption) error {
// TODO: this is probably inefficient, we might want to just maintain an open connection
// it may be easier to add broadcast to the tunnel
c, err := t.tunnel.Dial(ctx, topic, tunnel.DialMode(tunnel.Multicast))
if err != nil {
return err
}
defer c.Close()
return c.Send(&transport.Message{
Header: m.Header,
Body: m.Body,
})
}
func (t *tunBroker) BatchSubscribe(ctx context.Context, topic string, h broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
l, err := t.tunnel.Listen(ctx, topic, tunnel.ListenMode(tunnel.Multicast))
if err != nil {
return nil, err
}
tunSub := &tunBatchSubscriber{
topic: topic,
handler: h,
opts: broker.NewSubscribeOptions(opts...),
closed: make(chan bool),
listener: l,
}
// start processing
go tunSub.run()
return tunSub, nil
}
func (t *tunBroker) Subscribe(ctx context.Context, topic string, h broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
l, err := t.tunnel.Listen(ctx, topic, tunnel.ListenMode(tunnel.Multicast))
if err != nil {
return nil, err
}
tunSub := &tunSubscriber{
topic: topic,
handler: h,
opts: broker.NewSubscribeOptions(opts...),
closed: make(chan bool),
listener: l,
}
// start processing
go tunSub.run()
return tunSub, nil
}
func (t *tunBroker) String() string {
return "tunnel"
}
func (t *tunBatchSubscriber) run() {
for {
// accept a new connection
c, err := t.listener.Accept()
if err != nil {
select {
case <-t.closed:
return
default:
continue
}
}
// receive message
m := new(transport.Message)
if err := c.Recv(m); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error(t.opts.Context, err.Error())
}
if err = c.Close(); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error(t.opts.Context, err.Error())
}
}
continue
}
// close the connection
c.Close()
evts := broker.Events{&tunEvent{
topic: t.topic,
message: &broker.Message{
Header: m.Header,
Body: m.Body,
},
}}
// handle the message
go func() {
_ = t.handler(evts)
}()
}
}
func (t *tunSubscriber) run() {
for {
// accept a new connection
c, err := t.listener.Accept()
if err != nil {
select {
case <-t.closed:
return
default:
continue
}
}
// receive message
m := new(transport.Message)
if err := c.Recv(m); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error(t.opts.Context, err.Error())
}
if err = c.Close(); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error(t.opts.Context, err.Error())
}
}
continue
}
// close the connection
c.Close()
// handle the message
go func() {
_ = t.handler(&tunEvent{
topic: t.topic,
message: &broker.Message{
Header: m.Header,
Body: m.Body,
},
})
}()
}
}
func (t *tunBatchSubscriber) Options() broker.SubscribeOptions {
return t.opts
}
func (t *tunBatchSubscriber) Topic() string {
return t.topic
}
func (t *tunBatchSubscriber) Unsubscribe(ctx context.Context) error {
select {
case <-t.closed:
return nil
default:
close(t.closed)
return t.listener.Close()
}
}
func (t *tunSubscriber) Options() broker.SubscribeOptions {
return t.opts
}
func (t *tunSubscriber) Topic() string {
return t.topic
}
func (t *tunSubscriber) Unsubscribe(ctx context.Context) error {
select {
case <-t.closed:
return nil
default:
close(t.closed)
return t.listener.Close()
}
}
func (t *tunEvent) Topic() string {
return t.topic
}
func (t *tunEvent) Message() *broker.Message {
return t.message
}
func (t *tunEvent) Ack() error {
return nil
}
func (t *tunEvent) Error() error {
return t.err
}
func (t *tunEvent) SetError(err error) {
t.err = err
}
// NewBroker returns new tunnel broker
func NewBroker(opts ...broker.Option) (broker.Broker, error) {
options := broker.NewOptions(opts...)
t, ok := options.Context.Value(tunnelKey{}).(tunnel.Tunnel)
if !ok {
return nil, fmt.Errorf("tunnel not set")
}
a, ok := options.Context.Value(tunnelAddr{}).(string)
if ok {
// initialise address
if err := t.Init(tunnel.Address(a)); err != nil {
return nil, err
}
}
if len(options.Addrs) > 0 {
// initialise nodes
if err := t.Init(tunnel.Nodes(options.Addrs...)); err != nil {
return nil, err
}
}
return &tunBroker{
opts: options,
tunnel: t,
}, nil
}
// WithAddress sets the tunnel address
func WithAddress(a string) broker.Option {
return func(o *broker.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, tunnelAddr{}, a)
}
}
// WithTunnel sets the internal tunnel
func WithTunnel(t tunnel.Tunnel) broker.Option {
return func(o *broker.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, tunnelKey{}, t)
}
}

192
network/tunnel/options.go Normal file
View File

@ -0,0 +1,192 @@
package tunnel
import (
"time"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/tracer"
"go.unistack.org/micro/v3/util/id"
)
var (
// DefaultAddress is default tunnel bind address
DefaultAddress = ":0"
// DefaultToken the shared default token
DefaultToken = "go.micro.tunnel"
)
// Option func signature
type Option func(*Options)
// Options provides network configuration options
type Options struct {
// Logger used for logging
Logger logger.Logger
// Meter used for metrics
Meter meter.Meter
// Tracer used for tracing
Tracer tracer.Tracer
// Transport used for communication
Transport transport.Transport
// Token the shared auth token
Token string
// Name holds the tunnel name
Name string
// ID holds the tunnel id
ID string
// Address holds the tunnel address
Address string
// Nodes holds the tunnel nodes
Nodes []string
}
// DialOption func
type DialOption func(*DialOptions)
// DialOptions provides dial options
type DialOptions struct {
// Link specifies the link to use
Link string
// specify mode of the session
Mode Mode
// Wait for connection to be accepted
Wait bool
// the dial timeout
Timeout time.Duration
}
// ListenOption func
type ListenOption func(*ListenOptions)
// ListenOptions provides listen options
type ListenOptions struct {
// Mode specify mode of the session
Mode Mode
// Timeout the read timeout
Timeout time.Duration
}
// ID sets the tunnel id
func ID(id string) Option {
return func(o *Options) {
o.ID = id
}
}
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Meter sets the meter
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Address sets the tunnel address
func Address(a string) Option {
return func(o *Options) {
o.Address = a
}
}
// Nodes specify remote network nodes
func Nodes(n ...string) Option {
return func(o *Options) {
o.Nodes = n
}
}
// Token sets the shared token for auth
func Token(t string) Option {
return func(o *Options) {
o.Token = t
}
}
// Transport listens for incoming connections
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
// ListenMode option
func ListenMode(m Mode) ListenOption {
return func(o *ListenOptions) {
o.Mode = m
}
}
// ListenTimeout for reads and writes on the listener session
func ListenTimeout(t time.Duration) ListenOption {
return func(o *ListenOptions) {
o.Timeout = t
}
}
// DialMode multicast sets the multicast option to send only to those mapped
func DialMode(m Mode) DialOption {
return func(o *DialOptions) {
o.Mode = m
}
}
// DialTimeout sets the dial timeout of the connection
func DialTimeout(t time.Duration) DialOption {
return func(o *DialOptions) {
o.Timeout = t
}
}
// DialLink specifies the link to pin this connection to.
// This is not applicable if the multicast option is set.
func DialLink(id string) DialOption {
return func(o *DialOptions) {
o.Link = id
}
}
// DialWait specifies whether to wait for the connection
// to be accepted before returning the session
func DialWait(b bool) DialOption {
return func(o *DialOptions) {
o.Wait = b
}
}
// NewOptions returns router default options with filled values
func NewOptions(opts ...Option) Options {
options := Options{
ID: id.Must(),
Address: DefaultAddress,
Token: DefaultToken,
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
}
for _, o := range opts {
o(&options)
}
return options
}
// Tracer to be used for tracing
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Name sets the name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}

View File

@ -0,0 +1,30 @@
package transport
import (
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/network/tunnel"
)
type tunListener struct {
l tunnel.Listener
}
func (t *tunListener) Addr() string {
return t.l.Channel()
}
func (t *tunListener) Close() error {
return t.l.Close()
}
func (t *tunListener) Accept(fn func(socket transport.Socket)) error {
for {
// accept connection
c, err := t.l.Accept()
if err != nil {
return err
}
// execute the function
go fn(c)
}
}

View File

@ -0,0 +1,113 @@
// Package transport provides a tunnel transport
package transport // import "go.unistack.org/micro/v3/network/tunnel/transport"
import (
"context"
"fmt"
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/network/tunnel"
)
type tunTransport struct {
tunnel tunnel.Tunnel
options transport.Options
}
type tunnelKey struct{}
type transportKey struct{}
func (t *tunTransport) Init(opts ...transport.Option) error {
for _, o := range opts {
o(&t.options)
}
// close the existing tunnel
if t.tunnel != nil {
t.tunnel.Close(context.TODO())
}
// get the tunnel
tun, ok := t.options.Context.Value(tunnelKey{}).(tunnel.Tunnel)
if !ok {
return fmt.Errorf("tunnel not set")
}
// get the transport
tr, ok := t.options.Context.Value(transportKey{}).(transport.Transport)
if ok {
_ = tun.Init(tunnel.Transport(tr))
}
// set the tunnel
t.tunnel = tun
return nil
}
func (t *tunTransport) Dial(ctx context.Context, addr string, opts ...transport.DialOption) (transport.Client, error) {
if err := t.tunnel.Connect(ctx); err != nil {
return nil, err
}
c, err := t.tunnel.Dial(ctx, addr)
if err != nil {
return nil, err
}
return c, nil
}
func (t *tunTransport) Listen(ctx context.Context, addr string, opts ...transport.ListenOption) (transport.Listener, error) {
if err := t.tunnel.Connect(ctx); err != nil {
return nil, err
}
l, err := t.tunnel.Listen(ctx, addr)
if err != nil {
return nil, err
}
return &tunListener{l}, nil
}
func (t *tunTransport) Options() transport.Options {
return t.options
}
func (t *tunTransport) String() string {
return "tunnel"
}
// NewTransport honours the initialiser used in
func NewTransport(opts ...transport.Option) transport.Transport {
t := &tunTransport{
options: transport.Options{},
}
// initialise
// t.Init(opts...)
return t
}
// WithTunnel sets the internal tunnel
func WithTunnel(t tunnel.Tunnel) transport.Option {
return func(o *transport.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, tunnelKey{}, t)
}
}
// WithTransport sets the internal transport
func WithTransport(t transport.Transport) transport.Option {
return func(o *transport.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, transportKey{}, t)
}
}

106
network/tunnel/tunnel.go Normal file
View File

@ -0,0 +1,106 @@
// Package tunnel provides gre network tunnelling
package tunnel // import "go.unistack.org/micro/v3/network/transport/tunnel"
import (
"context"
"errors"
"time"
"go.unistack.org/micro/v3/network/transport"
)
// DefaultTunnel contains default tunnel implementation
var DefaultTunnel Tunnel
const (
// Unicast send over one link
Unicast Mode = iota
// Multicast send to all channel listeners
Multicast
// Broadcast send to all links
Broadcast
)
var (
// DefaultDialTimeout is the dial timeout if none is specified
DefaultDialTimeout = time.Second * 5
// ErrDialTimeout is returned by a call to Dial where the timeout occurs
ErrDialTimeout = errors.New("dial timeout")
// ErrDiscoverChan is returned when we failed to receive the "announce" back from a discovery
ErrDiscoverChan = errors.New("failed to discover channel")
// ErrLinkNotFound is returned when a link is specified at dial time and does not exist
ErrLinkNotFound = errors.New("link not found")
// ErrLinkDisconnected is returned when a link we attempt to send to is disconnected
ErrLinkDisconnected = errors.New("link not connected")
// ErrLinkLoopback is returned when attempting to send an outbound message over loopback link
ErrLinkLoopback = errors.New("link is loopback")
// ErrLinkRemote is returned when attempting to send a loopback message over remote link
ErrLinkRemote = errors.New("link is remote")
// ErrReadTimeout is a timeout on session.Recv
ErrReadTimeout = errors.New("read timeout")
// ErrDecryptingData is for when theres a nonce error
ErrDecryptingData = errors.New("error decrypting data")
)
// Mode of the session
type Mode uint8
// Tunnel creates a gre tunnel on top of the micro/transport.
// It establishes multiple streams using the Micro-Tunnel-Channel header
// and Micro-Tunnel-Session header. The tunnel id is a hash of
// the address being requested.
type Tunnel interface {
// Init initializes tunnel with options
Init(opts ...Option) error
// Address returns the address the tunnel is listening on
Address() string
// Connect connects the tunnel
Connect(ctx context.Context) error
// Close closes the tunnel
Close(ctx context.Context) error
// Links returns all the links the tunnel is connected to
Links() []Link
// Dial allows a client to connect to a channel
Dial(ctx context.Context, channel string, opts ...DialOption) (Session, error)
// Listen allows to accept connections on a channel
Listen(ctx context.Context, channel string, opts ...ListenOption) (Listener, error)
// String returns the name of the tunnel implementation
String() string
}
// Link represents internal links to the tunnel
type Link interface {
// Id returns the link unique Id
Id() string
// Delay is the current load on the link (lower is better)
Delay() int64
// Length returns the roundtrip time as nanoseconds (lower is better)
Length() int64
// Current transfer rate as bits per second (lower is better)
Rate() float64
// Is this a loopback link
Loopback() bool
// State of the link: connected/closed/error
State() string
// honours transport socket
transport.Socket
}
// Listener provides similar constructs to the transport.Listener
type Listener interface {
Accept() (Session, error)
Channel() string
Close() error
}
// Session is a unique session created when dialling or accepting connections on the tunnel
type Session interface {
// The unique session id
Id() string
// The channel name
Channel() string
// The link the session is on
Link() string
// a transport socket
transport.Socket
}

View File

@ -5,18 +5,17 @@ import (
"fmt"
"time"
"go.unistack.org/micro/v4/broker"
"go.unistack.org/micro/v4/client"
"go.unistack.org/micro/v4/config"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/register"
"go.unistack.org/micro/v4/router"
"go.unistack.org/micro/v4/server"
"go.unistack.org/micro/v4/store"
"go.unistack.org/micro/v4/tracer"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/config"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/router"
"go.unistack.org/micro/v3/server"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/tracer"
)
// Options for micro service
@ -91,7 +90,33 @@ type Option func(*Options) error
// Broker to be used for client and server
func Broker(b broker.Broker, opts ...BrokerOption) Option {
return func(o *Options) error {
o.Brokers = []broker.Broker{b}
var err error
bopts := brokerOptions{}
for _, opt := range opts {
opt(&bopts)
}
all := false
if len(opts) == 0 {
all = true
}
for _, srv := range o.Servers {
for _, os := range bopts.servers {
if srv.Name() == os || all {
if err = srv.Init(server.Broker(b)); err != nil {
return err
}
}
}
}
for _, cli := range o.Clients {
for _, oc := range bopts.clients {
if cli.Name() == oc || all {
if err = cli.Init(client.Broker(b)); err != nil {
return err
}
}
}
}
return nil
}
}
@ -202,7 +227,7 @@ func Logger(l logger.Logger, opts ...LoggerOption) Option {
for _, srv := range o.Servers {
for _, os := range lopts.servers {
if srv.Name() == os || all {
if err = srv.Init(options.Logger(l)); err != nil {
if err = srv.Init(server.Logger(l)); err != nil {
return err
}
}
@ -211,7 +236,7 @@ func Logger(l logger.Logger, opts ...LoggerOption) Option {
for _, cli := range o.Clients {
for _, oc := range lopts.clients {
if cli.Name() == oc || all {
if err = cli.Init(options.Logger(l)); err != nil {
if err = cli.Init(client.Logger(l)); err != nil {
return err
}
}
@ -220,7 +245,7 @@ func Logger(l logger.Logger, opts ...LoggerOption) Option {
for _, brk := range o.Brokers {
for _, ob := range lopts.brokers {
if brk.Name() == ob || all {
if err = brk.Init(options.Logger(l)); err != nil {
if err = brk.Init(broker.Logger(l)); err != nil {
return err
}
}
@ -238,25 +263,17 @@ func Logger(l logger.Logger, opts ...LoggerOption) Option {
for _, str := range o.Stores {
for _, or := range lopts.stores {
if str.Name() == or || all {
if err = str.Init(options.Logger(l)); err != nil {
return err
}
}
}
}
for _, mtr := range o.Meters {
for _, or := range lopts.meters {
if mtr.Name() == or || all {
if err = mtr.Init(options.Logger(l)); err != nil {
if err = str.Init(store.Logger(l)); err != nil {
return err
}
}
}
}
for _, trc := range o.Tracers {
for _, ot := range lopts.tracers {
if trc.Name() == ot || all {
if err = trc.Init(options.Logger(l)); err != nil {
if err = trc.Init(tracer.Logger(l)); err != nil {
return err
}
}
@ -330,7 +347,7 @@ func Register(r register.Register, opts ...RegisterOption) Option {
for _, srv := range o.Servers {
for _, os := range ropts.servers {
if srv.Name() == os || all {
if err = srv.Init(options.Register(r)); err != nil {
if err = srv.Init(server.Register(r)); err != nil {
return err
}
}
@ -339,7 +356,7 @@ func Register(r register.Register, opts ...RegisterOption) Option {
for _, brk := range o.Brokers {
for _, os := range ropts.brokers {
if brk.Name() == os || all {
if err = brk.Init(options.Register(r)); err != nil {
if err = brk.Init(broker.Register(r)); err != nil {
return err
}
}
@ -396,7 +413,7 @@ func Tracer(t tracer.Tracer, opts ...TracerOption) Option {
for _, srv := range o.Servers {
for _, os := range topts.servers {
if srv.Name() == os || all {
if err = srv.Init(options.Tracer(t)); err != nil {
if err = srv.Init(server.Tracer(t)); err != nil {
return err
}
}
@ -405,7 +422,7 @@ func Tracer(t tracer.Tracer, opts ...TracerOption) Option {
for _, cli := range o.Clients {
for _, os := range topts.clients {
if cli.Name() == os || all {
if err = cli.Init(options.Tracer(t)); err != nil {
if err = cli.Init(client.Tracer(t)); err != nil {
return err
}
}
@ -414,7 +431,7 @@ func Tracer(t tracer.Tracer, opts ...TracerOption) Option {
for _, str := range o.Stores {
for _, os := range topts.stores {
if str.Name() == os || all {
if err = str.Init(options.Tracer(t)); err != nil {
if err = str.Init(store.Tracer(t)); err != nil {
return err
}
}
@ -423,7 +440,7 @@ func Tracer(t tracer.Tracer, opts ...TracerOption) Option {
for _, brk := range o.Brokers {
for _, os := range topts.brokers {
if brk.Name() == os || all {
if err = brk.Init(options.Tracer(t)); err != nil {
if err = brk.Init(broker.Tracer(t)); err != nil {
return err
}
}
@ -522,7 +539,7 @@ func Router(r router.Router, opts ...RouterOption) Option {
for _, cli := range o.Clients {
for _, os := range ropts.clients {
if cli.Name() == os || all {
if err = cli.Init(options.Router(r)); err != nil {
if err = cli.Init(client.Router(r)); err != nil {
return err
}
}
@ -557,7 +574,7 @@ func Address(addr string) Option {
default:
return fmt.Errorf("cant set same address for multiple servers")
}
return o.Servers[0].Init(options.Address(addr))
return o.Servers[0].Init(server.Address(addr))
}
}

View File

@ -1,4 +1,4 @@
package options // import "go.unistack.org/micro/v4/options"
package options
// Hook func interface
type Hook interface{}

View File

@ -1,221 +0,0 @@
package options
import (
"context"
"crypto/tls"
"reflect"
"time"
"go.unistack.org/micro/v4/metadata"
rutil "go.unistack.org/micro/v4/util/reflect"
)
// Option func signature
type Option func(interface{}) error
// Set assign value to struct by its path
func Set(src interface{}, dst interface{}, path string) error {
return rutil.SetFieldByPath(src, dst, path)
}
// Get returns value from struct by its path
func Get(src interface{}, path string) (interface{}, error) {
return rutil.StructFieldByPath(src, path)
}
// Name set Name value
func Name(v ...string) Option {
return func(src interface{}) error {
return Set(src, v, ".Name")
}
}
// Address set Address value to single string or slice of strings
func Address(v ...string) Option {
return func(src interface{}) error {
return Set(src, v, ".Address")
}
}
// Broker set Broker value
func Broker(v interface{}) Option {
return func(src interface{}) error {
return Set(src, v, ".Broker")
}
}
// Logger set Logger value
func Logger(v interface{}) Option {
return func(src interface{}) error {
return Set(src, v, ".Logger")
}
}
// Meter set Meter value
func Meter(v interface{}) Option {
return func(src interface{}) error {
return Set(src, v, ".Meter")
}
}
// Tracer set Tracer value
func Tracer(v interface{}) Option {
return func(src interface{}) error {
return Set(src, v, ".Tracer")
}
}
// Store set Store value
func Store(v interface{}) Option {
return func(src interface{}) error {
return Set(src, v, ".Store")
}
}
// Register set Register value
func Register(v interface{}) Option {
return func(src interface{}) error {
return Set(src, v, ".Register")
}
}
// Router set Router value
func Router(v interface{}) Option {
return func(src interface{}) error {
return Set(src, v, ".Router")
}
}
// Codec set Codec value
func Codec(v interface{}) Option {
return func(src interface{}) error {
return Set(src, v, ".Codec")
}
}
// Client set Client value
func Client(v interface{}) Option {
return func(src interface{}) error {
return Set(src, v, ".Client")
}
}
// Codecs to be used to encode/decode requests for a given content type
func Codecs(ct string, v interface{}) Option {
return func(src interface{}) error {
cm, err := Get(src, ".Codecs")
if err != nil {
return err
} else if rutil.IsZero(cm) {
cm = reflect.MakeMap(reflect.TypeOf(cm)).Interface()
}
cv := reflect.ValueOf(cm)
cv.SetMapIndex(reflect.ValueOf(ct), reflect.ValueOf(v))
return Set(src, cv.Interface(), ".Codecs")
}
}
// Context set Context value
func Context(v context.Context) Option {
return func(src interface{}) error {
return Set(src, v, ".Context")
}
}
// TLSConfig set TLSConfig value
func TLSConfig(v *tls.Config) Option {
return func(src interface{}) error {
return Set(src, v, ".TLSConfig")
}
}
func ContextOption(k, v interface{}) Option {
return func(src interface{}) error {
ctx, err := Get(src, ".Context")
if err != nil {
return err
}
if ctx == nil {
ctx = context.Background()
}
err = Set(src, context.WithValue(ctx.(context.Context), k, v), ".Context")
return err
}
}
// ContentType pass ContentType for message data
func ContentType(ct string) Option {
return func(src interface{}) error {
return Set(src, ct, ".ContentType")
}
}
// Metadata pass additional metadata
func Metadata(md ...any) Option {
var result metadata.Metadata
if len(md) == 1 {
switch vt := md[0].(type) {
case metadata.Metadata:
result = metadata.Copy(vt)
case map[string]string:
result = make(metadata.Metadata, len(vt))
for k, v := range vt {
result.Set(k, v)
}
case map[string][]string:
result = metadata.Copy(vt)
default:
result = metadata.New(0)
}
} else {
result = metadata.New(len(md) / 2)
for idx := 0; idx <= len(md)/2; idx += 2 {
k, ok := md[idx].(string)
switch vt := md[idx+1].(type) {
case string:
if ok {
result.Set(k, vt)
}
case []string:
if ok {
result.Append(k, vt...)
}
}
}
}
return func(src interface{}) error {
return Set(src, result, ".Metadata")
}
}
// Namespace to use
func Namespace(ns string) Option {
return func(src interface{}) error {
return Set(src, ns, ".Namespace")
}
}
// Labels sets the labels
func Labels(ls ...interface{}) Option {
return func(src interface{}) error {
v, err := Get(src, ".Labels")
if err != nil {
return err
} else if rutil.IsZero(v) {
v = reflect.MakeSlice(reflect.TypeOf(v), 0, len(ls)).Interface()
}
cv := reflect.ValueOf(v)
for _, l := range ls {
cv = reflect.Append(cv, reflect.ValueOf(l))
}
return Set(src, cv.Interface(), ".Labels")
}
}
// Timeout pass timeout time.Duration
func Timeout(td time.Duration) Option {
return func(src interface{}) error {
return Set(src, td, ".Timeout")
}
}

View File

@ -1,149 +0,0 @@
package options_test
import (
"testing"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/util/reflect"
)
func TestAddress(t *testing.T) {
var err error
type s struct {
Address []string
}
src := &s{}
var opts []options.Option
opts = append(opts, options.Address("host:port"))
for _, o := range opts {
if err = o(src); err != nil {
t.Fatal(err)
}
}
if src.Address[0] != "host:port" {
t.Fatal("failed to set Address")
}
}
func TestCodecs(t *testing.T) {
var err error
type s struct {
Codecs map[string]codec.Codec
}
src := &s{}
var opts []options.Option
c := codec.NewCodec()
opts = append(opts, options.Codecs("text/html", c))
for _, o := range opts {
if err = o(src); err != nil {
t.Fatal(err)
}
}
for k, v := range src.Codecs {
if k != "text/html" || v != c {
continue
}
return
}
t.Fatalf("failed to set Codecs")
}
func TestLabels(t *testing.T) {
type str1 struct {
Labels []string
}
type str2 struct {
Labels []interface{}
}
x1 := &str1{}
if err := options.Labels("one", "two")(x1); err != nil {
t.Fatal(err)
}
if len(x1.Labels) != 2 {
t.Fatal("failed to set labels")
}
x2 := &str2{}
if err := options.Labels("key", "val")(x2); err != nil {
t.Fatal(err)
}
if len(x2.Labels) != 2 {
t.Fatal("failed to set labels")
}
if x2.Labels[0] != "key" {
t.Fatal("failed to set labels")
}
}
func TestMetadataAny(t *testing.T) {
type s struct {
Metadata metadata.Metadata
}
testCases := []struct {
Name string
Data any
Expected metadata.Metadata
}{
{
"strings_even",
[]any{"Strkey1", []string{"val1"}, "Strkey2", []string{"val2"}},
metadata.Pairs("Strkey1", "val1", "Strkey2", "val2"),
},
{
"strings_odd",
[]any{"key1", "val1", "key2"},
metadata.Pairs("Key1", "val1"),
},
{
Name: "map",
Data: map[string][]string{
"Mapkey1": {"val1"},
"Mapkey2": {"val2"},
},
Expected: metadata.Metadata{
"Mapkey1": []string{"val1"},
"Mapkey2": []string{"val2"},
},
},
{
"metadata.Metadata",
metadata.Pairs("key1", "val1", "key2", "val2"),
metadata.Pairs("Key1", "val1", "Key2", "val2"),
},
}
for _, tt := range testCases {
t.Run(tt.Name, func(t *testing.T) {
src := &s{}
var opts []options.Option
switch valData := tt.Data.(type) {
case []any:
opts = append(opts, options.Metadata(valData...))
case map[string]string, map[string][]string, metadata.Metadata:
opts = append(opts, options.Metadata(valData))
}
for _, o := range opts {
if err := o(src); err != nil {
t.Fatal(err)
}
if !reflect.Equal(tt.Expected, src.Metadata) {
t.Fatalf("expected: %v, actual: %v", tt.Expected, src.Metadata)
}
}
})
}
}

View File

@ -1,5 +1,5 @@
// Package http enables the http profiler
package http // import "go.unistack.org/micro/v4/profiler/http"
package http // import "go.unistack.org/micro/v3/profiler/http"
import (
"context"
@ -7,7 +7,7 @@ import (
"net/http/pprof"
"sync"
profile "go.unistack.org/micro/v4/profiler"
profile "go.unistack.org/micro/v3/profiler"
)
type httpProfile struct {

View File

@ -1,5 +1,5 @@
// Package pprof provides a pprof profiler which writes output to /tmp/[name].{cpu,mem}.pprof
package pprof // import "go.unistack.org/micro/v4/profiler/pprof"
package pprof // import "go.unistack.org/micro/v3/profiler/pprof"
import (
"os"
@ -9,7 +9,7 @@ import (
"sync"
"time"
profile "go.unistack.org/micro/v4/profiler"
profile "go.unistack.org/micro/v3/profiler"
)
type profiler struct {

View File

@ -1,5 +1,5 @@
// Package profiler is for profilers
package profiler // import "go.unistack.org/micro/v4/profiler"
package profiler // import "go.unistack.org/micro/v3/profiler"
// Profiler interface
type Profiler interface {

98
proxy/options.go Normal file
View File

@ -0,0 +1,98 @@
// Package proxy is a transparent proxy built on the micro/server
package proxy
import (
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/router"
"go.unistack.org/micro/v3/tracer"
)
// Options for proxy
type Options struct {
// Tracer used for tracing
Tracer tracer.Tracer
// Client for communication
Client client.Client
// Router for routing
Router router.Router
// Logger used for logging
Logger logger.Logger
// Meter used for metrics
Meter meter.Meter
// Links holds the communication links
Links map[string]client.Client
// Endpoint holds the destination address
Endpoint string
}
// Option func signature
type Option func(o *Options)
// NewOptions returns new options struct that filled by opts
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
}
for _, o := range opts {
o(&options)
}
return options
}
// WithEndpoint sets a proxy endpoint
func WithEndpoint(e string) Option {
return func(o *Options) {
o.Endpoint = e
}
}
// WithClient sets the client
func WithClient(c client.Client) Option {
return func(o *Options) {
o.Client = c
}
}
// WithRouter specifies the router to use
func WithRouter(r router.Router) Option {
return func(o *Options) {
o.Router = r
}
}
// WithLogger specifies the logger to use
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// WithMeter specifies the meter to use
func WithMeter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// WithLink sets a link for outbound requests
func WithLink(name string, c client.Client) Option {
return func(o *Options) {
if o.Links == nil {
o.Links = make(map[string]client.Client)
}
o.Links[name] = c
}
}
// Tracer to be used for tracing
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}

21
proxy/proxy.go Normal file
View File

@ -0,0 +1,21 @@
// Package proxy is a transparent proxy built on the micro/server
package proxy // import "go.unistack.org/micro/v3/proxy"
import (
"context"
"go.unistack.org/micro/v3/server"
)
// DefaultEndpoint holds default proxy address
var DefaultEndpoint = "localhost:9090"
// Proxy can be used as a proxy server for micro services
type Proxy interface {
// ProcessMessage handles inbound messages
ProcessMessage(context.Context, server.Message) error
// ServeRequest handles inbound requests
ServeRequest(context.Context, server.Request, server.Response) error
// Name of the proxy protocol
String() string
}

View File

@ -6,7 +6,7 @@ import (
"unicode"
"unicode/utf8"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v3/metadata"
)
// ExtractValue from reflect.Type from specified depth

View File

@ -1,15 +1,13 @@
package memory
package register
import (
"context"
"sync"
"time"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/register"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/util/id"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/util/id"
)
var (
@ -26,16 +24,16 @@ type node struct {
type record struct {
Name string
Version string
Metadata metadata.Metadata
Metadata map[string]string
Nodes map[string]*node
Endpoints []*register.Endpoint
}
type memory struct {
sync.RWMutex
records map[string]services
watchers map[string]*watcher
opts register.Options
sync.RWMutex
}
// services is a KV map with service name as the key and a map of records as the value
@ -66,7 +64,7 @@ func (m *memory) ttlPrune() {
for id, n := range record.Nodes {
if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, "RegisterTTL expired for node "+n.ID+" of service "+service)
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.ID, service)
}
delete(m.records[domain][service][version].Nodes, id)
}
@ -102,20 +100,10 @@ func (m *memory) sendEvent(r *register.Result) {
}
func (m *memory) Connect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
return nil
}
func (m *memory) Disconnect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
return nil
}
@ -136,11 +124,6 @@ func (m *memory) Options() register.Options {
}
func (m *memory) Register(ctx context.Context, s *register.Service, opts ...register.RegisterOption) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
m.Lock()
defer m.Unlock()
@ -154,11 +137,11 @@ func (m *memory) Register(ctx context.Context, s *register.Service, opts ...regi
// domain is set in metadata so it can be passed to watchers
if s.Metadata == nil {
s.Metadata = metadata.New(0)
s.Metadata = map[string]string{"domain": options.Domain}
} else {
s.Metadata["domain"] = options.Domain
}
s.Metadata.Set("domain", options.Domain)
// ensure the service name exists
r := serviceToRecord(s, options.TTL)
if _, ok := srvs[s.Name]; !ok {
@ -168,7 +151,7 @@ func (m *memory) Register(ctx context.Context, s *register.Service, opts ...regi
if _, ok := srvs[s.Name][s.Version]; !ok {
srvs[s.Name][s.Version] = r
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, "register added new service: "+s.Name+", version "+s.Version)
m.opts.Logger.Debugf(m.opts.Context, "Register added new service: %s, version: %s", s.Name, s.Version)
}
m.records[options.Domain] = srvs
go m.sendEvent(&register.Result{Action: "create", Service: s})
@ -182,8 +165,15 @@ func (m *memory) Register(ctx context.Context, s *register.Service, opts ...regi
continue
}
metadata := metadata.Copy(n.Metadata)
metadata.Set("domain", options.Domain)
metadata := make(map[string]string, len(n.Metadata))
// make copy of metadata
for k, v := range n.Metadata {
metadata[k] = v
}
// set the domain
metadata["domain"] = options.Domain
// add the node
srvs[s.Name][s.Version].Nodes[n.ID] = &node{
@ -201,14 +191,14 @@ func (m *memory) Register(ctx context.Context, s *register.Service, opts ...regi
if addedNodes {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, "register added new node to service: "+s.Name+", version "+s.Version)
m.opts.Logger.Debugf(m.opts.Context, "Register added new node to service: %s, version: %s", s.Name, s.Version)
}
go m.sendEvent(&register.Result{Action: "update", Service: s})
} else {
// refresh TTL and timestamp
for _, n := range s.Nodes {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, "updated registration for service: "+s.Name+", version "+s.Version)
m.opts.Logger.Debugf(m.opts.Context, "Updated registration for service: %s, version: %s", s.Name, s.Version)
}
srvs[s.Name][s.Version].Nodes[n.ID].TTL = options.TTL
srvs[s.Name][s.Version].Nodes[n.ID].LastSeen = time.Now()
@ -227,9 +217,10 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
// domain is set in metadata so it can be passed to watchers
if s.Metadata == nil {
s.Metadata = metadata.New(0)
s.Metadata = map[string]string{"domain": options.Domain}
} else {
s.Metadata["domain"] = options.Domain
}
s.Metadata.Set("domain", options.Domain)
// if the domain doesn't exist, there is nothing to deregister
services, ok := m.records[options.Domain]
@ -252,7 +243,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
for _, n := range s.Nodes {
if _, ok := version.Nodes[n.ID]; ok {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, "register removed node from service: "+s.Name+", version "+s.Version)
m.opts.Logger.Debugf(m.opts.Context, "Register removed node from service: %s, version: %s", s.Name, s.Version)
}
delete(version.Nodes, n.ID)
}
@ -273,7 +264,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
go m.sendEvent(&register.Result{Action: "delete", Service: s})
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, "register removed service: "+s.Name)
m.opts.Logger.Debugf(m.opts.Context, "Register removed service: %s", s.Name)
}
return nil
}
@ -282,7 +273,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
delete(m.records[options.Domain][s.Name], s.Version)
go m.sendEvent(&register.Result{Action: "delete", Service: s})
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, "register removed service: "+s.Name+", version "+s.Version)
m.opts.Logger.Debugf(m.opts.Context, "Register removed service: %s, version: %s", s.Name, s.Version)
}
return nil
@ -434,24 +425,16 @@ func (m *watcher) Next() (*register.Result, error) {
continue
}
if m.wo.Domain == register.WildcardDomain {
return r, nil
}
if r.Service.Metadata == nil {
continue
}
// extract domain from service metadata
var domain string
if v, ok := r.Service.Metadata.Get("domain"); ok && v != "" {
domain = v
if r.Service.Metadata != nil && len(r.Service.Metadata["domain"]) > 0 {
domain = r.Service.Metadata["domain"]
} else {
domain = register.DefaultDomain
}
// only send the event if watching the wildcard or this specific domain
if m.wo.Domain == domain {
if m.wo.Domain == register.WildcardDomain || m.wo.Domain == domain {
return r, nil
}
case <-m.exit:
@ -470,7 +453,10 @@ func (m *watcher) Stop() {
}
func serviceToRecord(s *register.Service, ttl time.Duration) *record {
metadata := metadata.Copy(s.Metadata)
metadata := make(map[string]string, len(s.Metadata))
for k, v := range s.Metadata {
metadata[k] = v
}
nodes := make(map[string]*node, len(s.Nodes))
for _, n := range s.Nodes {
@ -482,7 +468,9 @@ func serviceToRecord(s *register.Service, ttl time.Duration) *record {
}
endpoints := make([]*register.Endpoint, len(s.Endpoints))
copy(endpoints, s.Endpoints)
for i, e := range s.Endpoints {
endpoints[i] = e
}
return &record{
Name: s.Name,
@ -494,11 +482,20 @@ func serviceToRecord(s *register.Service, ttl time.Duration) *record {
}
func recordToService(r *record, domain string) *register.Service {
metadata := make(map[string]string, len(r.Metadata))
for k, v := range r.Metadata {
metadata[k] = v
}
// set the domain in metadata so it can be determined when a wildcard query is performed
metadata["domain"] = domain
endpoints := make([]*register.Endpoint, len(r.Endpoints))
for i, e := range r.Endpoints {
md := metadata.Copy(e.Metadata)
// set the domain in metadata so it can be determined when a wildcard query is performed
md.Set("domain", domain)
md := make(map[string]string, len(e.Metadata))
for k, v := range e.Metadata {
md[k] = v
}
endpoints[i] = &register.Endpoint{
Name: e.Name,
@ -511,10 +508,15 @@ func recordToService(r *record, domain string) *register.Service {
nodes := make([]*register.Node, len(r.Nodes))
i := 0
for _, n := range r.Nodes {
md := make(map[string]string, len(n.Metadata))
for k, v := range n.Metadata {
md[k] = v
}
nodes[i] = &register.Node{
ID: n.ID,
Address: n.Address,
Metadata: metadata.Copy(n.Metadata),
Metadata: md,
}
i++
}
@ -522,7 +524,7 @@ func recordToService(r *record, domain string) *register.Service {
return &register.Service{
Name: r.Name,
Version: r.Version,
Metadata: metadata.Copy(r.Metadata),
Metadata: metadata,
Endpoints: endpoints,
Nodes: nodes,
}

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