Compare commits

..

131 Commits

Author SHA1 Message Date
fa8fb3aed7 fixes and improvements (#55)
* util/router: sync from github
* config: add watcher interface

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-03 00:24:40 +03:00
cfd2d53a79 config: cleanup tests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-27 23:58:45 +03:00
d306f77ffc util/token/jwt: change library
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-27 23:58:29 +03:00
e5b0a7e20d server: add BatchSubscriber
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-27 23:58:06 +03:00
9a5b158b4d change jwt lib
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-27 12:43:56 +03:00
af8d81f3c6 logger: add DefaultCallerSkipCount
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-26 09:48:15 +03:00
5c9b3dae33 broker: improve option naming, move BatchBroker to Broker interface
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-24 16:16:18 +03:00
9f3957d101 client: improve option naming, add BatchPublish to noop client
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-24 16:14:42 +03:00
8fd8bdcb39 logger: fix default logger funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-24 15:22:01 +03:00
80e3d239ab broker/memory: optimize
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 15:12:20 +03:00
419cd486cf broker/memory: cleanup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 15:06:10 +03:00
e64269b2a8 broker: add BatchBroker interface to avoid breaking older brokers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 12:55:36 +03:00
d18429e024 metadata: add HeaderAuthorization
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 12:17:00 +03:00
675e121049 metadata: add default headers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 12:03:18 +03:00
d357fb1e0d WIP: broker batch processing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-22 22:53:44 +03:00
e4673bcc50 remove old cruft
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-22 15:45:44 +03:00
a839f75a2f util/reflect: add new funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-22 15:45:31 +03:00
a7e6d61b95 meter: fast path for only one label
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-21 14:29:13 +03:00
650d167313 meter: add BuildLabels func that sorts and deletes duplicates
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-21 14:10:20 +03:00
c6ba2a91e6 meter: BuildName func to combine metric name with labels into string
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-21 12:39:59 +03:00
7ece08896f server: use 127.0.0.1:0 if no address provided
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-17 01:57:39 +06:00
dependabot[bot]
57f6f23294 build(deps): bump github.com/google/uuid from 1.2.0 to 1.3.0 (#53)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Commits](https://github.com/google/uuid/compare/v1.2.0...v1.3.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-16 00:27:56 +03:00
09e6fa2fed flow: implement new methods, add Async ExecutionOption
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-16 00:17:16 +03:00
10a09a5c6f flow: improve store
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 22:56:34 +03:00
b4e5d9462a util/router: move some messages to Trace level
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 22:56:34 +03:00
96aa0b6906 store/memory: fix List
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 22:53:12 +03:00
f54658830d store/memory: fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 12:11:55 +03:00
1e43122660 store/memory: small fixups for flow usage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 11:59:35 +03:00
42800fa247 flow: improve steps handling
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 17:12:54 +03:00
5b9c810653 logger: add compile time test for interface compat
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 17:12:09 +03:00
c3def24bf4 store: add Wrappers support, create Namespace wrapper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 17:11:37 +03:00
0d1ef31764 client: change AuthToken option signature
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-09 10:47:40 +03:00
d49afa230f logger: add omit logger
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-05 23:04:20 +03:00
e545eb4e13 logger: add wrapper support
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-05 22:32:47 +03:00
f28b107372 broker: fix RawMessage marshal
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-01 23:23:01 +03:00
c592fabe2a minor fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-01 15:56:22 +03:00
eb107020c7 broker: replace Message.Body []byte slice to RawMessage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-01 15:11:17 +03:00
bd4d4c363e flow improvements (#52)
* flow improvements

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-30 17:50:58 +03:00
2a548634fd config: add Save/Load options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-20 23:57:13 +03:00
598dddc476 util/reflect: fix time.Time StructFields parsing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-19 15:58:36 +03:00
887b48f1e7 util/reflect: improve StructFields func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-17 12:53:23 +03:00
6e55d07636 client: allow to publish body only
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-13 12:20:35 +03:00
919520219c client: WithBodyOnly publish option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-11 14:14:41 +03:00
60a5e737f8 util/reflect: return pointer from helper funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-25 22:44:22 +03:00
34f0b209cc codec: add ability to control codec via struct tags
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-25 22:20:39 +03:00
ba8e1889fe dependabot
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-16 17:18:56 +03:00
dae5c57a60 Create dependabot.yml 2021-05-15 14:46:22 +03:00
ea590d57df meter/wrapper: add inflight request/message count (#47)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-10 17:59:40 +03:00
Renovate Bot
9aa6969836 fix(deps): update golang.org/x/net commit hash to 4163338 2021-05-10 14:29:32 +00:00
Renovate Bot
c00c705c24 fix(deps): update golang.org/x/net commit hash to 16afe75 2021-05-08 09:02:01 +00:00
Renovate Bot
0239f795d8 fix(deps): update golang.org/x/net commit hash to 7fd8e65 2021-05-03 10:07:41 +00:00
Renovate Bot
e69b43881d fix(deps): update golang.org/x/net commit hash to f8dd838 2021-05-01 23:20:59 +00:00
3a48a613fe not fail on lint now
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 08:36:11 +03:00
86626c5922 fieldalignment of all structs to save memory
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 08:32:47 +03:00
ee11f39a2f fieldaligment
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
3bdfdd8fd2 meter: fix labels
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
6dfdff7fd8 fieldaligment
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
00a4785df3 fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
Renovate Bot
bae3b0ef94 fix(deps): update golang.org/x/net commit hash to 5f58ad6 2021-04-23 23:52:34 +00:00
Renovate Bot
89b0565062 fix(deps): update golang.org/x/net commit hash to 4e50805 2021-04-22 03:04:18 +00:00
1f8b0aeb61 store: remove unused Value type
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-22 00:57:06 +03:00
Renovate Bot
5b6f849e0a fix(deps): update golang.org/x/net commit hash to 798c215 2021-04-20 23:13:59 +00:00
Renovate Bot
3b416fffde fix(deps): update golang.org/x/net commit hash to d25e304 2021-04-20 15:04:17 +00:00
3a60103aed server: drop Internal option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-20 12:45:14 +03:00
41837a67f8 register: drop verbose values export
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-20 12:39:21 +03:00
852f19598d util/reflect: fix protobuf field name detection
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-19 11:34:28 +03:00
6537b35773 util/reflect: add interface merging
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-19 01:19:37 +03:00
b733f1316f remove stale generate stuff
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-16 17:36:27 +03:00
Renovate Bot
840af5574c fix(deps): update golang.org/x/net commit hash to e915ea6 2021-04-16 00:56:52 +00:00
Renovate Bot
56e5b7001c fix(deps): update golang.org/x/net commit hash to 0645797 2021-04-14 21:41:15 +00:00
Renovate Bot
11dc6fd752 fix(deps): update golang.org/x/net commit hash to afb366f 2021-04-10 11:09:36 +00:00
a2695d8699 util/reflect: rewrite struct merging with map
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-10 01:22:40 +03:00
618421de05 client: allow to set content-type for call
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-09 23:09:12 +03:00
Renovate Bot
30baaabd9f fix(deps): update golang.org/x/net commit hash to a5a99cb 2021-04-05 19:46:46 +00:00
df5bce1191 util/reflect: fix StructURLValues
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-03 11:50:23 +03:00
Renovate Bot
089d0fe4df fix(deps): update golang.org/x/net commit hash to 0fccb6f 2021-03-31 22:50:38 +00:00
a06f535303 util/reflect: add StructURLValues func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-01 00:30:26 +03:00
Renovate Bot
eba586a329 fix(deps): update golang.org/x/net commit hash to cb1fcc7 2021-03-31 09:19:27 +00:00
Renovate Bot
d74a8645e8 fix(deps): update golang.org/x/net commit hash to e572328 2021-03-31 00:52:53 +00:00
Renovate Bot
5a00786192 fix(deps): update golang.org/x/net commit hash to cd0ac97 2021-03-30 22:28:38 +00:00
Renovate Bot
b3e9941634 fix(deps): update golang.org/x/net commit hash to c8897c2 2021-03-30 16:02:28 +00:00
Renovate Bot
a5a5904302 fix(deps): update golang.org/x/net commit hash to 22f4162 2021-03-30 11:44:00 +00:00
Renovate Bot
a59832e57e fix(deps): update golang.org/x/net commit hash to df645c7 2021-03-30 05:11:12 +00:00
0e42033e7f meter/handler: more idiomatic option handling
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-29 17:51:44 +03:00
52d8255974 service init with own context
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 23:42:02 +03:00
9830cb48a9 fix compilation
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:31:03 +03:00
92d7ab2105 regen handlers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:28:01 +03:00
d2935ef399 meter/handler: fix proto and generated code
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:18:11 +03:00
ce4c96ae0a server/health: add health check handler
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:18:11 +03:00
Renovate Bot
14026d15be fix(deps): update golang.org/x/net commit hash to 61e0566 2021-03-27 01:29:30 +00:00
Renovate Bot
2df0c7643e fix(deps): update golang.org/x/net commit hash to 6b15177 2021-03-26 19:17:30 +00:00
e13c2c48fd client: use router.DefaultRouter in NewOptions helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-26 17:45:55 +03:00
8db55d2e55 router: use dns as default router
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-26 17:43:39 +03:00
ed61cad961 meter/handler: regen
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-26 15:45:09 +03:00
040fc4548f client: add TLSConfig option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-26 15:44:34 +03:00
6189a1b980 add SkipEndpoints for wrappers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-25 23:30:38 +03:00
eb2a450a7b meter/handler: fix func signature
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-25 12:36:48 +03:00
Renovate Bot
d2a30a5da1 fix(deps): update golang.org/x/net commit hash to d1beb07 2021-03-24 23:28:38 +00:00
65889c66f6 meter/wrapper: add DefaultSkipEndpoints
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-25 00:06:38 +03:00
dcdf133d5b server: mask router
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-24 13:26:36 +03:00
8742b55305 auth: fix Init method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-24 13:12:03 +03:00
4a64ee72f7 meter/handler: provide default metrics handler
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-23 17:26:29 +03:00
881d7afeea meter/handler: provide initial meter handler
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-23 17:12:13 +03:00
8c95448535 util/reflect: add IsZero helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-21 16:17:50 +03:00
c1dc041d8c client: fix NewOptions with CallOptions filling
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-16 19:09:49 +03:00
Renovate Bot
25be0ac0f0 fix(deps): update golang.org/x/net commit hash to d523dce 2021-03-16 13:12:17 +00:00
Renovate Bot
86f73cac4e fix(deps): update golang.org/x/net commit hash to 34ac3e1 2021-03-15 22:09:13 +00:00
485eda6ce9 meter/wrapper: fix wrapper build
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-15 00:49:26 +03:00
dbbdb24631 meter: rework labels
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-15 00:44:13 +03:00
723ceb4f32 regsiter: fix extractor
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-12 16:09:20 +03:00
bac9869bb3 register: support map in ExtractValue
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-12 15:48:05 +03:00
610427445f codec: provide proto for codec.Frame
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-11 07:42:42 +03:00
Renovate Bot
c84a66c713 fix(deps): update module github.com/imdario/mergo to v0.3.12 2021-03-10 00:35:57 +00:00
00eaae717b lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-06 23:44:54 +03:00
a102e95433 spell fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-06 23:33:37 +03:00
39f66cc86c add logger wrapper, fix default logger fields method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-06 23:26:47 +03:00
bbbcb22565 fieldalignment of all structs to save memory
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-06 19:45:13 +03:00
cb70dfa664 meter/wrapper: use meter.DefaultMeter in NewOptions
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-05 17:40:03 +03:00
1f0482fbd5 tracer: finalize tracer implementation
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-04 01:12:16 +03:00
a862562284 fixup domain in ListServices
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-03 18:16:54 +03:00
c320c23913 metadata: minor fixup for NewXXXContext functions
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-01 13:00:53 +03:00
Renovate Bot
ae848ba8bb fix(deps): update golang.org/x/net commit hash to e18ecbb 2021-02-26 19:46:14 +00:00
Renovate Bot
8e264cbb3e fix(deps): update golang.org/x/net commit hash to 39120d0 2021-02-26 12:05:10 +00:00
Renovate Bot
54e523ab3f fix(deps): update golang.org/x/net commit hash to 3d97a24 2021-02-26 08:40:37 +00:00
09973af099 server: add error helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-22 00:52:18 +03:00
3247da3dd0 metadata: add Pairs helper func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-22 00:08:05 +03:00
b505455f7c run go mod tidy in renovate update
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-21 23:55:19 +03:00
293949f081 metadata: add Append func to Incoming/Outgoing context
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-21 23:54:59 +03:00
8d7e442b3a server: add SubscriberBodyOnly option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-20 18:12:13 +03:00
renovate[bot]
f7b5211af3 fix(deps): update golang.org/x/net commit hash to 5f55cee (#20)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-20 11:40:35 +03:00
7eb6d030dc meter: fix internal labels sorting
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-18 15:57:42 +03:00
163 changed files with 6926 additions and 2636 deletions

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

@@ -0,0 +1,19 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
# Maintain dependencies for Golang
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"

15
.github/generate.sh vendored
View File

@@ -1,15 +0,0 @@
#!/bin/bash -e
find . -type f -name '*.pb.*.go' -o -name '*.pb.go' -a ! -name 'message.pb.go' -delete
PROTOS=$(find . -type f -name '*.proto' | grep -v proto/google/api)
mkdir -p proto/google/api
curl -s -o proto/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto
curl -s -o proto/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto
for PROTO in $PROTOS; do
echo $PROTO
protoc -I./proto -I. -I$(dirname $PROTO) --go-grpc_out=paths=source_relative:. --go_out=paths=source_relative:. --micro_out=paths=source_relative:. $PROTO
done
rm -r proto

19
.github/renovate.json vendored
View File

@@ -1,19 +0,0 @@
{
"extends": [
"config:base"
],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true
},
{
"groupName": "all deps",
"separateMajorMinor": true,
"groupSlug": "all",
"packagePatterns": [
"*"
]
}
]
}

13
.github/stale.sh vendored
View File

@@ -1,13 +0,0 @@
#!/bin/bash -ex
export PATH=$PATH:$(pwd)/bin
export GO111MODULE=on
export GOBIN=$(pwd)/bin
#go get github.com/rvflash/goup@v0.4.1
#goup -v ./...
#go get github.com/psampaz/go-mod-outdated@v0.6.0
go list -u -m -mod=mod -json all | go-mod-outdated -update -direct -ci || true
#go list -u -m -json all | go-mod-outdated -update

View File

@@ -53,7 +53,7 @@ jobs:
continue-on-error: true continue-on-error: true
with: with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.30 version: v1.39
# Optional: working directory, useful for monorepos # Optional: working directory, useful for monorepos
# working-directory: somedir # working-directory: somedir
# Optional: golangci-lint command line arguments. # Optional: golangci-lint command line arguments.

View File

@@ -53,7 +53,7 @@ jobs:
continue-on-error: true continue-on-error: true
with: with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.30 version: v1.39
# Optional: working directory, useful for monorepos # Optional: working directory, useful for monorepos
# working-directory: somedir # working-directory: somedir
# Optional: golangci-lint command line arguments. # Optional: golangci-lint command line arguments.

View File

@@ -1,30 +1,44 @@
run: run:
concurrency: 4
deadline: 5m deadline: 5m
modules-download-mode: readonly issues-exit-code: 1
skip-files: tests: true
- ".*\\.pb\\.go$"
- ".*\\.pb\\.micro\\.go$" linters-settings:
govet:
check-shadowing: true
enable:
- fieldalignment
linters: linters:
disable-all: false
enable-all: false
enable: enable:
- megacheck
- staticcheck
- deadcode
- varcheck
- gosimple
- unused
- prealloc
- scopelint
- gocritic
- goimports
- unconvert
- govet - govet
- nakedret - deadcode
- errcheck
- govet
- ineffassign
- staticcheck
- structcheck - structcheck
- gosec
disable:
- maligned
- interfacer
- typecheck - typecheck
- dupl - unused
- varcheck
- bodyclose
- gci
- goconst
- gocritic
- gosimple
- gofmt
- gofumpt
- goimports
- golint
- gosec
- makezero
- misspell
- nakedret
- nestif
- nilerr
- noctx
- prealloc
- unconvert
- unparam
disable-all: false

View File

@@ -34,20 +34,20 @@ type Option func(*Options) error
type Endpoint struct { type Endpoint struct {
// Name Greeter.Hello // Name Greeter.Hello
Name string Name string
// Description e.g what's this endpoint for // Desciption for endpoint
Description string Description string
// Handler e.g rpc, proxy // Handler e.g rpc, proxy
Handler string Handler string
// Body destination
// "*" or "" - top level message value
// "string" - inner message value
Body string
// Host e.g example.com // Host e.g example.com
Host []string Host []string
// Method e.g GET, POST // Method e.g GET, POST
Method []string Method []string
// Path e.g /greeter. Expect POSIX regex // Path e.g /greeter. Expect POSIX regex
Path []string Path []string
// Body destination
// "*" or "" - top level message value
// "string" - inner message value
Body string
// Stream flag // Stream flag
Stream bool Stream bool
} }

View File

@@ -149,5 +149,4 @@ func TestValidate(t *testing.T) {
if err := Validate(epPcreInvalid); err == nil { if err := Validate(epPcreInvalid); err == nil {
t.Fatalf("invalid pcre %v", epPcreInvalid.Path[0]) t.Fatalf("invalid pcre %v", epPcreInvalid.Path[0])
} }
} }

View File

@@ -6,18 +6,16 @@ import (
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
) )
var ( // DefaultMaxRecvSize specifies max recv size for handler
// DefaultMaxRecvSize specifies max recv size for handler var DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
)
// Options struct holds handler options // Options struct holds handler options
type Options struct { type Options struct {
MaxRecvSize int64
Namespace string
Router router.Router Router router.Router
Client client.Client Client client.Client
Logger logger.Logger Logger logger.Logger
Namespace string
MaxRecvSize int64
} }
// Option func signature // Option func signature

View File

@@ -8,9 +8,12 @@ import (
// Options struct // Options struct
type Options struct { type Options struct {
Handler string // Context is for external defined options
Context context.Context
// Handler name
Handler string
// ServicePrefix is the prefix
ServicePrefix string ServicePrefix string
Context context.Context
} }
// Option func // Option func

View File

@@ -15,12 +15,12 @@ import (
// NewResolver creates new subdomain api resolver // NewResolver creates new subdomain api resolver
func NewResolver(parent resolver.Resolver, opts ...resolver.Option) resolver.Resolver { func NewResolver(parent resolver.Resolver, opts ...resolver.Option) resolver.Resolver {
options := resolver.NewOptions(opts...) options := resolver.NewOptions(opts...)
return &subdomainResolver{options, parent} return &subdomainResolver{opts: options, Resolver: parent}
} }
type subdomainResolver struct { type subdomainResolver struct {
opts resolver.Options
resolver.Resolver resolver.Resolver
opts resolver.Options
} }
// Resolve resolve endpoint based on subdomain // Resolve resolve endpoint based on subdomain

View File

@@ -19,9 +19,7 @@ type vpathResolver struct {
opts resolver.Options opts resolver.Options
} }
var ( var re = regexp.MustCompile("^v[0-9]+$")
re = regexp.MustCompile("^v[0-9]+$")
)
// Resolve endpoint // Resolve endpoint
func (r *vpathResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) { func (r *vpathResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {

View File

@@ -11,11 +11,16 @@ import (
// Options holds the options for api router // Options holds the options for api router
type Options struct { type Options struct {
Handler string // Register for service lookup
Register register.Register Register register.Register
// Resolver to use
Resolver resolver.Resolver Resolver resolver.Resolver
Logger logger.Logger // Logger micro logger
Context context.Context Logger logger.Logger
// Context is for external options
Context context.Context
// Handler name
Handler string
} }
// Option func signature // Option func signature

View File

@@ -7,10 +7,8 @@ import (
"github.com/unistack-org/micro/v3/api" "github.com/unistack-org/micro/v3/api"
) )
var ( // DefaultRouter contains default router implementation
// DefaultRouter contains default router implementation var DefaultRouter Router
DefaultRouter Router
)
// Router is used to determine an endpoint for a request // Router is used to determine an endpoint for a request
type Router interface { type Router interface {

View File

@@ -30,7 +30,7 @@ var (
// Auth provides authentication and authorization // Auth provides authentication and authorization
type Auth interface { type Auth interface {
// Init the auth // Init the auth
Init(opts ...Option) Init(opts ...Option) error
// Options set for auth // Options set for auth
Options() Options Options() Options
// Generate a new account // Generate a new account
@@ -53,30 +53,30 @@ type Auth interface {
// Account provided by an auth provider // Account provided by an auth provider
type Account struct { type Account struct {
// ID of the account e.g. email // Metadata any other associated metadata
Metadata metadata.Metadata `json:"metadata"`
// ID of the account e.g. email or uuid
ID string `json:"id"` ID string `json:"id"`
// Type of the account, e.g. service // Type of the account, e.g. service
Type string `json:"type"` Type string `json:"type"`
// Issuer of the account // Issuer of the account
Issuer string `json:"issuer"` Issuer string `json:"issuer"`
// Any other associated metadata
Metadata metadata.Metadata `json:"metadata"`
// Scopes the account has access to
Scopes []string `json:"scopes"`
// Secret for the account, e.g. the password // Secret for the account, e.g. the password
Secret string `json:"secret"` Secret string `json:"secret"`
// Scopes the account has access to
Scopes []string `json:"scopes"`
} }
// Token can be short or long lived // Token can be short or long lived
type Token struct { type Token struct {
// The token to be used for accessing resources
AccessToken string `json:"access_token"`
// RefreshToken to be used to generate a new token
RefreshToken string `json:"refresh_token"`
// Time of token creation // Time of token creation
Created time.Time `json:"created"` Created time.Time `json:"created"`
// Time of token expiry // Time of token expiry
Expiry time.Time `json:"expiry"` Expiry time.Time `json:"expiry"`
// The token to be used for accessing resources
AccessToken string `json:"access_token"`
// RefreshToken to be used to generate a new token
RefreshToken string `json:"refresh_token"`
} }
// Expired returns a boolean indicating if the token needs to be refreshed // Expired returns a boolean indicating if the token needs to be refreshed
@@ -106,17 +106,15 @@ const (
// Rule is used to verify access to a resource // Rule is used to verify access to a resource
type Rule struct { type Rule struct {
// ID of the rule, e.g. "public" // Resource that rule belongs to
ID string
// Scope the rule requires, a blank scope indicates open to the public and * indicates the rule
// applies to any valid account
Scope string
// Resource the rule applies to
Resource *Resource Resource *Resource
// Access determines if the rule grants or denies access to the resource // ID of the rule
ID string
// Scope of the rule
Scope string
// Access flag allow/deny
Access Access Access Access
// Priority the rule should take when verifying a request, the higher the value the sooner the // Priority holds the rule priority
// rule will be applied
Priority int32 Priority int32
} }

View File

@@ -14,10 +14,11 @@ func (n *noopAuth) String() string {
} }
// Init the auth // Init the auth
func (n *noopAuth) Init(opts ...Option) { func (n *noopAuth) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
} }
return nil
} }
// Options set for auth // Options set for auth

View File

@@ -26,33 +26,34 @@ func NewOptions(opts ...Option) Options {
// Options struct holds auth options // Options struct holds auth options
type Options struct { type Options struct {
Name string // Context holds the external options
// Issuer of the service's account Context context.Context
Issuer string // Meter used for metrics
// ID is the services auth ID Meter meter.Meter
ID string // Logger used for logging
// Secret is used to authenticate the service Logger logger.Logger
Secret string // Tracer used for tracing
Tracer tracer.Tracer
// Store used for stre data
Store store.Store
// Token is the services token used to authenticate itself // Token is the services token used to authenticate itself
Token *Token Token *Token
// PublicKey for decoding JWTs
PublicKey string
// PrivateKey for encoding JWTs
PrivateKey string
// LoginURL is the relative url path where a user can login // LoginURL is the relative url path where a user can login
LoginURL string LoginURL string
// Store to back auth // PrivateKey for encoding JWTs
Store store.Store PrivateKey string
// PublicKey for decoding JWTs
PublicKey string
// Secret is used to authenticate the service
Secret string
// ID is the services auth ID
ID string
// Issuer of the service's account
Issuer string
// Name holds the auth name
Name string
// Addrs sets the addresses of auth // Addrs sets the addresses of auth
Addrs []string Addrs []string
// Logger sets the logger
Logger logger.Logger
// Meter sets tht meter
Meter meter.Meter
// Tracer
Tracer tracer.Tracer
// Context to store other options
Context context.Context
} }
// Option func // Option func
@@ -124,18 +125,12 @@ func LoginURL(url string) Option {
// GenerateOptions struct // GenerateOptions struct
type GenerateOptions struct { type GenerateOptions struct {
// Metadata associated with the account
Metadata metadata.Metadata Metadata metadata.Metadata
// Scopes the account has access too
Scopes []string
// Provider of the account, e.g. oauth
Provider string Provider string
// Type of the account, e.g. user Type string
Type string Secret string
// Secret used to authenticate the account Issuer string
Secret string Scopes []string
// Issuer of the account, e.g. micro
Issuer string
} }
// GenerateOption func // GenerateOption func
@@ -194,16 +189,11 @@ func NewGenerateOptions(opts ...GenerateOption) GenerateOptions {
// TokenOptions struct // TokenOptions struct
type TokenOptions struct { type TokenOptions struct {
// ID for the account ID string
ID string Secret string
// Secret for the account
Secret string
// RefreshToken is used to refesh a token
RefreshToken string RefreshToken string
// Expiry is the time the token should live for Issuer string
Expiry time.Duration Expiry time.Duration
// Issuer of the account
Issuer string
} }
// TokenOption func // TokenOption func

View File

@@ -24,11 +24,11 @@ func TestVerify(t *testing.T) {
} }
tt := []struct { tt := []struct {
Name string Error error
Rules []*Rule
Account *Account Account *Account
Resource *Resource Resource *Resource
Error error Name string
Rules []*Rule
}{ }{
{ {
Name: "NoRules", Name: "NoRules",

View File

@@ -3,48 +3,128 @@ package broker
import ( import (
"context" "context"
"errors"
"github.com/unistack-org/micro/v3/metadata" "github.com/unistack-org/micro/v3/metadata"
) )
// DefaultBroker default memory broker
var DefaultBroker Broker = NewBroker()
var ( var (
// DefaultBroker default broker // ErrNotConnected returns when broker used but not connected yet
DefaultBroker Broker = NewBroker() ErrNotConnected = errors.New("broker not connected")
// ErrDisconnected returns when broker disconnected
ErrDisconnected = errors.New("broker disconnected")
) )
// Broker is an interface used for asynchronous messaging. // Broker is an interface used for asynchronous messaging.
type Broker interface { type Broker interface {
// Name returns broker instance name
Name() string Name() string
Init(...Option) error // Init initilize broker
Init(opts ...Option) error
// Options returns broker options
Options() Options Options() Options
// Address return configured address
Address() string Address() string
Connect(context.Context) error // Connect connects to broker
Disconnect(context.Context) error Connect(ctx context.Context) error
Publish(context.Context, string, *Message, ...PublishOption) error // Disconnect disconnect from broker
Subscribe(context.Context, string, Handler, ...SubscribeOption) (Subscriber, error) Disconnect(ctx context.Context) 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, 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 String() string
} }
// Handler is used to process messages via a subscription of a topic. // Handler is used to process messages via a subscription of a topic.
type Handler func(Event) error type Handler func(Event) error
// Events contains multiple events
type Events []Event
func (evs Events) Ack() error {
var err error
for _, ev := range evs {
if err = ev.Ack(); err != nil {
return err
}
}
return nil
}
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 // Event is given to a subscription handler for processing
type Event interface { type Event interface {
// Topic returns event topic
Topic() string Topic() string
// Message returns broker message
Message() *Message Message() *Message
// Ack acknowledge message
Ack() error Ack() error
// Error returns message error (like decoding errors or some other)
Error() error Error() error
// SetError set event processing error
SetError(err error)
}
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding.
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m *RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return *m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("RawMessage UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
} }
// Message is used to transfer data // Message is used to transfer data
type Message struct { type Message struct {
Header metadata.Metadata // contains message metadata // Header contains message metadata
Body []byte // contains message body Header metadata.Metadata
// Body contains message body
Body 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 // Subscriber is a convenience return type for the Subscribe method
type Subscriber interface { type Subscriber interface {
// Options returns subscriber options
Options() SubscribeOptions Options() SubscribeOptions
// Topic returns topic for subscription
Topic() string Topic() string
Unsubscribe(context.Context) error // Unsubscribe from topic
Unsubscribe(ctx context.Context) error
} }

View File

@@ -2,39 +2,39 @@ package broker
import ( import (
"context" "context"
"errors"
"sync" "sync"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
maddr "github.com/unistack-org/micro/v3/util/addr" maddr "github.com/unistack-org/micro/v3/util/addr"
mnet "github.com/unistack-org/micro/v3/util/net" mnet "github.com/unistack-org/micro/v3/util/net"
"github.com/unistack-org/micro/v3/util/rand" "github.com/unistack-org/micro/v3/util/rand"
) )
type memoryBroker struct { type memoryBroker struct {
opts Options subscribers map[string][]*memorySubscriber
addr string
addr string opts Options
sync.RWMutex sync.RWMutex
connected bool connected bool
Subscribers map[string][]*memorySubscriber
} }
type memoryEvent struct { type memoryEvent struct {
opts Options
topic string
err error err error
message interface{} message interface{}
topic string
opts Options
} }
type memorySubscriber struct { type memorySubscriber struct {
id string ctx context.Context
topic string exit chan bool
exit chan bool handler Handler
handler Handler batchhandler BatchHandler
opts SubscribeOptions id string
ctx context.Context topic string
opts SubscribeOptions
} }
func (m *memoryBroker) Options() Options { func (m *memoryBroker) Options() Options {
@@ -78,7 +78,6 @@ func (m *memoryBroker) Disconnect(ctx context.Context) error {
} }
m.connected = false m.connected = false
return nil return nil
} }
@@ -93,67 +92,190 @@ func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *Message,
m.RLock() m.RLock()
if !m.connected { if !m.connected {
m.RUnlock() m.RUnlock()
return errors.New("not connected") return ErrNotConnected
} }
subs, ok := m.Subscribers[topic]
m.RUnlock() m.RUnlock()
if !ok {
return nil
}
var v interface{} options := NewPublishOptions(opts...)
if m.opts.Codec != nil { vs := make([]msgWrapper, 0, 1)
if m.opts.Codec == nil || options.BodyOnly {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
vs = append(vs, msgWrapper{topic: topic, body: msg})
} else {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
buf, err := m.opts.Codec.Marshal(msg) buf, err := m.opts.Codec.Marshal(msg)
if err != nil { if err != nil {
return err return err
} }
v = buf vs = append(vs, msgWrapper{topic: topic, body: buf})
}
return m.publish(ctx, vs, opts...)
}
type msgWrapper struct {
topic string
body interface{}
}
func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
m.RLock()
if !m.connected {
m.RUnlock()
return ErrNotConnected
}
m.RUnlock()
options := NewPublishOptions(opts...)
vs := make([]msgWrapper, 0, len(msgs))
if m.opts.Codec == nil || options.BodyOnly {
for _, msg := range msgs {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
vs = append(vs, msgWrapper{topic: topic, body: msg})
}
} else { } else {
v = msg for _, msg := range msgs {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
buf, err := m.opts.Codec.Marshal(msg)
if err != nil {
return err
}
vs = append(vs, msgWrapper{topic: topic, body: buf})
}
} }
p := &memoryEvent{ return m.publish(ctx, vs, opts...)
topic: topic, }
message: v,
opts: m.opts, func (m *memoryBroker) publish(ctx context.Context, vs []msgWrapper, opts ...PublishOption) error {
var err error
msgTopicMap := make(map[string]Events)
for _, v := range vs {
p := &memoryEvent{
topic: v.topic,
message: v.body,
opts: m.opts,
}
msgTopicMap[p.topic] = append(msgTopicMap[p.topic], p)
} }
beh := m.opts.BatchErrorHandler
eh := m.opts.ErrorHandler eh := m.opts.ErrorHandler
for _, sub := range subs { for t, ms := range msgTopicMap {
if err := sub.handler(p); err != nil { m.RLock()
p.err = err subs, ok := m.subscribers[t]
if sub.opts.ErrorHandler != nil { m.RUnlock()
eh = sub.opts.ErrorHandler if !ok {
}
if eh != nil {
eh(p)
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
continue continue
} }
for _, sub := range subs {
// batch processing
if sub.batchhandler != nil {
if err = sub.batchhandler(ms); err != nil {
ms.SetError(err)
if sub.opts.BatchErrorHandler != nil {
beh = sub.opts.BatchErrorHandler
}
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
if sub.handler != nil {
for _, p := range ms {
if err = sub.handler(p); err != nil {
p.SetError(err)
if sub.opts.ErrorHandler != nil {
eh = sub.opts.ErrorHandler
}
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 return nil
} }
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) { func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) {
m.RLock() m.RLock()
if !m.connected { if !m.connected {
m.RUnlock() m.RUnlock()
return nil, errors.New("not connected") return nil, ErrNotConnected
} }
m.RUnlock() m.RUnlock()
options := NewSubscribeOptions(opts...)
id, err := uuid.NewRandom() id, err := uuid.NewRandom()
if err != nil { if err != nil {
return nil, err return nil, err
} }
options := NewSubscribeOptions(opts...)
sub := &memorySubscriber{
exit: make(chan bool, 1),
id: id.String(),
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 Handler, opts ...SubscribeOption) (Subscriber, error) {
m.RLock()
if !m.connected {
m.RUnlock()
return nil, ErrNotConnected
}
m.RUnlock()
id, err := uuid.NewRandom()
if err != nil {
return nil, err
}
options := NewSubscribeOptions(opts...)
sub := &memorySubscriber{ sub := &memorySubscriber{
exit: make(chan bool, 1), exit: make(chan bool, 1),
id: id.String(), id: id.String(),
@@ -164,20 +286,20 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand
} }
m.Lock() m.Lock()
m.Subscribers[topic] = append(m.Subscribers[topic], sub) m.subscribers[topic] = append(m.subscribers[topic], sub)
m.Unlock() m.Unlock()
go func() { go func() {
<-sub.exit <-sub.exit
m.Lock() m.Lock()
var newSubscribers []*memorySubscriber newSubscribers := make([]*memorySubscriber, 0, len(m.subscribers)-1)
for _, sb := range m.Subscribers[topic] { for _, sb := range m.subscribers[topic] {
if sb.id == sub.id { if sb.id == sub.id {
continue continue
} }
newSubscribers = append(newSubscribers, sb) newSubscribers = append(newSubscribers, sb)
} }
m.Subscribers[topic] = newSubscribers m.subscribers[topic] = newSubscribers
m.Unlock() m.Unlock()
}() }()
@@ -222,6 +344,10 @@ func (m *memoryEvent) Error() error {
return m.err return m.err
} }
func (m *memoryEvent) SetError(err error) {
m.err = err
}
func (m *memorySubscriber) Options() SubscribeOptions { func (m *memorySubscriber) Options() SubscribeOptions {
return m.opts return m.opts
} }
@@ -239,6 +365,6 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
func NewBroker(opts ...Option) Broker { func NewBroker(opts ...Option) Broker {
return &memoryBroker{ return &memoryBroker{
opts: NewOptions(opts...), opts: NewOptions(opts...),
Subscribers: make(map[string][]*memorySubscriber), subscribers: make(map[string][]*memorySubscriber),
} }
} }

View File

@@ -4,8 +4,55 @@ import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"github.com/unistack-org/micro/v3/metadata"
) )
func TestMemoryBatchBroker(t *testing.T) {
b := NewBroker()
ctx := context.Background()
if err := b.Connect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err)
}
topic := "test"
count := 10
fn := func(evts Events) error {
return evts.Ack()
}
sub, err := b.BatchSubscribe(ctx, topic, fn)
if err != nil {
t.Fatalf("Unexpected error subscribing %v", err)
}
msgs := make([]*Message, 0, 0)
for i := 0; i < count; i++ {
message := &Message{
Header: map[string]string{
metadata.HeaderTopic: topic,
"foo": "bar",
"id": fmt.Sprintf("%d", i),
},
Body: []byte(`"hello world"`),
}
msgs = append(msgs, message)
}
if err := b.BatchPublish(ctx, msgs); err != nil {
t.Fatalf("Unexpected error publishing %v", err)
}
if err := sub.Unsubscribe(ctx); err != nil {
t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err)
}
if err := b.Disconnect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err)
}
}
func TestMemoryBroker(t *testing.T) { func TestMemoryBroker(t *testing.T) {
b := NewBroker() b := NewBroker()
ctx := context.Background() ctx := context.Background()
@@ -26,20 +73,27 @@ func TestMemoryBroker(t *testing.T) {
t.Fatalf("Unexpected error subscribing %v", err) t.Fatalf("Unexpected error subscribing %v", err)
} }
msgs := make([]*Message, 0, 0)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
message := &Message{ message := &Message{
Header: map[string]string{ Header: map[string]string{
"foo": "bar", metadata.HeaderTopic: topic,
"id": fmt.Sprintf("%d", i), "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 { if err := b.Publish(ctx, topic, message); err != nil {
t.Fatalf("Unexpected error publishing %d", i) t.Fatalf("Unexpected error publishing %d err: %v", i, err)
} }
} }
if err := b.BatchPublish(ctx, msgs); err != nil {
t.Fatalf("Unexpected error publishing %v", err)
}
if err := sub.Unsubscribe(ctx); err != nil { if err := sub.Unsubscribe(ctx); err != nil {
t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err) t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err)
} }

View File

@@ -3,6 +3,7 @@ package broker
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"time"
"github.com/unistack-org/micro/v3/codec" "github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
@@ -13,25 +14,28 @@ import (
// Options struct // Options struct
type Options struct { type Options struct {
Name string // Tracer used for tracing
// Addrs useed by broker
Addrs []string
// ErrorHandler executed when errors occur processing messages
ErrorHandler Handler
// Codec used to marshal/unmarshal messages
Codec codec.Codec
// Logger the used logger
Logger logger.Logger
// Meter the used for metrics
Meter meter.Meter
// Tracer used for trace
Tracer tracer.Tracer Tracer tracer.Tracer
// TLSConfig for secure communication // Register can be used for clustering
TLSConfig *tls.Config
// Register used for clustering
Register register.Register Register register.Register
// Context is used for non default options // Codec holds the codec for marshal/unmarshal
Codec codec.Codec
// Logger used for logging
Logger logger.Logger
// Meter used for metrics
Meter meter.Meter
// Context holds external options
Context context.Context Context context.Context
// TLSConfig holds tls.TLSConfig options
TLSConfig *tls.Config
// 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
// Addrs holds the broker address
Addrs []string
} }
// NewOptions create new Options // NewOptions create new Options
@@ -59,10 +63,10 @@ func Context(ctx context.Context) Option {
// PublishOptions struct // PublishOptions struct
type PublishOptions struct { type PublishOptions struct {
// BodyOnly says that only body of the message must be published // Context holds external options
BodyOnly bool
// Context for non default options
Context context.Context Context context.Context
// BodyOnly flag says the message contains raw body bytes
BodyOnly bool
} }
// NewPublishOptions creates PublishOptions struct // NewPublishOptions creates PublishOptions struct
@@ -70,32 +74,30 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
options := PublishOptions{ options := PublishOptions{
Context: context.Background(), Context: context.Background(),
} }
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
return options return options
} }
// SubscribeOptions struct // SubscribeOptions struct
type SubscribeOptions struct { type SubscribeOptions struct {
// AutoAck ack messages if handler returns nil err // Context holds external options
AutoAck bool
// ErrorHandler executed when errors occur processing messages
ErrorHandler Handler
// Group for subscriber, Subscribers with the same group name
// will create a shared subscription where each
// receives a subset of messages.
Group string
// BodyOnly says that consumed only body of the message
BodyOnly bool
// Context is used for non default options
Context context.Context Context context.Context
// 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
BodyOnly bool
// BatchSize flag specifies max batch size
BatchSize int
// BatchWait flag specifies max wait time for batch filling
BatchWait time.Duration
} }
// Option func // Option func
@@ -118,23 +120,6 @@ func PublishContext(ctx context.Context) PublishOption {
} }
} }
// SubscribeOption func
type SubscribeOption func(*SubscribeOptions)
// NewSubscribeOptions creates new SubscribeOptions
func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
options := SubscribeOptions{
AutoAck: true,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// Addrs sets the host addresses to be used by the broker // Addrs sets the host addresses to be used by the broker
func Addrs(addrs ...string) Option { func Addrs(addrs ...string) Option {
return func(o *Options) { return func(o *Options) {
@@ -150,28 +135,6 @@ func Codec(c codec.Codec) Option {
} }
} }
// DisableAutoAck disables auto ack
func DisableAutoAck() SubscribeOption {
return func(o *SubscribeOptions) {
o.AutoAck = false
}
}
// SubscribeAutoAck will disable 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
}
}
// ErrorHandler will catch all broker errors that cant be handled // ErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors // in normal way, for example Codec errors
func ErrorHandler(h Handler) Option { func ErrorHandler(h Handler) Option {
@@ -180,6 +143,14 @@ func ErrorHandler(h Handler) Option {
} }
} }
// 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 // SubscribeErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors // in normal way, for example Codec errors
func SubscribeErrorHandler(h Handler) SubscribeOption { func SubscribeErrorHandler(h Handler) SubscribeOption {
@@ -188,6 +159,14 @@ func SubscribeErrorHandler(h Handler) SubscribeOption {
} }
} }
// 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 // Queue sets the subscribers queue
// Deprecated // Deprecated
func Queue(name string) SubscribeOption { func Queue(name string) SubscribeOption {
@@ -251,3 +230,55 @@ func SubscribeContext(ctx context.Context) SubscribeOption {
o.Context = ctx 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 ...SubscribeOption) SubscribeOptions {
options := SubscribeOptions{
AutoAck: true,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}

View File

@@ -21,12 +21,12 @@ type Source struct {
// Package is packaged format for source // Package is packaged format for source
type Package struct { type Package struct {
// Source of the package
Source *Source
// Name of the package // Name of the package
Name string Name string
// Location of the package // Location of the package
Path string Path string
// Type of package e.g tarball, binary, docker // Type of package e.g tarball, binary, docker
Type string Type string
// Source of the package
Source *Source
} }

View File

@@ -40,6 +40,7 @@ type Client interface {
Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
Publish(ctx context.Context, msg Message, opts ...PublishOption) error Publish(ctx context.Context, msg Message, opts ...PublishOption) error
BatchPublish(ctx context.Context, msg []Message, opts ...PublishOption) error
String() string String() string
} }

View File

@@ -9,16 +9,10 @@ import (
"github.com/unistack-org/micro/v3/metadata" "github.com/unistack-org/micro/v3/metadata"
) )
var ( // DefaultCodecs will be used to encode/decode data
// DefaultCodecs will be used to encode/decode data var DefaultCodecs = map[string]codec.Codec{
DefaultCodecs = map[string]codec.Codec{ "application/octet-stream": codec.NewCodec(),
//"application/json": cjson.NewCodec, }
//"application/json-rpc": cjsonrpc.NewCodec,
//"application/protobuf": cproto.NewCodec,
//"application/proto-rpc": cprotorpc.NewCodec,
"application/octet-stream": codec.NewCodec(),
}
)
type noopClient struct { type noopClient struct {
opts Options opts Options
@@ -31,12 +25,12 @@ type noopMessage struct {
} }
type noopRequest struct { type noopRequest struct {
body interface{}
codec codec.Codec
service string service string
method string method string
endpoint string endpoint string
contentType string contentType string
body interface{}
codec codec.Codec
stream bool stream bool
} }
@@ -175,11 +169,11 @@ func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opt
} }
func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request { func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request {
return &noopRequest{} return &noopRequest{service: service, endpoint: endpoint}
} }
func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message { func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message {
options := NewMessageOptions(opts...) options := NewMessageOptions(append([]MessageOption{MessageContentType(n.opts.ContentType)}, opts...)...)
return &noopMessage{topic: topic, payload: msg, opts: options} return &noopMessage{topic: topic, payload: msg, opts: options}
} }
@@ -187,45 +181,60 @@ func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption
return &noopStream{}, nil return &noopStream{}, nil
} }
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error { func (n *noopClient) BatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
var body []byte return n.publish(ctx, ps, opts...)
}
func (n *noopClient) Publish(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...) options := NewPublishOptions(opts...)
md, ok := metadata.FromOutgoingContext(ctx) msgs := make([]*broker.Message, 0, len(ps))
if !ok {
md = metadata.New(0)
}
md["Content-Type"] = p.ContentType()
md["Micro-Topic"] = p.Topic()
// passed in raw data for _, p := range ps {
if d, ok := p.Payload().(*codec.Frame); ok { md, ok := metadata.FromOutgoingContext(ctx)
body = d.Data if !ok {
} else { md = metadata.New(0)
// use codec for payload }
cf, err := n.newCodec(p.ContentType()) md[metadata.HeaderContentType] = p.ContentType()
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error()) topic := p.Topic()
// get the exchange
if len(options.Exchange) > 0 {
topic = options.Exchange
} }
// set the body md[metadata.HeaderTopic] = topic
b, err := cf.Marshal(p.Payload())
if err != nil { var body []byte
return errors.InternalServerError("go.micro.client", err.Error())
// 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
} }
body = b
msgs = append(msgs, &broker.Message{Header: md, Body: body})
} }
topic := p.Topic() return n.opts.Broker.BatchPublish(ctx, msgs,
broker.PublishContext(options.Context),
// get the exchange broker.PublishBodyOnly(options.BodyOnly),
if len(options.Exchange) > 0 { )
topic = options.Exchange
}
return n.opts.Broker.Publish(ctx, topic, &broker.Message{
Header: md,
Body: body,
}, broker.PublishContext(options.Context))
} }

View File

@@ -2,6 +2,7 @@ package client
import ( import (
"context" "context"
"crypto/tls"
"time" "time"
"github.com/unistack-org/micro/v3/broker" "github.com/unistack-org/micro/v3/broker"
@@ -18,35 +19,42 @@ import (
// Options holds client options // Options holds client options
type Options struct { type Options struct {
Name string // Selector used to select needed address
// Used to select codec Selector selector.Selector
ContentType string // Logger used to log messages
// Proxy address to send requests via Logger logger.Logger
Proxy string // Tracer used for tracing
Tracer tracer.Tracer
// Plugged interfaces // Broker used to publish messages
Broker broker.Broker Broker broker.Broker
Codecs map[string]codec.Codec // Meter used for metrics
Router router.Router Meter meter.Meter
Selector selector.Selector // Router used to get route
Router router.Router
// Transport used for transfer messages
Transport transport.Transport Transport transport.Transport
Logger logger.Logger // Context is used for external options
Meter meter.Meter
// Lookup used for looking up routes
Lookup LookupFunc
// Connection Pool
PoolSize int
PoolTTL time.Duration
Tracer tracer.Tracer
// Wrapper that used client
Wrappers []Wrapper
// CallOptions that used by default
CallOptions CallOptions
// Context is used for non default options
Context context.Context Context context.Context
// Lookup func used to get destination addr
Lookup LookupFunc
// Codecs map
Codecs map[string]codec.Codec
// TLSConfig specifies tls.Config for secure connection
TLSConfig *tls.Config
// Proxy is used for proxy requests
Proxy string
// ContentType is used to select codec
ContentType string
// Name is the client name
Name string
// Wrappers contains wrappers
Wrappers []Wrapper
// CallOptions contains default CallOptions
CallOptions CallOptions
// PoolSize connection pool size
PoolSize int
// PoolTTL connection pool ttl
PoolTTL time.Duration
} }
// NewCallOptions creates new call options struct // NewCallOptions creates new call options struct
@@ -60,34 +68,36 @@ func NewCallOptions(opts ...CallOption) CallOptions {
// CallOptions holds client call options // CallOptions holds client call options
type CallOptions struct { type CallOptions struct {
// Address of remote hosts // Router used for route
Address []string
// Backoff func
Backoff BackoffFunc
// DialTimeout is the transport Dial Timeout
DialTimeout time.Duration
// Retries is the number of Call attempts
Retries int
// Retry func to be used for retries
Retry RetryFunc
// RequestTimeout specifies request timeout
RequestTimeout time.Duration
// Router to use for this call
Router router.Router Router router.Router
// Selector to use for the call // Selector selects addr
Selector selector.Selector Selector selector.Selector
// SelectOptions to use when selecting a route // Context used for deadline
SelectOptions []selector.SelectOption
// StreamTimeout timeout for the stream
StreamTimeout time.Duration
// AuthToken specifies the auth token as the authorization header
AuthToken bool
// Network to lookup the route within
Network string
// CallWrappers is for low level call func
CallWrappers []CallWrapper
// Context is used for non default options
Context context.Context Context context.Context
// Retry func used for retries
Retry RetryFunc
// Backoff func used for backoff when retry
Backoff BackoffFunc
// Network name
Network string
// Content-Type
ContentType string
// CallWrappers call wrappers
CallWrappers []CallWrapper
// SelectOptions selector options
SelectOptions []selector.SelectOption
// Address specifies static addr list
Address []string
// Retries specifies retries num
Retries int
// StreamTimeout stream timeout
StreamTimeout time.Duration
// RequestTimeout request timeout
RequestTimeout time.Duration
// DialTimeout dial timeout
DialTimeout time.Duration
// AuthToken string
AuthToken string
} }
// Context pass context to client // Context pass context to client
@@ -108,10 +118,12 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
// PublishOptions holds publish options // PublishOptions holds publish options
type PublishOptions struct { type PublishOptions struct {
// Exchange is the routing exchange for the message // BodyOnly will publish only message body
Exchange string BodyOnly bool
// Context holds additional options // Context used for external options
Context context.Context Context context.Context
// Exchange topic exchange name
Exchange string
} }
// NewMessageOptions creates message options struct // NewMessageOptions creates message options struct
@@ -125,6 +137,7 @@ func NewMessageOptions(opts ...MessageOption) MessageOptions {
// MessageOptions holds client message options // MessageOptions holds client message options
type MessageOptions struct { type MessageOptions struct {
// ContentType specify content-type of message
ContentType string ContentType string
} }
@@ -139,12 +152,12 @@ func NewRequestOptions(opts ...RequestOption) RequestOptions {
// RequestOptions holds client request options // RequestOptions holds client request options
type RequestOptions struct { type RequestOptions struct {
// ContentType specify content-type of request // Context used for external options
ContentType string
// Stream says that request is the streaming
Stream bool
// Context can hold other options
Context context.Context Context context.Context
// ContentType specify content-type of message
ContentType string
// Stream flag
Stream bool
} }
// NewOptions creates new options struct // NewOptions creates new options struct
@@ -154,20 +167,23 @@ func NewOptions(opts ...Option) Options {
ContentType: DefaultContentType, ContentType: DefaultContentType,
Codecs: make(map[string]codec.Codec), Codecs: make(map[string]codec.Codec),
CallOptions: CallOptions{ CallOptions: CallOptions{
Context: context.Background(),
Backoff: DefaultBackoff, Backoff: DefaultBackoff,
Retry: DefaultRetry, Retry: DefaultRetry,
Retries: DefaultRetries, Retries: DefaultRetries,
RequestTimeout: DefaultRequestTimeout, RequestTimeout: DefaultRequestTimeout,
DialTimeout: transport.DefaultDialTimeout, DialTimeout: transport.DefaultDialTimeout,
}, },
Lookup: LookupRoute, Lookup: LookupRoute,
PoolSize: DefaultPoolSize, PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL, PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(), Selector: random.NewSelector(),
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
Broker: broker.DefaultBroker, Broker: broker.DefaultBroker,
Meter: meter.DefaultMeter, Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer, Tracer: tracer.DefaultTracer,
Router: router.DefaultRouter,
Transport: transport.DefaultTransport,
} }
for _, o := range opts { for _, o := range opts {
@@ -305,6 +321,22 @@ func Lookup(l LookupFunc) Option {
} }
} }
// 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. // Retries sets the retry count when making the request.
func Retries(i int) Option { func Retries(i int) Option {
return func(o *Options) { return func(o *Options) {
@@ -341,12 +373,35 @@ func DialTimeout(d time.Duration) Option {
} }
// WithExchange sets the exchange to route a message through // WithExchange sets the exchange to route a message through
// Deprecated
func WithExchange(e string) PublishOption { func WithExchange(e string) PublishOption {
return func(o *PublishOptions) { return func(o *PublishOptions) {
o.Exchange = e 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 // PublishContext sets the context in publish options
func PublishContext(ctx context.Context) PublishOption { func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) { return func(o *PublishOptions) {
@@ -354,6 +409,13 @@ func PublishContext(ctx context.Context) PublishOption {
} }
} }
// 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 // WithAddress sets the remote addresses to use rather than using service discovery
func WithAddress(a ...string) CallOption { func WithAddress(a ...string) CallOption {
return func(o *CallOptions) { return func(o *CallOptions) {
@@ -417,9 +479,9 @@ func WithDialTimeout(d time.Duration) CallOption {
// WithAuthToken is a CallOption which overrides the // WithAuthToken is a CallOption which overrides the
// authorization header with the services own auth token // authorization header with the services own auth token
func WithAuthToken() CallOption { func WithAuthToken(t string) CallOption {
return func(o *CallOptions) { return func(o *CallOptions) {
o.AuthToken = true o.AuthToken = t
} }
} }
@@ -452,22 +514,30 @@ func WithSelectOptions(sops ...selector.SelectOption) CallOption {
} }
// WithMessageContentType sets the message content type // WithMessageContentType sets the message content type
// Deprecated
func WithMessageContentType(ct string) MessageOption { func WithMessageContentType(ct string) MessageOption {
return func(o *MessageOptions) { return func(o *MessageOptions) {
o.ContentType = ct o.ContentType = ct
} }
} }
// WithContentType specifies request content type // MessageContentType sets the message content type
func WithContentType(ct string) RequestOption { func MessageContentType(ct string) MessageOption {
return func(o *RequestOptions) { return func(o *MessageOptions) {
o.ContentType = ct o.ContentType = ct
} }
} }
// StreamingRequest specifies that request is streaming // StreamingRequest specifies that request is streaming
func StreamingRequest() RequestOption { func StreamingRequest(b bool) RequestOption {
return func(o *RequestOptions) { return func(o *RequestOptions) {
o.Stream = true o.Stream = b
}
}
// RequestContentType specifies request content type
func RequestContentType(ct string) RequestOption {
return func(o *RequestOptions) {
o.ContentType = ct
} }
} }

View File

@@ -5,12 +5,12 @@ import (
) )
type testRequest struct { type testRequest struct {
codec codec.Codec
body interface{}
service string service string
method string method string
endpoint string endpoint string
contentType string contentType string
codec codec.Codec
body interface{}
opts RequestOptions opts RequestOptions
} }

View File

@@ -28,6 +28,8 @@ var (
DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb
// DefaultCodec is the global default codec // DefaultCodec is the global default codec
DefaultCodec Codec = NewCodec() DefaultCodec Codec = NewCodec()
// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal
DefaultTagName = "codec"
) )
// MessageType specifies message type for codec // MessageType specifies message type for codec
@@ -51,16 +53,14 @@ type Codec interface {
// the communication, likely followed by the body. // the communication, likely followed by the body.
// In the case of an error, body may be nil. // In the case of an error, body may be nil.
type Message struct { type Message struct {
Id string Header metadata.Metadata
Type MessageType
Target string Target string
Method string Method string
Endpoint string Endpoint string
Error string Error string
Id string
// The values read from the socket Body []byte
Header metadata.Metadata Type MessageType
Body []byte
} }
// NewMessage creates new codec message // NewMessage creates new codec message

6
codec/frame.go Normal file
View File

@@ -0,0 +1,6 @@
package codec
// Frame gives us the ability to define raw data to send over the pipes
type Frame struct {
Data []byte
}

28
codec/frame.proto Normal file
View File

@@ -0,0 +1,28 @@
// 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
package micro.codec;
option cc_enable_arenas = true;
option go_package = "github.com/unistack-org/micro/v3/codec;codec";
option java_multiple_files = true;
option java_outer_classname = "MicroCodec";
option java_package = "micro.codec";
option objc_class_prefix = "MCODEC";
message Frame {
bytes data = 1;
}

View File

@@ -5,13 +5,7 @@ import (
"io" "io"
) )
type noopCodec struct { type noopCodec struct{}
}
// Frame gives us the ability to define raw data to send over the pipes
type Frame struct {
Data []byte
}
func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error { func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error {
return nil return nil

View File

@@ -11,10 +11,14 @@ type Option func(*Options)
// Options contains codec options // Options contains codec options
type Options struct { type Options struct {
// Meter used for metrics
Meter meter.Meter
// Logger used for logging
Logger logger.Logger
// Tracer used for tracing
Tracer tracer.Tracer
// MaxMsgSize specifies max messages size that reads by codec
MaxMsgSize int MaxMsgSize int
Meter meter.Meter
Logger logger.Logger
Tracer tracer.Tracer
} }
// MaxMsgSize sets the max message size // MaxMsgSize sets the max message size

View File

@@ -4,12 +4,14 @@ package config
import ( import (
"context" "context"
"errors" "errors"
"time"
) )
var ( // DefaultConfig default config
// DefaultConfig default config var DefaultConfig Config = NewConfig()
DefaultConfig Config = NewConfig()
) // DefaultWatcherInterval default interval for poll changes
var DefaultWatcherInterval = 5 * time.Second
var ( var (
// ErrCodecMissing is returned when codec needed and not specified // ErrCodecMissing is returned when codec needed and not specified
@@ -22,34 +24,38 @@ var (
// Config is an interface abstraction for dynamic configuration // Config is an interface abstraction for dynamic configuration
type Config interface { type Config interface {
// Name returns name of config
Name() string Name() string
// Init the config // Init the config
Init(opts ...Option) error Init(opts ...Option) error
// Options in the config // Options in the config
Options() Options Options() Options
// Load config from sources // Load config from sources
Load(context.Context) error Load(context.Context, ...LoadOption) error
// Save config to sources // Save config to sources
Save(context.Context) error Save(context.Context, ...SaveOption) error
// Watch a value for changes // Watch a config for changes
// Watch(interface{}) (Watcher, error) Watch(context.Context, ...WatchOption) (Watcher, error)
// String returns config type name
String() string String() string
} }
// Watcher is the config watcher // Watcher is the config watcher
//type Watcher interface { type Watcher interface {
// Next() (, error) // Next blocks until update happens or error returned
// Stop() error Next() (map[string]interface{}, error)
//} // Stop stops watcher
Stop() error
}
// Load loads config from config sources // Load loads config from config sources
func Load(ctx context.Context, cs ...Config) error { func Load(ctx context.Context, cs []Config, opts ...LoadOption) error {
var err error var err error
for _, c := range cs { for _, c := range cs {
if err = c.Init(); err != nil { if err = c.Init(); err != nil {
return err return err
} }
if err = c.Load(ctx); err != nil { if err = c.Load(ctx, opts...); err != nil {
return err return err
} }
} }

View File

@@ -5,6 +5,7 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/imdario/mergo" "github.com/imdario/mergo"
rutil "github.com/unistack-org/micro/v3/util/reflect" rutil "github.com/unistack-org/micro/v3/util/reflect"
@@ -25,18 +26,31 @@ func (c *defaultConfig) Init(opts ...Option) error {
return nil return nil
} }
func (c *defaultConfig) Load(ctx context.Context) error { func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
for _, fn := range c.opts.BeforeLoad { for _, fn := range c.opts.BeforeLoad {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail { if err := fn(ctx, c); err != nil && !c.opts.AllowFail {
return err return err
} }
} }
src, err := rutil.Zero(c.opts.Struct) options := NewLoadOptions(opts...)
mopts := []func(*mergo.Config){mergo.WithTypeCheck}
if options.Override {
mopts = append(mopts, mergo.WithOverride)
}
if options.Append {
mopts = append(mopts, mergo.WithAppendSlice)
}
dst := c.opts.Struct
if options.Struct != nil {
dst = options.Struct
}
src, err := rutil.Zero(dst)
if err == nil { if err == nil {
valueOf := reflect.ValueOf(src) if err = fillValues(reflect.ValueOf(src), c.opts.StructTag); err == nil {
if err = c.fillValues(ctx, valueOf); err == nil { err = mergo.Merge(dst, src, mopts...)
err = mergo.Merge(c.opts.Struct, src, mergo.WithOverride, mergo.WithTypeCheck, mergo.WithAppendSlice)
} }
} }
@@ -54,7 +68,7 @@ func (c *defaultConfig) Load(ctx context.Context) error {
} }
//nolint:gocyclo //nolint:gocyclo
func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val string) error { func fillValue(value reflect.Value, val string) error {
if !rutil.IsEmpty(value) { if !rutil.IsEmpty(value) {
return nil return nil
} }
@@ -71,10 +85,10 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
kv := strings.FieldsFunc(nval, func(c rune) bool { return c == '=' }) kv := strings.FieldsFunc(nval, func(c rune) bool { return c == '=' })
mkey := reflect.Indirect(reflect.New(kt)) mkey := reflect.Indirect(reflect.New(kt))
mval := reflect.Indirect(reflect.New(et)) mval := reflect.Indirect(reflect.New(et))
if err := c.fillValue(ctx, mkey, kv[0]); err != nil { if err := fillValue(mkey, kv[0]); err != nil {
return err return err
} }
if err := c.fillValue(ctx, mval, kv[1]); err != nil { if err := fillValue(mval, kv[1]); err != nil {
return err return err
} }
value.SetMapIndex(mkey, mval) value.SetMapIndex(mkey, mval)
@@ -84,7 +98,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
value.Set(reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), len(nvals), len(nvals))) value.Set(reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), len(nvals), len(nvals)))
for idx, nval := range nvals { for idx, nval := range nvals {
nvalue := reflect.Indirect(reflect.New(value.Type().Elem())) nvalue := reflect.Indirect(reflect.New(value.Type().Elem()))
if err := c.fillValue(ctx, nvalue, nval); err != nil { if err := fillValue(nvalue, nval); err != nil {
return err return err
} }
value.Index(idx).Set(nvalue) value.Index(idx).Set(nvalue)
@@ -173,7 +187,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
return nil return nil
} }
func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) error { func fillValues(valueOf reflect.Value, tname string) error {
var values reflect.Value var values reflect.Value
if valueOf.Kind() == reflect.Ptr { if valueOf.Kind() == reflect.Ptr {
@@ -200,7 +214,7 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
switch value.Kind() { switch value.Kind() {
case reflect.Struct: case reflect.Struct:
value.Set(reflect.Indirect(reflect.New(value.Type()))) value.Set(reflect.Indirect(reflect.New(value.Type())))
if err := c.fillValues(ctx, value); err != nil { if err := fillValues(value, tname); err != nil {
return err return err
} }
continue continue
@@ -214,17 +228,17 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
value.Set(reflect.New(value.Type().Elem())) value.Set(reflect.New(value.Type().Elem()))
} }
value = value.Elem() value = value.Elem()
if err := c.fillValues(ctx, value); err != nil { if err := fillValues(value, tname); err != nil {
return err return err
} }
continue continue
} }
tag, ok := field.Tag.Lookup(c.opts.StructTag) tag, ok := field.Tag.Lookup(tname)
if !ok { if !ok {
continue continue
} }
if err := c.fillValue(ctx, value, tag); err != nil { if err := fillValue(value, tag); err != nil {
return err return err
} }
} }
@@ -232,7 +246,7 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
return nil return nil
} }
func (c *defaultConfig) Save(ctx context.Context) error { func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error {
for _, fn := range c.opts.BeforeSave { for _, fn := range c.opts.BeforeSave {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail { if err := fn(ctx, c); err != nil && !c.opts.AllowFail {
return err return err
@@ -256,6 +270,20 @@ func (c *defaultConfig) Name() string {
return c.opts.Name return c.opts.Name
} }
func (c *defaultConfig) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
w := &defaultWatcher{
opts: c.opts,
wopts: NewWatchOptions(opts...),
done: make(chan bool),
vchan: make(chan map[string]interface{}),
echan: make(chan error),
}
go w.run()
return w, nil
}
// NewConfig returns new default config source // NewConfig returns new default config source
func NewConfig(opts ...Option) Config { func NewConfig(opts ...Option) Config {
options := NewOptions(opts...) options := NewOptions(opts...)
@@ -264,3 +292,73 @@ func NewConfig(opts ...Option) Config {
} }
return &defaultConfig{opts: options} return &defaultConfig{opts: options}
} }
type defaultWatcher struct {
opts Options
wopts WatchOptions
done chan bool
ticker *time.Ticker
vchan chan map[string]interface{}
echan chan error
}
func (w *defaultWatcher) run() {
ticker := time.NewTicker(w.wopts.Interval)
defer ticker.Stop()
src := w.opts.Struct
if w.wopts.Struct != nil {
src = w.wopts.Struct
}
for {
select {
case <-w.done:
return
case <-ticker.C:
dst, err := rutil.Zero(src)
if err == nil {
err = fillValues(reflect.ValueOf(dst), w.opts.StructTag)
}
if err != nil {
w.echan <- err
return
}
srcmp, err := rutil.StructFieldsMap(src)
if err != nil {
w.echan <- err
return
}
dstmp, err := rutil.StructFieldsMap(dst)
if err != nil {
w.echan <- err
return
}
for sk, sv := range srcmp {
if reflect.DeepEqual(dstmp[sk], sv) {
delete(dstmp, sk)
}
}
w.vchan <- dstmp
src = dst
}
}
}
func (w *defaultWatcher) Next() (map[string]interface{}, error) {
select {
case <-w.done:
break
case v, ok := <-w.vchan:
if !ok {
break
}
return v, nil
}
return nil, ErrWatcherStopped
}
func (w *defaultWatcher) Stop() error {
close(w.done)
return nil
}

View File

@@ -4,36 +4,88 @@ import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/unistack-org/micro/v3/config" "github.com/unistack-org/micro/v3/config"
) )
type Cfg struct { type Cfg struct {
StringValue string `default:"string_value"` StringValue string `default:"string_value"`
IntValue int `default:"99"`
IgnoreValue string `json:"-"` IgnoreValue string `json:"-"`
StructValue struct { StructValue struct {
StringValue string `default:"string_value"` StringValue string `default:"string_value"`
} }
IntValue int `default:"99"`
}
func TestWatch(t *testing.T) {
ctx := context.Background()
conf := &Cfg{IntValue: 10}
cfg := config.NewConfig(config.Struct(conf))
if err := cfg.Init(); err != nil {
t.Fatal(err)
}
if err := cfg.Load(ctx); err != nil {
t.Fatal(err)
}
w, err := cfg.Watch(ctx, config.WatchInterval(500*time.Millisecond))
if err != nil {
t.Fatal(err)
}
defer func() {
_ = w.Stop()
}()
done := make(chan struct{})
go func() {
for {
mp, err := w.Next()
if err != nil && err != config.ErrWatcherStopped {
t.Fatal(err)
} else if err == config.ErrWatcherStopped {
return
}
if len(mp) != 1 {
t.Fatal(fmt.Errorf("default watcher err: %v", mp))
}
v, ok := mp["IntValue"]
if !ok {
t.Fatal(fmt.Errorf("default watcher err: %v", v))
}
if nv, ok := v.(int); !ok || nv != 99 {
t.Fatal(fmt.Errorf("default watcher err: %v", v))
}
close(done)
return
}
}()
<-done
} }
func TestDefault(t *testing.T) { func TestDefault(t *testing.T) {
ctx := context.Background() ctx := context.Background()
conf := &Cfg{IntValue: 10} conf := &Cfg{IntValue: 10}
blfn := func(ctx context.Context, cfg config.Config) error { blfn := func(ctx context.Context, cfg config.Config) error {
conf, ok := cfg.Options().Struct.(*Cfg) nconf, ok := cfg.Options().Struct.(*Cfg)
if !ok { if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) return fmt.Errorf("failed to get Struct from options: %v", cfg.Options())
} }
conf.StringValue = "before_load" nconf.StringValue = "before_load"
return nil return nil
} }
alfn := func(ctx context.Context, cfg config.Config) error { alfn := func(ctx context.Context, cfg config.Config) error {
conf, ok := cfg.Options().Struct.(*Cfg) nconf, ok := cfg.Options().Struct.(*Cfg)
if !ok { if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) return fmt.Errorf("failed to get Struct from options: %v", cfg.Options())
} }
conf.StringValue = "after_load" nconf.StringValue = "after_load"
return nil return nil
} }
@@ -47,6 +99,6 @@ func TestDefault(t *testing.T) {
if conf.StringValue != "after_load" { if conf.StringValue != "after_load" {
t.Fatal("AfterLoad option not working") t.Fatal("AfterLoad option not working")
} }
_ = conf
t.Logf("%#+v\n", conf) //t.Logf("%#+v\n", conf)
} }

View File

@@ -2,6 +2,7 @@ package config
import ( import (
"context" "context"
"time"
"github.com/unistack-org/micro/v3/codec" "github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
@@ -11,26 +12,32 @@ import (
// Options hold the config options // Options hold the config options
type Options struct { type Options struct {
Name string // Struct holds the destination config struct
AllowFail bool
BeforeLoad []func(context.Context, Config) error
AfterLoad []func(context.Context, Config) error
BeforeSave []func(context.Context, Config) error
AfterSave []func(context.Context, Config) error
// Struct that holds config data
Struct interface{} Struct interface{}
// StructTag name
StructTag string
// Logger that will be used
Logger logger.Logger
// Meter that will be used
Meter meter.Meter
// Tracer used for trace
Tracer tracer.Tracer
// Codec that used for load/save // Codec that used for load/save
Codec codec.Codec Codec codec.Codec
// Context for alternative data // Tracer that will be used
Tracer tracer.Tracer
// Meter that will be used
Meter meter.Meter
// Logger that will be used
Logger logger.Logger
// Context used for external options
Context context.Context Context context.Context
// Name of the config
Name string
// StructTag name
StructTag string
// BeforeSave contains slice of funcs that runs before save
BeforeSave []func(context.Context, Config) error
// AfterLoad contains slice of funcs that runs after load
AfterLoad []func(context.Context, Config) error
// BeforeLoad contains slice of funcs that runs before load
BeforeLoad []func(context.Context, Config) error
// AfterSave contains slice of funcs that runs after save
AfterSave []func(context.Context, Config) error
// AllowFail flag to allow fail in config source
AllowFail bool
} }
// Option function signature // Option function signature
@@ -51,6 +58,69 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
// LoadOption function signature
type LoadOption func(o *LoadOptions)
// LoadOptions struct
type LoadOptions struct {
Struct interface{}
Override bool
Append bool
}
func NewLoadOptions(opts ...LoadOption) LoadOptions {
options := LoadOptions{}
for _, o := range opts {
o(&options)
}
return options
}
// LoadOverride override values when load
func LoadOverride(b bool) LoadOption {
return func(o *LoadOptions) {
o.Override = b
}
}
// LoadAppend override values when load
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{}
}
// 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 ...SaveOption) SaveOptions {
options := SaveOptions{}
for _, o := range opts {
o(&options)
}
return options
}
// AllowFail allows config source to fail // AllowFail allows config source to fail
func AllowFail(b bool) Option { func AllowFail(b bool) Option {
return func(o *Options) { return func(o *Options) {
@@ -134,3 +204,56 @@ func Name(n string) Option {
o.Name = n o.Name = n
} }
} }
// WatchOptions struuct
type WatchOptions struct {
// Context used by non default options
Context context.Context
// Coalesce multiple events to one
Coalesce bool
// Interval to periodically pull changes if config source not supports async notify
Interval time.Duration
// Struct for filling
Struct interface{}
}
type WatchOption func(*WatchOptions)
func NewWatchOptions(opts ...WatchOption) WatchOptions {
options := WatchOptions{
Context: context.Background(),
Interval: DefaultWatcherInterval,
}
for _, o := range opts {
o(&options)
}
return options
}
// 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 time.Duration for pulling changes
func WatchInterval(td time.Duration) WatchOption {
return func(o *WatchOptions) {
o.Interval = td
}
}
// WatchStruct overrides struct for fill
func WatchStruct(src interface{}) WatchOption {
return func(o *WatchOptions) {
o.Struct = src
}
}

View File

@@ -1,26 +0,0 @@
package config_test
import (
"testing"
rutil "github.com/unistack-org/micro/v3/util/reflect"
)
type Config struct {
Value string
SubConfig *SubConfig
Config *Config
}
type SubConfig struct {
Value string
}
func TestReflect(t *testing.T) {
cfg1 := &Config{Value: "cfg1", Config: &Config{Value: "cfg1_1"}, SubConfig: &SubConfig{Value: "cfg1"}}
cfg2, err := rutil.Zero(cfg1)
if err != nil {
t.Fatal(err)
}
t.Logf("dst: %#+v\n", cfg2)
}

View File

@@ -37,10 +37,14 @@ var (
// Error type // Error type
type Error struct { type Error struct {
Id string // Id holds error id or service, usually someting like my_service or uuid
Code int32 Id string
// Detail holds some useful details about error
Detail string Detail string
// Status usually holds text of http status
Status string Status string
// Code holds error code
Code int32
} }
// Error satisfies error interface // Error satisfies error interface
@@ -49,7 +53,7 @@ func (e *Error) Error() string {
return string(b) return string(b)
} }
// New generates a custom error. // New generates a custom error
func New(id, detail string, code int32) error { func New(id, detail string, code int32) error {
return &Error{ return &Error{
Id: id, Id: id,

View File

@@ -17,7 +17,6 @@ func TestFromError(t *testing.T) {
if merr.Id != "go.micro.test" || merr.Code != 404 { if merr.Id != "go.micro.test" || merr.Code != 404 {
t.Fatalf("invalid conversation %v != %v", err, merr) t.Fatalf("invalid conversation %v != %v", err, merr)
} }
} }
func TestEqual(t *testing.T) { func TestEqual(t *testing.T) {
@@ -32,7 +31,6 @@ func TestEqual(t *testing.T) {
if Equal(err1, err3) { if Equal(err1, err3) {
t.Fatal("errors must be not equal") t.Fatal("errors must be not equal")
} }
} }
func TestErrors(t *testing.T) { func TestErrors(t *testing.T) {

34
flow/context.go Normal file
View File

@@ -0,0 +1,34 @@
package flow
import (
"context"
)
type flowKey struct{}
// FromContext returns Flow from context
func FromContext(ctx context.Context) (Flow, bool) {
if ctx == nil {
return nil, false
}
c, ok := ctx.Value(flowKey{}).(Flow)
return c, ok
}
// NewContext stores Flow to context
func NewContext(ctx context.Context, f Flow) context.Context {
if ctx == nil {
ctx = context.Background()
}
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

@@ -44,10 +44,10 @@ func TestDag(t *testing.T) {
var steps [][]string var steps [][]string
fn := func(n dag.Vertex, idx int) error { fn := func(n dag.Vertex, idx int) error {
if idx == 0 { if idx == 0 {
steps = make([][]string, 1, 1) steps = make([][]string, 1)
steps[0] = make([]string, 0, 1) steps[0] = make([]string, 0, 1)
} else if idx >= len(steps) { } else if idx >= len(steps) {
tsteps := make([][]string, idx+1, idx+1) tsteps := make([][]string, idx+1)
copy(tsteps, steps) copy(tsteps, steps)
steps = tsteps steps = tsteps
steps[idx] = make([]string, 0, 1) steps[idx] = make([]string, 0, 1)

588
flow/default.go Normal file
View File

@@ -0,0 +1,588 @@
package flow
import (
"context"
"fmt"
"path/filepath"
"sync"
"github.com/google/uuid"
"github.com/silas/dag"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/store"
)
type microFlow struct {
opts Options
}
type microWorkflow struct {
id string
g *dag.AcyclicGraph
init bool
sync.RWMutex
opts Options
steps map[string]Step
status Status
}
func (w *microWorkflow) ID() string {
return w.id
}
func (w *microWorkflow) Steps() ([][]Step, error) {
return w.getSteps("", false)
}
func (w *microWorkflow) Status() Status {
return w.status
}
func (w *microWorkflow) AppendSteps(steps ...Step) error {
w.Lock()
for _, s := range steps {
w.steps[s.String()] = s
w.g.Add(s)
}
for _, dst := range steps {
for _, req := range dst.Requires() {
src, ok := w.steps[req]
if !ok {
return ErrStepNotExists
}
w.g.Connect(dag.BasicEdge(src, dst))
}
}
if err := w.g.Validate(); err != nil {
w.Unlock()
return err
}
w.g.TransitiveReduction()
w.Unlock()
return nil
}
func (w *microWorkflow) RemoveSteps(steps ...Step) error {
// TODO: handle case when some step requires or required by removed step
w.Lock()
for _, s := range steps {
delete(w.steps, s.String())
w.g.Remove(s)
}
for _, dst := range steps {
for _, req := range dst.Requires() {
src, ok := w.steps[req]
if !ok {
return ErrStepNotExists
}
w.g.Connect(dag.BasicEdge(src, dst))
}
}
if err := w.g.Validate(); err != nil {
w.Unlock()
return err
}
w.g.TransitiveReduction()
w.Unlock()
return nil
}
func (w *microWorkflow) getSteps(start string, reverse bool) ([][]Step, error) {
var steps [][]Step
var root dag.Vertex
var err error
fn := func(n dag.Vertex, idx int) error {
if idx == 0 {
steps = make([][]Step, 1)
steps[0] = make([]Step, 0, 1)
} else if idx >= len(steps) {
tsteps := make([][]Step, idx+1)
copy(tsteps, steps)
steps = tsteps
steps[idx] = make([]Step, 0, 1)
}
steps[idx] = append(steps[idx], n.(Step))
return nil
}
if start != "" {
var ok bool
w.RLock()
root, ok = w.steps[start]
w.RUnlock()
if !ok {
return nil, ErrStepNotExists
}
} else {
root, err = w.g.Root()
if err != nil {
return nil, err
}
}
if reverse {
err = w.g.SortedReverseDepthFirstWalk([]dag.Vertex{root}, fn)
} else {
err = w.g.SortedDepthFirstWalk([]dag.Vertex{root}, fn)
}
if err != nil {
return nil, err
}
return steps, nil
}
func (w *microWorkflow) Abort(ctx context.Context, eid string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())})
}
func (w *microWorkflow) Suspend(ctx context.Context, eid string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())})
}
func (w *microWorkflow) Resume(ctx context.Context, eid string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())})
}
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 {
w.Unlock()
return "", err
}
w.g.TransitiveReduction()
w.init = true
}
w.Unlock()
uid, err := uuid.NewRandom()
if err != nil {
return "", err
}
eid := uid.String()
stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", eid))
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
options := NewExecuteOptions(opts...)
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.Errorf(w.opts.Context, "store error: %v", werr)
}
return "", err
}
var wg sync.WaitGroup
cherr := make(chan error, 1)
chstatus := make(chan Status, 1)
nctx, cancel := context.WithCancel(ctx)
defer cancel()
nopts := make([]ExecuteOption, 0, len(opts)+5)
nopts = append(nopts,
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.Errorf(w.opts.Context, "store error: %v", werr)
return eid, werr
}
for idx := range steps {
for nidx := range steps[idx] {
cstep := steps[idx][nidx]
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
return eid, werr
}
}
}
go func() {
for idx := range steps {
for nidx := range steps[idx] {
wStatus := &codec.Frame{}
if werr := workflowStore.Read(w.opts.Context, "status", wStatus); werr != nil {
cherr <- werr
return
}
if status := StringStatus[string(wStatus.Data)]; status != StatusRunning {
chstatus <- status
return
}
if w.opts.Logger.V(logger.TraceLevel) {
w.opts.Logger.Tracef(nctx, "will be executed %v", steps[idx][nidx])
}
cstep := steps[idx][nidx]
if len(cstep.Requires()) == 0 {
wg.Add(1)
go func(step Step) {
defer wg.Done()
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "req"), req); werr != nil {
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
cherr <- werr
return
}
rsp, serr := step.Execute(nctx, req, nopts...)
if serr != nil {
step.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
cherr <- serr
return
} else {
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
}
}(cstep)
wg.Wait()
} else {
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "req"), req); werr != nil {
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
cherr <- werr
return
}
rsp, serr := cstep.Execute(nctx, req, nopts...)
if serr != nil {
cstep.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
cherr <- serr
return
} else {
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
cherr <- werr
return
}
}
}
}
}
close(done)
}()
if options.Async {
return eid, nil
}
logger.Tracef(ctx, "wait for finish or error")
select {
case <-nctx.Done():
err = nctx.Err()
case cerr := <-cherr:
err = cerr
case <-done:
close(cherr)
case <-chstatus:
close(chstatus)
return uid.String(), nil
}
switch {
case nctx.Err() != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusAborted.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
break
case err == nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
break
case err != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
break
}
return uid.String(), err
}
func NewFlow(opts ...Option) Flow {
options := NewOptions(opts...)
return &microFlow{opts: options}
}
func (f *microFlow) Options() Options {
return f.opts
}
func (f *microFlow) Init(opts ...Option) error {
for _, o := range opts {
o(&f.opts)
}
if err := f.opts.Client.Init(); err != nil {
return err
}
if err := f.opts.Tracer.Init(); err != nil {
return err
}
if err := f.opts.Logger.Init(); err != nil {
return err
}
if err := f.opts.Meter.Init(); err != nil {
return err
}
if err := f.opts.Store.Init(); err != nil {
return err
}
return nil
}
func (f *microFlow) WorkflowList(ctx context.Context) ([]Workflow, error) {
return nil, nil
}
func (f *microFlow) WorkflowCreate(ctx context.Context, id string, steps ...Step) (Workflow, error) {
w := &microWorkflow{opts: f.opts, id: id, g: &dag.AcyclicGraph{}, steps: make(map[string]Step, len(steps))}
for _, s := range steps {
w.steps[s.String()] = s
w.g.Add(s)
}
for _, dst := range steps {
for _, req := range dst.Requires() {
src, ok := w.steps[req]
if !ok {
return nil, ErrStepNotExists
}
w.g.Connect(dag.BasicEdge(src, dst))
}
}
if err := w.g.Validate(); err != nil {
return nil, err
}
w.g.TransitiveReduction()
w.init = true
return w, nil
}
func (f *microFlow) WorkflowRemove(ctx context.Context, id string) error {
return nil
}
func (f *microFlow) WorkflowSave(ctx context.Context, w Workflow) error {
return nil
}
func (f *microFlow) WorkflowLoad(ctx context.Context, id string) (Workflow, error) {
return nil, nil
}
type microCallStep struct {
opts StepOptions
service string
method string
rsp *Message
req *Message
status Status
}
func (s *microCallStep) Request() *Message {
return s.req
}
func (s *microCallStep) Response() *Message {
return s.rsp
}
func (s *microCallStep) ID() string {
return s.String()
}
func (s *microCallStep) Options() StepOptions {
return s.opts
}
func (s *microCallStep) Endpoint() string {
return s.method
}
func (s *microCallStep) Requires() []string {
return s.opts.Requires
}
func (s *microCallStep) Require(steps ...Step) error {
for _, step := range steps {
s.opts.Requires = append(s.opts.Requires, step.String())
}
return nil
}
func (s *microCallStep) String() string {
if s.opts.ID != "" {
return s.opts.ID
}
return fmt.Sprintf("%s.%s", s.service, s.method)
}
func (s *microCallStep) Name() string {
return s.String()
}
func (s *microCallStep) Hashcode() interface{} {
return s.String()
}
func (s *microCallStep) GetStatus() Status {
return s.status
}
func (s *microCallStep) SetStatus(status Status) {
s.status = status
}
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 := []client.CallOption{client.WithRetries(0)}
if options.Timeout > 0 {
copts = append(copts, 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)
if err != nil {
return nil, err
}
md, _ := metadata.FromOutgoingContext(nctx)
return &Message{Header: md, Body: rsp.Data}, err
}
type microPublishStep struct {
opts StepOptions
topic string
req *Message
rsp *Message
status Status
}
func (s *microPublishStep) Request() *Message {
return s.req
}
func (s *microPublishStep) Response() *Message {
return s.rsp
}
func (s *microPublishStep) ID() string {
return s.String()
}
func (s *microPublishStep) Options() StepOptions {
return s.opts
}
func (s *microPublishStep) Endpoint() string {
return s.topic
}
func (s *microPublishStep) Requires() []string {
return s.opts.Requires
}
func (s *microPublishStep) Require(steps ...Step) error {
for _, step := range steps {
s.opts.Requires = append(s.opts.Requires, step.String())
}
return nil
}
func (s *microPublishStep) String() string {
if s.opts.ID != "" {
return s.opts.ID
}
return fmt.Sprintf("%s", s.topic)
}
func (s *microPublishStep) Name() string {
return s.String()
}
func (s *microPublishStep) Hashcode() interface{} {
return s.String()
}
func (s *microPublishStep) GetStatus() Status {
return s.status
}
func (s *microPublishStep) SetStatus(status Status) {
s.status = status
}
func (s *microPublishStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) {
return nil, nil
}
func NewCallStep(service string, name string, method string, opts ...StepOption) Step {
options := NewStepOptions(opts...)
return &microCallStep{service: service, method: name + "." + method, opts: options}
}
func NewPublishStep(topic string, opts ...StepOption) Step {
options := NewStepOptions(opts...)
return &microPublishStep{topic: topic, opts: options}
}

View File

@@ -1,7 +1,152 @@
// Package flow is an interface used for saga pattern messaging // Package flow is an interface used for saga pattern microservice workflow
package flow package flow
type Step interface { import (
// Endpoint returns service_name.service_method "context"
Endpoint() string "errors"
"sync"
"sync/atomic"
"github.com/unistack-org/micro/v3/metadata"
)
var (
ErrStepNotExists = errors.New("step not exists")
ErrMissingClient = errors.New("client not set")
)
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding.
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m *RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return *m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("RawMessage UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
type Message struct {
Header metadata.Metadata
Body RawMessage
}
// Step represents dedicated workflow step
type Step interface {
// ID returns step id
ID() string
// Endpoint returns rpc endpoint service_name.service_method or broker topic
Endpoint() string
// Execute step run
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error)
// Requires returns dependent steps
Requires() []string
// Options returns step options
Options() StepOptions
// Require add required steps
Require(steps ...Step) error
// String
String() string
// GetStatus returns step status
GetStatus() Status
// SetStatus sets the step status
SetStatus(Status)
// Request returns step request message
Request() *Message
// Response returns step response message
Response() *Message
}
type Status int
func (status Status) String() string {
return StatusString[status]
}
const (
StatusPending Status = iota
StatusRunning
StatusFailure
StatusSuccess
StatusAborted
StatusSuspend
)
var (
StatusString = map[Status]string{
StatusPending: "StatusPending",
StatusRunning: "StatusRunning",
StatusFailure: "StatusFailure",
StatusSuccess: "StatusSuccess",
StatusAborted: "StatusAborted",
StatusSuspend: "StatusSuspend",
}
StringStatus = map[string]Status{
"StatusPending": StatusPending,
"StatusRunning": StatusRunning,
"StatusFailure": StatusFailure,
"StatusSuccess": StatusSuccess,
"StatusAborted": StatusAborted,
"StatusSuspend": StatusSuspend,
}
)
// Workflow contains all steps to execute
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 ...ExecuteOption) (string, error)
// RemoveSteps remove steps from workflow
RemoveSteps(steps ...Step) error
// AppendSteps append steps to workflow
AppendSteps(steps ...Step) error
// Status returns workflow status
Status() Status
// Steps returns steps slice where parallel steps returned on the same level
Steps() ([][]Step, error)
// Suspend suspends execution
Suspend(ctx context.Context, eid string) error
// Resume resumes execution
Resume(ctx context.Context, eid string) error
// Abort abort execution
Abort(ctx context.Context, eid string) error
}
// Flow the base interface to interact with workflows
type Flow interface {
// Options returns options
Options() Options
// Init initialize
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
WorkflowSave(ctx context.Context, w Workflow) error
// WorkflowLoad loads workflow with specific id
WorkflowLoad(ctx context.Context, id string) (Workflow, error)
// WorkflowList lists all workflows
WorkflowList(ctx context.Context) ([]Workflow, error)
}
var (
flowMu sync.Mutex
atomicSteps atomic.Value
)
func RegisterStep(step Step) {
flowMu.Lock()
steps, _ := atomicSteps.Load().([]Step)
atomicSteps.Store(append(steps, step))
flowMu.Unlock()
} }

230
flow/options.go Normal file
View File

@@ -0,0 +1,230 @@
package flow
import (
"context"
"time"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/meter"
"github.com/unistack-org/micro/v3/store"
"github.com/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
Context context.Context
// Client holds the client.Client
Client client.Client
// Tracer holds the tracer
Tracer tracer.Tracer
// Logger holds the logger
Logger logger.Logger
// Meter holds the meter
Meter meter.Meter
// Store used for intermediate results
Store store.Store
}
// NewOptions returns new options struct with default or passed values
func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Client: client.DefaultClient,
}
for _, o := range opts {
o(&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
// 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
}
}
// WorflowOption signature
type WorkflowOption func(*WorkflowOptions)
// WorkflowOptions holds workflow options
type WorkflowOptions struct {
ID string
Context context.Context
}
// WorkflowID set workflow id
func WorkflowID(id string) WorkflowOption {
return func(o *WorkflowOptions) {
o.ID = id
}
}
type ExecuteOptions struct {
// Client holds the client.Client
Client client.Client
// Tracer holds the tracer
Tracer tracer.Tracer
// Logger holds the logger
Logger logger.Logger
// Meter holds the meter
Meter meter.Meter
// Context can be used to abort execution or pass additional opts
Context context.Context
// Start step
Start string
// Reverse execution
Reverse bool
// Timeout for execution
Timeout time.Duration
// Async enables async execution
Async bool
}
type ExecuteOption func(*ExecuteOptions)
func ExecuteClient(c client.Client) ExecuteOption {
return func(o *ExecuteOptions) {
o.Client = c
}
}
func ExecuteTracer(t tracer.Tracer) ExecuteOption {
return func(o *ExecuteOptions) {
o.Tracer = t
}
}
func ExecuteLogger(l logger.Logger) ExecuteOption {
return func(o *ExecuteOptions) {
o.Logger = l
}
}
func ExecuteMeter(m meter.Meter) ExecuteOption {
return func(o *ExecuteOptions) {
o.Meter = m
}
}
func ExecuteContext(ctx context.Context) ExecuteOption {
return func(o *ExecuteOptions) {
o.Context = ctx
}
}
func ExecuteReverse(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Reverse = b
}
}
func ExecuteTimeout(td time.Duration) ExecuteOption {
return func(o *ExecuteOptions) {
o.Timeout = td
}
}
func ExecuteAsync(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Async = b
}
}
func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
options := ExecuteOptions{
Client: client.DefaultClient,
Logger: logger.DefaultLogger,
Tracer: tracer.DefaultTracer,
Meter: meter.DefaultMeter,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
type StepOptions struct {
ID string
Context context.Context
Requires []string
Fallback string
}
type StepOption func(*StepOptions)
func NewStepOptions(opts ...StepOption) StepOptions {
options := StepOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
func StepID(id string) StepOption {
return func(o *StepOptions) {
o.ID = id
}
}
func StepRequires(steps ...string) StepOption {
return func(o *StepOptions) {
o.Requires = steps
}
}
func StepFallback(step string) StepOption {
return func(o *StepOptions) {
o.Fallback = step
}
}

View File

@@ -1,3 +0,0 @@
package micro
//go:generate ./.github/generate.sh

14
go.mod
View File

@@ -3,15 +3,11 @@ module github.com/unistack-org/micro/v3
go 1.16 go 1.16
require ( require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/ef-ds/deque v1.0.4 github.com/ef-ds/deque v1.0.4
github.com/google/uuid v1.2.0 github.com/golang-jwt/jwt v3.2.1+incompatible
github.com/heimdalr/dag v1.0.1 // indirect github.com/google/uuid v1.3.0
github.com/imdario/mergo v0.3.11 github.com/imdario/mergo v0.3.12
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 // indirect github.com/silas/dag v0.0.0-20210121180416-41cf55125c34
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 golang.org/x/net v0.0.0-20210510120150-4163338589ed
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
) )

33
go.sum
View File

@@ -1,35 +1,22 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI= github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI=
github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg= github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/heimdalr/dag v1.0.1 h1:iR2K3DSUFDYx0GeV7iXBnZkedWS1xePSGrylQ197uxg= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/heimdalr/dag v1.0.1/go.mod h1:t+ZkR+sjKL4xhlE1B9rwpvwfo+x+2R0363efS+Oghns=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 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/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI= github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -21,9 +21,11 @@ func init() {
} }
type defaultLogger struct { type defaultLogger struct {
sync.RWMutex
opts Options
enc *json.Encoder enc *json.Encoder
opts Options
sync.RWMutex
logFunc LogFunc
logfFunc LogfFunc
} }
// Init(opts...) should only overwrite provided options // Init(opts...) should only overwrite provided options
@@ -33,6 +35,12 @@ func (l *defaultLogger) Init(opts ...Option) error {
o(&l.opts) o(&l.opts)
} }
l.enc = json.NewEncoder(l.opts.Out) l.enc = json.NewEncoder(l.opts.Out)
// wrap the Log func
for i := len(l.opts.Wrappers); i > 0; i-- {
l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc)
l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc)
}
l.Unlock() l.Unlock()
return nil return nil
} }
@@ -49,10 +57,18 @@ func (l *defaultLogger) V(level Level) bool {
} }
func (l *defaultLogger) Fields(fields map[string]interface{}) Logger { func (l *defaultLogger) Fields(fields map[string]interface{}) Logger {
l.Lock() nl := &defaultLogger{opts: l.opts, enc: l.enc}
l.opts.Fields = copyFields(fields) nl.opts.Fields = make(map[string]interface{}, len(l.opts.Fields)+len(fields))
l.Unlock() l.RLock()
return l for k, v := range l.opts.Fields {
nl.opts.Fields[k] = v
}
l.RUnlock()
for k, v := range fields {
nl.opts.Fields[k] = v
}
return nl
} }
func copyFields(src map[string]interface{}) map[string]interface{} { func copyFields(src map[string]interface{}) map[string]interface{} {
@@ -113,27 +129,27 @@ func (l *defaultLogger) Fatal(ctx context.Context, args ...interface{}) {
} }
func (l *defaultLogger) Infof(ctx context.Context, msg string, args ...interface{}) { func (l *defaultLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, InfoLevel, msg, args...) l.logfFunc(ctx, InfoLevel, msg, args...)
} }
func (l *defaultLogger) Errorf(ctx context.Context, msg string, args ...interface{}) { func (l *defaultLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, ErrorLevel, msg, args...) l.logfFunc(ctx, ErrorLevel, msg, args...)
} }
func (l *defaultLogger) Debugf(ctx context.Context, msg string, args ...interface{}) { func (l *defaultLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, DebugLevel, msg, args...) l.logfFunc(ctx, DebugLevel, msg, args...)
} }
func (l *defaultLogger) Warnf(ctx context.Context, msg string, args ...interface{}) { func (l *defaultLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, WarnLevel, msg, args...) l.logfFunc(ctx, WarnLevel, msg, args...)
} }
func (l *defaultLogger) Tracef(ctx context.Context, msg string, args ...interface{}) { func (l *defaultLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, TraceLevel, msg, args...) l.logfFunc(ctx, TraceLevel, msg, args...)
} }
func (l *defaultLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) { func (l *defaultLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, FatalLevel, msg, args...) l.logfFunc(ctx, FatalLevel, msg, args...)
os.Exit(1) os.Exit(1)
} }
@@ -153,7 +169,9 @@ func (l *defaultLogger) Log(ctx context.Context, level Level, args ...interface{
} }
fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05") fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05")
fields["msg"] = fmt.Sprint(args...) if len(args) > 0 {
fields["msg"] = fmt.Sprint(args...)
}
l.RLock() l.RLock()
_ = l.enc.Encode(fields) _ = l.enc.Encode(fields)
@@ -178,7 +196,7 @@ func (l *defaultLogger) Logf(ctx context.Context, level Level, msg string, args
fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05") fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05")
if len(args) > 0 { if len(args) > 0 {
fields["msg"] = fmt.Sprintf(msg, args...) fields["msg"] = fmt.Sprintf(msg, args...)
} else { } else if msg != "" {
fields["msg"] = msg fields["msg"] = msg
} }
l.RLock() l.RLock()
@@ -197,7 +215,11 @@ func (l *defaultLogger) Options() Options {
// NewLogger builds a new logger based on options // NewLogger builds a new logger based on options
func NewLogger(opts ...Option) Logger { func NewLogger(opts ...Option) Logger {
l := &defaultLogger{opts: NewOptions(opts...)} l := &defaultLogger{
opts: NewOptions(opts...),
}
l.logFunc = l.Log
l.logfFunc = l.Logf
l.enc = json.NewEncoder(l.opts.Out) l.enc = json.NewEncoder(l.opts.Out)
return l return l
} }

View File

@@ -8,6 +8,8 @@ var (
DefaultLogger Logger = NewLogger() DefaultLogger Logger = NewLogger()
// DefaultLevel used by logger // DefaultLevel used by logger
DefaultLevel Level = InfoLevel DefaultLevel Level = InfoLevel
// DefaultCallerSkipCount used by logger
DefaultCallerSkipCount = 2
) )
// Logger is a generic logging interface // Logger is a generic logging interface

