Compare commits

..

1 Commits

Author SHA1 Message Date
a8d050d934 Merge branch 'v3' into mdfix
All checks were successful
test / test (pull_request) Successful in 44s
lint / lint (pull_request) Successful in 10m2s
2024-12-09 18:03:59 +03:00
64 changed files with 654 additions and 1225 deletions

View File

@@ -1,51 +0,0 @@
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 }}

View File

@@ -12,18 +12,15 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v4
with:
filter: 'blob:none'
- name: setup go
- name: setup-go
uses: actions/setup-go@v5
with:
cache-dependency-path: "**/*.sum"
go-version: 'stable'
- name: setup deps
run: go get -v ./...
- name: run lint
go-version: 'stable'
- name: checkout
uses: actions/checkout@v3
- name: deps
run: go get -v -d ./...
- name: lint
uses: https://github.com/golangci/golangci-lint-action@v6
with:
version: 'latest'

View File

@@ -17,18 +17,15 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v4
with:
filter: 'blob:none'
- name: setup go
- name: setup-go
uses: actions/setup-go@v5
with:
cache-dependency-path: "**/*.sum"
go-version: 'stable'
- name: setup deps
run: go get -v ./...
- name: run test
- name: checkout
uses: actions/checkout@v3
- name: deps
run: go get -v -d ./...
- name: test
env:
INTEGRATION_TESTS: yes
run: go test -mod readonly -v ./...

View File

@@ -1,53 +0,0 @@
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

View File

