Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d846044fc6 | ||
29d956e74e | |||
fcc4faff8a | |||
5df8f83f45 | |||
|
27fa6e9173 | ||
bd55a35dc3 | |||
653bd386cc | |||
|
558c6f4d7c | ||
d7dd6fbeb2 | |||
a00cf2c8d9 | |||
|
a3e8ab2492 | ||
06da500ef4 | |||
277f04ba19 | |||
|
470263ff5f | ||
b8232e02be | |||
|
f8c5e10c1d | ||
397e71f815 | |||
74e31d99f6 | |||
f39de15d93 | |||
d291102877 | |||
37ffbb18d8 | |||
9a85dead86 | |||
a489aab1c3 | |||
d160664ef1 | |||
fa868edcaa | |||
|
6ed0b0e090 | ||
533b265d19 | |||
1ace2631a4 | |||
3dd5ca68d1 | |||
66ccd6021f | |||
e5346f7e4f | |||
|
daf19f031a | ||
5989fd54ca | |||
ed30c26324 | |||
0f8f93d09a | |||
|
f460e2f8dd | ||
70d6a79274 | |||
664b1586af | |||
8d747c64a8 | |||
94beb5ed3b | |||
98981ba86c | |||
1013f50d0e | |||
0b190997b1 | |||
69a44eb190 | |||
0476028f69 | |||
330d8b149a | |||
19b04fe070 | |||
4cd55875c6 | |||
a7896cc728 | |||
ff991bf49c | |||
5a6551b703 | |||
9406a33d60 | |||
8f185abd9d | |||
86492e0644 | |||
b21972964a | |||
f5ee065d09 | |||
8cb02f2b08 | |||
bc926cd6bd | |||
356abfd818 | |||
18444d3f98 | |||
d5f07922e8 | |||
675e717410 | |||
7b6aea235a | |||
2cb7200467 | |||
f430f97a97 | |||
0060c4377a | |||
e46579fe9a | |||
ca52973194 | |||
5bb33c7e1d | |||
b71fc25328 | |||
9345dd075a | |||
1c1b9c0a28 | |||
2969494c5a | |||
cbd3fa38ba | |||
569a36383d | |||
90bed77526 | |||
ba4478a5e0 | |||
6dc76cdfea | |||
e266683d96 | |||
275b0a64e5 |
51
.gitea/workflows/job_coverage.yml
Normal file
51
.gitea/workflows/job_coverage.yml
Normal file
@ -0,0 +1,51 @@
|
||||
name: coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, v3, v4 ]
|
||||
pull_request:
|
||||
branches: [ main, v3, v4 ]
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
filter: 'blob:none'
|
||||
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
cache-dependency-path: "**/*.sum"
|
||||
go-version: 'stable'
|
||||
|
||||
- name: test coverage
|
||||
run: |
|
||||
go test -v -cover ./... -covermode=count -coverprofile coverage.out -coverpkg ./...
|
||||
go tool cover -func coverage.out -o coverage.out
|
||||
|
||||
- name: coverage badge
|
||||
uses: tj-actions/coverage-badge-go@v2
|
||||
with:
|
||||
green: 80
|
||||
filename: coverage.out
|
||||
|
||||
- uses: stefanzweifel/git-auto-commit-action@v4
|
||||
name: autocommit
|
||||
with:
|
||||
commit_message: Apply Code Coverage Badge
|
||||
skip_fetch: true
|
||||
skip_checkout: true
|
||||
file_pattern: ./README.md
|
||||
|
||||
- name: push
|
||||
if: steps.auto-commit-action.outputs.changes_detected == 'true'
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
branch: ${{ github.ref }}
|
@ -12,15 +12,18 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup-go
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
filter: 'blob:none'
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: deps
|
||||
run: go get -v -d ./...
|
||||
- name: lint
|
||||
cache-dependency-path: "**/*.sum"
|
||||
go-version: 'stable'
|
||||
- name: setup deps
|
||||
run: go get -v ./...
|
||||
- name: run lint
|
||||
uses: https://github.com/golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: 'latest'
|
||||
|
@ -17,15 +17,18 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup-go
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
filter: 'blob:none'
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
cache-dependency-path: "**/*.sum"
|
||||
go-version: 'stable'
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: deps
|
||||
run: go get -v -d ./...
|
||||
- name: test
|
||||
- name: setup deps
|
||||
run: go get -v ./...
|
||||
- name: run test
|
||||
env:
|
||||
INTEGRATION_TESTS: yes
|
||||
run: go test -mod readonly -v ./...
|
||||
|
53
.gitea/workflows/job_tests.yml
Normal file
53
.gitea/workflows/job_tests.yml
Normal file
@ -0,0 +1,53 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
branches:
|
||||
- master
|
||||
- v3
|
||||
- v4
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v3
|
||||
- v4
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
filter: 'blob:none'
|
||||
- name: checkout tests
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
filter: 'blob:none'
|
||||
repository: unistack-org/micro-tests
|
||||
path: micro-tests
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
cache-dependency-path: "**/*.sum"
|
||||
go-version: 'stable'
|
||||
- name: setup go work
|
||||
env:
|
||||
GOWORK: /workspace/${{ github.repository_owner }}/go.work
|
||||
run: |
|
||||
go work init
|
||||
go work use .
|
||||
go work use micro-tests
|
||||
- name: setup deps
|
||||
env:
|
||||
GOWORK: /workspace/${{ github.repository_owner }}/go.work
|
||||
run: go get -v ./...
|
||||
- name: run tests
|
||||
env:
|
||||
INTEGRATION_TESTS: yes
|
||||
GOWORK: /workspace/${{ github.repository_owner }}/go.work
|
||||
run: |
|
||||
cd micro-tests
|
||||
go test -mod readonly -v ./... || true
|
28
README.md
28
README.md
@ -1,4 +1,10 @@
|
||||
# Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [![Status](https://github.com/unistack-org/micro/workflows/build/badge.svg?branch=master)](https://github.com/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Amaster+event%3Apush) [![Lint](https://goreportcard.com/badge/go.unistack.org/micro/v3)](https://goreportcard.com/report/go.unistack.org/micro/v3) [![Coverage](https://codecov.io/gh/unistack-org/micro/branch/v3/graph/badge.svg?token=OZPO2LP7VS)](https://codecov.io/gh/unistack-org/micro)
|
||||
# Micro
|
||||
![Coverage](https://img.shields.io/badge/Coverage-44.8%25-yellow)
|
||||
[![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0)
|
||||
[![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/go.unistack.org/micro/v3?tab=overview)
|
||||
[![Status](https://git.unistack.org/unistack-org/micro/actions/workflows/job_tests.yml/badge.svg?branch=v3)](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av3+event%3Apush)
|
||||
[![Lint](https://goreportcard.com/badge/go.unistack.org/micro/v3)](https://goreportcard.com/report/go.unistack.org/micro/v3)
|
||||
![Coverage](https://img.shields.io/badge/coverage-44.6%25-yellow)
|
||||
|
||||
Micro is a standard library for microservices.
|
||||
|
||||
@ -10,30 +16,20 @@ Micro provides the core requirements for distributed systems development includi
|
||||
|
||||
Micro abstracts away the details of distributed systems. Here are the main features.
|
||||
|
||||
- **Authentication** - Auth is built in as a first class citizen. Authentication and authorization enable secure
|
||||
zero trust networking by providing every service an identity and certificates. This additionally includes rule
|
||||
based access control.
|
||||
|
||||
- **Dynamic Config** - Load and hot reload dynamic config from anywhere. The config interface provides a way to load application
|
||||
level config from any source such as env vars, file, etcd. You can merge the sources and even define fallbacks.
|
||||
level config from any source such as env vars, cmdline, file, consul, vault... You can merge the sources and even define fallbacks.
|
||||
|
||||
- **Data Storage** - A simple data store interface to read, write and delete records. It includes support for memory, file and
|
||||
CockroachDB by default. State and persistence becomes a core requirement beyond prototyping and Micro looks to build that into the framework.
|
||||
s3. State and persistence becomes a core requirement beyond prototyping and Micro looks to build that into the framework.
|
||||
|
||||
- **Service Discovery** - Automatic service registration and name resolution. Service discovery is at the core of micro service
|
||||
development. When service A needs to speak to service B it needs the location of that service.
|
||||
|
||||
- **Load Balancing** - Client side load balancing built on service discovery. Once we have the addresses of any number of instances
|
||||
of a service we now need a way to decide which node to route to. We use random hashed load balancing to provide even distribution
|
||||
across the services and retry a different node if there's a problem.
|
||||
|
||||
- **Message Encoding** - Dynamic message encoding based on content-type. The client and server will use codecs along with content-type
|
||||
to seamlessly encode and decode Go types for you. Any variety of messages could be encoded and sent from different clients. The client
|
||||
and server handle this by default.
|
||||
|
||||
- **Transport** - gRPC or http based request/response with support for bidirectional streaming. We provide an abstraction for synchronous communication. A request made to a service will be automatically resolved, load balanced, dialled and streamed.
|
||||
|
||||
- **Async Messaging** - PubSub is built in as a first class citizen for asynchronous communication and event driven architectures.
|
||||
- **Async Messaging** - Pub/Sub is built in as a first class citizen for asynchronous communication and event driven architectures.
|
||||
Event notifications are a core pattern in micro service development.
|
||||
|
||||
- **Synchronization** - Distributed systems are often built in an eventually consistent manner. Support for distributed locking and
|
||||
@ -42,10 +38,6 @@ leadership are built in as a Sync interface. When using an eventually consistent
|
||||
- **Pluggable Interfaces** - Micro makes use of Go interfaces for each system abstraction. Because of this these interfaces
|
||||
are pluggable and allows Micro to be runtime agnostic.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To be created.
|
||||
|
||||
## License
|
||||
|
||||
Micro is Apache 2.0 licensed.
|
||||
|
@ -15,6 +15,15 @@ func FromContext(ctx context.Context) (Broker, bool) {
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MustContext returns broker from passed context
|
||||
func MustContext(ctx context.Context) Broker {
|
||||
b, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing broker")
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// NewContext savess broker in context
|
||||
func NewContext(ctx context.Context, s Broker) context.Context {
|
||||
if ctx == nil {
|
||||
|
@ -109,7 +109,7 @@ func (m *memoryBroker) Init(opts ...broker.Option) error {
|
||||
m.funcSubscribe = m.fnSubscribe
|
||||
m.funcBatchSubscribe = m.fnBatchSubscribe
|
||||
|
||||
m.opts.Hooks.EachNext(func(hook options.Hook) {
|
||||
m.opts.Hooks.EachPrev(func(hook options.Hook) {
|
||||
switch h := hook.(type) {
|
||||
case broker.HookPublish:
|
||||
m.funcPublish = h(m.funcPublish)
|
||||
|
@ -59,7 +59,7 @@ func (b *NoopBroker) Init(opts ...Option) error {
|
||||
b.funcSubscribe = b.fnSubscribe
|
||||
b.funcBatchSubscribe = b.fnBatchSubscribe
|
||||
|
||||
b.opts.Hooks.EachNext(func(hook options.Hook) {
|
||||
b.opts.Hooks.EachPrev(func(hook options.Hook) {
|
||||
switch h := hook.(type) {
|
||||
case HookPublish:
|
||||
b.funcPublish = h(b.funcPublish)
|
||||
|
@ -15,6 +15,15 @@ func FromContext(ctx context.Context) (Client, bool) {
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MustContext get client from context
|
||||
func MustContext(ctx context.Context) Client {
|
||||
c, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing client")
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// NewContext put client in context
|
||||
func NewContext(ctx context.Context, c Client) context.Context {
|
||||
if ctx == nil {
|
||||
|
@ -194,7 +194,7 @@ func (n *noopClient) Init(opts ...Option) error {
|
||||
n.funcPublish = n.fnPublish
|
||||
n.funcBatchPublish = n.fnBatchPublish
|
||||
|
||||
n.opts.Hooks.EachNext(func(hook options.Hook) {
|
||||
n.opts.Hooks.EachPrev(func(hook options.Hook) {
|
||||
switch h := hook.(type) {
|
||||
case HookCall:
|
||||
n.funcCall = h(n.funcCall)
|
||||
|
@ -3,6 +3,8 @@ package codec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -54,3 +56,22 @@ func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||
*m = append((*m)[0:0], data...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML returns m as the JSON encoding of m.
|
||||
func (m *RawMessage) MarshalYAML() ([]byte, error) {
|
||||
if m == nil {
|
||||
return []byte("null"), nil
|
||||
} else if len(*m) == 0 {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return *m, nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML sets *m to a copy of data.
|
||||
func (m *RawMessage) UnmarshalYAML(n *yaml.Node) error {
|
||||
if m == nil {
|
||||
return errors.New("RawMessage UnmarshalYAML on nil pointer")
|
||||
}
|
||||
*m = append((*m)[0:0], []byte(n.Value)...)
|
||||
return nil
|
||||
}
|
||||
|
@ -15,6 +15,15 @@ func FromContext(ctx context.Context) (Codec, bool) {
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MustContext returns codec from context
|
||||
func MustContext(ctx context.Context) Codec {
|
||||
c, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing codec")
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// NewContext put codec in context
|
||||
func NewContext(ctx context.Context, c Codec) context.Context {
|
||||
if ctx == nil {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package codec
|
||||
|
||||
import "gopkg.in/yaml.v3"
|
||||
|
||||
// Frame gives us the ability to define raw data to send over the pipes
|
||||
type Frame struct {
|
||||
Data []byte
|
||||
@ -20,6 +22,17 @@ func (m *Frame) UnmarshalJSON(data []byte) error {
|
||||
return m.Unmarshal(data)
|
||||
}
|
||||
|
||||
// MarshalYAML returns frame data
|
||||
func (m *Frame) MarshalYAML() ([]byte, error) {
|
||||
return m.Marshal()
|
||||
}
|
||||
|
||||
// UnmarshalYAML set frame data
|
||||
func (m *Frame) UnmarshalYAML(n *yaml.Node) error {
|
||||
m.Data = []byte(n.Value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProtoMessage noop func
|
||||
func (m *Frame) ProtoMessage() {}
|
||||
|
||||
|
@ -15,6 +15,15 @@ func FromContext(ctx context.Context) (Config, bool) {
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MustContext returns store from context
|
||||
func MustContext(ctx context.Context) Config {
|
||||
c, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing config")
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// NewContext put store in context
|
||||
func NewContext(ctx context.Context, c Config) context.Context {
|
||||
if ctx == nil {
|
||||
|
@ -37,7 +37,7 @@ func (c *defaultConfig) Init(opts ...Option) error {
|
||||
c.funcLoad = c.fnLoad
|
||||
c.funcSave = c.fnSave
|
||||
|
||||
c.opts.Hooks.EachNext(func(hook options.Hook) {
|
||||
c.opts.Hooks.EachPrev(func(hook options.Hook) {
|
||||
switch h := hook.(type) {
|
||||
case HookLoad:
|
||||
c.funcLoad = h(c.funcLoad)
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v3/config"
|
||||
mid "go.unistack.org/micro/v3/util/id"
|
||||
mtime "go.unistack.org/micro/v3/util/time"
|
||||
)
|
||||
|
||||
@ -115,8 +114,6 @@ func TestDefault(t *testing.T) {
|
||||
|
||||
if conf.IDValue == "" {
|
||||
t.Fatalf("id value empty")
|
||||
} else if len(conf.IDValue) != mid.DefaultSize {
|
||||
t.Fatalf("id value invalid: %s", conf.IDValue)
|
||||
}
|
||||
_ = conf
|
||||
// t.Logf("%#+v\n", conf)
|
||||
|
@ -15,6 +15,15 @@ func FromContext(ctx context.Context) (Flow, bool) {
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MustContext returns Flow from context
|
||||
func MustContext(ctx context.Context) Flow {
|
||||
f, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing flow")
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// NewContext stores Flow to context
|
||||
func NewContext(ctx context.Context, f Flow) context.Context {
|
||||
if ctx == nil {
|
||||
|
46
go.mod
46
go.mod
@ -1,33 +1,43 @@
|
||||
module go.unistack.org/micro/v3
|
||||
|
||||
go 1.22
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
dario.cat/mergo v1.0.1
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/KimMachineGun/automemlimit v0.6.1
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/ash3in/uuidv8 v1.2.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/matoous/go-nanoid v1.5.1
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go.unistack.org/micro-proto/v3 v3.4.1
|
||||
golang.org/x/sync v0.3.0
|
||||
google.golang.org/grpc v1.57.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
golang.org/x/sync v0.10.0
|
||||
google.golang.org/grpc v1.69.2
|
||||
google.golang.org/protobuf v1.36.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cilium/ebpf v0.9.1 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.0.4 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.0.2 // indirect
|
||||
github.com/cilium/ebpf v0.16.0 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.4 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.2.0 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
go.uber.org/goleak v1.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
139
go.sum
139
go.sum
@ -1,80 +1,103 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/KimMachineGun/automemlimit v0.6.1 h1:ILa9j1onAAMadBsyyUJv5cack8Y1WT26yLj/V+ulKp8=
|
||||
github.com/KimMachineGun/automemlimit v0.6.1/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY=
|
||||
github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4=
|
||||
github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY=
|
||||
github.com/containerd/cgroups/v3 v3.0.1 h1:4hfGvu8rfGIwVIDd+nLzn/B9ZXx4BcCjzt5ToenJRaE=
|
||||
github.com/containerd/cgroups/v3 v3.0.1/go.mod h1:/vtwk1VXrtoa5AaZLkypuOJgA/6DyPMZHJPGQNtlHnw=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/ash3in/uuidv8 v1.2.0 h1:2oogGdtCPwaVtyvPPGin4TfZLtOGE5F+W++E880G6SI=
|
||||
github.com/ash3in/uuidv8 v1.2.0/go.mod h1:BnU0wJBxnzdEKmVg4xckBkD+VZuecTFTUP3M0dWgyY4=
|
||||
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
|
||||
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
|
||||
github.com/containerd/cgroups/v3 v3.0.4 h1:2fs7l3P0Qxb1nKWuJNFiwhp2CqiKzho71DQkDrHJIo4=
|
||||
github.com/containerd/cgroups/v3 v3.0.4/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
|
||||
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
|
||||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
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/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/matoous/go-nanoid v1.5.1 h1:aCjdvTyO9LLnTIi0fgdXhOPPvOHjpXN6Ik9DaNjIct4=
|
||||
github.com/matoous/go-nanoid v1.5.1/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
|
||||
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
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/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 h1:G/FZtUu7a6NTWl3KUHMV9jkLAh/Rvtf03NWMHaEDl+E=
|
||||
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.unistack.org/micro-proto/v3 v3.4.1 h1:UTjLSRz2YZuaHk9iSlVqqsA50JQNAEK2ZFboGqtEa9Q=
|
||||
go.unistack.org/micro-proto/v3 v3.4.1/go.mod h1:okx/cnOhzuCX0ggl/vToatbCupi0O44diiiLLsZ93Zo=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
||||
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
||||
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -4,17 +4,6 @@ import "context"
|
||||
|
||||
type loggerKey struct{}
|
||||
|
||||
// MustContext returns logger from passed context or DefaultLogger if empty
|
||||
func MustContext(ctx context.Context) Logger {
|
||||
if ctx == nil {
|
||||
return DefaultLogger.Clone()
|
||||
}
|
||||
if l, ok := ctx.Value(loggerKey{}).(Logger); ok && l != nil {
|
||||
return l
|
||||
}
|
||||
return DefaultLogger.Clone()
|
||||
}
|
||||
|
||||
// FromContext returns logger from passed context
|
||||
func FromContext(ctx context.Context) (Logger, bool) {
|
||||
if ctx == nil {
|
||||
@ -24,6 +13,15 @@ func FromContext(ctx context.Context) (Logger, bool) {
|
||||
return l, ok
|
||||
}
|
||||
|
||||
// MustContext returns logger from passed context or DefaultLogger if empty
|
||||
func MustContext(ctx context.Context) Logger {
|
||||
l, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing logger")
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// NewContext stores logger into passed context
|
||||
func NewContext(ctx context.Context, l Logger) context.Context {
|
||||
if ctx == nil {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v3/meter"
|
||||
@ -29,7 +30,6 @@ type Options struct {
|
||||
StacktraceKey string
|
||||
// Name holds the logger name
|
||||
Name string
|
||||
|
||||
// Out holds the output writer
|
||||
Out io.Writer
|
||||
// Context holds exernal options
|
||||
@ -38,12 +38,10 @@ type Options struct {
|
||||
Meter meter.Meter
|
||||
// TimeFunc used to obtain current time
|
||||
TimeFunc func() time.Time
|
||||
|
||||
// Fields holds additional metadata
|
||||
Fields []interface{}
|
||||
// ContextAttrFuncs contains funcs that executed before log func on context
|
||||
ContextAttrFuncs []ContextAttrFunc
|
||||
|
||||
// callerSkipCount number of frmaes to skip
|
||||
CallerSkipCount int
|
||||
// The logging level the logger should log
|
||||
@ -52,6 +50,8 @@ type Options struct {
|
||||
AddSource bool
|
||||
// AddStacktrace controls writing of stacktaces on error
|
||||
AddStacktrace bool
|
||||
// DedupKeys deduplicate keys in log output
|
||||
DedupKeys bool
|
||||
}
|
||||
|
||||
// NewOptions creates new options struct
|
||||
@ -83,10 +83,32 @@ func WithContextAttrFuncs(fncs ...ContextAttrFunc) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithDedupKeys dont log duplicate keys
|
||||
func WithDedupKeys(b bool) Option {
|
||||
return func(o *Options) {
|
||||
o.DedupKeys = b
|
||||
}
|
||||
}
|
||||
|
||||
// WithAddFields add fields for the logger
|
||||
func WithAddFields(fields ...interface{}) Option {
|
||||
return func(o *Options) {
|
||||
o.Fields = append(o.Fields, fields...)
|
||||
if o.DedupKeys {
|
||||
for i := 0; i < len(o.Fields); i += 2 {
|
||||
for j := 0; j < len(fields); j += 2 {
|
||||
iv, iok := o.Fields[i].(string)
|
||||
jv, jok := fields[j].(string)
|
||||
if iok && jok && iv == jv {
|
||||
fields = slices.Delete(fields, j, j+2)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(fields) > 0 {
|
||||
o.Fields = append(o.Fields, fields...)
|
||||
}
|
||||
} else {
|
||||
o.Fields = append(o.Fields, fields...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"reflect"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v3/logger"
|
||||
"go.unistack.org/micro/v3/semconv"
|
||||
@ -20,6 +22,7 @@ const (
|
||||
badKey = "!BADKEY"
|
||||
// defaultCallerSkipCount used by logger
|
||||
defaultCallerSkipCount = 3
|
||||
timeFormat = "2006-01-02T15:04:05.000000000Z07:00"
|
||||
)
|
||||
|
||||
var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`)
|
||||
@ -62,6 +65,7 @@ func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr {
|
||||
a.Key = s.opts.SourceKey
|
||||
case slog.TimeKey:
|
||||
a.Key = s.opts.TimeKey
|
||||
a.Value = slog.StringValue(a.Value.Time().Format(timeFormat))
|
||||
case slog.MessageKey:
|
||||
a.Key = s.opts.MessageKey
|
||||
case slog.LevelKey:
|
||||
@ -142,6 +146,7 @@ func (s *slogLogger) Fields(fields ...interface{}) logger.Logger {
|
||||
s.mu.RUnlock()
|
||||
|
||||
l := &slogLogger{opts: options}
|
||||
logger.WithAddFields(fields...)(&l.opts)
|
||||
|
||||
if len(options.ContextAttrFuncs) == 0 {
|
||||
options.ContextAttrFuncs = logger.DefaultContextAttrFuncs
|
||||
@ -223,6 +228,10 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}
|
||||
|
||||
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
|
||||
s.printLog(ctx, logger.FatalLevel, msg, attrs...)
|
||||
if closer, ok := s.opts.Out.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -269,7 +278,7 @@ func (s *slogLogger) printLog(ctx context.Context, lvl logger.Level, msg string,
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
|
||||
if (s.opts.AddStacktrace || lvl == logger.FatalLevel) || (s.opts.AddStacktrace && lvl == logger.ErrorLevel) {
|
||||
stackInfo := make([]byte, 1024*1024)
|
||||
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
|
||||
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
|
||||
|
@ -9,12 +9,126 @@ import (
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.unistack.org/micro/v3/logger"
|
||||
"go.unistack.org/micro/v3/metadata"
|
||||
"go.unistack.org/micro/v3/util/buffer"
|
||||
)
|
||||
|
||||
// always first to have proper check
|
||||
func TestStacktrace(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf),
|
||||
WithHandlerFunc(slog.NewTextHandler),
|
||||
logger.WithAddStacktrace(true),
|
||||
)
|
||||
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l.Error(ctx, "msg1", errors.New("err"))
|
||||
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`slog_test.go:32`)) {
|
||||
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelayedBuffer(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
dbuf := buffer.NewDelayedBuffer(100, 100*time.Millisecond, buf)
|
||||
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(dbuf),
|
||||
WithHandlerFunc(slog.NewTextHandler),
|
||||
logger.WithAddStacktrace(true),
|
||||
)
|
||||
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l.Error(ctx, "msg1", errors.New("err"))
|
||||
time.Sleep(120 * time.Millisecond)
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`key1=val1`)) {
|
||||
t.Fatalf("logger delayed buffer not works, buf contains: %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf),
|
||||
WithHandlerFunc(slog.NewTextHandler),
|
||||
logger.WithAddStacktrace(true),
|
||||
logger.WithTimeFunc(func() time.Time {
|
||||
return time.Unix(0, 0)
|
||||
}),
|
||||
)
|
||||
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l.Error(ctx, "msg1", errors.New("err"))
|
||||
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`timestamp=1970-01-01T03:00:00.000000000+03:00`)) &&
|
||||
!bytes.Contains(buf.Bytes(), []byte(`timestamp=1970-01-01T00:00:00.000000000Z`)) {
|
||||
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithFields(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf),
|
||||
WithHandlerFunc(slog.NewTextHandler),
|
||||
logger.WithDedupKeys(true),
|
||||
)
|
||||
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l.Info(ctx, "msg1")
|
||||
|
||||
l = l.Fields("key1", "val2")
|
||||
|
||||
l.Info(ctx, "msg2")
|
||||
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`msg=msg2 key1=val1`)) {
|
||||
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithDedupKeysWithAddFields(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf),
|
||||
WithHandlerFunc(slog.NewTextHandler),
|
||||
logger.WithDedupKeys(true),
|
||||
)
|
||||
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l.Info(ctx, "msg1")
|
||||
|
||||
if err := l.Init(logger.WithAddFields("key2", "val2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l.Info(ctx, "msg2")
|
||||
|
||||
if err := l.Init(logger.WithAddFields("key2", "val3", "key1", "val4")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l.Info(ctx, "msg3")
|
||||
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`msg=msg3 key1=val1 key2=val2`)) {
|
||||
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithHandlerFunc(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
@ -24,6 +24,17 @@ func FromIncomingContext(ctx context.Context) (Metadata, bool) {
|
||||
return md.md, ok
|
||||
}
|
||||
|
||||
// MustIncomingContext returns metadata from incoming ctx
|
||||
// returned metadata shoud not be modified or race condition happens.
|
||||
// If metadata not exists panics.
|
||||
func MustIncomingContext(ctx context.Context) Metadata {
|
||||
md, ok := FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// FromOutgoingContext returns metadata from outgoing ctx
|
||||
// returned metadata shoud not be modified or race condition happens
|
||||
func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
|
||||
@ -37,6 +48,17 @@ func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
|
||||
return md.md, ok
|
||||
}
|
||||
|
||||
// MustOutgoingContext returns metadata from outgoing ctx
|
||||
// returned metadata shoud not be modified or race condition happens.
|
||||
// If metadata not exists panics.
|
||||
func MustOutgoingContext(ctx context.Context) Metadata {
|
||||
md, ok := FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// FromContext returns metadata from the given context
|
||||
// returned metadata shoud not be modified or race condition happens
|
||||
func FromContext(ctx context.Context) (Metadata, bool) {
|
||||
@ -50,6 +72,16 @@ func FromContext(ctx context.Context) (Metadata, bool) {
|
||||
return md.md, ok
|
||||
}
|
||||
|
||||
// MustContext returns metadata from the given context
|
||||
// returned metadata shoud not be modified or race condition happens
|
||||
func MustContext(ctx context.Context) Metadata {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// NewContext creates a new context with the given metadata
|
||||
func NewContext(ctx context.Context, md Metadata) context.Context {
|
||||
if ctx == nil {
|
||||
@ -111,7 +143,7 @@ func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
|
||||
for k, v := range md {
|
||||
omd.Set(k, v)
|
||||
}
|
||||
return NewOutgoingContext(ctx, omd)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// AppendIncomingContext apends new md to context
|
||||
@ -127,5 +159,21 @@ func AppendIncomingContext(ctx context.Context, kv ...string) context.Context {
|
||||
for k, v := range md {
|
||||
omd.Set(k, v)
|
||||
}
|
||||
return NewIncomingContext(ctx, omd)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// AppendContext apends new md to context
|
||||
func AppendContext(ctx context.Context, kv ...string) context.Context {
|
||||
md, ok := Pairs(kv...)
|
||||
if !ok {
|
||||
return ctx
|
||||
}
|
||||
omd, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
return NewContext(ctx, md)
|
||||
}
|
||||
for k, v := range md {
|
||||
omd.Set(k, v)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
@ -67,6 +67,14 @@ func (md Metadata) Iterator() *Iterator {
|
||||
return iter
|
||||
}
|
||||
|
||||
func (md Metadata) MustGet(key string) string {
|
||||
val, ok := md.Get(key)
|
||||
if !ok {
|
||||
panic("missing metadata key")
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Get returns value from metadata by key
|
||||
func (md Metadata) Get(key string) (string, bool) {
|
||||
// fast path
|
||||
@ -103,11 +111,18 @@ func (md Metadata) Del(keys ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy makes a copy of the metadata
|
||||
func (md Metadata) CopyTo(dst Metadata) {
|
||||
for k, v := range md {
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Copy makes a copy of the metadata
|
||||
func Copy(md Metadata, exclude ...string) Metadata {
|
||||
nmd := New(len(md))
|
||||
for key, val := range md {
|
||||
nmd.Set(key, val)
|
||||
for k, v := range md {
|
||||
nmd[k] = v
|
||||
}
|
||||
nmd.Del(exclude...)
|
||||
return nmd
|
||||
@ -131,7 +146,7 @@ func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata {
|
||||
case ok && !overwrite:
|
||||
continue
|
||||
case val != "":
|
||||
nmd.Set(key, val)
|
||||
nmd[key] = val
|
||||
case ok && val == "":
|
||||
nmd.Del(key)
|
||||
}
|
||||
@ -145,6 +160,8 @@ func Pairs(kv ...string) (Metadata, bool) {
|
||||
return nil, false
|
||||
}
|
||||
md := New(len(kv) / 2)
|
||||
md.Set(kv...)
|
||||
for idx := 0; idx < len(kv); idx += 2 {
|
||||
md[kv[idx]] = kv[idx+1]
|
||||
}
|
||||
return md, true
|
||||
}
|
||||
|
@ -32,8 +32,8 @@ func TestMultipleUsage(t *testing.T) {
|
||||
m.Del("key1_3")
|
||||
return ctx
|
||||
}(ctx)
|
||||
t.Logf("imd %#+v", imd)
|
||||
t.Logf("omd %#+v", omd)
|
||||
_ = imd
|
||||
_ = omd
|
||||
}
|
||||
|
||||
func TestMetadataSetMultiple(t *testing.T) {
|
||||
|
@ -15,6 +15,15 @@ func FromContext(ctx context.Context) (Meter, bool) {
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MustContext get meter from context
|
||||
func MustContext(ctx context.Context) Meter {
|
||||
m, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing meter")
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NewContext put meter in context
|
||||
func NewContext(ctx context.Context, c Meter) context.Context {
|
||||
if ctx == nil {
|
||||
|
@ -119,7 +119,7 @@ func Tracer(t tracer.Tracer) Option {
|
||||
// NewOptions returns network default options
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
ID: id.Must(),
|
||||
ID: id.MustNew(),
|
||||
Name: "go.micro",
|
||||
Address: ":0",
|
||||
Logger: logger.DefaultLogger,
|
||||
|
@ -164,7 +164,7 @@ func DialWait(b bool) DialOption {
|
||||
// NewOptions returns router default options with filled values
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
ID: id.Must(),
|
||||
ID: id.MustNew(),
|
||||
Address: DefaultAddress,
|
||||
Token: DefaultToken,
|
||||
Logger: logger.DefaultLogger,
|
||||
|
@ -15,6 +15,15 @@ func FromContext(ctx context.Context) (Register, bool) {
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MustContext get register from context
|
||||
func MustContext(ctx context.Context) Register {
|
||||
r, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing register")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// NewContext put register in context
|
||||
func NewContext(ctx context.Context, c Register) context.Context {
|
||||
if ctx == nil {
|
||||
|
@ -1,12 +1,9 @@
|
||||
package register
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"go.unistack.org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
// ExtractValue from reflect.Type from specified depth
|
||||
@ -38,53 +35,6 @@ func ExtractValue(v reflect.Type, d int) string {
|
||||
return v.Name()
|
||||
}
|
||||
|
||||
// ExtractEndpoint extract *Endpoint from reflect.Method
|
||||
func ExtractEndpoint(method reflect.Method) *Endpoint {
|
||||
if method.PkgPath != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var rspType, reqType reflect.Type
|
||||
var stream bool
|
||||
mt := method.Type
|
||||
|
||||
switch mt.NumIn() {
|
||||
case 3:
|
||||
reqType = mt.In(1)
|
||||
rspType = mt.In(2)
|
||||
case 4:
|
||||
reqType = mt.In(2)
|
||||
rspType = mt.In(3)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
// are we dealing with a stream?
|
||||
switch rspType.Kind() {
|
||||
case reflect.Func, reflect.Interface:
|
||||
stream = true
|
||||
}
|
||||
|
||||
request := ExtractValue(reqType, 0)
|
||||
response := ExtractValue(rspType, 0)
|
||||
if request == "" || response == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ep := &Endpoint{
|
||||
Name: method.Name,
|
||||
Request: request,
|
||||
Response: response,
|
||||
Metadata: metadata.New(0),
|
||||
}
|
||||
|
||||
if stream {
|
||||
ep.Metadata.Set("stream", fmt.Sprintf("%v", stream))
|
||||
}
|
||||
|
||||
return ep
|
||||
}
|
||||
|
||||
// ExtractSubValue exctact *Value from reflect.Type
|
||||
func ExtractSubValue(typ reflect.Type) string {
|
||||
var reqType reflect.Type
|
||||
|
@ -2,8 +2,6 @@ package register
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestHandler struct{}
|
||||
@ -15,40 +13,3 @@ type TestResponse struct{}
|
||||
func (t *TestHandler) Test(ctx context.Context, req *TestRequest, rsp *TestResponse) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestExtractEndpoint(t *testing.T) {
|
||||
handler := &TestHandler{}
|
||||
typ := reflect.TypeOf(handler)
|
||||
|
||||
var endpoints []*Endpoint
|
||||
|
||||
for m := 0; m < typ.NumMethod(); m++ {
|
||||
if e := ExtractEndpoint(typ.Method(m)); e != nil {
|
||||
endpoints = append(endpoints, e)
|
||||
}
|
||||
}
|
||||
|
||||
if i := len(endpoints); i != 1 {
|
||||
t.Fatalf("Expected 1 endpoint, have %d", i)
|
||||
}
|
||||
|
||||
if endpoints[0].Name != "Test" {
|
||||
t.Fatalf("Expected handler Test, got %s", endpoints[0].Name)
|
||||
}
|
||||
|
||||
if endpoints[0].Request == "" {
|
||||
t.Fatal("Expected non nil Request")
|
||||
}
|
||||
|
||||
if endpoints[0].Response == "" {
|
||||
t.Fatal("Expected non nil Request")
|
||||
}
|
||||
|
||||
if endpoints[0].Request != "TestRequest" {
|
||||
t.Fatalf("Expected TestRequest got %s", endpoints[0].Request)
|
||||
}
|
||||
|
||||
if endpoints[0].Response != "TestResponse" {
|
||||
t.Fatalf("Expected TestResponse got %s", endpoints[0].Response)
|
||||
}
|
||||
}
|
||||
|
@ -23,11 +23,10 @@ type node struct {
|
||||
}
|
||||
|
||||
type record struct {
|
||||
Name string
|
||||
Version string
|
||||
Metadata map[string]string
|
||||
Nodes map[string]*node
|
||||
Endpoints []*register.Endpoint
|
||||
Name string
|
||||
Version string
|
||||
Metadata map[string]string
|
||||
Nodes map[string]*node
|
||||
}
|
||||
|
||||
type memory struct {
|
||||
@ -59,7 +58,7 @@ func (m *memory) ttlPrune() {
|
||||
|
||||
for range prune.C {
|
||||
m.Lock()
|
||||
for domain, services := range m.records {
|
||||
for namespace, services := range m.records {
|
||||
for service, versions := range services {
|
||||
for version, record := range versions {
|
||||
for id, n := range record.Nodes {
|
||||
@ -67,7 +66,7 @@ func (m *memory) ttlPrune() {
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register TTL expired for node %s of service %s", n.ID, service))
|
||||
}
|
||||
delete(m.records[domain][service][version].Nodes, id)
|
||||
delete(m.records[namespace][service][version].Nodes, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -131,17 +130,12 @@ func (m *memory) Register(_ context.Context, s *register.Service, opts ...regist
|
||||
options := register.NewRegisterOptions(opts...)
|
||||
|
||||
// get the services for this domain from the register
|
||||
srvs, ok := m.records[options.Domain]
|
||||
srvs, ok := m.records[options.Namespace]
|
||||
if !ok {
|
||||
srvs = make(services)
|
||||
}
|
||||
|
||||
// domain is set in metadata so it can be passed to watchers
|
||||
if s.Metadata == nil {
|
||||
s.Metadata = map[string]string{"domain": options.Domain}
|
||||
} else {
|
||||
s.Metadata["domain"] = options.Domain
|
||||
}
|
||||
s.Namespace = options.Namespace
|
||||
|
||||
// ensure the service name exists
|
||||
r := serviceToRecord(s, options.TTL)
|
||||
@ -154,8 +148,8 @@ func (m *memory) Register(_ context.Context, s *register.Service, opts ...regist
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register added new service: %s, version: %s", s.Name, s.Version))
|
||||
}
|
||||
m.records[options.Domain] = srvs
|
||||
go m.sendEvent(®ister.Result{Action: "create", Service: s})
|
||||
m.records[options.Namespace] = srvs
|
||||
go m.sendEvent(®ister.Result{Action: register.EventCreate, Service: s})
|
||||
}
|
||||
|
||||
var addedNodes bool
|
||||
@ -173,9 +167,6 @@ func (m *memory) Register(_ context.Context, s *register.Service, opts ...regist
|
||||
metadata[k] = v
|
||||
}
|
||||
|
||||
// set the domain
|
||||
metadata["domain"] = options.Domain
|
||||
|
||||
// add the node
|
||||
srvs[s.Name][s.Version].Nodes[n.ID] = &node{
|
||||
Node: ®ister.Node{
|
||||
@ -194,7 +185,7 @@ func (m *memory) Register(_ context.Context, s *register.Service, opts ...regist
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register added new node to service: %s, version: %s", s.Name, s.Version))
|
||||
}
|
||||
go m.sendEvent(®ister.Result{Action: "update", Service: s})
|
||||
go m.sendEvent(®ister.Result{Action: register.EventUpdate, Service: s})
|
||||
} else {
|
||||
// refresh TTL and timestamp
|
||||
for _, n := range s.Nodes {
|
||||
@ -206,7 +197,7 @@ func (m *memory) Register(_ context.Context, s *register.Service, opts ...regist
|
||||
}
|
||||
}
|
||||
|
||||
m.records[options.Domain] = srvs
|
||||
m.records[options.Namespace] = srvs
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -216,15 +207,8 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
|
||||
|
||||
options := register.NewDeregisterOptions(opts...)
|
||||
|
||||
// domain is set in metadata so it can be passed to watchers
|
||||
if s.Metadata == nil {
|
||||
s.Metadata = map[string]string{"domain": options.Domain}
|
||||
} else {
|
||||
s.Metadata["domain"] = options.Domain
|
||||
}
|
||||
|
||||
// if the domain doesn't exist, there is nothing to deregister
|
||||
services, ok := m.records[options.Domain]
|
||||
services, ok := m.records[options.Namespace]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@ -253,16 +237,16 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
|
||||
// if the nodes not empty, we replace the version in the store and exist, the rest of the logic
|
||||
// is cleanup
|
||||
if len(version.Nodes) > 0 {
|
||||
m.records[options.Domain][s.Name][s.Version] = version
|
||||
go m.sendEvent(®ister.Result{Action: "update", Service: s})
|
||||
m.records[options.Namespace][s.Name][s.Version] = version
|
||||
go m.sendEvent(®ister.Result{Action: register.EventUpdate, Service: s})
|
||||
return nil
|
||||
}
|
||||
|
||||
// if this version was the only version of the service, we can remove the whole service from the
|
||||
// register and exit
|
||||
if len(versions) == 1 {
|
||||
delete(m.records[options.Domain], s.Name)
|
||||
go m.sendEvent(®ister.Result{Action: "delete", Service: s})
|
||||
delete(m.records[options.Namespace], s.Name)
|
||||
go m.sendEvent(®ister.Result{Action: register.EventDelete, Service: s})
|
||||
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed service: %s", s.Name))
|
||||
@ -271,8 +255,8 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
|
||||
}
|
||||
|
||||
// there are other versions of the service running, so only remove this version of it
|
||||
delete(m.records[options.Domain][s.Name], s.Version)
|
||||
go m.sendEvent(®ister.Result{Action: "delete", Service: s})
|
||||
delete(m.records[options.Namespace][s.Name], s.Version)
|
||||
go m.sendEvent(®ister.Result{Action: register.EventDelete, Service: s})
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed service: %s, version: %s", s.Name, s.Version))
|
||||
}
|
||||
@ -284,15 +268,15 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...registe
|
||||
options := register.NewLookupOptions(opts...)
|
||||
|
||||
// if it's a wildcard domain, return from all domains
|
||||
if options.Domain == register.WildcardDomain {
|
||||
if options.Namespace == register.WildcardNamespace {
|
||||
m.RLock()
|
||||
recs := m.records
|
||||
m.RUnlock()
|
||||
|
||||
var services []*register.Service
|
||||
|
||||
for domain := range recs {
|
||||
srvs, err := m.LookupService(ctx, name, append(opts, register.LookupDomain(domain))...)
|
||||
for namespace := range recs {
|
||||
srvs, err := m.LookupService(ctx, name, append(opts, register.LookupNamespace(namespace))...)
|
||||
if err == register.ErrNotFound {
|
||||
continue
|
||||
} else if err != nil {
|
||||
@ -311,7 +295,7 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...registe
|
||||
defer m.RUnlock()
|
||||
|
||||
// check the domain exists
|
||||
services, ok := m.records[options.Domain]
|
||||
services, ok := m.records[options.Namespace]
|
||||
if !ok {
|
||||
return nil, register.ErrNotFound
|
||||
}
|
||||
@ -328,7 +312,7 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...registe
|
||||
var i int
|
||||
|
||||
for _, r := range versions {
|
||||
result[i] = recordToService(r, options.Domain)
|
||||
result[i] = recordToService(r, options.Namespace)
|
||||
i++
|
||||
}
|
||||
|
||||
@ -339,15 +323,15 @@ func (m *memory) ListServices(ctx context.Context, opts ...register.ListOption)
|
||||
options := register.NewListOptions(opts...)
|
||||
|
||||
// if it's a wildcard domain, list from all domains
|
||||
if options.Domain == register.WildcardDomain {
|
||||
if options.Namespace == register.WildcardNamespace {
|
||||
m.RLock()
|
||||
recs := m.records
|
||||
m.RUnlock()
|
||||
|
||||
var services []*register.Service
|
||||
|
||||
for domain := range recs {
|
||||
srvs, err := m.ListServices(ctx, append(opts, register.ListDomain(domain))...)
|
||||
for namespace := range recs {
|
||||
srvs, err := m.ListServices(ctx, append(opts, register.ListNamespace(namespace))...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -361,7 +345,7 @@ func (m *memory) ListServices(ctx context.Context, opts ...register.ListOption)
|
||||
defer m.RUnlock()
|
||||
|
||||
// ensure the domain exists
|
||||
services, ok := m.records[options.Domain]
|
||||
services, ok := m.records[options.Namespace]
|
||||
if !ok {
|
||||
return make([]*register.Service, 0), nil
|
||||
}
|
||||
@ -371,7 +355,7 @@ func (m *memory) ListServices(ctx context.Context, opts ...register.ListOption)
|
||||
|
||||
for _, service := range services {
|
||||
for _, version := range service {
|
||||
result = append(result, recordToService(version, options.Domain))
|
||||
result = append(result, recordToService(version, options.Namespace))
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,16 +410,13 @@ func (m *watcher) Next() (*register.Result, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// extract domain from service metadata
|
||||
var domain string
|
||||
if r.Service.Metadata != nil && len(r.Service.Metadata["domain"]) > 0 {
|
||||
domain = r.Service.Metadata["domain"]
|
||||
} else {
|
||||
domain = register.DefaultDomain
|
||||
namespace := register.DefaultNamespace
|
||||
if r.Service.Namespace != "" {
|
||||
namespace = r.Service.Namespace
|
||||
}
|
||||
|
||||
// only send the event if watching the wildcard or this specific domain
|
||||
if m.wo.Domain == register.WildcardDomain || m.wo.Domain == domain {
|
||||
if m.wo.Namespace == register.WildcardNamespace || m.wo.Namespace == namespace {
|
||||
return r, nil
|
||||
}
|
||||
case <-m.exit:
|
||||
@ -454,11 +435,6 @@ func (m *watcher) Stop() {
|
||||
}
|
||||
|
||||
func serviceToRecord(s *register.Service, ttl time.Duration) *record {
|
||||
metadata := make(map[string]string, len(s.Metadata))
|
||||
for k, v := range s.Metadata {
|
||||
metadata[k] = v
|
||||
}
|
||||
|
||||
nodes := make(map[string]*node, len(s.Nodes))
|
||||
for _, n := range s.Nodes {
|
||||
nodes[n.ID] = &node{
|
||||
@ -468,42 +444,19 @@ func serviceToRecord(s *register.Service, ttl time.Duration) *record {
|
||||
}
|
||||
}
|
||||
|
||||
endpoints := make([]*register.Endpoint, len(s.Endpoints))
|
||||
copy(endpoints, s.Endpoints)
|
||||
|
||||
return &record{
|
||||
Name: s.Name,
|
||||
Version: s.Version,
|
||||
Metadata: metadata,
|
||||
Nodes: nodes,
|
||||
Endpoints: endpoints,
|
||||
Name: s.Name,
|
||||
Version: s.Version,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
func recordToService(r *record, domain string) *register.Service {
|
||||
func recordToService(r *record, namespace string) *register.Service {
|
||||
metadata := make(map[string]string, len(r.Metadata))
|
||||
for k, v := range r.Metadata {
|
||||
metadata[k] = v
|
||||
}
|
||||
|
||||
// set the domain in metadata so it can be determined when a wildcard query is performed
|
||||
metadata["domain"] = domain
|
||||
|
||||
endpoints := make([]*register.Endpoint, len(r.Endpoints))
|
||||
for i, e := range r.Endpoints {
|
||||
md := make(map[string]string, len(e.Metadata))
|
||||
for k, v := range e.Metadata {
|
||||
md[k] = v
|
||||
}
|
||||
|
||||
endpoints[i] = ®ister.Endpoint{
|
||||
Name: e.Name,
|
||||
Request: e.Request,
|
||||
Response: e.Response,
|
||||
Metadata: md,
|
||||
}
|
||||
}
|
||||
|
||||
nodes := make([]*register.Node, len(r.Nodes))
|
||||
i := 0
|
||||
for _, n := range r.Nodes {
|
||||
@ -523,8 +476,7 @@ func recordToService(r *record, domain string) *register.Service {
|
||||
return ®ister.Service{
|
||||
Name: r.Name,
|
||||
Version: r.Version,
|
||||
Metadata: metadata,
|
||||
Endpoints: endpoints,
|
||||
Nodes: nodes,
|
||||
Namespace: namespace,
|
||||
}
|
||||
}
|
||||
|
@ -253,32 +253,32 @@ func TestMemoryWildcard(t *testing.T) {
|
||||
|
||||
testSrv := ®ister.Service{Name: "foo", Version: "1.0.0"}
|
||||
|
||||
if err := m.Register(ctx, testSrv, register.RegisterDomain("one")); err != nil {
|
||||
if err := m.Register(ctx, testSrv, register.RegisterNamespace("one")); err != nil {
|
||||
t.Fatalf("Register err: %v", err)
|
||||
}
|
||||
if err := m.Register(ctx, testSrv, register.RegisterDomain("two")); err != nil {
|
||||
if err := m.Register(ctx, testSrv, register.RegisterNamespace("two")); err != nil {
|
||||
t.Fatalf("Register err: %v", err)
|
||||
}
|
||||
|
||||
if recs, err := m.ListServices(ctx, register.ListDomain("one")); err != nil {
|
||||
if recs, err := m.ListServices(ctx, register.ListNamespace("one")); err != nil {
|
||||
t.Errorf("List err: %v", err)
|
||||
} else if len(recs) != 1 {
|
||||
t.Errorf("Expected 1 record, got %v", len(recs))
|
||||
}
|
||||
|
||||
if recs, err := m.ListServices(ctx, register.ListDomain("*")); err != nil {
|
||||
if recs, err := m.ListServices(ctx, register.ListNamespace("*")); err != nil {
|
||||
t.Errorf("List err: %v", err)
|
||||
} else if len(recs) != 2 {
|
||||
t.Errorf("Expected 2 records, got %v", len(recs))
|
||||
}
|
||||
|
||||
if recs, err := m.LookupService(ctx, testSrv.Name, register.LookupDomain("one")); err != nil {
|
||||
if recs, err := m.LookupService(ctx, testSrv.Name, register.LookupNamespace("one")); err != nil {
|
||||
t.Errorf("Lookup err: %v", err)
|
||||
} else if len(recs) != 1 {
|
||||
t.Errorf("Expected 1 record, got %v", len(recs))
|
||||
}
|
||||
|
||||
if recs, err := m.LookupService(ctx, testSrv.Name, register.LookupDomain("*")); err != nil {
|
||||
if recs, err := m.LookupService(ctx, testSrv.Name, register.LookupNamespace("*")); err != nil {
|
||||
t.Errorf("Lookup err: %v", err)
|
||||
} else if len(recs) != 2 {
|
||||
t.Errorf("Expected 2 records, got %v", len(recs))
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v3/codec"
|
||||
"go.unistack.org/micro/v3/logger"
|
||||
"go.unistack.org/micro/v3/meter"
|
||||
"go.unistack.org/micro/v3/tracer"
|
||||
@ -26,6 +27,8 @@ type Options struct {
|
||||
Name string
|
||||
// Addrs specifies register addrs
|
||||
Addrs []string
|
||||
// Codec used to marshal/unmarshal data in register
|
||||
Codec codec.Codec
|
||||
// Timeout specifies timeout
|
||||
Timeout time.Duration
|
||||
}
|
||||
@ -37,6 +40,7 @@ func NewOptions(opts ...Option) Options {
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Context: context.Background(),
|
||||
Codec: codec.NewCodec(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@ -46,17 +50,17 @@ func NewOptions(opts ...Option) Options {
|
||||
|
||||
// RegisterOptions holds options for register method
|
||||
type RegisterOptions struct { // nolint: golint,revive
|
||||
Context context.Context
|
||||
Domain string
|
||||
TTL time.Duration
|
||||
Attempts int
|
||||
Context context.Context
|
||||
Namespace string
|
||||
TTL time.Duration
|
||||
Attempts int
|
||||
}
|
||||
|
||||
// NewRegisterOptions returns register options struct filled by opts
|
||||
func NewRegisterOptions(opts ...RegisterOption) RegisterOptions {
|
||||
options := RegisterOptions{
|
||||
Domain: DefaultDomain,
|
||||
Context: context.Background(),
|
||||
Namespace: DefaultNamespace,
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@ -72,15 +76,15 @@ type WatchOptions struct {
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
Context context.Context
|
||||
// Domain to watch
|
||||
Domain string
|
||||
// Namespace to watch
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// NewWatchOptions returns watch options filled by opts
|
||||
func NewWatchOptions(opts ...WatchOption) WatchOptions {
|
||||
options := WatchOptions{
|
||||
Domain: DefaultDomain,
|
||||
Context: context.Background(),
|
||||
Namespace: DefaultNamespace,
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@ -91,8 +95,8 @@ func NewWatchOptions(opts ...WatchOption) WatchOptions {
|
||||
// DeregisterOptions holds options for deregister method
|
||||
type DeregisterOptions struct {
|
||||
Context context.Context
|
||||
// Domain the service was registered in
|
||||
Domain string
|
||||
// Namespace the service was registered in
|
||||
Namespace string
|
||||
// Atempts specify max attempts for deregister
|
||||
Attempts int
|
||||
}
|
||||
@ -100,8 +104,8 @@ type DeregisterOptions struct {
|
||||
// NewDeregisterOptions returns options for deregister filled by opts
|
||||
func NewDeregisterOptions(opts ...DeregisterOption) DeregisterOptions {
|
||||
options := DeregisterOptions{
|
||||
Domain: DefaultDomain,
|
||||
Context: context.Background(),
|
||||
Namespace: DefaultNamespace,
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@ -112,15 +116,15 @@ func NewDeregisterOptions(opts ...DeregisterOption) DeregisterOptions {
|
||||
// LookupOptions holds lookup options
|
||||
type LookupOptions struct {
|
||||
Context context.Context
|
||||
// Domain to scope the request to
|
||||
Domain string
|
||||
// Namespace to scope the request to
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// NewLookupOptions returns lookup options filled by opts
|
||||
func NewLookupOptions(opts ...LookupOption) LookupOptions {
|
||||
options := LookupOptions{
|
||||
Domain: DefaultDomain,
|
||||
Context: context.Background(),
|
||||
Namespace: DefaultNamespace,
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@ -130,16 +134,17 @@ func NewLookupOptions(opts ...LookupOption) LookupOptions {
|
||||
|
||||
// ListOptions holds the list options for list method
|
||||
type ListOptions struct {
|
||||
// Context used to store additional options
|
||||
Context context.Context
|
||||
// Domain to scope the request to
|
||||
Domain string
|
||||
// Namespace to scope the request to
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// NewListOptions returns list options filled by opts
|
||||
func NewListOptions(opts ...ListOption) ListOptions {
|
||||
options := ListOptions{
|
||||
Domain: DefaultDomain,
|
||||
Context: context.Background(),
|
||||
Namespace: DefaultNamespace,
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@ -217,10 +222,10 @@ func RegisterContext(ctx context.Context) RegisterOption { // nolint: golint,rev
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterDomain secifies register domain
|
||||
func RegisterDomain(d string) RegisterOption { // nolint: golint,revive
|
||||
// RegisterNamespace secifies register Namespace
|
||||
func RegisterNamespace(d string) RegisterOption { // nolint: golint,revive
|
||||
return func(o *RegisterOptions) {
|
||||
o.Domain = d
|
||||
o.Namespace = d
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,10 +243,10 @@ func WatchContext(ctx context.Context) WatchOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WatchDomain sets the domain for watch
|
||||
func WatchDomain(d string) WatchOption {
|
||||
// WatchNamespace sets the Namespace for watch
|
||||
func WatchNamespace(d string) WatchOption {
|
||||
return func(o *WatchOptions) {
|
||||
o.Domain = d
|
||||
o.Namespace = d
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,10 +264,10 @@ func DeregisterContext(ctx context.Context) DeregisterOption {
|
||||
}
|
||||
}
|
||||
|
||||
// DeregisterDomain specifies deregister domain
|
||||
func DeregisterDomain(d string) DeregisterOption {
|
||||
// DeregisterNamespace specifies deregister Namespace
|
||||
func DeregisterNamespace(d string) DeregisterOption {
|
||||
return func(o *DeregisterOptions) {
|
||||
o.Domain = d
|
||||
o.Namespace = d
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,10 +278,10 @@ func LookupContext(ctx context.Context) LookupOption {
|
||||
}
|
||||
}
|
||||
|
||||
// LookupDomain sets the domain for lookup
|
||||
func LookupDomain(d string) LookupOption {
|
||||
// LookupNamespace sets the Namespace for lookup
|
||||
func LookupNamespace(d string) LookupOption {
|
||||
return func(o *LookupOptions) {
|
||||
o.Domain = d
|
||||
o.Namespace = d
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,10 +292,10 @@ func ListContext(ctx context.Context) ListOption {
|
||||
}
|
||||
}
|
||||
|
||||
// ListDomain sets the domain for list method
|
||||
func ListDomain(d string) ListOption {
|
||||
// ListNamespace sets the Namespace for list method
|
||||
func ListNamespace(d string) ListOption {
|
||||
return func(o *ListOptions) {
|
||||
o.Domain = d
|
||||
o.Namespace = d
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,3 +305,9 @@ func Name(n string) Option {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
func Codec(c codec.Codec) Option {
|
||||
return func(o *Options) {
|
||||
o.Codec = c
|
||||
}
|
||||
}
|
||||
|
@ -9,12 +9,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// WildcardDomain indicates any domain
|
||||
WildcardDomain = "*"
|
||||
// WildcardNamespace indicates any Namespace
|
||||
WildcardNamespace = "*"
|
||||
)
|
||||
|
||||
// DefaultDomain to use if none was provided in options
|
||||
var DefaultDomain = "micro"
|
||||
// DefaultNamespace to use if none was provided in options
|
||||
var DefaultNamespace = "micro"
|
||||
|
||||
var (
|
||||
// DefaultRegister is the global default register
|
||||
@ -59,26 +59,17 @@ type Register interface {
|
||||
|
||||
// Service holds service register info
|
||||
type Service struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Metadata metadata.Metadata `json:"metadata"`
|
||||
Endpoints []*Endpoint `json:"endpoints"`
|
||||
Nodes []*Node `json:"nodes"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Nodes []*Node `json:"nodes,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// Node holds node register info
|
||||
type Node struct {
|
||||
Metadata metadata.Metadata `json:"metadata"`
|
||||
ID string `json:"id"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// Endpoint holds endpoint register info
|
||||
type Endpoint struct {
|
||||
Request string `json:"request"`
|
||||
Response string `json:"response"`
|
||||
Metadata metadata.Metadata `json:"metadata"`
|
||||
Name string `json:"name"`
|
||||
Metadata metadata.Metadata `json:"metadata,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
}
|
||||
|
||||
// Option func signature
|
||||
|
@ -15,31 +15,31 @@ type Watcher interface {
|
||||
// the watcher. Actions can be create, update, delete
|
||||
type Result struct {
|
||||
// Service holds register service
|
||||
Service *Service
|
||||
Service *Service `json:"service,omitempty"`
|
||||
// Action holds the action
|
||||
Action string
|
||||
Action EventType `json:"action,omitempty"`
|
||||
}
|
||||
|
||||
// EventType defines register event type
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
// Create is emitted when a new service is registered
|
||||
Create EventType = iota
|
||||
// Delete is emitted when an existing service is deregistered
|
||||
Delete
|
||||
// Update is emitted when an existing service is updated
|
||||
Update
|
||||
// EventCreate is emitted when a new service is registered
|
||||
EventCreate EventType = iota
|
||||
// EventDelete is emitted when an existing service is deregistered
|
||||
EventDelete
|
||||
// EventUpdate is emitted when an existing service is updated
|
||||
EventUpdate
|
||||
)
|
||||
|
||||
// String returns human readable event type
|
||||
func (t EventType) String() string {
|
||||
switch t {
|
||||
case Create:
|
||||
case EventCreate:
|
||||
return "create"
|
||||
case Delete:
|
||||
case EventDelete:
|
||||
return "delete"
|
||||
case Update:
|
||||
case EventUpdate:
|
||||
return "update"
|
||||
default:
|
||||
return "unknown"
|
||||
@ -49,11 +49,11 @@ func (t EventType) String() string {
|
||||
// Event is register event
|
||||
type Event struct {
|
||||
// Timestamp is event timestamp
|
||||
Timestamp time.Time
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
// Service is register service
|
||||
Service *Service
|
||||
Service *Service `json:"service,omitempty"`
|
||||
// ID is register id
|
||||
ID string
|
||||
ID string `json:"id,omitempty"`
|
||||
// Type defines type of event
|
||||
Type EventType
|
||||
Type EventType `json:"type,omitempty"`
|
||||
}
|
||||
|
@ -15,6 +15,15 @@ func FromContext(ctx context.Context) (Router, bool) {
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MustContext get router from context
|
||||
func MustContext(ctx context.Context) Router {
|
||||
r, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing router")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// NewContext put router in context
|
||||
func NewContext(ctx context.Context, c Router) context.Context {
|
||||
if ctx == nil {
|
||||
|
@ -80,7 +80,7 @@ func Name(n string) Option {
|
||||
// NewOptions returns router default options
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
ID: id.Must(),
|
||||
ID: id.MustNew(),
|
||||
Network: DefaultNetwork,
|
||||
Register: register.DefaultRegister,
|
||||
Logger: logger.DefaultLogger,
|
||||
|
18
semconv/metadata.go
Normal file
18
semconv/metadata.go
Normal file
@ -0,0 +1,18 @@
|
||||
package semconv
|
||||
|
||||
var (
|
||||
// HeaderTopic is the header name that contains topic name
|
||||
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"
|
||||
// HeaderXRequestID specifies request id
|
||||
HeaderXRequestID = "X-Request-Id"
|
||||
)
|
@ -15,6 +15,15 @@ func FromContext(ctx context.Context) (Server, bool) {
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MustContext returns Server from context
|
||||
func MustContext(ctx context.Context) Server {
|
||||
s, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing server")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NewContext stores Server to context
|
||||
func NewContext(ctx context.Context, s Server) context.Context {
|
||||
if ctx == nil {
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -32,38 +31,21 @@ const (
|
||||
)
|
||||
|
||||
type rpcHandler struct {
|
||||
opts HandlerOptions
|
||||
handler interface{}
|
||||
name string
|
||||
endpoints []*register.Endpoint
|
||||
opts HandlerOptions
|
||||
handler interface{}
|
||||
name string
|
||||
}
|
||||
|
||||
func newRPCHandler(handler interface{}, opts ...HandlerOption) Handler {
|
||||
options := NewHandlerOptions(opts...)
|
||||
|
||||
typ := reflect.TypeOf(handler)
|
||||
hdlr := reflect.ValueOf(handler)
|
||||
name := reflect.Indirect(hdlr).Type().Name()
|
||||
|
||||
var endpoints []*register.Endpoint
|
||||
|
||||
for m := 0; m < typ.NumMethod(); m++ {
|
||||
if e := register.ExtractEndpoint(typ.Method(m)); e != nil {
|
||||
e.Name = name + "." + e.Name
|
||||
|
||||
for k, v := range options.Metadata[e.Name] {
|
||||
e.Metadata[k] = v
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, e)
|
||||
}
|
||||
}
|
||||
|
||||
return &rpcHandler{
|
||||
name: name,
|
||||
handler: handler,
|
||||
endpoints: endpoints,
|
||||
opts: options,
|
||||
name: name,
|
||||
handler: handler,
|
||||
opts: options,
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,10 +57,6 @@ func (r *rpcHandler) Handler() interface{} {
|
||||
return r.handler
|
||||
}
|
||||
|
||||
func (r *rpcHandler) Endpoints() []*register.Endpoint {
|
||||
return r.endpoints
|
||||
}
|
||||
|
||||
func (r *rpcHandler) Options() HandlerOptions {
|
||||
return r.opts
|
||||
}
|
||||
@ -249,35 +227,6 @@ func (n *noopServer) Register() error {
|
||||
return err
|
||||
}
|
||||
|
||||
n.RLock()
|
||||
handlerList := make([]string, 0, len(n.handlers))
|
||||
for n := range n.handlers {
|
||||
handlerList = append(handlerList, n)
|
||||
}
|
||||
|
||||
sort.Strings(handlerList)
|
||||
|
||||
subscriberList := make([]*subscriber, 0, len(n.subscribers))
|
||||
for e := range n.subscribers {
|
||||
subscriberList = append(subscriberList, e)
|
||||
}
|
||||
sort.Slice(subscriberList, func(i, j int) bool {
|
||||
return subscriberList[i].topic > subscriberList[j].topic
|
||||
})
|
||||
|
||||
endpoints := make([]*register.Endpoint, 0, len(handlerList)+len(subscriberList))
|
||||
for _, h := range handlerList {
|
||||
endpoints = append(endpoints, n.handlers[h].Endpoints()...)
|
||||
}
|
||||
for _, e := range subscriberList {
|
||||
endpoints = append(endpoints, e.Endpoints()...)
|
||||
}
|
||||
n.RUnlock()
|
||||
|
||||
service.Nodes[0].Metadata["protocol"] = "noop"
|
||||
service.Nodes[0].Metadata["transport"] = service.Nodes[0].Metadata["protocol"]
|
||||
service.Endpoints = endpoints
|
||||
|
||||
n.RLock()
|
||||
registered := n.registered
|
||||
n.RUnlock()
|
||||
@ -576,7 +525,6 @@ func (n *noopServer) Stop() error {
|
||||
}
|
||||
|
||||
func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
|
||||
var endpoints []*register.Endpoint
|
||||
var handlers []*handler
|
||||
|
||||
options := NewSubscriberOptions(opts...)
|
||||
@ -595,18 +543,7 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
|
||||
}
|
||||
|
||||
handlers = append(handlers, h)
|
||||
ep := ®ister.Endpoint{
|
||||
Name: "Func",
|
||||
Request: register.ExtractSubValue(typ),
|
||||
Metadata: metadata.New(2),
|
||||
}
|
||||
ep.Metadata.Set("topic", topic)
|
||||
ep.Metadata.Set("subscriber", "true")
|
||||
endpoints = append(endpoints, ep)
|
||||
} else {
|
||||
hdlr := reflect.ValueOf(sub)
|
||||
name := reflect.Indirect(hdlr).Type().Name()
|
||||
|
||||
for m := 0; m < typ.NumMethod(); m++ {
|
||||
method := typ.Method(m)
|
||||
h := &handler{
|
||||
@ -622,14 +559,6 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
|
||||
}
|
||||
|
||||
handlers = append(handlers, h)
|
||||
ep := ®ister.Endpoint{
|
||||
Name: name + "." + method.Name,
|
||||
Request: register.ExtractSubValue(method.Type),
|
||||
Metadata: metadata.New(2),
|
||||
}
|
||||
ep.Metadata.Set("topic", topic)
|
||||
ep.Metadata.Set("subscriber", "true")
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
}
|
||||
|
||||
@ -639,7 +568,6 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
|
||||
topic: topic,
|
||||
subscriber: sub,
|
||||
handlers: handlers,
|
||||
endpoints: endpoints,
|
||||
opts: options,
|
||||
}
|
||||
}
|
||||
@ -723,7 +651,7 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
||||
return nil
|
||||
}
|
||||
|
||||
opts.Hooks.EachNext(func(hook options.Hook) {
|
||||
opts.Hooks.EachPrev(func(hook options.Hook) {
|
||||
if h, ok := hook.(HookSubHandler); ok {
|
||||
fn = h(fn)
|
||||
}
|
||||
@ -766,10 +694,6 @@ func (s *subscriber) Subscriber() interface{} {
|
||||
return s.subscriber
|
||||
}
|
||||
|
||||
func (s *subscriber) Endpoints() []*register.Endpoint {
|
||||
return s.endpoints
|
||||
}
|
||||
|
||||
func (s *subscriber) Options() SubscriberOptions {
|
||||
return s.opts
|
||||
}
|
||||
@ -780,8 +704,7 @@ type subscriber struct {
|
||||
typ reflect.Type
|
||||
subscriber interface{}
|
||||
|
||||
endpoints []*register.Endpoint
|
||||
handlers []*handler
|
||||
handlers []*handler
|
||||
|
||||
rcvr reflect.Value
|
||||
opts SubscriberOptions
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"go.unistack.org/micro/v3/client"
|
||||
"go.unistack.org/micro/v3/codec"
|
||||
"go.unistack.org/micro/v3/logger"
|
||||
"go.unistack.org/micro/v3/options"
|
||||
"go.unistack.org/micro/v3/server"
|
||||
)
|
||||
|
||||
@ -84,3 +85,40 @@ func TestNoopSub(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func TestHooks_Wrap(t *testing.T) {
|
||||
n := 5
|
||||
fn1 := func(next server.FuncSubHandler) server.FuncSubHandler {
|
||||
return func(ctx context.Context, msg server.Message) (err error) {
|
||||
n *= 2
|
||||
return next(ctx, msg)
|
||||
}
|
||||
}
|
||||
fn2 := func(next server.FuncSubHandler) server.FuncSubHandler {
|
||||
return func(ctx context.Context, msg server.Message) (err error) {
|
||||
n -= 10
|
||||
return next(ctx, msg)
|
||||
}
|
||||
}
|
||||
|
||||
hs := &options.Hooks{}
|
||||
hs.Append(server.HookSubHandler(fn1), server.HookSubHandler(fn2))
|
||||
|
||||
var fn = func(ctx context.Context, msg server.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
hs.EachPrev(func(hook options.Hook) {
|
||||
if h, ok := hook.(server.HookSubHandler); ok {
|
||||
fn = h(fn)
|
||||
}
|
||||
})
|
||||
|
||||
if err := fn(nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n != 0 {
|
||||
t.Fatalf("uncorrected hooks call")
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ func NewOptions(opts ...Option) Options {
|
||||
Address: DefaultAddress,
|
||||
Name: DefaultName,
|
||||
Version: DefaultVersion,
|
||||
ID: id.Must(),
|
||||
ID: id.MustNew(),
|
||||
Namespace: DefaultNamespace,
|
||||
GracefulTimeout: DefaultGracefulTimeout,
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ var (
|
||||
|
||||
opts := []register.RegisterOption{
|
||||
register.RegisterTTL(config.RegisterTTL),
|
||||
register.RegisterDomain(config.Namespace),
|
||||
register.RegisterNamespace(config.Namespace),
|
||||
}
|
||||
|
||||
for i := 0; i <= config.RegisterAttempts; i++ {
|
||||
@ -36,7 +36,7 @@ var (
|
||||
var err error
|
||||
|
||||
opts := []register.DeregisterOption{
|
||||
register.DeregisterDomain(config.Namespace),
|
||||
register.DeregisterNamespace(config.Namespace),
|
||||
}
|
||||
|
||||
for i := 0; i <= config.DeregisterAttempts; i++ {
|
||||
@ -82,9 +82,8 @@ func NewRegisterService(s Server) (*register.Service, error) {
|
||||
node.Metadata["register"] = opts.Register.String()
|
||||
|
||||
return ®ister.Service{
|
||||
Name: opts.Name,
|
||||
Version: opts.Version,
|
||||
Nodes: []*register.Node{node},
|
||||
Metadata: metadata.New(0),
|
||||
Name: opts.Name,
|
||||
Version: opts.Version,
|
||||
Nodes: []*register.Node{node},
|
||||
}, nil
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
|
||||
"go.unistack.org/micro/v3/codec"
|
||||
"go.unistack.org/micro/v3/metadata"
|
||||
"go.unistack.org/micro/v3/register"
|
||||
)
|
||||
|
||||
// DefaultServer default server
|
||||
@ -170,7 +169,6 @@ type Stream interface {
|
||||
type Handler interface {
|
||||
Name() string
|
||||
Handler() interface{}
|
||||
Endpoints() []*register.Endpoint
|
||||
Options() HandlerOptions
|
||||
}
|
||||
|
||||
@ -180,6 +178,5 @@ type Handler interface {
|
||||
type Subscriber interface {
|
||||
Topic() string
|
||||
Subscriber() interface{}
|
||||
Endpoints() []*register.Endpoint
|
||||
Options() SubscriberOptions
|
||||
}
|
||||
|
@ -15,6 +15,15 @@ func FromContext(ctx context.Context) (Store, bool) {
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MustContext get store from context
|
||||
func MustContext(ctx context.Context) Store {
|
||||
s, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing store")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NewContext put store in context
|
||||
func NewContext(ctx context.Context, c Store) context.Context {
|
||||
if ctx == nil {
|
||||
|
@ -123,7 +123,7 @@ func (m *memoryStore) Init(opts ...store.Option) error {
|
||||
m.funcList = m.fnList
|
||||
m.funcDelete = m.fnDelete
|
||||
|
||||
m.opts.Hooks.EachNext(func(hook options.Hook) {
|
||||
m.opts.Hooks.EachPrev(func(hook options.Hook) {
|
||||
switch h := hook.(type) {
|
||||
case store.HookRead:
|
||||
m.funcRead = h(m.funcRead)
|
||||
|
@ -54,7 +54,7 @@ func (n *noopStore) Init(opts ...Option) error {
|
||||
n.funcList = n.fnList
|
||||
n.funcDelete = n.fnDelete
|
||||
|
||||
n.opts.Hooks.EachNext(func(hook options.Hook) {
|
||||
n.opts.Hooks.EachPrev(func(hook options.Hook) {
|
||||
switch h := hook.(type) {
|
||||
case HookRead:
|
||||
n.funcRead = h(n.funcRead)
|
||||
|
@ -18,6 +18,15 @@ func FromContext(ctx context.Context) (Tracer, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// MustContext returns a tracer from context
|
||||
func MustContext(ctx context.Context) Tracer {
|
||||
t, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing tracer")
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// NewContext saves the tracer in the context
|
||||
func NewContext(ctx context.Context, tracer Tracer) context.Context {
|
||||
if ctx == nil {
|
||||
@ -28,6 +37,15 @@ func NewContext(ctx context.Context, tracer Tracer) context.Context {
|
||||
|
||||
type spanKey struct{}
|
||||
|
||||
// SpanFromContext returns a span from context
|
||||
func SpanMustContext(ctx context.Context) Span {
|
||||
sp, ok := SpanFromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing span")
|
||||
}
|
||||
return sp
|
||||
}
|
||||
|
||||
// SpanFromContext returns a span from context
|
||||
func SpanFromContext(ctx context.Context) (Span, bool) {
|
||||
if ctx == nil {
|
||||
|
@ -25,6 +25,7 @@ func (t *Tracer) Start(ctx context.Context, name string, opts ...tracer.SpanOpti
|
||||
name: name,
|
||||
ctx: ctx,
|
||||
tracer: t,
|
||||
labels: options.Labels,
|
||||
kind: options.Kind,
|
||||
startTime: time.Now(),
|
||||
}
|
||||
@ -37,6 +38,14 @@ func (t *Tracer) Start(ctx context.Context, name string, opts ...tracer.SpanOpti
|
||||
return tracer.NewSpanContext(ctx, span), span
|
||||
}
|
||||
|
||||
type memoryStringer struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (s memoryStringer) String() string {
|
||||
return s.s
|
||||
}
|
||||
|
||||
func (t *Tracer) Flush(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
@ -52,14 +61,6 @@ func (t *Tracer) Name() string {
|
||||
return t.opts.Name
|
||||
}
|
||||
|
||||
type noopStringer struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (s noopStringer) String() string {
|
||||
return s.s
|
||||
}
|
||||
|
||||
type Span struct {
|
||||
ctx context.Context
|
||||
tracer tracer.Tracer
|
||||
@ -67,8 +68,8 @@ type Span struct {
|
||||
statusMsg string
|
||||
startTime time.Time
|
||||
finishTime time.Time
|
||||
traceID noopStringer
|
||||
spanID noopStringer
|
||||
traceID memoryStringer
|
||||
spanID memoryStringer
|
||||
events []*Event
|
||||
labels []interface{}
|
||||
logs []interface{}
|
||||
|
@ -2,6 +2,7 @@ package tracer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v3/util/id"
|
||||
)
|
||||
@ -20,18 +21,18 @@ func (t *noopTracer) Spans() []Span {
|
||||
func (t *noopTracer) Start(ctx context.Context, name string, opts ...SpanOption) (context.Context, Span) {
|
||||
options := NewSpanOptions(opts...)
|
||||
span := &noopSpan{
|
||||
name: name,
|
||||
ctx: ctx,
|
||||
tracer: t,
|
||||
labels: options.Labels,
|
||||
kind: options.Kind,
|
||||
name: name,
|
||||
ctx: ctx,
|
||||
tracer: t,
|
||||
startTime: time.Now(),
|
||||
labels: options.Labels,
|
||||
kind: options.Kind,
|
||||
}
|
||||
span.spanID.s, _ = id.New()
|
||||
span.traceID.s, _ = id.New()
|
||||
if span.ctx == nil {
|
||||
span.ctx = context.Background()
|
||||
}
|
||||
t.spans = append(t.spans, span)
|
||||
return NewSpanContext(ctx, span), span
|
||||
}
|
||||
|
||||
@ -58,23 +59,18 @@ func (t *noopTracer) Name() string {
|
||||
return t.opts.Name
|
||||
}
|
||||
|
||||
type noopEvent struct {
|
||||
name string
|
||||
labels []interface{}
|
||||
}
|
||||
|
||||
type noopSpan struct {
|
||||
ctx context.Context
|
||||
tracer Tracer
|
||||
name string
|
||||
statusMsg string
|
||||
traceID noopStringer
|
||||
spanID noopStringer
|
||||
events []*noopEvent
|
||||
labels []interface{}
|
||||
logs []interface{}
|
||||
kind SpanKind
|
||||
status SpanStatus
|
||||
ctx context.Context
|
||||
tracer Tracer
|
||||
name string
|
||||
statusMsg string
|
||||
startTime time.Time
|
||||
finishTime time.Time
|
||||
traceID noopStringer
|
||||
spanID noopStringer
|
||||
labels []interface{}
|
||||
kind SpanKind
|
||||
status SpanStatus
|
||||
}
|
||||
|
||||
func (s *noopSpan) TraceID() string {
|
||||
@ -86,6 +82,7 @@ func (s *noopSpan) SpanID() string {
|
||||
}
|
||||
|
||||
func (s *noopSpan) Finish(_ ...SpanOption) {
|
||||
s.finishTime = time.Now()
|
||||
}
|
||||
|
||||
func (s *noopSpan) Context() context.Context {
|
||||
@ -97,8 +94,6 @@ func (s *noopSpan) Tracer() Tracer {
|
||||
}
|
||||
|
||||
func (s *noopSpan) AddEvent(name string, opts ...EventOption) {
|
||||
options := NewEventOptions(opts...)
|
||||
s.events = append(s.events, &noopEvent{name: name, labels: options.Labels})
|
||||
}
|
||||
|
||||
func (s *noopSpan) SetName(name string) {
|
||||
@ -106,7 +101,6 @@ func (s *noopSpan) SetName(name string) {
|
||||
}
|
||||
|
||||
func (s *noopSpan) AddLogs(kv ...interface{}) {
|
||||
s.logs = append(s.logs, kv...)
|
||||
}
|
||||
|
||||
func (s *noopSpan) AddLabels(kv ...interface{}) {
|
||||
|
@ -1,27 +0,0 @@
|
||||
package buf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
var _ io.Closer = &Buffer{}
|
||||
|
||||
// Buffer bytes.Buffer wrapper to satisfie io.Closer interface
|
||||
type Buffer struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
// Close reset buffer contents
|
||||
func (b *Buffer) Close() error {
|
||||
b.Buffer.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
// New creates new buffer that satisfies Closer interface
|
||||
func New(b *bytes.Buffer) *Buffer {
|
||||
if b == nil {
|
||||
b = bytes.NewBuffer(nil)
|
||||
}
|
||||
return &Buffer{b}
|
||||
}
|
85
util/buffer/buffer.go
Normal file
85
util/buffer/buffer.go
Normal file
@ -0,0 +1,85 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ io.WriteCloser = (*DelayedBuffer)(nil)
|
||||
|
||||
// DelayedBuffer is the buffer that holds items until either the buffer filled or a specified time limit is reached
|
||||
type DelayedBuffer struct {
|
||||
mu sync.Mutex
|
||||
maxWait time.Duration
|
||||
flushTime time.Time
|
||||
buffer chan []byte
|
||||
ticker *time.Ticker
|
||||
w io.Writer
|
||||
err error
|
||||
}
|
||||
|
||||
func NewDelayedBuffer(size int, maxWait time.Duration, w io.Writer) *DelayedBuffer {
|
||||
b := &DelayedBuffer{
|
||||
buffer: make(chan []byte, size),
|
||||
ticker: time.NewTicker(maxWait),
|
||||
w: w,
|
||||
flushTime: time.Now(),
|
||||
maxWait: maxWait,
|
||||
}
|
||||
b.loop()
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *DelayedBuffer) loop() {
|
||||
go func() {
|
||||
for range b.ticker.C {
|
||||
b.mu.Lock()
|
||||
if time.Since(b.flushTime) > b.maxWait {
|
||||
b.flush()
|
||||
}
|
||||
b.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (b *DelayedBuffer) flush() {
|
||||
bufLen := len(b.buffer)
|
||||
if bufLen > 0 {
|
||||
tmp := make([][]byte, bufLen)
|
||||
for i := 0; i < bufLen; i++ {
|
||||
tmp[i] = <-b.buffer
|
||||
}
|
||||
for _, t := range tmp {
|
||||
_, b.err = b.w.Write(t)
|
||||
}
|
||||
b.flushTime = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *DelayedBuffer) Put(items ...[]byte) {
|
||||
b.mu.Lock()
|
||||
for _, item := range items {
|
||||
select {
|
||||
case b.buffer <- item:
|
||||
default:
|
||||
b.flush()
|
||||
b.buffer <- item
|
||||
}
|
||||
}
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
func (b *DelayedBuffer) Close() error {
|
||||
b.mu.Lock()
|
||||
b.flush()
|
||||
close(b.buffer)
|
||||
b.ticker.Stop()
|
||||
b.mu.Unlock()
|
||||
return b.err
|
||||
}
|
||||
|
||||
func (b *DelayedBuffer) Write(data []byte) (int, error) {
|
||||
b.Put(data)
|
||||
return len(data), b.err
|
||||
}
|
22
util/buffer/buffer_test.go
Normal file
22
util/buffer/buffer_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTimedBuffer(t *testing.T) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
b := NewDelayedBuffer(100, 300*time.Millisecond, buf)
|
||||
for i := 0; i < 100; i++ {
|
||||
_, _ = b.Write([]byte(`test`))
|
||||
}
|
||||
if buf.Len() != 0 {
|
||||
t.Fatal("delayed write not worked")
|
||||
}
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
if buf.Len() == 0 {
|
||||
t.Fatal("delayed write not worked")
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2021 Matous Dzivjak <matousdzivjak@gmail.com>
|
||||
Copyright (c) 2021 Unistack LLC <v.tolstov@unistack.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
167
util/id/id.go
167
util/id/id.go
@ -1,112 +1,159 @@
|
||||
package id
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v3/logger"
|
||||
uuidv8 "github.com/ash3in/uuidv8"
|
||||
"github.com/google/uuid"
|
||||
nanoid "github.com/matoous/go-nanoid"
|
||||
)
|
||||
|
||||
// DefaultAlphabet is the alphabet used for ID characters by default
|
||||
var DefaultAlphabet = []rune("6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz")
|
||||
var generatedNode [6]byte
|
||||
|
||||
// DefaultSize is the size used for ID by default
|
||||
// To get uuid like collision specify 21
|
||||
var DefaultSize = 16
|
||||
|
||||
// getMask generates bit mask used to obtain bits from the random bytes that are used to get index of random character
|
||||
// from the alphabet. Example: if the alphabet has 6 = (110)_2 characters it is sufficient to use mask 7 = (111)_2
|
||||
func getMask(alphabetSize int) int {
|
||||
for i := 1; i <= 8; i++ {
|
||||
mask := (2 << uint(i)) - 1
|
||||
if mask >= alphabetSize-1 {
|
||||
return mask
|
||||
}
|
||||
func init() {
|
||||
if _, err := rand.Read(generatedNode[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Type int
|
||||
|
||||
const (
|
||||
TypeUnspecified Type = iota
|
||||
TypeNanoid
|
||||
TypeUUIDv8
|
||||
)
|
||||
|
||||
// DefaultNanoidAlphabet is the alphabet used for ID characters by default
|
||||
var DefaultNanoidAlphabet = "6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz"
|
||||
|
||||
// DefaultNanoidSize is the size used for ID by default
|
||||
// To get uuid like collision specify 21
|
||||
var DefaultNanoidSize = 16
|
||||
|
||||
type Generator struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
func (g *Generator) MustNew() string {
|
||||
id, err := g.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func (g *Generator) New() (string, error) {
|
||||
switch g.opts.Type {
|
||||
case TypeNanoid:
|
||||
if len(g.opts.NanoidAlphabet) == 0 || len(g.opts.NanoidAlphabet) > 255 {
|
||||
return "", errors.New("invalid option, NanoidAlphabet must not be empty and contain no more than 255 chars")
|
||||
}
|
||||
if g.opts.NanoidSize <= 0 {
|
||||
return "", errors.New("invalid option, NanoidSize must be positive integer")
|
||||
}
|
||||
|
||||
return nanoid.Generate(g.opts.NanoidAlphabet, g.opts.NanoidSize)
|
||||
case TypeUUIDv8:
|
||||
timestamp := uint64(time.Now().UnixNano())
|
||||
clockSeq := make([]byte, 2)
|
||||
if _, err := rand.Read(clockSeq); err != nil {
|
||||
return "", fmt.Errorf("failed to generate random clock sequence: %w", err)
|
||||
}
|
||||
clockSeqValue := binary.BigEndian.Uint16(clockSeq) & 0x0FFF // Mask to 12 bits
|
||||
return uuidv8.NewWithParams(timestamp, clockSeqValue, g.opts.UUIDNode[:], uuidv8.TimestampBits48)
|
||||
}
|
||||
return "", errors.New("invalid option, Type unspecified")
|
||||
}
|
||||
|
||||
// New returns new id or error
|
||||
func New(opts ...Option) (string, error) {
|
||||
options := NewOptions(opts...)
|
||||
|
||||
if len(options.Alphabet) == 0 || len(options.Alphabet) > 255 {
|
||||
return "", errors.New("alphabet must not be empty and contain no more than 255 chars")
|
||||
}
|
||||
if options.Size <= 0 {
|
||||
return "", errors.New("size must be positive integer")
|
||||
}
|
||||
|
||||
chars := options.Alphabet
|
||||
|
||||
mask := getMask(len(chars))
|
||||
// estimate how many random bytes we will need for the ID, we might actually need more but this is tradeoff
|
||||
// between average case and worst case
|
||||
ceilArg := 1.6 * float64(mask*options.Size) / float64(len(options.Alphabet))
|
||||
step := int(math.Ceil(ceilArg))
|
||||
|
||||
id := make([]rune, options.Size)
|
||||
bytes := make([]byte, step)
|
||||
for j := 0; ; {
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
switch options.Type {
|
||||
case TypeNanoid:
|
||||
if len(options.NanoidAlphabet) == 0 || len(options.NanoidAlphabet) > 255 {
|
||||
return "", errors.New("invalid option, NanoidAlphabet must not be empty and contain no more than 255 chars")
|
||||
}
|
||||
for i := 0; i < step; i++ {
|
||||
currByte := bytes[i] & byte(mask)
|
||||
if currByte < byte(len(chars)) {
|
||||
id[j] = chars[currByte]
|
||||
j++
|
||||
if j == options.Size {
|
||||
return string(id[:options.Size]), nil
|
||||
}
|
||||
}
|
||||
if options.NanoidSize <= 0 {
|
||||
return "", errors.New("invalid option, NanoidSize must be positive integer")
|
||||
}
|
||||
|
||||
return nanoid.Generate(options.NanoidAlphabet, options.NanoidSize)
|
||||
case TypeUUIDv8:
|
||||
timestamp := uint64(time.Now().UnixNano())
|
||||
clockSeq := make([]byte, 2)
|
||||
if _, err := rand.Read(clockSeq); err != nil {
|
||||
return "", fmt.Errorf("failed to generate random clock sequence: %w", err)
|
||||
}
|
||||
clockSeqValue := binary.BigEndian.Uint16(clockSeq) & 0x0FFF // Mask to 12 bits
|
||||
return uuidv8.NewWithParams(timestamp, clockSeqValue, options.UUIDNode[:], uuidv8.TimestampBits48)
|
||||
}
|
||||
|
||||
return "", errors.New("invalid option, Type unspecified")
|
||||
}
|
||||
|
||||
func ToUUID(s string) uuid.UUID {
|
||||
return uuid.MustParse(s)
|
||||
}
|
||||
|
||||
// Must is the same as New but fatals on error
|
||||
func Must(opts ...Option) string {
|
||||
func MustNew(opts ...Option) string {
|
||||
id, err := New(opts...)
|
||||
if err != nil {
|
||||
logger.DefaultLogger.Fatal(context.TODO(), "Must call is failed", err)
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Options contains id deneration options
|
||||
type Options struct {
|
||||
Alphabet []rune
|
||||
Size int
|
||||
Type Type
|
||||
NanoidAlphabet string
|
||||
NanoidSize int
|
||||
UUIDNode [6]byte
|
||||
}
|
||||
|
||||
// Option func signature
|
||||
type Option func(*Options)
|
||||
|
||||
// Alphabet specifies alphabet to use
|
||||
func Alphabet(alphabet string) Option {
|
||||
// WithNanoidAlphabet specifies alphabet to use
|
||||
func WithNanoidAlphabet(alphabet string) Option {
|
||||
return func(o *Options) {
|
||||
o.Alphabet = []rune(alphabet)
|
||||
o.NanoidAlphabet = alphabet
|
||||
}
|
||||
}
|
||||
|
||||
// Size specifies id size
|
||||
func Size(size int) Option {
|
||||
// WithNanoidSize specifies generated id size
|
||||
func WithNanoidSize(size int) Option {
|
||||
return func(o *Options) {
|
||||
o.Size = size
|
||||
o.NanoidSize = size
|
||||
}
|
||||
}
|
||||
|
||||
// WithUUIDNode specifies node component for UUIDv8
|
||||
func WithUUIDNode(node [6]byte) Option {
|
||||
return func(o *Options) {
|
||||
o.UUIDNode = node
|
||||
}
|
||||
}
|
||||
|
||||
// NewOptions returns new Options struct filled by opts
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Alphabet: DefaultAlphabet,
|
||||
Size: DefaultSize,
|
||||
Type: TypeUUIDv8,
|
||||
NanoidAlphabet: DefaultNanoidAlphabet,
|
||||
NanoidSize: DefaultNanoidSize,
|
||||
UUIDNode: generatedNode,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
20
util/id/id_test.go
Normal file
20
util/id/id_test.go
Normal file
@ -0,0 +1,20 @@
|
||||
package id
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUUIDv8(t *testing.T) {
|
||||
id, err := New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = id
|
||||
}
|
||||
|
||||
func TestToUUID(t *testing.T) {
|
||||
id, err := New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
u := ToUUID(id)
|
||||
_ = u
|
||||
}
|
@ -71,14 +71,6 @@ func CopyService(service *register.Service) *register.Service {
|
||||
}
|
||||
s.Nodes = nodes
|
||||
|
||||
// copy endpoints
|
||||
eps := make([]*register.Endpoint, len(service.Endpoints))
|
||||
for j, ep := range service.Endpoints {
|
||||
e := ®ister.Endpoint{}
|
||||
*e = *ep
|
||||
eps[j] = e
|
||||
}
|
||||
s.Endpoints = eps
|
||||
return s
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ func (b *Buffer) Stream() (<-chan *Entry, chan bool) {
|
||||
defer b.Unlock()
|
||||
|
||||
entries := make(chan *Entry, 128)
|
||||
id := id.Must()
|
||||
id := id.MustNew()
|
||||
stop := make(chan bool)
|
||||
|
||||
b.streams[id] = &Stream{
|
||||
|
@ -1,7 +1,6 @@
|
||||
package sort
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
@ -9,7 +8,7 @@ import (
|
||||
type byKey []interface{}
|
||||
|
||||
func (k byKey) Len() int { return len(k) / 2 }
|
||||
func (k byKey) Less(i, j int) bool { return fmt.Sprintf("%s", k[i*2]) < fmt.Sprintf("%s", k[j*2]) }
|
||||
func (k byKey) Less(i, j int) bool { return k[i*2].(string) < k[j*2].(string) }
|
||||
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]
|
||||
|
@ -35,11 +35,11 @@ func TestUnmarshalYAML(t *testing.T) {
|
||||
t.Fatalf("invalid duration %v != 10000000", v.TTL)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal([]byte(`{"ttl":"1y"}`), v)
|
||||
err = yaml.Unmarshal([]byte(`{"ttl":"1d"}`), v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if *(v.TTL) != 31622400000000000 {
|
||||
t.Fatalf("invalid duration %v != 31622400000000000", v.TTL)
|
||||
} else if *(v.TTL) != 86400000000000 {
|
||||
t.Fatalf("invalid duration %v != 86400000000000", *v.TTL)
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,11 +68,11 @@ func TestUnmarshalJSON(t *testing.T) {
|
||||
t.Fatalf("invalid duration %v != 10000000", v.TTL)
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(`{"ttl":"1y"}`), v)
|
||||
err = json.Unmarshal([]byte(`{"ttl":"1d"}`), v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if v.TTL != 31622400000000000 {
|
||||
t.Fatalf("invalid duration %v != 31622400000000000", v.TTL)
|
||||
} else if v.TTL != 86400000000000 {
|
||||
t.Fatalf("invalid duration %v != 86400000000000", v.TTL)
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,11 +87,11 @@ func TestParseDuration(t *testing.T) {
|
||||
if td.String() != "340h0m0s" {
|
||||
t.Fatalf("ParseDuration 14d != 340h0m0s : %s", td.String())
|
||||
}
|
||||
td, err = ParseDuration("1y")
|
||||
td, err = ParseDuration("1d")
|
||||
if err != nil {
|
||||
t.Fatalf("ParseDuration error: %v", err)
|
||||
}
|
||||
if td.String() != "8784h0m0s" {
|
||||
t.Fatalf("ParseDuration 1y != 8784h0m0s : %s", td.String())
|
||||
if td.String() != "24h0m0s" {
|
||||
t.Fatalf("ParseDuration 1d != 24h0m0s : %s", td.String())
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user