View File

@@ -1,13 +1,15 @@
package logger package logger
import ( import (
"bytes"
"context" "context"
"testing" "testing"
) )
func TestLogger(t *testing.T) { func TestLogger(t *testing.T) {
ctx := context.TODO() ctx := context.TODO()
l := NewLogger(WithLevel(TraceLevel)) buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil { if err := l.Init(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -15,4 +17,52 @@ func TestLogger(t *testing.T) {
l.Warn(ctx, "warn_msg1") l.Warn(ctx, "warn_msg1")
l.Fields(map[string]interface{}{"error": "test"}).Info(ctx, "error message") l.Fields(map[string]interface{}{"error": "test"}).Info(ctx, "error message")
l.Warn(ctx, "first", " ", "second") l.Warn(ctx, "first", " ", "second")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"warn","msg":"warn_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"test","level":"info","msg":"error message"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"level":"warn","msg":"first second"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestLoggerWrapper(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(WrapLogger(NewOmitWrapper())); err != nil {
t.Fatal(err)
}
type secret struct {
Name string
Passw string `logger:"omit"`
}
s := &secret{Name: "name", Passw: "secret"}
l.Errorf(ctx, "test %#+v", s)
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
}
}
func TestOmitLoggerWrapper(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewOmitLogger(NewLogger(WithLevel(TraceLevel), WithOutput(buf)))
if err := l.Init(); err != nil {
t.Fatal(err)
}
type secret struct {
Name string
Passw string `logger:"omit"`
}
s := &secret{Name: "name", Passw: "secret"}
l.Errorf(ctx, "test %#+v", s)
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
}
} }