@@ -1,9 +1,4 @@
# Micro
![Coverage](https://img.shields.io/badge/Coverage-44.7%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)
# Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [![Status](https://github.com/unistack-org/micro/workflows/build/badge.svg?branch=master)](https://github.com/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Amaster+event%3Apush) [![Lint](https://goreportcard.com/badge/go.unistack.org/micro/v3)](https://goreportcard.com/report/go.unistack.org/micro/v3) [![Coverage](https://codecov.io/gh/unistack-org/micro/branch/v3/graph/badge.svg?token=OZPO2LP7VS)](https://codecov.io/gh/unistack-org/micro)
Micro is a standard library for microservices.
@@ -15,20 +10,30 @@ 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, cmdline, file, consul, vault... You can merge the sources and even define fallbacks.
level config from any source such as env vars, file, etcd. 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
s3. State and persistence becomes a core requirement beyond prototyping and Micro looks to build that into the framework.
CockroachDB by default. 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.
- **Async Messaging** - Pub/Sub is built in as a first class citizen for asynchronous communication and event driven architectures.
- **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.
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
@@ -37,6 +42,10 @@ 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.

View File

@@ -15,15 +15,6 @@ 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 {

View File

@@ -109,7 +109,7 @@ func (m *memoryBroker) Init(opts ...broker.Option) error {
m.funcSubscribe = m.fnSubscribe
m.funcBatchSubscribe = m.fnBatchSubscribe
m.opts.Hooks.EachPrev(func(hook options.Hook) {
m.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case broker.HookPublish:
m.funcPublish = h(m.funcPublish)

View File

@@ -59,7 +59,7 @@ func (b *NoopBroker) Init(opts ...Option) error {
b.funcSubscribe = b.fnSubscribe
b.funcBatchSubscribe = b.fnBatchSubscribe
b.opts.Hooks.EachPrev(func(hook options.Hook) {
b.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookPublish:
b.funcPublish = h(b.funcPublish)

View File

@@ -15,15 +15,6 @@ 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 {

View File

@@ -194,7 +194,7 @@ func (n *noopClient) Init(opts ...Option) error {
n.funcPublish = n.fnPublish
n.funcBatchPublish = n.fnBatchPublish
n.opts.Hooks.EachPrev(func(hook options.Hook) {
n.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookCall:
n.funcCall = h(n.funcCall)
@@ -588,6 +588,7 @@ func (n *noopClient) publish(ctx context.Context, ps []Message, opts ...PublishO
for _, p := range ps {
md := metadata.Copy(omd)
md[metadata.HeaderContentType] = p.ContentType()
topic := p.Topic()
if len(exchange) > 0 {
topic = exchange
@@ -599,8 +600,6 @@ func (n *noopClient) publish(ctx context.Context, ps []Message, opts ...PublishO
md.Set(k, v)
}
md[metadata.HeaderContentType] = p.ContentType()
var body []byte
// passed in raw data

View File

@@ -3,8 +3,6 @@ package codec
import (
"errors"
"gopkg.in/yaml.v3"
)
var (
@@ -56,22 +54,3 @@ 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
}

View File

@@ -15,15 +15,6 @@ 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 {

View File

@@ -1,7 +1,5 @@
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
@@ -22,17 +20,6 @@ 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() {}

View File

@@ -15,15 +15,6 @@ 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 {

View File

@@ -37,7 +37,7 @@ func (c *defaultConfig) Init(opts ...Option) error {
c.funcLoad = c.fnLoad
c.funcSave = c.fnSave
c.opts.Hooks.EachPrev(func(hook options.Hook) {
c.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookLoad:
c.funcLoad = h(c.funcLoad)

View File

@@ -8,6 +8,7 @@ import (
"time"
"go.unistack.org/micro/v3/config"
mid "go.unistack.org/micro/v3/util/id"
mtime "go.unistack.org/micro/v3/util/time"
)
@@ -114,6 +115,8 @@ 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)

View File

@@ -15,15 +15,6 @@ 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
View File

@@ -1,43 +1,33 @@
module go.unistack.org/micro/v3
go 1.22.0
go 1.22
require (
dario.cat/mergo v1.0.1
github.com/DATA-DOG/go-sqlmock v1.5.2
dario.cat/mergo v1.0.0
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/KimMachineGun/automemlimit v0.6.1
github.com/ash3in/uuidv8 v1.2.0
github.com/google/uuid v1.6.0
github.com/matoous/go-nanoid v1.5.1
github.com/google/uuid v1.3.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
go.uber.org/automaxprocs v1.6.0
go.unistack.org/micro-proto/v3 v3.4.1
golang.org/x/sync v0.10.0
google.golang.org/grpc v1.69.2
google.golang.org/protobuf v1.36.1
golang.org/x/sync v0.3.0
google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.33.0
gopkg.in/yaml.v3 v3.0.1
)
require (
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/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/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // 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
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
)

139
go.sum
View File

@@ -1,103 +1,80 @@
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=
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=
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/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/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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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/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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/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/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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
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/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.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=
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=
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.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
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.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/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=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -4,6 +4,17 @@ import "context"
type loggerKey struct{}
// MustContext returns logger from passed context or DefaultLogger if empty
func MustContext(ctx context.Context) Logger {
if ctx == nil {
return DefaultLogger.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 {
@@ -13,15 +24,6 @@ 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 {

View File

@@ -2,11 +2,9 @@ package logger
import (
"context"
"fmt"
"io"
"log/slog"
"os"
"slices"
"time"
"go.unistack.org/micro/v3/meter"
@@ -31,6 +29,7 @@ type Options struct {
StacktraceKey string
// Name holds the logger name
Name string
// Out holds the output writer
Out io.Writer
// Context holds exernal options
@@ -39,10 +38,12 @@ 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
@@ -51,8 +52,6 @@ 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
@@ -84,36 +83,10 @@ 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) {
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 {
fmt.Printf("AAAA o.Fields:%v old:%v new:%v\n", o.Fields[i], o.Fields[i+1], fields[j+1])
o.Fields[i+1] = fields[j+1]
fmt.Printf("BBBB o.Fields:%v old:%v new:%v\n", o.Fields[i], o.Fields[i+1], fields[j+1])
fields = slices.Delete(fields, j, j+2)
}
}
}
if len(fields) > 0 {
o.Fields = append(o.Fields, fields...)
}
} else {
o.Fields = append(o.Fields, fields...)
}
o.Fields = append(o.Fields, fields...)
}
}

View File

@@ -2,7 +2,6 @@ package slog
import (
"context"
"io"
"log/slog"
"os"
"reflect"
@@ -11,7 +10,6 @@ import (
"strconv"
"sync"
"sync/atomic"
"time"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/semconv"
@@ -22,7 +20,6 @@ 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`)
@@ -65,7 +62,6 @@ 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:
@@ -146,7 +142,6 @@ 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
@@ -228,10 +223,6 @@ 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)
}
@@ -278,7 +269,7 @@ func (s *slogLogger) printLog(ctx context.Context, lvl logger.Level, msg string,
}
}
if s.opts.AddStacktrace && (lvl == logger.FatalLevel || lvl == logger.ErrorLevel) {
if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)

View File

@@ -9,126 +9,12 @@ 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.DebugLevel), 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=val4 key2=val3`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestWithHandlerFunc(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)

View File

@@ -24,17 +24,6 @@ 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) {
@@ -48,17 +37,6 @@ 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) {
@@ -72,16 +50,6 @@ 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 {
@@ -143,7 +111,7 @@ func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
for k, v := range md {
omd.Set(k, v)
}
return ctx
return NewOutgoingContext(ctx, omd)
}
// AppendIncomingContext apends new md to context
@@ -159,21 +127,5 @@ func AppendIncomingContext(ctx context.Context, kv ...string) context.Context {
for k, v := range md {
omd.Set(k, v)
}
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
return NewIncomingContext(ctx, omd)
}

View File

@@ -67,14 +67,6 @@ 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
@@ -111,18 +103,11 @@ 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 k, v := range md {
nmd[k] = v
for key, val := range md {
nmd.Set(key, val)
}
nmd.Del(exclude...)
return nmd
@@ -146,7 +131,7 @@ func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata {
case ok && !overwrite:
continue
case val != "":
nmd[key] = val
nmd.Set(key, val)
case ok && val == "":
nmd.Del(key)
}
@@ -160,8 +145,6 @@ func Pairs(kv ...string) (Metadata, bool) {
return nil, false
}
md := New(len(kv) / 2)
for idx := 0; idx < len(kv); idx += 2 {
md[kv[idx]] = kv[idx+1]
}
md.Set(kv...)
return md, true
}

View File

@@ -32,8 +32,8 @@ func TestMultipleUsage(t *testing.T) {
m.Del("key1_3")
return ctx
}(ctx)
_ = imd
_ = omd
t.Logf("imd %#+v", imd)
t.Logf("omd %#+v", omd)
}
func TestMetadataSetMultiple(t *testing.T) {

View File

@@ -15,15 +15,6 @@ 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 {

View File

@@ -119,7 +119,7 @@ func Tracer(t tracer.Tracer) Option {
// NewOptions returns network default options
func NewOptions(opts ...Option) Options {
options := Options{
ID: id.MustNew(),
ID: id.Must(),
Name: "go.micro",
Address: ":0",
Logger: logger.DefaultLogger,

View File

@@ -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.MustNew(),
ID: id.Must(),
Address: DefaultAddress,
Token: DefaultToken,
Logger: logger.DefaultLogger,

View File

@@ -15,15 +15,6 @@ 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 {

View File

@@ -1,9 +1,12 @@
package register
import (
"fmt"
"reflect"
"unicode"
"unicode/utf8"
"go.unistack.org/micro/v3/metadata"
)
// ExtractValue from reflect.Type from specified depth
@@ -35,6 +38,53 @@ 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

View File

@@ -2,6 +2,8 @@ package register
import (
"context"
"reflect"
"testing"
)
type TestHandler struct{}
@@ -13,3 +15,40 @@ 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)
}
}

View File

@@ -23,10 +23,11 @@ type node struct {
}
type record struct {
Name string
Version string
Metadata map[string]string
Nodes map[string]*node
Name string
Version string
Metadata map[string]string
Nodes map[string]*node
Endpoints []*register.Endpoint
}
type memory struct {
@@ -58,7 +59,7 @@ func (m *memory) ttlPrune() {
for range prune.C {
m.Lock()
for namespace, services := range m.records {
for domain, services := range m.records {
for service, versions := range services {
for version, record := range versions {
for id, n := range record.Nodes {
@@ -66,7 +67,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[namespace][service][version].Nodes, id)
delete(m.records[domain][service][version].Nodes, id)
}
}
}
@@ -130,12 +131,17 @@ 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.Namespace]
srvs, ok := m.records[options.Domain]
if !ok {
srvs = make(services)
}
s.Namespace = options.Namespace
// 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
}
// ensure the service name exists
r := serviceToRecord(s, options.TTL)
@@ -148,8 +154,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.Namespace] = srvs
go m.sendEvent(&register.Result{Action: register.EventCreate, Service: s})
m.records[options.Domain] = srvs
go m.sendEvent(&register.Result{Action: "create", Service: s})
}
var addedNodes bool
@@ -167,6 +173,9 @@ 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: &register.Node{
@@ -185,7 +194,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(&register.Result{Action: register.EventUpdate, Service: s})
go m.sendEvent(&register.Result{Action: "update", Service: s})
} else {
// refresh TTL and timestamp
for _, n := range s.Nodes {
@@ -197,7 +206,7 @@ func (m *memory) Register(_ context.Context, s *register.Service, opts ...regist
}
}
m.records[options.Namespace] = srvs
m.records[options.Domain] = srvs
return nil
}
@@ -207,8 +216,15 @@ 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.Namespace]
services, ok := m.records[options.Domain]
if !ok {
return nil
}
@@ -237,16 +253,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.Namespace][s.Name][s.Version] = version
go m.sendEvent(&register.Result{Action: register.EventUpdate, Service: s})
m.records[options.Domain][s.Name][s.Version] = version
go m.sendEvent(&register.Result{Action: "update", 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.Namespace], s.Name)
go m.sendEvent(&register.Result{Action: register.EventDelete, Service: s})
delete(m.records[options.Domain], s.Name)
go m.sendEvent(&register.Result{Action: "delete", Service: s})
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed service: %s", s.Name))
@@ -255,8 +271,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.Namespace][s.Name], s.Version)
go m.sendEvent(&register.Result{Action: register.EventDelete, Service: s})
delete(m.records[options.Domain][s.Name], s.Version)
go m.sendEvent(&register.Result{Action: "delete", Service: s})
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed service: %s, version: %s", s.Name, s.Version))
}
@@ -268,15 +284,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.Namespace == register.WildcardNamespace {
if options.Domain == register.WildcardDomain {
m.RLock()
recs := m.records
m.RUnlock()
var services []*register.Service
for namespace := range recs {
srvs, err := m.LookupService(ctx, name, append(opts, register.LookupNamespace(namespace))...)
for domain := range recs {
srvs, err := m.LookupService(ctx, name, append(opts, register.LookupDomain(domain))...)
if err == register.ErrNotFound {
continue
} else if err != nil {
@@ -295,7 +311,7 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...registe
defer m.RUnlock()
// check the domain exists
services, ok := m.records[options.Namespace]
services, ok := m.records[options.Domain]
if !ok {
return nil, register.ErrNotFound
}
@@ -312,7 +328,7 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...registe
var i int
for _, r := range versions {
result[i] = recordToService(r, options.Namespace)
result[i] = recordToService(r, options.Domain)
i++
}
@@ -323,15 +339,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.Namespace == register.WildcardNamespace {
if options.Domain == register.WildcardDomain {
m.RLock()
recs := m.records
m.RUnlock()
var services []*register.Service
for namespace := range recs {
srvs, err := m.ListServices(ctx, append(opts, register.ListNamespace(namespace))...)
for domain := range recs {
srvs, err := m.ListServices(ctx, append(opts, register.ListDomain(domain))...)
if err != nil {
return nil, err
}
@@ -345,7 +361,7 @@ func (m *memory) ListServices(ctx context.Context, opts ...register.ListOption)
defer m.RUnlock()
// ensure the domain exists
services, ok := m.records[options.Namespace]
services, ok := m.records[options.Domain]
if !ok {
return make([]*register.Service, 0), nil
}
@@ -355,7 +371,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.Namespace))
result = append(result, recordToService(version, options.Domain))
}
}
@@ -410,13 +426,16 @@ func (m *watcher) Next() (*register.Result, error) {
continue
}
namespace := register.DefaultNamespace
if r.Service.Namespace != "" {
namespace = r.Service.Namespace
// 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
}
// only send the event if watching the wildcard or this specific domain
if m.wo.Namespace == register.WildcardNamespace || m.wo.Namespace == namespace {
if m.wo.Domain == register.WildcardDomain || m.wo.Domain == domain {
return r, nil
}
case <-m.exit:
@@ -435,6 +454,11 @@ 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{
@@ -444,19 +468,42 @@ 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,
Nodes: nodes,
Name: s.Name,
Version: s.Version,
Metadata: metadata,
Nodes: nodes,
Endpoints: endpoints,
}
}
func recordToService(r *record, namespace string) *register.Service {
func recordToService(r *record, domain string) *register.Service {
metadata := make(map[string]string, len(r.Metadata))
for k, v := range r.Metadata {
metadata[k] = v
}
// set the domain in metadata so it can be determined when a wildcard query is performed
metadata["domain"] = domain
endpoints := make([]*register.Endpoint, len(r.Endpoints))
for i, e := range r.Endpoints {
md := make(map[string]string, len(e.Metadata))
for k, v := range e.Metadata {
md[k] = v
}
endpoints[i] = &register.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 {
@@ -476,7 +523,8 @@ func recordToService(r *record, namespace string) *register.Service {
return &register.Service{
Name: r.Name,
Version: r.Version,
Metadata: metadata,
Endpoints: endpoints,
Nodes: nodes,
Namespace: namespace,
}
}

View File

@@ -253,32 +253,32 @@ func TestMemoryWildcard(t *testing.T) {
testSrv := &register.Service{Name: "foo", Version: "1.0.0"}
if err := m.Register(ctx, testSrv, register.RegisterNamespace("one")); err != nil {
if err := m.Register(ctx, testSrv, register.RegisterDomain("one")); err != nil {
t.Fatalf("Register err: %v", err)
}
if err := m.Register(ctx, testSrv, register.RegisterNamespace("two")); err != nil {
if err := m.Register(ctx, testSrv, register.RegisterDomain("two")); err != nil {
t.Fatalf("Register err: %v", err)
}
if recs, err := m.ListServices(ctx, register.ListNamespace("one")); err != nil {
if recs, err := m.ListServices(ctx, register.ListDomain("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.ListNamespace("*")); err != nil {
if recs, err := m.ListServices(ctx, register.ListDomain("*")); 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.LookupNamespace("one")); err != nil {
if recs, err := m.LookupService(ctx, testSrv.Name, register.LookupDomain("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.LookupNamespace("*")); err != nil {
if recs, err := m.LookupService(ctx, testSrv.Name, register.LookupDomain("*")); err != nil {
t.Errorf("Lookup err: %v", err)
} else if len(recs) != 2 {
t.Errorf("Expected 2 records, got %v", len(recs))

View File

@@ -5,7 +5,6 @@ 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"
@@ -27,8 +26,6 @@ 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
}
@@ -40,7 +37,6 @@ func NewOptions(opts ...Option) Options {
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Context: context.Background(),
Codec: codec.NewCodec(),
}
for _, o := range opts {
o(&options)
@@ -50,17 +46,17 @@ func NewOptions(opts ...Option) Options {
// RegisterOptions holds options for register method
type RegisterOptions struct { // nolint: golint,revive
Context context.Context
Namespace string
TTL time.Duration
Attempts int
Context context.Context
Domain string
TTL time.Duration
Attempts int
}
// NewRegisterOptions returns register options struct filled by opts
func NewRegisterOptions(opts ...RegisterOption) RegisterOptions {
options := RegisterOptions{
Namespace: DefaultNamespace,
Context: context.Background(),
Domain: DefaultDomain,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
@@ -76,15 +72,15 @@ type WatchOptions struct {
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
// Namespace to watch
Namespace string
// Domain to watch
Domain string
}
// NewWatchOptions returns watch options filled by opts
func NewWatchOptions(opts ...WatchOption) WatchOptions {
options := WatchOptions{
Namespace: DefaultNamespace,
Context: context.Background(),
Domain: DefaultDomain,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
@@ -95,8 +91,8 @@ func NewWatchOptions(opts ...WatchOption) WatchOptions {
// DeregisterOptions holds options for deregister method
type DeregisterOptions struct {
Context context.Context
// Namespace the service was registered in
Namespace string
// Domain the service was registered in
Domain string
// Atempts specify max attempts for deregister
Attempts int
}
@@ -104,8 +100,8 @@ type DeregisterOptions struct {
// NewDeregisterOptions returns options for deregister filled by opts
func NewDeregisterOptions(opts ...DeregisterOption) DeregisterOptions {
options := DeregisterOptions{
Namespace: DefaultNamespace,
Context: context.Background(),
Domain: DefaultDomain,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
@@ -116,15 +112,15 @@ func NewDeregisterOptions(opts ...DeregisterOption) DeregisterOptions {
// LookupOptions holds lookup options
type LookupOptions struct {
Context context.Context
// Namespace to scope the request to
Namespace string
// Domain to scope the request to
Domain string
}
// NewLookupOptions returns lookup options filled by opts
func NewLookupOptions(opts ...LookupOption) LookupOptions {
options := LookupOptions{
Namespace: DefaultNamespace,
Context: context.Background(),
Domain: DefaultDomain,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
@@ -134,17 +130,16 @@ 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
// Namespace to scope the request to
Namespace string
// Domain to scope the request to
Domain string
}
// NewListOptions returns list options filled by opts
func NewListOptions(opts ...ListOption) ListOptions {
options := ListOptions{
Namespace: DefaultNamespace,
Context: context.Background(),
Domain: DefaultDomain,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
@@ -222,10 +217,10 @@ func RegisterContext(ctx context.Context) RegisterOption { // nolint: golint,rev
}
}
// RegisterNamespace secifies register Namespace
func RegisterNamespace(d string) RegisterOption { // nolint: golint,revive
// RegisterDomain secifies register domain
func RegisterDomain(d string) RegisterOption { // nolint: golint,revive
return func(o *RegisterOptions) {
o.Namespace = d
o.Domain = d
}
}
@@ -243,10 +238,10 @@ func WatchContext(ctx context.Context) WatchOption {
}
}
// WatchNamespace sets the Namespace for watch
func WatchNamespace(d string) WatchOption {
// WatchDomain sets the domain for watch
func WatchDomain(d string) WatchOption {
return func(o *WatchOptions) {
o.Namespace = d
o.Domain = d
}
}
@@ -264,10 +259,10 @@ func DeregisterContext(ctx context.Context) DeregisterOption {
}
}
// DeregisterNamespace specifies deregister Namespace
func DeregisterNamespace(d string) DeregisterOption {
// DeregisterDomain specifies deregister domain
func DeregisterDomain(d string) DeregisterOption {
return func(o *DeregisterOptions) {
o.Namespace = d
o.Domain = d
}
}
@@ -278,10 +273,10 @@ func LookupContext(ctx context.Context) LookupOption {
}
}
// LookupNamespace sets the Namespace for lookup
func LookupNamespace(d string) LookupOption {
// LookupDomain sets the domain for lookup
func LookupDomain(d string) LookupOption {
return func(o *LookupOptions) {
o.Namespace = d
o.Domain = d
}
}
@@ -292,10 +287,10 @@ func ListContext(ctx context.Context) ListOption {
}
}
// ListNamespace sets the Namespace for list method
func ListNamespace(d string) ListOption {
// ListDomain sets the domain for list method
func ListDomain(d string) ListOption {
return func(o *ListOptions) {
o.Namespace = d
o.Domain = d
}
}
@@ -305,9 +300,3 @@ func Name(n string) Option {
o.Name = n
}
}
func Codec(c codec.Codec) Option {
return func(o *Options) {
o.Codec = c
}
}

View File

@@ -9,12 +9,12 @@ import (
)
const (
// WildcardNamespace indicates any Namespace
WildcardNamespace = "*"
// WildcardDomain indicates any domain
WildcardDomain = "*"
)
// DefaultNamespace to use if none was provided in options
var DefaultNamespace = "micro"
// DefaultDomain to use if none was provided in options
var DefaultDomain = "micro"
var (
// DefaultRegister is the global default register
@@ -59,17 +59,26 @@ type Register interface {
// Service holds service register info
type Service struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Nodes []*Node `json:"nodes,omitempty"`
Namespace string `json:"namespace,omitempty"`
Name string `json:"name"`
Version string `json:"version"`
Metadata metadata.Metadata `json:"metadata"`
Endpoints []*Endpoint `json:"endpoints"`
Nodes []*Node `json:"nodes"`
}
// Node holds node register info
type Node struct {
Metadata metadata.Metadata `json:"metadata,omitempty"`
ID string `json:"id,omitempty"`
Address string `json:"address,omitempty"`
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"`
}
// Option func signature

View File

@@ -15,31 +15,31 @@ type Watcher interface {
// the watcher. Actions can be create, update, delete
type Result struct {
// Service holds register service
Service *Service `json:"service,omitempty"`
Service *Service
// Action holds the action
Action EventType `json:"action,omitempty"`
Action string
}
// EventType defines register event type
type EventType int
const (
// 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
// 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
)
// String returns human readable event type
func (t EventType) String() string {
switch t {
case EventCreate:
case Create:
return "create"
case EventDelete:
case Delete:
return "delete"
case EventUpdate:
case Update:
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 `json:"timestamp,omitempty"`
Timestamp time.Time
// Service is register service
Service *Service `json:"service,omitempty"`
Service *Service
// ID is register id
ID string `json:"id,omitempty"`
ID string
// Type defines type of event
Type EventType `json:"type,omitempty"`
Type EventType
}

View File

@@ -15,15 +15,6 @@ 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 {

View File

@@ -80,7 +80,7 @@ func Name(n string) Option {
// NewOptions returns router default options
func NewOptions(opts ...Option) Options {
options := Options{
ID: id.MustNew(),
ID: id.Must(),
Network: DefaultNetwork,
Register: register.DefaultRegister,
Logger: logger.DefaultLogger,

View File

@@ -1,18 +0,0 @@
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"
)

View File

@@ -15,15 +15,6 @@ 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 {

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"reflect"
"runtime/debug"
"sort"
"strings"
"sync"
"time"
@@ -31,21 +32,38 @@ const (
)
type rpcHandler struct {
opts HandlerOptions
handler interface{}
name string
opts HandlerOptions
handler interface{}
name string
endpoints []*register.Endpoint
}
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,
opts: options,
name: name,
handler: handler,
endpoints: endpoints,
opts: options,
}
}
@@ -57,6 +75,10 @@ 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
}
@@ -227,6 +249,35 @@ 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()
@@ -525,6 +576,7 @@ func (n *noopServer) Stop() error {
}
func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
var endpoints []*register.Endpoint
var handlers []*handler
options := NewSubscriberOptions(opts...)
@@ -543,7 +595,18 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
}
handlers = append(handlers, h)
ep := &register.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{
@@ -559,6 +622,14 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
}
handlers = append(handlers, h)
ep := &register.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)
}
}
@@ -568,6 +639,7 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
topic: topic,
subscriber: sub,
handlers: handlers,
endpoints: endpoints,
opts: options,
}
}
@@ -651,7 +723,7 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
return nil
}
opts.Hooks.EachPrev(func(hook options.Hook) {
opts.Hooks.EachNext(func(hook options.Hook) {
if h, ok := hook.(HookSubHandler); ok {
fn = h(fn)
}
@@ -694,6 +766,10 @@ 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
}
@@ -704,7 +780,8 @@ type subscriber struct {
typ reflect.Type
subscriber interface{}
handlers []*handler
endpoints []*register.Endpoint
handlers []*handler
rcvr reflect.Value
opts SubscriberOptions

View File

@@ -9,7 +9,6 @@ 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"
)
@@ -85,40 +84,3 @@ 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")
}
}

View File

@@ -100,7 +100,7 @@ func NewOptions(opts ...Option) Options {
Address: DefaultAddress,
Name: DefaultName,
Version: DefaultVersion,
ID: id.MustNew(),
ID: id.Must(),
Namespace: DefaultNamespace,
GracefulTimeout: DefaultGracefulTimeout,
}

View File

@@ -17,7 +17,7 @@ var (
opts := []register.RegisterOption{
register.RegisterTTL(config.RegisterTTL),
register.RegisterNamespace(config.Namespace),
register.RegisterDomain(config.Namespace),
}
for i := 0; i <= config.RegisterAttempts; i++ {
@@ -36,7 +36,7 @@ var (
var err error
opts := []register.DeregisterOption{
register.DeregisterNamespace(config.Namespace),
register.DeregisterDomain(config.Namespace),
}
for i := 0; i <= config.DeregisterAttempts; i++ {
@@ -82,8 +82,9 @@ func NewRegisterService(s Server) (*register.Service, error) {
node.Metadata["register"] = opts.Register.String()
return &register.Service{
Name: opts.Name,
Version: opts.Version,
Nodes: []*register.Node{node},
Name: opts.Name,
Version: opts.Version,
Nodes: []*register.Node{node},
Metadata: metadata.New(0),
}, nil
}

View File

@@ -7,6 +7,7 @@ import (
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/register"
)
// DefaultServer default server
@@ -169,6 +170,7 @@ type Stream interface {
type Handler interface {
Name() string
Handler() interface{}
Endpoints() []*register.Endpoint
Options() HandlerOptions
}
@@ -178,5 +180,6 @@ type Handler interface {
type Subscriber interface {
Topic() string
Subscriber() interface{}
Endpoints() []*register.Endpoint
Options() SubscriberOptions
}

View File

@@ -15,15 +15,6 @@ 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 {

View File

@@ -123,7 +123,7 @@ func (m *memoryStore) Init(opts ...store.Option) error {
m.funcList = m.fnList
m.funcDelete = m.fnDelete
m.opts.Hooks.EachPrev(func(hook options.Hook) {
m.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case store.HookRead:
m.funcRead = h(m.funcRead)

View File

@@ -54,7 +54,7 @@ func (n *noopStore) Init(opts ...Option) error {
n.funcList = n.fnList
n.funcDelete = n.fnDelete
n.opts.Hooks.EachPrev(func(hook options.Hook) {
n.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookRead:
n.funcRead = h(n.funcRead)

View File

@@ -18,15 +18,6 @@ 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 {
@@ -37,15 +28,6 @@ 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 {

View File

@@ -25,7 +25,6 @@ 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(),
}
@@ -38,14 +37,6 @@ 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
}
@@ -61,6 +52,14 @@ 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
@@ -68,8 +67,8 @@ type Span struct {
statusMsg string
startTime time.Time
finishTime time.Time
traceID memoryStringer
spanID memoryStringer
traceID noopStringer
spanID noopStringer
events []*Event
labels []interface{}
logs []interface{}

View File

@@ -2,7 +2,6 @@ package tracer
import (
"context"
"time"
"go.unistack.org/micro/v3/util/id"
)
@@ -21,18 +20,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,
startTime: time.Now(),
labels: options.Labels,
kind: options.Kind,
name: name,
ctx: ctx,
tracer: t,
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
}
@@ -59,18 +58,23 @@ 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
startTime time.Time
finishTime time.Time
traceID noopStringer
spanID noopStringer
labels []interface{}
kind SpanKind
status SpanStatus
ctx context.Context
tracer Tracer
name string
statusMsg string
traceID noopStringer
spanID noopStringer
events []*noopEvent
labels []interface{}
logs []interface{}
kind SpanKind
status SpanStatus
}
func (s *noopSpan) TraceID() string {
@@ -82,7 +86,6 @@ func (s *noopSpan) SpanID() string {
}
func (s *noopSpan) Finish(_ ...SpanOption) {
s.finishTime = time.Now()
}
func (s *noopSpan) Context() context.Context {
@@ -94,6 +97,8 @@ 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) {
@@ -101,6 +106,7 @@ func (s *noopSpan) SetName(name string) {
}
func (s *noopSpan) AddLogs(kv ...interface{}) {
s.logs = append(s.logs, kv...)
}
func (s *noopSpan) AddLabels(kv ...interface{}) {

27
util/buf/buf.go Normal file
View File

@@ -0,0 +1,27 @@
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}
}

View File

@@ -1,85 +0,0 @@
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
}

View File

@@ -1,22 +0,0 @@
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")
}
}

View File

@@ -1,78 +0,0 @@
package buffer
import "io"
var _ interface {
io.ReadCloser
io.ReadSeeker
} = (*SeekerBuffer)(nil)
// Buffer is a ReadWriteCloser that supports seeking. It's intended to
// replicate the functionality of bytes.Buffer that I use in my projects.
//
// Note that the seeking is limited to the read marker; all writes are
// append-only.
type SeekerBuffer struct {
data []byte
pos int64
}
func NewSeekerBuffer(data []byte) *SeekerBuffer {
return &SeekerBuffer{
data: data,
}
}
func (b *SeekerBuffer) Read(p []byte) (int, error) {
if b.pos >= int64(len(b.data)) {
return 0, io.EOF
}
n := copy(p, b.data[b.pos:])
b.pos += int64(n)
return n, nil
}
func (b *SeekerBuffer) Write(p []byte) (int, error) {
b.data = append(b.data, p...)
return len(p), nil
}
// Seek sets the read pointer to pos.
func (b *SeekerBuffer) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekStart:
b.pos = offset
case io.SeekEnd:
b.pos = int64(len(b.data)) + offset
case io.SeekCurrent:
b.pos += offset
}
return b.pos, nil
}
// Rewind resets the read pointer to 0.
func (b *SeekerBuffer) Rewind() error {
if _, err := b.Seek(0, io.SeekStart); err != nil {
return err
}
return nil
}
// Close clears all the data out of the buffer and sets the read position to 0.
func (b *SeekerBuffer) Close() error {
b.data = nil
b.pos = 0
return nil
}
// Len returns the length of data remaining to be read.
func (b *SeekerBuffer) Len() int {
return len(b.data[b.pos:])
}
// Bytes returns the underlying bytes from the current position.
func (b *SeekerBuffer) Bytes() []byte {
return b.data[b.pos:]
}

View File

@@ -1,55 +0,0 @@
package buffer
import (
"fmt"
"strings"
"testing"
)
func noErrorT(t *testing.T, err error) {
if nil != err {
t.Fatalf("%s", err)
}
}
func boolT(t *testing.T, cond bool, s ...string) {
if !cond {
what := strings.Join(s, ", ")
if len(what) > 0 {
what = ": " + what
}
t.Fatalf("assert.Bool failed%s", what)
}
}
func TestSeeking(t *testing.T) {
partA := []byte("hello, ")
partB := []byte("world!")
buf := NewSeekerBuffer(partA)
boolT(t, buf.Len() == len(partA), fmt.Sprintf("on init: have length %d, want length %d", buf.Len(), len(partA)))
b := make([]byte, 32)
n, err := buf.Read(b)
noErrorT(t, err)
boolT(t, buf.Len() == 0, fmt.Sprintf("after reading 1: have length %d, want length 0", buf.Len()))
boolT(t, n == len(partA), fmt.Sprintf("after reading 2: have length %d, want length %d", n, len(partA)))
n, err = buf.Write(partB)
noErrorT(t, err)
boolT(t, n == len(partB), fmt.Sprintf("after writing: have length %d, want length %d", n, len(partB)))
n, err = buf.Read(b)
noErrorT(t, err)
boolT(t, buf.Len() == 0, fmt.Sprintf("after rereading 1: have length %d, want length 0", buf.Len()))
boolT(t, n == len(partB), fmt.Sprintf("after rereading 2: have length %d, want length %d", n, len(partB)))
partsLen := len(partA) + len(partB)
_ = buf.Rewind()
boolT(t, buf.Len() == partsLen, fmt.Sprintf("after rewinding: have length %d, want length %d", buf.Len(), partsLen))
buf.Close()
boolT(t, buf.Len() == 0, fmt.Sprintf("after closing, have length %d, want length 0", buf.Len()))
}

22
util/id/LICENSE Normal file
View File

@@ -0,0 +1,22 @@
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.

View File

@@ -1,159 +1,112 @@
package id
import (
"context"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"time"
"math"
uuidv8 "github.com/ash3in/uuidv8"
"github.com/google/uuid"
nanoid "github.com/matoous/go-nanoid"
"go.unistack.org/micro/v3/logger"
)
var generatedNode [6]byte
// DefaultAlphabet is the alphabet used for ID characters by default
var DefaultAlphabet = []rune("6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz")
func init() {
if _, err := rand.Read(generatedNode[:]); err != nil {
panic(err)
}
}
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
// DefaultSize is the size used for ID by default
// To get uuid like collision specify 21
var DefaultNanoidSize = 16
var DefaultSize = 16
type Generator struct {
opts Options
}
func (g *Generator) MustNew() string {
id, err := g.New()
if err != nil {
panic(err)
// 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
}
}
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")
return 0
}
// New returns new id or error
func New(opts ...Option) (string, error) {
options := NewOptions(opts...)
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")
}
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)
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")
}
return "", errors.New("invalid option, Type unspecified")
}
chars := options.Alphabet
func ToUUID(s string) uuid.UUID {
return uuid.MustParse(s)
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
}
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
}
}
}
}
}
// Must is the same as New but fatals on error
func MustNew(opts ...Option) string {
func Must(opts ...Option) string {
id, err := New(opts...)
if err != nil {
panic(err)
logger.DefaultLogger.Fatal(context.TODO(), "Must call is failed", err)
}
return id
}
// Options contains id deneration options
type Options struct {
Type Type
NanoidAlphabet string
NanoidSize int
UUIDNode [6]byte
Alphabet []rune
Size int
}
// Option func signature
type Option func(*Options)
// WithNanoidAlphabet specifies alphabet to use
func WithNanoidAlphabet(alphabet string) Option {
// Alphabet specifies alphabet to use
func Alphabet(alphabet string) Option {
return func(o *Options) {
o.NanoidAlphabet = alphabet
o.Alphabet = []rune(alphabet)
}
}
// WithNanoidSize specifies generated id size
func WithNanoidSize(size int) Option {
// Size specifies id size
func Size(size int) Option {
return func(o *Options) {
o.NanoidSize = size
}
}
// WithUUIDNode specifies node component for UUIDv8
func WithUUIDNode(node [6]byte) Option {
return func(o *Options) {
o.UUIDNode = node
o.Size = size
}
}
// NewOptions returns new Options struct filled by opts
func NewOptions(opts ...Option) Options {
options := Options{
Type: TypeUUIDv8,
NanoidAlphabet: DefaultNanoidAlphabet,
NanoidSize: DefaultNanoidSize,
UUIDNode: generatedNode,
Alphabet: DefaultAlphabet,
Size: DefaultSize,
}
for _, o := range opts {
o(&options)
}
return options
}

View File

@@ -1,20 +0,0 @@
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
}

View File

@@ -71,6 +71,14 @@ 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 := &register.Endpoint{}
*e = *ep
eps[j] = e
}
s.Endpoints = eps
return s
}

View File

@@ -113,7 +113,7 @@ func (b *Buffer) Stream() (<-chan *Entry, chan bool) {
defer b.Unlock()
entries := make(chan *Entry, 128)
id := id.MustNew()
id := id.Must()
stop := make(chan bool)
b.streams[id] = &Stream{

View File

@@ -1,6 +1,7 @@
package sort
import (
"fmt"
"sort"
)
@@ -8,7 +9,7 @@ import (
type byKey []interface{}
func (k byKey) Len() int { return len(k) / 2 }
func (k byKey) Less(i, j int) bool { return k[i*2].(string) < k[j*2].(string) }
func (k byKey) Less(i, j int) bool { return fmt.Sprintf("%s", k[i*2]) < fmt.Sprintf("%s", k[j*2]) }
func (k byKey) Swap(i, j int) {
k[i*2], k[j*2] = k[j*2], k[i*2]
k[i*2+1], k[j*2+1] = k[j*2+1], k[i*2+1]

View File

@@ -35,11 +35,11 @@ func TestUnmarshalYAML(t *testing.T) {
t.Fatalf("invalid duration %v != 10000000", v.TTL)
}
err = yaml.Unmarshal([]byte(`{"ttl":"1d"}`), v)
err = yaml.Unmarshal([]byte(`{"ttl":"1y"}`), v)
if err != nil {
t.Fatal(err)
} else if *(v.TTL) != 86400000000000 {
t.Fatalf("invalid duration %v != 86400000000000", *v.TTL)
} else if *(v.TTL) != 31622400000000000 {
t.Fatalf("invalid duration %v != 31622400000000000", v.TTL)
}
}
@@ -68,11 +68,11 @@ func TestUnmarshalJSON(t *testing.T) {
t.Fatalf("invalid duration %v != 10000000", v.TTL)
}
err = json.Unmarshal([]byte(`{"ttl":"1d"}`), v)
err = json.Unmarshal([]byte(`{"ttl":"1y"}`), v)
if err != nil {
t.Fatal(err)
} else if v.TTL != 86400000000000 {
t.Fatalf("invalid duration %v != 86400000000000", v.TTL)
} else if v.TTL != 31622400000000000 {
t.Fatalf("invalid duration %v != 31622400000000000", 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("1d")
td, err = ParseDuration("1y")
if err != nil {
t.Fatalf("ParseDuration error: %v", err)
}
if td.String() != "24h0m0s" {
t.Fatalf("ParseDuration 1d != 24h0m0s : %s", td.String())
if td.String() != "8784h0m0s" {
t.Fatalf("ParseDuration 1y != 8784h0m0s : %s", td.String())
}
}