View File

@@ -11,17 +11,20 @@ type Option func(*Options)
// Options holds logger options // Options holds logger options
type Options struct { type Options struct {
Name string // Out holds the output writer
// The logging level the logger should log at. default is `InfoLevel`
Level Level
// fields to always be logged
Fields map[string]interface{}
// It's common to set this to a file, or leave it default which is `os.Stderr`
Out io.Writer Out io.Writer
// Caller skip frame count for file:line info // Context holds exernal options
CallerSkipCount int
// Alternative options
Context context.Context Context context.Context
// Fields holds additional metadata
Fields map[string]interface{}
// Name holds the logger name
Name string
// CallerSkipCount number of frmaes to skip
CallerSkipCount int
// The logging level the logger should log
Level Level
// Wrappers logger wrapper that called before actual Log/Logf function
Wrappers []Wrapper
} }
// NewOptions creates new options struct // NewOptions creates new options struct
@@ -30,7 +33,7 @@ func NewOptions(opts ...Option) Options {
Level: DefaultLevel, Level: DefaultLevel,
Fields: make(map[string]interface{}), Fields: make(map[string]interface{}),
Out: os.Stderr, Out: os.Stderr,
CallerSkipCount: 0, CallerSkipCount: DefaultCallerSkipCount,
Context: context.Background(), Context: context.Background(),
} }
for _, o := range opts { for _, o := range opts {
@@ -75,8 +78,15 @@ func WithContext(ctx context.Context) Option {
} }
// WithName sets the name // WithName sets the name
func withName(n string) Option { func WithName(n string) Option {
return func(o *Options) { return func(o *Options) {
o.Name = n o.Name = n
} }
} }
// WrapLogger adds a logger Wrapper to a list of options passed into the logger
func WrapLogger(w Wrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
}
}

154
logger/wrapper.go Normal file
View File

@@ -0,0 +1,154 @@
package logger
import (
"context"
"reflect"
rutil "github.com/unistack-org/micro/v3/util/reflect"
)
// LogFunc function used for Log method
type LogFunc func(ctx context.Context, level Level, args ...interface{})
// LogfFunc function used for Logf method
type LogfFunc func(ctx context.Context, level Level, msg string, args ...interface{})
type Wrapper interface {
// Log logs message with needed level
Log(LogFunc) LogFunc
// Logf logs message with needed level
Logf(LogfFunc) LogfFunc
}
var (
_ Logger = &OmitLogger{}
)
type OmitLogger struct {
l Logger
}
func NewOmitLogger(l Logger) Logger {
return &OmitLogger{l: l}
}
func (w *OmitLogger) Init(opts ...Option) error {
return w.l.Init(append(opts, WrapLogger(NewOmitWrapper()))...)
}
func (w *OmitLogger) V(level Level) bool {
return w.l.V(level)
}
func (w *OmitLogger) Options() Options {
return w.l.Options()
}
func (w *OmitLogger) Fields(fields map[string]interface{}) Logger {
return w.l.Fields(fields)
}
func (w *OmitLogger) Info(ctx context.Context, args ...interface{}) {
w.l.Info(ctx, args...)
}
func (w *OmitLogger) Trace(ctx context.Context, args ...interface{}) {
w.l.Trace(ctx, args...)
}
func (w *OmitLogger) Debug(ctx context.Context, args ...interface{}) {
w.l.Debug(ctx, args...)
}
func (w *OmitLogger) Warn(ctx context.Context, args ...interface{}) {
w.l.Warn(ctx, args...)
}
func (w *OmitLogger) Error(ctx context.Context, args ...interface{}) {
w.l.Error(ctx, args...)
}
func (w *OmitLogger) Fatal(ctx context.Context, args ...interface{}) {
w.l.Fatal(ctx, args...)
}
func (w *OmitLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
w.l.Infof(ctx, msg, args...)
}
func (w *OmitLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
w.l.Tracef(ctx, msg, args...)
}
func (w *OmitLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
w.l.Debugf(ctx, msg, args...)
}
func (w *OmitLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
w.l.Warnf(ctx, msg, args...)
}
func (w *OmitLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
w.l.Errorf(ctx, msg, args...)
}
func (w *OmitLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
w.l.Fatalf(ctx, msg, args...)
}
func (w *OmitLogger) Log(ctx context.Context, level Level, args ...interface{}) {
w.l.Log(ctx, level, args...)
}
func (w *OmitLogger) Logf(ctx context.Context, level Level, msg string, args ...interface{}) {
w.l.Logf(ctx, level, msg, args...)
}
func (w *OmitLogger) String() string {
return w.l.String()
}
type OmitWrapper struct{}
func NewOmitWrapper() Wrapper {
return &OmitWrapper{}
}
func getArgs(args []interface{}) []interface{} {
nargs := make([]interface{}, 0, len(args))
var err error
for _, arg := range args {
val := reflect.ValueOf(arg)
switch val.Kind() {
case reflect.Ptr:
val = val.Elem()
}
narg := arg
if val.Kind() == reflect.Struct {
if narg, err = rutil.Zero(arg); err == nil {
rutil.CopyDefaults(narg, arg)
if flds, ferr := rutil.StructFields(narg); ferr == nil {
for _, fld := range flds {
if tv, ok := fld.Field.Tag.Lookup("logger"); ok && tv == "omit" {
fld.Value.Set(reflect.Zero(fld.Value.Type()))
}
}
}
}
}
nargs = append(nargs, narg)
}
return nargs
}
func (w *OmitWrapper) Log(fn LogFunc) LogFunc {
return func(ctx context.Context, level Level, args ...interface{}) {
fn(ctx, level, getArgs(args)...)
}
}
func (w *OmitWrapper) Logf(fn LogfFunc) LogfFunc {
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
fn(ctx, level, msg, getArgs(args)...)
}
}

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

@@ -0,0 +1,410 @@
// Package wrapper provides wrapper for Logger
package wrapper
import (
"context"
"fmt"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/server"
)
var (
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 = 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 = 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 = 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 = 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 = 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 = []string{"Meter.Metrics"}
)
type lWrapper struct {
client.Client
serverHandler server.HandlerFunc
serverSubscriber server.SubscriberFunc
clientCallFunc client.CallFunc
opts Options
}
type (
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
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)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).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)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).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)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).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)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).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)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).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)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).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

@@ -5,9 +5,11 @@ import (
"context" "context"
) )
type mdIncomingKey struct{} type (
type mdOutgoingKey struct{} mdIncomingKey struct{}
type mdKey struct{} mdOutgoingKey struct{}
mdKey struct{}
)
// FromIncomingContext returns metadata from incoming ctx // FromIncomingContext returns metadata from incoming ctx
// returned metadata shoud not be modified or race condition happens // returned metadata shoud not be modified or race condition happens
@@ -93,7 +95,9 @@ func NewIncomingContext(ctx context.Context, md Metadata) context.Context {
ctx = context.Background() ctx = context.Background()
} }
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{md}) ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{md})
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{}) if v, ok := ctx.Value(mdOutgoingKey{}).(*rawMetadata); !ok || v == nil {
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{})
}
return ctx return ctx
} }
@@ -103,6 +107,40 @@ func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
ctx = context.Background() ctx = context.Background()
} }
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{md}) ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{md})
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{}) if v, ok := ctx.Value(mdIncomingKey{}).(*rawMetadata); !ok || v == nil {
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{})
}
return ctx return ctx
} }
// AppendOutgoingContext apends new md to context
func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
md, ok := Pairs(kv...)
if !ok {
return ctx
}
omd, ok := FromOutgoingContext(ctx)
if !ok {
return NewOutgoingContext(ctx, md)
}
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, ok := Pairs(kv...)
if !ok {
return ctx
}
omd, ok := FromIncomingContext(ctx)
if !ok {
return NewIncomingContext(ctx, md)
}
for k, v := range md {
omd.Set(k, v)
}
return NewIncomingContext(ctx, omd)
}

View File

@@ -7,8 +7,18 @@ import (
) )
var ( var (
// HeaderPrefix for all headers passed // HeaderTopic is the header name that contains topic name
HeaderPrefix = "Micro-" HeaderTopic = "Micro-Topic"
// HeaderContentType specifies content type of message
HeaderContentType = "Content-Type"
// HeaderEndpoint specifies endpoint in service
HeaderEndpoint = "Micro-Endpoint"
// HeaderService specifies service
HeaderService = "Micro-Service"
// HeaderTimeout specifies timeout of operation
HeaderTimeout = "Micro-Timeout"
// HeaderAuthorization specifies Authorization header
HeaderAuthorization = "Authorization"
) )
// Metadata is our way of representing request headers internally. // Metadata is our way of representing request headers internally.
@@ -20,17 +30,15 @@ type rawMetadata struct {
md Metadata md Metadata
} }
var ( // defaultMetadataSize used when need to init new Metadata
// defaultMetadataSize used when need to init new Metadata var defaultMetadataSize = 2
defaultMetadataSize = 2
)
// Iterator used to iterate over metadata with order // Iterator used to iterate over metadata with order
type Iterator struct { type Iterator struct {
md Metadata
keys []string
cur int cur int
cnt int cnt int
keys []string
md Metadata
} }
// Next advance iterator to next element // Next advance iterator to next element
@@ -111,3 +119,20 @@ func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata {
} }
return nmd return nmd
} }
// Pairs from which metadata created
func Pairs(kv ...string) (Metadata, bool) {
if len(kv)%2 == 1 {
return nil, false
}
md := New(len(kv) / 2)
var k string
for i, v := range kv {
if i%2 == 0 {
k = v
continue
}
md.Set(k, v)
}
return md, true
}

View File

@@ -5,6 +5,28 @@ import (
"testing" "testing"
) )
func TestAppend(t *testing.T) {
ctx := context.Background()
ctx = AppendIncomingContext(ctx, "key1", "val1", "key2", "val2")
md, ok := FromIncomingContext(ctx)
if !ok {
t.Fatal("metadata empty")
}
if _, ok := md.Get("key1"); !ok {
t.Fatal("key1 not found")
}
}
func TestPairs(t *testing.T) {
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 testCtx(ctx context.Context) { func testCtx(ctx context.Context) {
md := New(2) md := New(2)
md.Set("Key1", "Val1_new") md.Set("Key1", "Val1_new")
@@ -54,7 +76,7 @@ func TestIterator(t *testing.T) {
var k, v string var k, v string
for iter.Next(&k, &v) { for iter.Next(&k, &v) {
//fmt.Printf("k: %s, v: %s\n", k, v) // fmt.Printf("k: %s, v: %s\n", k, v)
} }
} }
@@ -80,7 +102,6 @@ func TestMedataCanonicalKey(t *testing.T) {
} else if v != "12345" { } else if v != "12345" {
t.Fatalf("invalid metadata value: %s != %s", "12345", v) t.Fatalf("invalid metadata value: %s != %s", "12345", v)
} }
} }
func TestMetadataSet(t *testing.T) { func TestMetadataSet(t *testing.T) {
@@ -108,7 +129,6 @@ func TestMetadataDelete(t *testing.T) {
if ok { if ok {
t.Fatal("key Baz not deleted") t.Fatal("key Baz not deleted")
} }
} }
func TestNilContext(t *testing.T) { func TestNilContext(t *testing.T) {

3
meter/generate.go Normal file
View File

@@ -0,0 +1,3 @@
package meter
//go:generate protoc -I./handler -I../ -I/home/vtolstov/.cache/go-path/pkg/mod/github.com/unistack-org/micro-proto@v0.0.1 --micro_out=components=micro|http|server,standalone=false,debug=true,paths=source_relative:./handler handler/handler.proto

67
meter/handler/handler.go Normal file
View File

@@ -0,0 +1,67 @@
package handler
import (
"bytes"
"context"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/meter"
)
// guard to fail early
var _ MeterServer = &Handler{}
type Handler struct {
opts Options
}
type Option func(*Options)
type Options struct {
Meter meter.Meter
Name string
MeterOptions []meter.Option
}
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
func Name(name string) Option {
return func(o *Options) {
o.Name = name
}
}
func MeterOptions(opts ...meter.Option) Option {
return func(o *Options) {
o.MeterOptions = append(o.MeterOptions, opts...)
}
}
func NewOptions(opts ...Option) Options {
options := Options{Meter: meter.DefaultMeter}
for _, o := range opts {
o(&options)
}
return options
}
func NewHandler(opts ...Option) *Handler {
options := NewOptions(opts...)
return &Handler{opts: options}
}
func (h *Handler) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
buf := bytes.NewBuffer(nil)
if err := h.opts.Meter.Write(buf, h.opts.MeterOptions...); err != nil {
return errors.InternalServerError(h.opts.Name, "%v", err)
}
rsp.Data = buf.Bytes()
return nil
}

View File

@@ -0,0 +1,28 @@
syntax = "proto3";
package micro.meter.handler;
option go_package = "github.com/unistack-org/micro/v3/meter/handler;handler";
import "api/annotations.proto";
import "openapiv2/annotations.proto";
import "codec/frame.proto";
service Meter {
rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = {
operation_id: "Metrics";
responses: {
key: "default";
value: {
description: "Error response";
schema: {
json_schema: {
ref: "micro.codec.Frame";
}
}
}
}
};
option (micro.api.http) = { get: "/metrics"; };
};
};

View File

@@ -0,0 +1,24 @@
// Code generated by protoc-gen-micro
// source: handler.proto
package handler
import (
context "context"
api "github.com/unistack-org/micro/v3/api"
codec "github.com/unistack-org/micro/v3/codec"
)
func NewMeterEndpoints() []*api.Endpoint {
return []*api.Endpoint{
&api.Endpoint{
Name: "Meter.Metrics",
Path: []string{"/metrics"},
Method: []string{"GET"},
Handler: "rpc",
},
}
}
type MeterServer interface {
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
}

View File

@@ -0,0 +1,33 @@
// Code generated by protoc-gen-micro
// source: handler.proto
package handler
import (
context "context"
api "github.com/unistack-org/micro/v3/api"
codec "github.com/unistack-org/micro/v3/codec"
server "github.com/unistack-org/micro/v3/server"
)
type meterServer struct {
MeterServer
}
func (h *meterServer) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
return h.MeterServer.Metrics(ctx, req, rsp)
}
func RegisterMeterServer(s server.Server, sh MeterServer, opts ...server.HandlerOption) error {
type meter interface {
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
}
type Meter struct {
meter
}
h := &meterServer{sh}
var nopts []server.HandlerOption
for _, endpoint := range NewMeterEndpoints() {
nopts = append(nopts, api.WithEndpoint(endpoint))
}
return s.Handle(s.NewHandler(&Meter{h}, append(nopts, opts...)...))
}

View File

@@ -4,6 +4,8 @@ package meter
import ( import (
"io" "io"
"sort" "sort"
"strconv"
"strings"
"time" "time"
) )
@@ -28,13 +30,13 @@ var (
type Meter interface { type Meter interface {
Name() string Name() string
Init(opts ...Option) error Init(opts ...Option) error
Counter(name string, opts ...Option) Counter Counter(name string, labels ...string) Counter
FloatCounter(name string, opts ...Option) FloatCounter FloatCounter(name string, labels ...string) FloatCounter
Gauge(name string, fn func() float64, opts ...Option) Gauge Gauge(name string, fn func() float64, labels ...string) Gauge
Set(opts ...Option) Meter Set(opts ...Option) Meter
Histogram(name string, opts ...Option) Histogram Histogram(name string, labels ...string) Histogram
Summary(name string, opts ...Option) Summary Summary(name string, labels ...string) Summary
SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary
Write(w io.Writer, opts ...Option) error Write(w io.Writer, opts ...Option) error
Options() Options Options() Options
String() string String() string
@@ -76,65 +78,62 @@ type Summary interface {
UpdateDuration(time.Time) UpdateDuration(time.Time)
} }
// Labels holds the metrics labels with k, v // sort labels alphabeticaly by label name
type Labels struct { type byKey []string
keys []string
vals []string func (k byKey) Len() int { return len(k) / 2 }
func (k byKey) Less(i, j int) bool { return k[i*2] < k[j*2] }
func (k byKey) Swap(i, j int) {
k[i*2], k[j*2] = k[j*2], k[i*2]
k[i*2+1], k[j*2+1] = k[j*2+1], k[i*2+1]
} }
type labels Labels // BuildLables used to sort labels and delete duplicates.
// Last value wins in case of duplicate label keys.
func (ls labels) sort() { func BuildLabels(labels ...string) []string {
sort.Sort(ls) if len(labels)%2 == 1 {
} labels = labels[:len(labels)-1]
func (ls labels) Len() int {
return len(ls.keys)
}
func (ls labels) Swap(i, j int) {
ls.keys[i], ls.keys[j] = ls.keys[j], ls.keys[i]
ls.vals[i], ls.vals[j] = ls.vals[j], ls.vals[i]
}
func (ls labels) Less(i, j int) bool {
return ls.vals[i] < ls.vals[j]
}
// Append adds labels to label set
func (ls Labels) Append(nls Labels) Labels {
for n := range nls.keys {
ls.keys = append(ls.keys, nls.keys[n])
ls.vals = append(ls.vals, nls.vals[n])
} }
return ls sort.Sort(byKey(labels))
return labels
} }
func (ls Labels) Len() int { // BuildName used to combine metric with labels.
return len(ls.keys) // If labels count is odd, drop last element
} func BuildName(name string, labels ...string) string {
if len(labels)%2 == 1 {
// LabelIter holds the labels = labels[:len(labels)-1]
type LabelIter struct {
labels Labels
cnt int
cur int
}
// Iter returns labels iterator
func (ls Labels) Iter() *LabelIter {
labels(ls).sort()
return &LabelIter{labels: ls, cnt: len(ls.keys)}
}
// Next advance itarator to new pos
func (iter *LabelIter) Next(k, v *string) bool {
if iter.cur+1 > iter.cnt {
return false
} }
*k = iter.labels.keys[iter.cur] if len(labels) > 2 {
*v = iter.labels.vals[iter.cur] sort.Sort(byKey(labels))
iter.cur++
return true idx := 0
for {
if labels[idx] == labels[idx+2] {
copy(labels[idx:], labels[idx+2:])
labels = labels[:len(labels)-2]
} else {
idx += 2
}
if idx+2 >= len(labels) {
break
}
}
}
var b strings.Builder
_, _ = b.WriteString(name)
_, _ = b.WriteRune('{')
for idx := 0; idx < len(labels); idx += 2 {
if idx > 0 {
_, _ = b.WriteRune(',')
}
_, _ = b.WriteString(labels[idx])
_, _ = b.WriteString(`=`)
_, _ = b.WriteString(strconv.Quote(labels[idx+1]))
}
_, _ = b.WriteRune('}')
return b.String()
} }

View File

@@ -5,44 +5,66 @@ import (
) )
func TestNoopMeter(t *testing.T) { func TestNoopMeter(t *testing.T) {
meter := NewMeter(Path("/noop")) m := NewMeter(Path("/noop"))
if "/noop" != meter.Options().Path { if "/noop" != m.Options().Path {
t.Fatalf("invalid options parsing: %v", meter.Options()) t.Fatalf("invalid options parsing: %v", m.Options())
} }
cnt := meter.Counter("counter", Label("server", "noop")) cnt := m.Counter("counter", "server", "noop")
cnt.Inc() cnt.Inc()
} }
func TestLabelsAppend(t *testing.T) { func testEq(a, b []string) bool {
var ls Labels if len(a) != len(b) {
ls.keys = []string{"type", "server"} return false
ls.vals = []string{"noop", "http"}
var nls Labels
nls.keys = []string{"register"}
nls.vals = []string{"gossip"}
ls = ls.Append(nls)
//ls.Sort()
if ls.keys[0] != "type" || ls.vals[0] != "noop" {
t.Fatalf("append error: %v", ls)
} }
} for i := range a {
if a[i] != b[i] {
func TestIterator(t *testing.T) { return false
var ls Labels }
ls.keys = []string{"type", "server", "register"} }
ls.vals = []string{"noop", "http", "gossip"} return true
}
iter := ls.Iter()
var k, v string func TestBuildLabels(t *testing.T) {
cnt := 0 type testData struct {
for iter.Next(&k, &v) { src []string
if cnt == 1 && (k != "server" || v != "http") { dst []string
t.Fatalf("iter error: %s != %s || %s != %s", k, "server", v, "http") }
data := []testData{
testData{
src: []string{"zerolabel", "value3", "firstlabel", "value2"},
dst: []string{"firstlabel", "value2", "zerolabel", "value3"},
},
}
for _, d := range data {
if !testEq(d.dst, BuildLabels(d.src...)) {
t.Fatalf("slices not properly sorted: %v %v", d.dst, d.src)
}
}
}
func TestBuildName(t *testing.T) {
data := map[string][]string{
`my_metric{firstlabel="value2",zerolabel="value3"}`: []string{
"my_metric",
"zerolabel", "value3", "firstlabel", "value2",
},
`my_metric{broker="broker2",register="mdns",server="tcp"}`: []string{
"my_metric",
"broker", "broker1", "broker", "broker2", "server", "http", "server", "tcp", "register", "mdns",
},
`my_metric{aaa="aaa"}`: []string{
"my_metric",
"aaa", "aaa",
},
}
for e, d := range data {
if x := BuildName(d[0], d[1:]...); x != e {
t.Fatalf("expect: %s, result: %s", e, x)
} }
cnt++
} }
} }

View File

@@ -28,57 +28,33 @@ func (r *noopMeter) Init(opts ...Option) error {
} }
// Counter implements the Meter interface // Counter implements the Meter interface
func (r *noopMeter) Counter(name string, opts ...Option) Counter { func (r *noopMeter) Counter(name string, labels ...string) Counter {
options := Options{} return &noopCounter{labels: labels}
for _, o := range opts {
o(&options)
}
return &noopCounter{labels: options.Labels}
} }
// FloatCounter implements the Meter interface // FloatCounter implements the Meter interface
func (r *noopMeter) FloatCounter(name string, opts ...Option) FloatCounter { func (r *noopMeter) FloatCounter(name string, labels ...string) FloatCounter {
options := Options{} return &noopFloatCounter{labels: labels}
for _, o := range opts {
o(&options)
}
return &noopFloatCounter{labels: options.Labels}
} }
// Gauge implements the Meter interface // Gauge implements the Meter interface
func (r *noopMeter) Gauge(name string, f func() float64, opts ...Option) Gauge { func (r *noopMeter) Gauge(name string, f func() float64, labels ...string) Gauge {
options := Options{} return &noopGauge{labels: labels}
for _, o := range opts {
o(&options)
}
return &noopGauge{labels: options.Labels}
} }
// Summary implements the Meter interface // Summary implements the Meter interface
func (r *noopMeter) Summary(name string, opts ...Option) Summary { func (r *noopMeter) Summary(name string, labels ...string) Summary {
options := Options{} return &noopSummary{labels: labels}
for _, o := range opts {
o(&options)
}
return &noopSummary{labels: options.Labels}
} }
// SummaryExt implements the Meter interface // SummaryExt implements the Meter interface
func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary { func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary {
options := Options{} return &noopSummary{labels: labels}
for _, o := range opts {
o(&options)
}
return &noopSummary{labels: options.Labels}
} }
// Histogram implements the Meter interface // Histogram implements the Meter interface
func (r *noopMeter) Histogram(name string, opts ...Option) Histogram { func (r *noopMeter) Histogram(name string, labels ...string) Histogram {
options := Options{} return &noopHistogram{labels: labels}
for _, o := range opts {
o(&options)
}
return &noopHistogram{labels: options.Labels}
} }
// Set implements the Meter interface // Set implements the Meter interface
@@ -107,15 +83,13 @@ func (r *noopMeter) String() string {
} }
type noopCounter struct { type noopCounter struct {
labels Labels labels []string
} }
func (r *noopCounter) Add(int) { func (r *noopCounter) Add(int) {
} }
func (r *noopCounter) Dec() { func (r *noopCounter) Dec() {
} }
func (r *noopCounter) Get() uint64 { func (r *noopCounter) Get() uint64 {
@@ -123,19 +97,16 @@ func (r *noopCounter) Get() uint64 {
} }
func (r *noopCounter) Inc() { func (r *noopCounter) Inc() {
} }
func (r *noopCounter) Set(uint64) { func (r *noopCounter) Set(uint64) {
} }
type noopFloatCounter struct { type noopFloatCounter struct {
labels Labels labels []string
} }
func (r *noopFloatCounter) Add(float64) { func (r *noopFloatCounter) Add(float64) {
} }
func (r *noopFloatCounter) Get() float64 { func (r *noopFloatCounter) Get() float64 {
@@ -143,15 +114,13 @@ func (r *noopFloatCounter) Get() float64 {
} }
func (r *noopFloatCounter) Set(float64) { func (r *noopFloatCounter) Set(float64) {
} }
func (r *noopFloatCounter) Sub(float64) { func (r *noopFloatCounter) Sub(float64) {
} }
type noopGauge struct { type noopGauge struct {
labels Labels labels []string
} }
func (r *noopGauge) Get() float64 { func (r *noopGauge) Get() float64 {
@@ -159,31 +128,26 @@ func (r *noopGauge) Get() float64 {
} }
type noopSummary struct { type noopSummary struct {
labels Labels labels []string
} }
func (r *noopSummary) Update(float64) { func (r *noopSummary) Update(float64) {
} }
func (r *noopSummary) UpdateDuration(time.Time) { func (r *noopSummary) UpdateDuration(time.Time) {
} }
type noopHistogram struct { type noopHistogram struct {
labels Labels labels []string
} }
func (r *noopHistogram) Reset() { func (r *noopHistogram) Reset() {
} }
func (r *noopHistogram) Update(float64) { func (r *noopHistogram) Update(float64) {
} }
func (r *noopHistogram) UpdateDuration(time.Time) { func (r *noopHistogram) UpdateDuration(time.Time) {
} }
//func (r *noopHistogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) {} // func (r *noopHistogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) {}

View File

@@ -11,17 +11,26 @@ type Option func(*Options)
// Options for metrics implementations: // Options for metrics implementations:
type Options struct { type Options struct {
Name string // Logger used for logging
Logger logger.Logger
// Context holds external options
Context context.Context
// Name holds the meter name
Name string
// Address holds the address that serves metrics
Address string Address string
Path string // Path holds the path for metrics
Labels Labels Path string
//TimingObjectives map[float64]float64 // MetricPrefix holds the prefix for all metrics
Logger logger.Logger MetricPrefix string
Context context.Context // LabelPrefix holds the prefix for all labels
MetricPrefix string LabelPrefix string
LabelPrefix string // Labels holds the default labels
Labels []string
// WriteProcessMetrics flag to write process metrics
WriteProcessMetrics bool WriteProcessMetrics bool
WriteFDMetrics bool // WriteFDMetrics flag to write fd metrics
WriteFDMetrics bool
} }
// NewOptions prepares a set of options: // NewOptions prepares a set of options:
@@ -79,11 +88,9 @@ func Logger(l logger.Logger) Option {
} }
} }
// Label sets the label func Labels(ls ...string) Option {
func Label(key, val string) Option {
return func(o *Options) { return func(o *Options) {
o.Labels.keys = append(o.Labels.keys, key) o.Labels = append(o.Labels, ls...)
o.Labels.vals = append(o.Labels.vals, val)
} }
} }

View File

@@ -11,36 +11,49 @@ import (
) )
var ( var (
ClientRequestDurationSeconds = "client_request_duration_seconds" ClientRequestDurationSeconds = "client_request_duration_seconds"
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds" ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
ClientRequestTotal = "client_request_total" ClientRequestTotal = "client_request_total"
ServerRequestDurationSeconds = "server_request_duration_seconds" ClientRequestInflight = "client_request_inflight"
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
ServerRequestTotal = "server_request_total" ServerRequestDurationSeconds = "server_request_duration_seconds"
PublishMessageDurationSeconds = "publish_message_duration_seconds" ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds" ServerRequestTotal = "server_request_total"
PublishMessageTotal = "publish_message_total" ServerRequestInflight = "server_request_inflight"
PublishMessageDurationSeconds = "publish_message_duration_seconds"
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
PublishMessageTotal = "publish_message_total"
PublishMessageInflight = "publish_message_inflight"
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds" SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds" SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds"
SubscribeMessageTotal = "subscribe_message_total" SubscribeMessageTotal = "subscribe_message_total"
SubscribeMessageInflight = "subscribe_message_inflight"
labelSuccess = "success" labelSuccess = "success"
labelFailure = "failure" labelFailure = "failure"
labelStatus = "status" labelStatus = "status"
labelEndpoint = "endpoint" labelEndpoint = "endpoint"
// DefaultSkipEndpoints contains list of endpoints that not evaluted by wrapper
DefaultSkipEndpoints = []string{"Meter.Metrics"}
) )
type Options struct { type Options struct {
Meter meter.Meter Meter meter.Meter
Name string lopts []meter.Option
Version string SkipEndpoints []string
ID string
} }
type Option func(*Options) type Option func(*Options)
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{} options := Options{
Meter: meter.DefaultMeter,
lopts: make([]meter.Option, 0, 5),
SkipEndpoints: DefaultSkipEndpoints,
}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
@@ -49,19 +62,19 @@ func NewOptions(opts ...Option) Options {
func ServiceName(name string) Option { func ServiceName(name string) Option {
return func(o *Options) { return func(o *Options) {
o.Name = name o.lopts = append(o.lopts, meter.Labels("name", name))
} }
} }
func ServiceVersion(version string) Option { func ServiceVersion(version string) Option {
return func(o *Options) { return func(o *Options) {
o.Version = version o.lopts = append(o.lopts, meter.Labels("version", version))
} }
} }
func ServiceID(id string) Option { func ServiceID(id string) Option {
return func(o *Options) { return func(o *Options) {
o.ID = id o.lopts = append(o.lopts, meter.Labels("id", id))
} }
} }
@@ -71,10 +84,16 @@ func Meter(m meter.Meter) Option {
} }
} }
func SkipEndoints(eps ...string) Option {
return func(o *Options) {
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
}
}
type wrapper struct { type wrapper struct {
opts Options
callFunc client.CallFunc
client.Client client.Client
callFunc client.CallFunc
opts Options
} }
func NewClientWrapper(opts ...Option) client.Wrapper { func NewClientWrapper(opts ...Option) client.Wrapper {
@@ -99,69 +118,90 @@ func NewCallWrapper(opts ...Option) client.CallWrapper {
func (w *wrapper) CallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error { func (w *wrapper) CallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()) endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.callFunc(ctx, addr, req, rsp, opts)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now() ts := time.Now()
err := w.callFunc(ctx, addr, req, rsp, opts) err := w.callFunc(ctx, addr, req, rsp, opts)
te := time.Since(ts) te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
lopts := make([]meter.Option, 0, 2) w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
lopts = append(lopts, meter.Label(labelEndpoint, endpoint)) w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil { if err == nil {
lopts = append(lopts, meter.Label(labelStatus, labelSuccess)) labels = append(labels, labelStatus, labelSuccess)
} else { } else {
lopts = append(lopts, meter.Label(labelStatus, labelFailure)) labels = append(labels, labelStatus, labelFailure)
} }
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc() w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err return err
} }
func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) 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()) endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.Client.Call(ctx, req, rsp, opts...)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now() ts := time.Now()
err := w.Client.Call(ctx, req, rsp, opts...) err := w.Client.Call(ctx, req, rsp, opts...)
te := time.Since(ts) te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
lopts := make([]meter.Option, 0, 2) w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
lopts = append(lopts, meter.Label(labelEndpoint, endpoint)) w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil { if err == nil {
lopts = append(lopts, meter.Label(labelStatus, labelSuccess)) labels = append(labels, labelStatus, labelSuccess)
} else { } else {
lopts = append(lopts, meter.Label(labelStatus, labelFailure)) labels = append(labels, labelStatus, labelFailure)
} }
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc() w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err return err
} }
func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (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()) endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.Client.Stream(ctx, req, opts...)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now() ts := time.Now()
stream, err := w.Client.Stream(ctx, req, opts...) stream, err := w.Client.Stream(ctx, req, opts...)
te := time.Since(ts) te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
lopts := make([]meter.Option, 0, 2) w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
lopts = append(lopts, meter.Label(labelEndpoint, endpoint)) w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil { if err == nil {
lopts = append(lopts, meter.Label(labelStatus, labelSuccess)) labels = append(labels, labelStatus, labelSuccess)
} else { } else {
lopts = append(lopts, meter.Label(labelStatus, labelFailure)) labels = append(labels, labelStatus, labelFailure)
} }
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc() w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return stream, err return stream, err
} }
@@ -169,22 +209,24 @@ func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client
func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error { func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
endpoint := p.Topic() endpoint := p.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Inc()
ts := time.Now() ts := time.Now()
err := w.Client.Publish(ctx, p, opts...) err := w.Client.Publish(ctx, p, opts...)
te := time.Since(ts) te := time.Since(ts)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Dec()
lopts := make([]meter.Option, 0, 2) w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, labels...).Update(te.Seconds())
lopts = append(lopts, meter.Label(labelEndpoint, endpoint)) w.opts.Meter.Histogram(PublishMessageDurationSeconds, labels...).Update(te.Seconds())
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(PublishMessageDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil { if err == nil {
lopts = append(lopts, meter.Label(labelStatus, labelSuccess)) labels = append(labels, labelStatus, labelSuccess)
} else { } else {
lopts = append(lopts, meter.Label(labelStatus, labelFailure)) labels = append(labels, labelStatus, labelFailure)
} }
w.opts.Meter.Counter(PublishMessageTotal, lopts...).Inc() w.opts.Meter.Counter(PublishMessageTotal, labels...).Inc()
return err return err
} }
@@ -199,23 +241,30 @@ func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc { func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error { return func(ctx context.Context, req server.Request, rsp interface{}) error {
endpoint := req.Endpoint() endpoint := req.Endpoint()
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return fn(ctx, req, rsp)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ServerRequestInflight, labels...).Inc()
ts := time.Now() ts := time.Now()
err := fn(ctx, req, rsp) err := fn(ctx, req, rsp)
te := time.Since(ts) te := time.Since(ts)
w.opts.Meter.Counter(ServerRequestInflight, labels...).Dec()
lopts := make([]meter.Option, 0, 2) w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, labels...).Update(te.Seconds())
lopts = append(lopts, meter.Label(labelEndpoint, endpoint)) w.opts.Meter.Histogram(ServerRequestDurationSeconds, labels...).Update(te.Seconds())
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ServerRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil { if err == nil {
lopts = append(lopts, meter.Label(labelStatus, labelSuccess)) labels = append(labels, labelStatus, labelSuccess)
} else { } else {
lopts = append(lopts, meter.Label(labelStatus, labelFailure)) labels = append(labels, labelStatus, labelFailure)
} }
w.opts.Meter.Counter(ServerRequestTotal, lopts...).Inc() w.opts.Meter.Counter(ServerRequestTotal, labels...).Inc()
return err return err
} }
@@ -232,22 +281,24 @@ func (w *wrapper) SubscriberFunc(fn server.SubscriberFunc) server.SubscriberFunc
return func(ctx context.Context, msg server.Message) error { return func(ctx context.Context, msg server.Message) error {
endpoint := msg.Topic() endpoint := msg.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Inc()
ts := time.Now() ts := time.Now()
err := fn(ctx, msg) err := fn(ctx, msg)
te := time.Since(ts) te := time.Since(ts)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Dec()
lopts := make([]meter.Option, 0, 2) w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, labels...).Update(te.Seconds())
lopts = append(lopts, meter.Label(labelEndpoint, endpoint)) w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, labels...).Update(te.Seconds())
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil { if err == nil {
lopts = append(lopts, meter.Label(labelStatus, labelSuccess)) labels = append(labels, labelStatus, labelSuccess)
} else { } else {
lopts = append(lopts, meter.Label(labelStatus, labelFailure)) labels = append(labels, labelStatus, labelFailure)
} }
w.opts.Meter.Counter(SubscribeMessageTotal, lopts...).Inc() w.opts.Meter.Counter(SubscribeMessageTotal, labels...).Inc()
return err return err
} }

View File

@@ -15,6 +15,18 @@ type Option func(*Options)
// Options configure network // Options configure network
type Options struct { 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 of the node
Id string Id string
// Name of the network // Name of the network
@@ -25,18 +37,6 @@ type Options struct {
Advertise string Advertise string
// Nodes is a list of nodes to connect to // Nodes is a list of nodes to connect to
Nodes []string Nodes []string
// Tunnel is network tunnel
Tunnel tunnel.Tunnel
// Router is network router
Router router.Router
// Proxy is network proxy
Proxy proxy.Proxy
// Logger
Logger logger.Logger
// Meter
Meter meter.Meter
// Tracer
Tracer tracer.Tracer
} }
// Id sets the id of the network node // Id sets the id of the network node

View File

@@ -14,19 +14,14 @@ import (
) )
type memorySocket struct { type memorySocket struct {
recv chan *Message
send chan *Message
// sock exit
exit chan bool
// listener exit
lexit chan bool
local string
remote string
// for send/recv transport.Timeout
timeout time.Duration
ctx context.Context ctx context.Context
recv chan *Message
exit chan bool
lexit chan bool
send chan *Message
local string
remote string
timeout time.Duration
sync.RWMutex sync.RWMutex
} }
@@ -36,19 +31,19 @@ type memoryClient struct {
} }
type memoryListener struct { type memoryListener struct {
addr string lopts ListenOptions
ctx context.Context
exit chan bool exit chan bool
conn chan *memorySocket conn chan *memorySocket
lopts ListenOptions addr string
topts Options topts Options
sync.RWMutex sync.RWMutex
ctx context.Context
} }
type memoryTransport struct { type memoryTransport struct {
opts Options
sync.RWMutex
listeners map[string]*memoryListener listeners map[string]*memoryListener
opts Options
sync.RWMutex
} }
func (ms *memorySocket) Recv(m *Message) error { func (ms *memorySocket) Recv(m *Message) error {

View File

@@ -27,9 +27,9 @@ func TestMemoryTransport(t *testing.T) {
if len(os.Getenv("INTEGRATION_TESTS")) == 0 { if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
t.Logf("Server Received %s", string(m.Body)) t.Logf("Server Received %s", string(m.Body))
} }
if err := sock.Send(&Message{ if cerr := sock.Send(&Message{
Body: []byte(`pong`), Body: []byte(`pong`),
}); err != nil { }); cerr != nil {
return return
} }
} }
@@ -60,7 +60,6 @@ func TestMemoryTransport(t *testing.T) {
t.Logf("Client Received %s", string(m.Body)) t.Logf("Client Received %s", string(m.Body))
} }
} }
} }
func TestListener(t *testing.T) { func TestListener(t *testing.T) {

View File

@@ -13,26 +13,24 @@ import (
// Options struct holds the transport options // Options struct holds the transport options
type Options struct { type Options struct {
Name string // Meter used for metrics
// Addrs is the list of intermediary addresses to connect to
Addrs []string
// Codec is the codec interface to use where headers are not supported
// by the transport and the entire payload must be encoded
Codec codec.Codec
// TLSConfig to secure the connection. The assumption is that this
// is mTLS keypair
TLSConfig *tls.Config
// Timeout sets the timeout for Send/Recv
Timeout time.Duration
// Logger sets the logger
Logger logger.Logger
// Meter sets the meter
Meter meter.Meter Meter meter.Meter
// Tracer sets the tracer // Tracer used for tracing
Tracer tracer.Tracer Tracer tracer.Tracer
// Other options for implementations of the interface // Codec used for marshal/unmarshal messages
// can be stored in a context Codec codec.Codec
// Logger used for logging
Logger logger.Logger
// Context holds external options
Context context.Context 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 // NewOptions returns new options
@@ -53,18 +51,12 @@ func NewOptions(opts ...Option) Options {
// DialOptions struct // DialOptions struct
type DialOptions struct { type DialOptions struct {
// Tells the transport this is a streaming connection with // Context holds the external options
// multiple calls to send/recv and that send may not even be called
Stream bool
// Timeout for dialing
Timeout time.Duration
// TODO: add tls options when dialling
// Currently set in global options
// Other options for implementations of the interface
// can be stored in a context
Context context.Context Context context.Context
// Timeout holds the timeout
Timeout time.Duration
// Stream flag
Stream bool
} }
// NewDialOptions returns new DialOptions // NewDialOptions returns new DialOptions
@@ -85,10 +77,10 @@ func NewDialOptions(opts ...DialOption) DialOptions {
type ListenOptions struct { type ListenOptions struct {
// TODO: add tls options when listening // TODO: add tls options when listening
// Currently set in global options // Currently set in global options
// Context holds the external options
// Other options for implementations of the interface
// can be stored in a context
Context context.Context Context context.Context
// TLSConfig holds the *tls.Config options
TLSConfig *tls.Config
} }
// NewListenOptions returns new ListenOptions // NewListenOptions returns new ListenOptions

View File

@@ -12,27 +12,37 @@ import (
) )
type tunBroker struct { type tunBroker struct {
opts broker.Options
tunnel tunnel.Tunnel tunnel tunnel.Tunnel
opts broker.Options
} }
type tunSubscriber struct { type tunSubscriber struct {
topic string
handler broker.Handler
opts broker.SubscribeOptions
closed chan bool
listener tunnel.Listener 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 { type tunEvent struct {
topic string
message *broker.Message message *broker.Message
topic string
err error
} }
// used to access tunnel from options context // used to access tunnel from options context
type tunnelKey struct{} type (
type tunnelAddr struct{} tunnelKey struct{}
tunnelAddr struct{}
)
func (t *tunBroker) Init(opts ...broker.Option) error { func (t *tunBroker) Init(opts ...broker.Option) error {
for _, o := range opts { for _, o := range opts {
@@ -61,6 +71,36 @@ func (t *tunBroker) Disconnect(ctx context.Context) error {
return t.tunnel.Close(ctx) 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 { 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 // TODO: this is probably inefficient, we might want to just maintain an open connection
// it may be easier to add broadcast to the tunnel // it may be easier to add broadcast to the tunnel
@@ -76,6 +116,26 @@ func (t *tunBroker) Publish(ctx context.Context, topic string, m *broker.Message
}) })
} }
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) { 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)) l, err := t.tunnel.Listen(ctx, topic, tunnel.ListenMode(tunnel.Multicast))
if err != nil { if err != nil {
@@ -100,6 +160,49 @@ func (t *tunBroker) String() string {
return "tunnel" 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 t.handler(evts)
}
}
func (t *tunSubscriber) run() { func (t *tunSubscriber) run() {
for { for {
// accept a new connection // accept a new connection
@@ -141,6 +244,24 @@ func (t *tunSubscriber) run() {
} }
} }
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 { func (t *tunSubscriber) Options() broker.SubscribeOptions {
return t.opts return t.opts
} }
@@ -172,7 +293,11 @@ func (t *tunEvent) Ack() error {
} }
func (t *tunEvent) Error() error { func (t *tunEvent) Error() error {
return nil return t.err
}
func (t *tunEvent) SetError(err error) {
t.err = err
} }
// NewBroker returns new tunnel broker // NewBroker returns new tunnel broker

View File

@@ -22,23 +22,24 @@ type Option func(*Options)
// Options provides network configuration options // Options provides network configuration options
type Options struct { type Options struct {
Name string // Logger used for logging
// Id is tunnel id
Id string
// Address is tunnel address
Address string
// Nodes are remote nodes
Nodes []string
// The shared auth token
Token string
// Transport listens to incoming connections
Transport transport.Transport
// Logger
Logger logger.Logger Logger logger.Logger
// Meter // Meter used for metrics
Meter meter.Meter Meter meter.Meter
// Tracer // Tracer used for tracing
Tracer tracer.Tracer 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 // DialOption func
@@ -61,16 +62,16 @@ type ListenOption func(*ListenOptions)
// ListenOptions provides listen options // ListenOptions provides listen options
type ListenOptions struct { type ListenOptions struct {
// specify mode of the session // Mode specify mode of the session
Mode Mode Mode Mode
// The read timeout // Timeout the read timeout
Timeout time.Duration Timeout time.Duration
} }
// Id sets the tunnel id // ID sets the tunnel id
func Id(id string) Option { func ID(id string) Option {
return func(o *Options) { return func(o *Options) {
o.Id = id o.ID = id
} }
} }
@@ -163,7 +164,7 @@ func DialWait(b bool) DialOption {
// NewOptions returns router default options with filled values // NewOptions returns router default options with filled values
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Id: uuid.New().String(), ID: uuid.New().String(),
Address: DefaultAddress, Address: DefaultAddress,
Token: DefaultToken, Token: DefaultToken,
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,

View File

@@ -10,9 +10,8 @@ import (
) )
type tunTransport struct { type tunTransport struct {
tunnel tunnel.Tunnel
options transport.Options options transport.Options
tunnel tunnel.Tunnel
} }
type tunnelKey struct{} type tunnelKey struct{}
@@ -88,7 +87,7 @@ func NewTransport(opts ...transport.Option) transport.Transport {
} }
// initialise // initialise
//t.Init(opts...) // t.Init(opts...)
return t return t
} }

View File

@@ -9,10 +9,8 @@ import (
"github.com/unistack-org/micro/v3/network/transport" "github.com/unistack-org/micro/v3/network/transport"
) )
var ( // DefaultTunnel contains default tunnel implementation
// DefaultTunnel contains default tunnel implementation var DefaultTunnel Tunnel
DefaultTunnel Tunnel
)
const ( const (
// Unicast send over one link // Unicast send over one link

View File

@@ -17,39 +17,48 @@ import (
"github.com/unistack-org/micro/v3/server" "github.com/unistack-org/micro/v3/server"
"github.com/unistack-org/micro/v3/store" "github.com/unistack-org/micro/v3/store"
"github.com/unistack-org/micro/v3/tracer" "github.com/unistack-org/micro/v3/tracer"
// "github.com/unistack-org/micro/v3/profiler"
// "github.com/unistack-org/micro/v3/runtime"
) )
// Options for micro service // Options for micro service
type Options struct { type Options struct {
Name string // Context holds external options or cancel stuff
Version string
Metadata metadata.Metadata
Auths []auth.Auth
Brokers []broker.Broker
Loggers []logger.Logger
Meters []meter.Meter
Configs []config.Config
Clients []client.Client
Servers []server.Server
Stores []store.Store
Registers []register.Register
Tracers []tracer.Tracer
Routers []router.Router
// Runtime runtime.Runtime
// Profile profile.Profile
// Before and After funcs
BeforeStart []func(context.Context) error
BeforeStop []func(context.Context) error
AfterStart []func(context.Context) error
AfterStop []func(context.Context) error
// Other options for implementations of the interface
// can be stored in a context
Context context.Context Context context.Context
// Metadata holds service metadata
Metadata metadata.Metadata
// Version holds service version
Version string
// Name holds service name
Name string
// Brokers holds brokers
Brokers []broker.Broker
// Loggers holds loggers
Loggers []logger.Logger
// Meters holds meter
Meters []meter.Meter
// Configs holds config
Configs []config.Config
// Clients holds clients
Clients []client.Client
// Auths holds auths
Auths []auth.Auth
// Stores holds stores
Stores []store.Store
// Registers holds registers
Registers []register.Register
// Tracers holds tracers
Tracers []tracer.Tracer
// Routers holds routers
Routers []router.Router
// BeforeStart holds funcs that runs before service starts
BeforeStart []func(context.Context) error
// BeforeStop holds funcs that runs before service stops
BeforeStop []func(context.Context) error
// AfterStart holds funcs that runs after service starts
AfterStart []func(context.Context) error
// AfterStop holds funcs that runs after service stops
AfterStop []func(context.Context) error
// Servers holds servers
Servers []server.Server
} }
// NewOptions returns new Options filled with defaults and overrided by provided opts // NewOptions returns new Options filled with defaults and overrided by provided opts
@@ -67,8 +76,8 @@ func NewOptions(opts ...Option) Options {
Meters: []meter.Meter{meter.DefaultMeter}, Meters: []meter.Meter{meter.DefaultMeter},
Configs: []config.Config{config.DefaultConfig}, Configs: []config.Config{config.DefaultConfig},
Stores: []store.Store{store.DefaultStore}, Stores: []store.Store{store.DefaultStore},
//Runtime runtime.Runtime // Runtime runtime.Runtime
//Profile profile.Profile // Profile profile.Profile
} }
for _, o := range opts { for _, o := range opts {

View File

@@ -11,15 +11,13 @@ import (
) )
type httpProfile struct { type httpProfile struct {
server *http.Server
sync.Mutex sync.Mutex
running bool running bool
server *http.Server
} }
var ( // DefaultAddress for http profiler
// DefaultAddress for http profiler var DefaultAddress = ":6060"
DefaultAddress = ":6060"
)
// Start the profiler // Start the profiler
func (h *httpProfile) Start() error { func (h *httpProfile) Start() error {

View File

@@ -13,16 +13,12 @@ import (
) )
type profiler struct { type profiler struct {
opts profile.Options exit chan bool
cpuFile *os.File
memFile *os.File
opts profile.Options
sync.Mutex sync.Mutex
running bool running bool
exit chan bool
// where the cpu profile is written
cpuFile *os.File
// where the mem profile is written
memFile *os.File
} }
func (p *profiler) writeHeap(f *os.File) { func (p *profiler) writeHeap(f *os.File) {

View File

@@ -11,10 +11,8 @@ type Profiler interface {
String() string String() string
} }
var ( // DefaultProfiler holds the default profiler
// DefaultProfiler holds the default profiler var DefaultProfiler Profiler = NewProfiler()
DefaultProfiler Profiler = NewProfiler()
)
// Options holds the options for profiler // Options holds the options for profiler
type Options struct { type Options struct {

View File

@@ -11,20 +11,20 @@ import (
// Options for proxy // Options for proxy
type Options struct { type Options struct {
// Specific endpoint to always call // Tracer used for tracing
Endpoint string
// The default client to use
Client client.Client
// The default router to use
Router router.Router
// Extra links for different clients
Links map[string]client.Client
// Logger
Logger logger.Logger
// Meter
Meter meter.Meter
// Tracer
Tracer tracer.Tracer 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 // Option func signature

View File

@@ -7,10 +7,8 @@ import (
"github.com/unistack-org/micro/v3/server" "github.com/unistack-org/micro/v3/server"
) )
var ( // DefaultEndpoint holds default proxy address
// DefaultEndpoint holds default proxy address var DefaultEndpoint = "localhost:9090"
DefaultEndpoint = "localhost:9090"
)
// Proxy can be used as a proxy server for micro services // Proxy can be used as a proxy server for micro services
type Proxy interface { type Proxy interface {

View File

@@ -3,7 +3,6 @@ package register
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@@ -11,20 +10,21 @@ import (
) )
// ExtractValue from reflect.Type from specified depth // ExtractValue from reflect.Type from specified depth
func ExtractValue(v reflect.Type, d int) *Value { func ExtractValue(v reflect.Type, d int) string {
if d == 3 { if d == 3 {
return nil return ""
} }
if v == nil { if v == nil {
return nil return ""
} }
if v.Kind() == reflect.Ptr { if v.Kind() == reflect.Ptr {
v = v.Elem() v = v.Elem()
} }
if len(v.Name()) == 0 { // slices and maps don't have a defined name
return nil if (v.Kind() == reflect.Slice || v.Kind() == reflect.Map) || len(v.Name()) == 0 {
return ""
} }
// get the rune character // get the rune character
@@ -32,48 +32,10 @@ func ExtractValue(v reflect.Type, d int) *Value {
// crude check for is unexported field // crude check for is unexported field
if unicode.IsLower(a) { if unicode.IsLower(a) {
return nil return ""
} }
arg := &Value{ return v.Name()
Name: v.Name(),
Type: v.Name(),
}
switch v.Kind() {
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
val := ExtractValue(f.Type, d+1)
if val == nil {
continue
}
// if we can find a json tag use it
if tags := f.Tag.Get("json"); len(tags) > 0 {
parts := strings.Split(tags, ",")
if parts[0] == "-" || parts[0] == "omitempty" {
continue
}
val.Name = parts[0]
}
// if there's no name default it
if len(val.Name) == 0 {
val.Name = v.Field(i).Name
}
arg.Values = append(arg.Values, val)
}
case reflect.Slice:
p := v.Elem()
if p.Kind() == reflect.Ptr {
p = p.Elem()
}
arg.Type = "[]" + p.Name()
}
return arg
} }
// ExtractEndpoint extract *Endpoint from reflect.Method // ExtractEndpoint extract *Endpoint from reflect.Method
@@ -105,7 +67,7 @@ func ExtractEndpoint(method reflect.Method) *Endpoint {
request := ExtractValue(reqType, 0) request := ExtractValue(reqType, 0)
response := ExtractValue(rspType, 0) response := ExtractValue(rspType, 0)
if request == nil || response == nil { if request == "" || response == "" {
return nil return nil
} }
@@ -124,7 +86,7 @@ func ExtractEndpoint(method reflect.Method) *Endpoint {
} }
// ExtractSubValue exctact *Value from reflect.Type // ExtractSubValue exctact *Value from reflect.Type
func ExtractSubValue(typ reflect.Type) *Value { func ExtractSubValue(typ reflect.Type) string {
var reqType reflect.Type var reqType reflect.Type
switch typ.NumIn() { switch typ.NumIn() {
case 1: case 1:
@@ -134,7 +96,7 @@ func ExtractSubValue(typ reflect.Type) *Value {
case 3: case 3:
reqType = typ.In(2) reqType = typ.In(2)
default: default:
return nil return ""
} }
return ExtractValue(reqType, 0) return ExtractValue(reqType, 0)
} }

View File

@@ -36,28 +36,21 @@ func TestExtractEndpoint(t *testing.T) {
t.Fatalf("Expected handler Test, got %s", endpoints[0].Name) t.Fatalf("Expected handler Test, got %s", endpoints[0].Name)
} }
if endpoints[0].Request == nil { if endpoints[0].Request == "" {
t.Fatal("Expected non nil Request") t.Fatal("Expected non nil Request")
} }
if endpoints[0].Response == nil { if endpoints[0].Response == "" {
t.Fatal("Expected non nil Request") t.Fatal("Expected non nil Request")
} }
if endpoints[0].Request.Name != "TestRequest" { if endpoints[0].Request != "TestRequest" {
t.Fatalf("Expected TestRequest got %s", endpoints[0].Request.Name) t.Fatalf("Expected TestRequest got %s", endpoints[0].Request)
} }
if endpoints[0].Response.Name != "TestResponse" { if endpoints[0].Response != "TestResponse" {
t.Fatalf("Expected TestResponse got %s", endpoints[0].Response.Name) t.Fatalf("Expected TestResponse got %s", endpoints[0].Response)
}
if endpoints[0].Request.Type != "TestRequest" {
t.Fatalf("Expected TestRequest type got %s", endpoints[0].Request.Type)
}
if endpoints[0].Response.Type != "TestResponse" {
t.Fatalf("Expected TestResponse type got %s", endpoints[0].Response.Type)
} }
t.Logf("XXX %#+v\n", endpoints[0])
} }

View File

@@ -16,9 +16,9 @@ var (
) )
type node struct { type node struct {
*Node
TTL time.Duration
LastSeen time.Time LastSeen time.Time
*Node
TTL time.Duration
} }
type record struct { type record struct {
@@ -30,10 +30,9 @@ type record struct {
} }
type memory struct { type memory struct {
opts Options
// records is a KV map with domain name as the key and a services map as the value
records map[string]services records map[string]services
watchers map[string]*watcher watchers map[string]*watcher
opts Options
sync.RWMutex sync.RWMutex
} }
@@ -65,7 +64,7 @@ func (m *memory) ttlPrune() {
for id, n := range record.Nodes { for id, n := range record.Nodes {
if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL { if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL {
if m.opts.Logger.V(logger.DebugLevel) { if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.Id, 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) delete(m.records[domain][service][version].Nodes, id)
} }
@@ -162,7 +161,7 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
for _, n := range s.Nodes { for _, n := range s.Nodes {
// check if already exists // check if already exists
if _, ok := srvs[s.Name][s.Version].Nodes[n.Id]; ok { if _, ok := srvs[s.Name][s.Version].Nodes[n.ID]; ok {
continue continue
} }
@@ -177,9 +176,9 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
metadata["domain"] = options.Domain metadata["domain"] = options.Domain
// add the node // add the node
srvs[s.Name][s.Version].Nodes[n.Id] = &node{ srvs[s.Name][s.Version].Nodes[n.ID] = &node{
Node: &Node{ Node: &Node{
Id: n.Id, ID: n.ID,
Address: n.Address, Address: n.Address,
Metadata: metadata, Metadata: metadata,
}, },
@@ -201,8 +200,8 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
if m.opts.Logger.V(logger.DebugLevel) { if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Updated registration for service: %s, version: %s", s.Name, 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].TTL = options.TTL
srvs[s.Name][s.Version].Nodes[n.Id].LastSeen = time.Now() srvs[s.Name][s.Version].Nodes[n.ID].LastSeen = time.Now()
} }
} }
@@ -242,11 +241,11 @@ func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterO
// deregister all of the service nodes from this version // deregister all of the service nodes from this version
for _, n := range s.Nodes { for _, n := range s.Nodes {
if _, ok := version.Nodes[n.Id]; ok { if _, ok := version.Nodes[n.ID]; ok {
if m.opts.Logger.V(logger.DebugLevel) { if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register removed node from service: %s, version: %s", s.Name, 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) delete(version.Nodes, n.ID)
} }
} }
@@ -369,9 +368,9 @@ func (m *memory) ListServices(ctx context.Context, opts ...ListOption) ([]*Servi
// serialize the result, each version counts as an individual service // serialize the result, each version counts as an individual service
var result []*Service var result []*Service
for domain, service := range services { for _, service := range services {
for _, version := range service { for _, version := range service {
result = append(result, recordToService(version, domain)) result = append(result, recordToService(version, options.Domain))
} }
} }
@@ -405,10 +404,10 @@ func (m *memory) String() string {
} }
type watcher struct { type watcher struct {
id string
wo WatchOptions
res chan *Result res chan *Result
exit chan bool exit chan bool
wo WatchOptions
id string
} }
func (m *watcher) Next() (*Result, error) { func (m *watcher) Next() (*Result, error) {
@@ -458,7 +457,7 @@ func serviceToRecord(s *Service, ttl time.Duration) *record {
nodes := make(map[string]*node, len(s.Nodes)) nodes := make(map[string]*node, len(s.Nodes))
for _, n := range s.Nodes { for _, n := range s.Nodes {
nodes[n.Id] = &node{ nodes[n.ID] = &node{
Node: n, Node: n,
TTL: ttl, TTL: ttl,
LastSeen: time.Now(), LastSeen: time.Now(),
@@ -490,40 +489,31 @@ func recordToService(r *record, domain string) *Service {
endpoints := make([]*Endpoint, len(r.Endpoints)) endpoints := make([]*Endpoint, len(r.Endpoints))
for i, e := range r.Endpoints { for i, e := range r.Endpoints {
request := new(Value) md := make(map[string]string, len(e.Metadata))
if e.Request != nil {
*request = *e.Request
}
response := new(Value)
if e.Response != nil {
*response = *e.Response
}
metadata := make(map[string]string, len(e.Metadata))
for k, v := range e.Metadata { for k, v := range e.Metadata {
metadata[k] = v md[k] = v
} }
endpoints[i] = &Endpoint{ endpoints[i] = &Endpoint{
Name: e.Name, Name: e.Name,
Request: request, Request: e.Request,
Response: response, Response: e.Response,
Metadata: metadata, Metadata: md,
} }
} }
nodes := make([]*Node, len(r.Nodes)) nodes := make([]*Node, len(r.Nodes))
i := 0 i := 0
for _, n := range r.Nodes { for _, n := range r.Nodes {
metadata := make(map[string]string, len(n.Metadata)) md := make(map[string]string, len(n.Metadata))
for k, v := range n.Metadata { for k, v := range n.Metadata {
metadata[k] = v md[k] = v
} }
nodes[i] = &Node{ nodes[i] = &Node{
Id: n.Id, ID: n.ID,
Address: n.Address, Address: n.Address,
Metadata: metadata, Metadata: md,
} }
i++ i++
} }

View File

@@ -8,72 +8,70 @@ import (
"time" "time"
) )
var ( var testData = map[string][]*Service{
testData = map[string][]*Service{ "foo": {
"foo": { {
{ Name: "foo",
Name: "foo", Version: "1.0.0",
Version: "1.0.0", Nodes: []*Node{
Nodes: []*Node{ {
{ ID: "foo-1.0.0-123",
Id: "foo-1.0.0-123", Address: "localhost:9999",
Address: "localhost:9999",
},
{
Id: "foo-1.0.0-321",
Address: "localhost:9999",
},
}, },
}, {
{ ID: "foo-1.0.0-321",
Name: "foo", Address: "localhost:9999",
Version: "1.0.1",
Nodes: []*Node{
{
Id: "foo-1.0.1-321",
Address: "localhost:6666",
},
},
},
{
Name: "foo",
Version: "1.0.3",
Nodes: []*Node{
{
Id: "foo-1.0.3-345",
Address: "localhost:8888",
},
}, },
}, },
}, },
"bar": { {
{ Name: "foo",
Name: "bar", Version: "1.0.1",
Version: "default", Nodes: []*Node{
Nodes: []*Node{ {
{ ID: "foo-1.0.1-321",
Id: "bar-1.0.0-123", Address: "localhost:6666",
Address: "localhost:9999",
},
{
Id: "bar-1.0.0-321",
Address: "localhost:9999",
},
},
},
{
Name: "bar",
Version: "latest",
Nodes: []*Node{
{
Id: "bar-1.0.1-321",
Address: "localhost:6666",
},
}, },
}, },
}, },
} {
) Name: "foo",
Version: "1.0.3",
Nodes: []*Node{
{
ID: "foo-1.0.3-345",
Address: "localhost:8888",
},
},
},
},
"bar": {
{
Name: "bar",
Version: "default",
Nodes: []*Node{
{
ID: "bar-1.0.0-123",
Address: "localhost:9999",
},
{
ID: "bar-1.0.0-321",
Address: "localhost:9999",
},
},
},
{
Name: "bar",
Version: "latest",
Nodes: []*Node{
{
ID: "bar-1.0.1-321",
Address: "localhost:6666",
},
},
},
},
}
//nolint:gocyclo //nolint:gocyclo
func TestMemoryRegistry(t *testing.T) { func TestMemoryRegistry(t *testing.T) {

View File

@@ -12,20 +12,22 @@ import (
// Options holds options for register // Options holds options for register
type Options struct { type Options struct {
Name string // Tracer used for tracing
Addrs []string
Timeout time.Duration
TLSConfig *tls.Config
// Logger that will be used
Logger logger.Logger
// Meter that will be used
Meter meter.Meter
// Tracer
Tracer tracer.Tracer Tracer tracer.Tracer
// Other options for implementations of the interface // Context holds external options
// can be stored in a context
Context context.Context Context context.Context
// Logged used for logging
Logger logger.Logger
// Meter used for metrics
Meter meter.Meter
// TLSConfig holds tls.TLSConfig options
TLSConfig *tls.Config
// Name holds the name of register
Name string
// Addrs specifies register addrs
Addrs []string
// Timeout specifies timeout
Timeout time.Duration
} }
// NewOptions returns options that filled by opts // NewOptions returns options that filled by opts
@@ -42,15 +44,12 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
// nolint: golint
// RegisterOptions holds options for register method // RegisterOptions holds options for register method
type RegisterOptions struct { type RegisterOptions struct {
TTL time.Duration Context context.Context
// Other options for implementations of the interface Domain string
// can be stored in a context TTL time.Duration
Context context.Context
// Domain to register the service in
Domain string
// Attempts specify attempts for register
Attempts int Attempts int
} }
@@ -198,6 +197,7 @@ func TLSConfig(t *tls.Config) Option {
} }
} }
// nolint: golint
// RegisterAttempts specifies register atempts count // RegisterAttempts specifies register atempts count
func RegisterAttempts(t int) RegisterOption { func RegisterAttempts(t int) RegisterOption {
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
@@ -205,6 +205,7 @@ func RegisterAttempts(t int) RegisterOption {
} }
} }
// nolint: golint
// RegisterTTL specifies register ttl // RegisterTTL specifies register ttl
func RegisterTTL(t time.Duration) RegisterOption { func RegisterTTL(t time.Duration) RegisterOption {
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
@@ -212,6 +213,7 @@ func RegisterTTL(t time.Duration) RegisterOption {
} }
} }
// nolint: golint
// RegisterContext sets the register context // RegisterContext sets the register context
func RegisterContext(ctx context.Context) RegisterOption { func RegisterContext(ctx context.Context) RegisterOption {
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
@@ -219,6 +221,7 @@ func RegisterContext(ctx context.Context) RegisterOption {
} }
} }
// nolint: golint
// RegisterDomain secifies register domain // RegisterDomain secifies register domain
func RegisterDomain(d string) RegisterOption { func RegisterDomain(d string) RegisterOption {
return func(o *RegisterOptions) { return func(o *RegisterOptions) {

View File

@@ -52,30 +52,24 @@ type Service struct {
// Node holds node register info // Node holds node register info
type Node struct { type Node struct {
Id string `json:"id"`
Address string `json:"address"`
Metadata metadata.Metadata `json:"metadata"` Metadata metadata.Metadata `json:"metadata"`
ID string `json:"id"`
Address string `json:"address"`
} }
// Endpoint holds endpoint register info // Endpoint holds endpoint register info
type Endpoint struct { type Endpoint struct {
Name string `json:"name"` Request string `json:"request"`
Request *Value `json:"request"` Response string `json:"response"`
Response *Value `json:"response"`
Metadata metadata.Metadata `json:"metadata"` Metadata metadata.Metadata `json:"metadata"`
} Name string `json:"name"`
// Value holds additional kv stuff
type Value struct {
Name string `json:"name"`
Type string `json:"type"`
Values []*Value `json:"values"`
} }
// Option func signature // Option func signature
type Option func(*Options) type Option func(*Options)
// RegisterOption option is used to register service // RegisterOption option is used to register service
// nolint: golint
type RegisterOption func(*RegisterOptions) type RegisterOption func(*RegisterOptions)
// WatchOption option is used to watch service changes // WatchOption option is used to watch service changes

View File

@@ -7,14 +7,17 @@ import "time"
type Watcher interface { type Watcher interface {
// Next is a blocking call // Next is a blocking call
Next() (*Result, error) Next() (*Result, error)
// Stop stops the watcher
Stop() Stop()
} }
// Result is returned by a call to Next on // Result is returned by a call to Next on
// the watcher. Actions can be create, update, delete // the watcher. Actions can be create, update, delete
type Result struct { type Result struct {
Action string // Service holds register service
Service *Service Service *Service
// Action holds the action
Action string
} }
// EventType defines register event type // EventType defines register event type
@@ -45,12 +48,12 @@ func (t EventType) String() string {
// Event is register event // Event is register event
type Event struct { type Event struct {
// Id is register id
Id string
// Type defines type of event
Type EventType
// Timestamp is event timestamp // Timestamp is event timestamp
Timestamp time.Time Timestamp time.Time
// Service is register service // Service is register service
Service *Service Service *Service
// ID is register id
ID string
// Type defines type of event
Type EventType
} }

View File

@@ -12,9 +12,9 @@ import (
// Resolver is a DNS network resolve // Resolver is a DNS network resolve
type Resolver struct { type Resolver struct {
// The resolver address to use
Address string
goresolver *net.Resolver goresolver *net.Resolver
// Address of resolver to use
Address string
sync.RWMutex sync.RWMutex
} }

91
router/dns.go Normal file
View File

@@ -0,0 +1,91 @@
package router
import (
"fmt"
"net"
"strconv"
)
// NewRouter returns an initialized dns router
func NewRouter(opts ...Option) Router {
options := NewOptions(opts...)
return &dns{options}
}
type dns struct {
options Options
}
func (d *dns) Init(opts ...Option) error {
for _, o := range opts {
o(&d.options)
}
return nil
}
func (d *dns) Options() Options {
return d.options
}
func (d *dns) Table() Table {
return nil
}
func (d *dns) Close() error {
return nil
}
func (d *dns) Lookup(opts ...QueryOption) ([]Route, error) {
options := NewQuery(opts...)
// check to see if we have the port provided in the service, e.g. go-micro-srv-foo:8000
host, port, err := net.SplitHostPort(options.Service)
if err == nil {
// lookup the service using A records
ips, err := net.LookupHost(host)
if err != nil {
return nil, err
}
p, _ := strconv.Atoi(port)
// convert the ip addresses to routes
result := make([]Route, len(ips))
for i, ip := range ips {
result[i] = Route{
Service: options.Service,
Address: fmt.Sprintf("%s:%d", ip, uint16(p)),
}
}
return result, nil
}
// we didn't get the port so we'll lookup the service using SRV records. If we can't lookup the
// service using the SRV record, we return the error.
_, nodes, err := net.LookupSRV(options.Service, "tcp", d.options.Network)
if err != nil {
return nil, err
}
// convert the nodes (net services) to routes
result := make([]Route, len(nodes))
for i, n := range nodes {
result[i] = Route{
Service: options.Service,
Address: fmt.Sprintf("%s:%d", n.Target, n.Port),
Network: d.options.Network,
}
}
return result, nil
}
func (d *dns) Watch(opts ...WatchOption) (Watcher, error) {
return nil, nil
}
func (d *dns) Name() string {
return d.options.Name
}
func (d *dns) String() string {
return "dns"
}

View File

@@ -10,23 +10,15 @@ import (
// Options are router options // Options are router options
type Options struct { type Options struct {
Name string Logger logger.Logger
// Id is router id Context context.Context
Id string
// Address is router address
Address string
// Gateway is network gateway
Gateway string
// Network is network address
Network string
// Register is the local register
Register register.Register Register register.Register
// Precache routes Name string
Gateway string
Network string
Id string
Address string
Precache bool Precache bool
// Logger
Logger logger.Logger
// Context for additional options
Context context.Context
} }
// Id sets Router Id // Id sets Router Id

View File

@@ -15,10 +15,10 @@ var (
// Route is network route // Route is network route
type Route struct { type Route struct {
// Metadata for the route
Metadata metadata.Metadata
// Service is destination service name // Service is destination service name
Service string Service string
// Address is service node address
Address string
// Gateway is route gateway // Gateway is route gateway
Gateway string Gateway string
// Network is network address // Network is network address
@@ -27,10 +27,10 @@ type Route struct {
Router string Router string
// Link is network link // Link is network link
Link string Link string
// Address is service node address
Address string
// Metric is the route cost metric // Metric is the route cost metric
Metric int64 Metric int64
// Metadata for the route
Metadata metadata.Metadata
} }
// Hash returns route hash sum. // Hash returns route hash sum.

View File

@@ -7,7 +7,7 @@ import (
var ( var (
// DefaultRouter is the global default router // DefaultRouter is the global default router
DefaultRouter Router DefaultRouter Router = NewRouter()
// DefaultNetwork is default micro network // DefaultNetwork is default micro network
DefaultNetwork = "micro" DefaultNetwork = "micro"
// ErrRouteNotFound is returned when no route was found in the routing table // ErrRouteNotFound is returned when no route was found in the routing table

View File

@@ -5,10 +5,8 @@ import (
"time" "time"
) )
var ( // ErrWatcherStopped is returned when routing table watcher has been stopped
// ErrWatcherStopped is returned when routing table watcher has been stopped var ErrWatcherStopped = errors.New("watcher stopped")
ErrWatcherStopped = errors.New("watcher stopped")
)
// EventType defines routing table event // EventType defines routing table event
type EventType int type EventType int
@@ -38,14 +36,14 @@ func (t EventType) String() string {
// Event is returned by a call to Next on the watcher. // Event is returned by a call to Next on the watcher.
type Event struct { type Event struct {
// Unique id of the event
Id string
// Type defines type of event
Type EventType
// Timestamp is event timestamp // Timestamp is event timestamp
Timestamp time.Time Timestamp time.Time
// Id of the event
Id string
// Route is table route // Route is table route
Route Route Route Route
// Type defines type of event
Type EventType
} }
// Watcher defines routing table watcher interface // Watcher defines routing table watcher interface

View File

@@ -10,18 +10,12 @@ import (
// Options configure runtime // Options configure runtime
type Options struct { type Options struct {
// Scheduler for updates
Scheduler Scheduler Scheduler Scheduler
// Service type to manage Client client.Client
Type string Logger logger.Logger
// Source of the services repository Type string
Source string Source string
// Base image to use Image string
Image string
// Client to use when making requests
Client client.Client
// Logger
Logger logger.Logger
} }
// Option func signature // Option func signature
@@ -77,42 +71,26 @@ type ReadOption func(o *ReadOptions)
// CreateOptions configure runtime services // CreateOptions configure runtime services
type CreateOptions struct { type CreateOptions struct {
// Command to execut Context context.Context
Command []string Output io.Writer
// Args to pass into command
Args []string
// Environment to configure
Env []string
// Log output
Output io.Writer
// Type of service to create
Type string
// Retries before failing deploy
Retries int
// Specify the image to use
Image string
// Namespace to create the service in
Namespace string
// Specify the context to use
Context context.Context
// Secrets to use
Secrets map[string]string
// Resources to allocate the service
Resources *Resources Resources *Resources
Secrets map[string]string
Image string
Namespace string
Type string
Command []string
Args []string
Env []string
Retries int
} }
// ReadOptions queries runtime services // ReadOptions queries runtime services
type ReadOptions struct { type ReadOptions struct {
// Service name Context context.Context
Service string Service string
// Version queries services with given version Version string
Version string Type string
// Type of service
Type string
// Namespace the service is running in
Namespace string Namespace string
// Specify the context to use
Context context.Context
} }
// CreateType sets the type of service to create // CreateType sets the type of service to create
@@ -238,12 +216,9 @@ type UpdateOption func(o *UpdateOptions)
// UpdateOptions struct // UpdateOptions struct
type UpdateOptions struct { type UpdateOptions struct {
// Namespace the service is running in Context context.Context
Secrets map[string]string
Namespace string Namespace string
// Specify the context to use
Context context.Context
// Secrets to use
Secrets map[string]string
} }
// UpdateSecret sets a secret to provide the service with // UpdateSecret sets a secret to provide the service with
@@ -276,10 +251,8 @@ type DeleteOption func(o *DeleteOptions)
// DeleteOptions struct // DeleteOptions struct
type DeleteOptions struct { type DeleteOptions struct {
// Namespace the service is running in Context context.Context
Namespace string Namespace string
// Specify the context to use
Context context.Context
} }
// DeleteNamespace sets the namespace // DeleteNamespace sets the namespace
@@ -301,14 +274,10 @@ type LogsOption func(o *LogsOptions)
// LogsOptions configure runtime logging // LogsOptions configure runtime logging
type LogsOptions struct { type LogsOptions struct {
// How many existing lines to show Context context.Context
Count int64
// Stream new lines?
Stream bool
// Namespace the service is running in
Namespace string Namespace string
// Specify the context to use Count int64
Context context.Context Stream bool
} }
// LogsCount confiures how many existing lines to show // LogsCount confiures how many existing lines to show

View File

@@ -8,10 +8,8 @@ import (
"github.com/unistack-org/micro/v3/metadata" "github.com/unistack-org/micro/v3/metadata"
) )
var ( // ErrAlreadyExists error
// ErrAlreadyExists error var ErrAlreadyExists = errors.New("already exists")
ErrAlreadyExists = errors.New("already exists")
)
// Runtime is a service runtime manager // Runtime is a service runtime manager
type Runtime interface { type Runtime interface {
@@ -37,15 +35,20 @@ type Runtime interface {
// Logs returns a log stream // Logs returns a log stream
type Logs interface { type Logs interface {
// Error returns error
Error() error Error() error
// Chan return chan log
Chan() chan Log Chan() chan Log
// Stop stops the log stream
Stop() error Stop() error
} }
// Log is a log message // Log is a log message
type Log struct { type Log struct {
Message string // Metadata holds metadata
Metadata metadata.Metadata Metadata metadata.Metadata
// Message holds the message
Message string
} }
// Scheduler is a runtime service scheduler // Scheduler is a runtime service scheduler
@@ -84,28 +87,28 @@ func (t EventType) String() string {
// Event is notification event // Event is notification event
type Event struct { type Event struct {
// ID of the event // Timestamp of event
ID string
// Type is event type
Type EventType
// Timestamp is event timestamp
Timestamp time.Time Timestamp time.Time
// Service the event relates to // Service the event relates to
Service *Service Service *Service
// Options to use when processing the event // Options to use when processing the event
Options *CreateOptions Options *CreateOptions
// ID of the event
ID string
// Type is event type
Type EventType
} }
// Service is runtime service // Service is runtime service
type Service struct { type Service struct {
// Metadata stores metadata
Metadata metadata.Metadata
// Name of the service // Name of the service
Name string Name string
// Version of the service // Version of the service
Version string Version string
// url location of source // Name of the service
Source string Source string
// Metadata stores metadata
Metadata metadata.Metadata
} }
// Resources which are allocated to a serivce // Resources which are allocated to a serivce

View File

@@ -5,10 +5,8 @@ import (
"errors" "errors"
) )
var ( // ErrNoneAvailable is returned by select when no routes were provided to select from
// ErrNoneAvailable is returned by select when no routes were provided to select from var ErrNoneAvailable = errors.New("none available")
ErrNoneAvailable = errors.New("none available")
)
// Selector selects a route from a pool // Selector selects a route from a pool
type Selector interface { type Selector interface {

59
server/errors.go Normal file
View File

@@ -0,0 +1,59 @@
package server
import "github.com/unistack-org/micro/v3/errors"
type Error struct {
id string
}
func NewError(id string) *Error {
return &Error{id}
}
func (e *Error) BadRequest(format string, a ...interface{}) error {
return errors.BadRequest(e.id, format, a...)
}
func (e *Error) Unauthorized(format string, a ...interface{}) error {
return errors.Unauthorized(e.id, format, a...)
}
func (e *Error) Forbidden(format string, a ...interface{}) error {
return errors.Forbidden(e.id, format, a...)
}
func (e *Error) NotFound(format string, a ...interface{}) error {
return errors.NotFound(e.id, format, a...)
}
func (e *Error) MethodNotAllowed(format string, a ...interface{}) error {
return errors.MethodNotAllowed(e.id, format, a...)
}
func (e *Error) Timeout(format string, a ...interface{}) error {
return errors.Timeout(e.id, format, a...)
}
func (e *Error) Conflict(format string, a ...interface{}) error {
return errors.Conflict(e.id, format, a...)
}
func (e *Error) InternalServerError(format string, a ...interface{}) error {
return errors.InternalServerError(e.id, format, a...)
}
func (e *Error) NotImplemented(format string, a ...interface{}) error {
return errors.NotImplemented(e.id, format, a...)
}
func (e *Error) BadGateway(format string, a ...interface{}) error {
return errors.BadGateway(e.id, format, a...)
}
func (e *Error) ServiceUnavailable(format string, a ...interface{}) error {
return errors.ServiceUnavailable(e.id, format, a...)
}
func (e *Error) GatewayTimeout(format string, a ...interface{}) error {
return errors.GatewayTimeout(e.id, format, a...)
}

19
server/errors_test.go Normal file
View File

@@ -0,0 +1,19 @@
package server
import (
"testing"
"github.com/unistack-org/micro/v3/errors"
)
func TestError(t *testing.T) {
e := NewError("svc1")
err := e.BadRequest("%s", "test")
merr, ok := err.(*errors.Error)
if !ok {
t.Fatal("error not *errors.Error")
}
if merr.Id != "svc1" {
t.Fatal("id != svc1")
}
}

3
server/generate.go Normal file
View File

@@ -0,0 +1,3 @@
package server
//go:generate protoc -I./health -I../ -I/home/vtolstov/.cache/go-path/pkg/mod/github.com/unistack-org/micro-proto@v0.0.1 --micro_out=components=micro|http|server,standalone=false,debug=true,paths=source_relative:./health health/health.proto

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