Compare commits

..

No commits in common. "v3" and "v3.10.5" have entirely different histories.
v3 ... v3.10.5

180 changed files with 3970 additions and 6882 deletions

View File

@ -1,18 +0,0 @@
---
name: Bug report
about: For reporting bugs in micro
title: "[BUG]"
labels: ''
assignees: ''
---
**Describe the bug**
1. What are you trying to do?
2. What did you expect to happen?
3. What happens instead?
**How to reproduce the bug:**
If possible, please include a minimal code snippet here.

View File

@ -1,17 +0,0 @@
---
name: Feature request / Enhancement
about: If you have a need not served by micro
title: "[FEATURE]"
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,8 +0,0 @@
---
name: Question
about: Ask a question about micro
title: ''
labels: ''
assignees: ''
---

View File

@ -1,28 +0,0 @@
name: "autoapprove"
on:
pull_request_target:
types: [assigned, opened, synchronize, reopened]
workflow_run:
workflows: ["prbuild"]
types:
- completed
permissions:
pull-requests: write
contents: write
jobs:
autoapprove:
runs-on: ubuntu-latest
steps:
- name: approve
run: [ "curl -o tea https://dl.gitea.com/tea/main/tea-main-linux-amd64",
"chmod +x ./tea",
"./tea login add --name unistack --token ${{ secrets.GITHUB_TOKEN }} --url https://git.unistack.org",
"./tea pr --repo ${{ github.event.repository.name }}"
]
if: github.actor == 'vtolstov'
id: approve
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

@ -1,23 +0,0 @@
name: pr
on:
pull_request:
branches:
- master
- v3
jobs:
test:
name: test
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
- name: setup-go
uses: actions/setup-go@v3
with:
go-version: 1.21
- name: deps
run: go get -v -t -d ./...
- name: test
env:
INTEGRATION_TESTS: yes
run: go test -mod readonly -v ./...

View File

@ -1,9 +0,0 @@
## Pull Request template
Please, go through these steps before clicking submit on this PR.
1. Give a descriptive title to your PR.
2. Provide a description of your changes.
3. Make sure you have some relevant tests.
4. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if applicable).
**PLEASE REMOVE THIS TEMPLATE BEFORE SUBMITTING**

View File

@ -37,4 +37,11 @@ jobs:
uses: golangci/golangci-lint-action@v3.4.0 uses: golangci/golangci-lint-action@v3.4.0
continue-on-error: true continue-on-error: true
with: with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.30 version: v1.30
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true

3
.gitignore vendored
View File

@ -1,8 +1,6 @@
# Develop tools # Develop tools
/.vscode/ /.vscode/
/.idea/ /.idea/
.idea
.vscode
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe *.exe
@ -15,7 +13,6 @@
_obj _obj
_test _test
_build _build
.DS_Store
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]

182
api/api.go Normal file
View File

@ -0,0 +1,182 @@
package api // import "go.unistack.org/micro/v3/api"
import (
"errors"
"regexp"
"strings"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/server"
)
// nolint: revive
// Api interface
type Api interface {
// Initialise options
Init(...Option) error
// Get the options
Options() Options
// Register a http handler
Register(*Endpoint) error
// Register a route
Deregister(*Endpoint) error
// Implementation of api
String() string
}
// Options holds the options
type Options struct{}
// Option func signature
type Option func(*Options) error
// Endpoint is a mapping between an RPC method and HTTP endpoint
type Endpoint struct {
// Name Greeter.Hello
Name string
// Desciption for endpoint
Description string
// Handler e.g rpc, proxy
Handler string
// Body destination
// "*" or "" - top level message value
// "string" - inner message value
Body string
// Host e.g example.com
Host []string
// Method e.g GET, POST
Method []string
// Path e.g /greeter. Expect POSIX regex
Path []string
// Stream flag
Stream bool
}
// Service represents an API service
type Service struct {
// Name of service
Name string
// Endpoint for this service
Endpoint *Endpoint
// Services that provides service
Services []*register.Service
}
// Encode encodes an endpoint to endpoint metadata
func Encode(e *Endpoint) map[string]string {
if e == nil {
return nil
}
// endpoint map
ep := make(map[string]string)
// set vals only if they exist
set := func(k, v string) {
if len(v) == 0 {
return
}
ep[k] = v
}
set("endpoint", e.Name)
set("description", e.Description)
set("handler", e.Handler)
set("method", strings.Join(e.Method, ","))
set("path", strings.Join(e.Path, ","))
set("host", strings.Join(e.Host, ","))
set("body", e.Body)
return ep
}
// Decode decodes endpoint metadata into an endpoint
func Decode(e metadata.Metadata) *Endpoint {
if e == nil {
return nil
}
ep := &Endpoint{}
ep.Name, _ = e.Get("endpoint")
ep.Description, _ = e.Get("description")
epmethod, _ := e.Get("method")
ep.Method = []string{epmethod}
eppath, _ := e.Get("path")
ep.Path = []string{eppath}
ephost, _ := e.Get("host")
ep.Host = []string{ephost}
ep.Handler, _ = e.Get("handler")
ep.Body, _ = e.Get("body")
return ep
}
// Validate validates an endpoint to guarantee it won't blow up when being served
func Validate(e *Endpoint) error {
if e == nil {
return errors.New("endpoint is nil")
}
if len(e.Name) == 0 {
return errors.New("name required")
}
for _, p := range e.Path {
ps := p[0]
pe := p[len(p)-1]
switch {
case ps == '^' && pe == '$':
if _, err := regexp.CompilePOSIX(p); err != nil {
return err
}
case ps == '^' && pe != '$':
return errors.New("invalid path")
case ps != '^' && pe == '$':
return errors.New("invalid path")
}
}
if len(e.Handler) == 0 {
return errors.New("invalid handler")
}
return nil
}
/*
Design ideas
// Gateway is an api gateway interface
type Gateway interface {
// Register a http handler
Handle(pattern string, http.Handler)
// Register a route
RegisterRoute(r Route)
// Init initialises the command line.
// It also parses further options.
Init(...Option) error
// Run the gateway
Run() error
}
// NewGateway returns a new api gateway
func NewGateway() Gateway {
return newGateway()
}
*/
// WithEndpoint returns a server.HandlerOption with endpoint metadata set
//
// Usage:
//
// proto.RegisterHandler(service.Server(), new(Handler), api.WithEndpoint(
// &api.Endpoint{
// Name: "Greeter.Hello",
// Path: []string{"/greeter"},
// },
// ))
func WithEndpoint(e *Endpoint) server.HandlerOption {
return server.EndpointMetadata(e.Name, Encode(e))
}

245
api/api_test.go Normal file
View File

@ -0,0 +1,245 @@
package api
import (
"strings"
"testing"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/server"
)
func TestDecode(t *testing.T) {
md := metadata.New(0)
md.Set("host", "localhost", "method", "GET", "path", "/")
ep := Decode(md)
if md == nil {
t.Fatalf("failed to decode md %#+v", md)
} else if len(ep.Host) != 1 || len(ep.Method) != 1 || len(ep.Path) != 1 {
t.Fatalf("ep invalid after decode %#+v", ep)
}
if ep.Host[0] != "localhost" || ep.Method[0] != "GET" || ep.Path[0] != "/" {
t.Fatalf("ep invalid after decode %#+v", ep)
}
}
//nolint:gocyclo
func TestEncode(t *testing.T) {
testData := []*Endpoint{
nil,
{
Name: "Foo.Bar",
Description: "A test endpoint",
Handler: "meta",
Host: []string{"foo.com"},
Method: []string{"GET"},
Path: []string{"/test"},
},
}
compare := func(expect, got []string) bool {
// no data to compare, return true
if len(expect) == 0 && len(got) == 0 {
return true
}
// no data expected but got some return false
if len(expect) == 0 && len(got) > 0 {
return false
}
// compare expected with what we got
for _, e := range expect {
var seen bool
for _, g := range got {
if e == g {
seen = true
break
}
}
if !seen {
return false
}
}
// we're done, return true
return true
}
for _, d := range testData {
// encode
e := Encode(d)
// decode
de := Decode(e)
// nil endpoint returns nil
if d == nil {
if e != nil {
t.Fatalf("expected nil got %v", e)
}
if de != nil {
t.Fatalf("expected nil got %v", de)
}
continue
}
// check encoded map
name := e["endpoint"]
desc := e["description"]
method := strings.Split(e["method"], ",")
path := strings.Split(e["path"], ",")
host := strings.Split(e["host"], ",")
handler := e["handler"]
if name != d.Name {
t.Fatalf("expected %v got %v", d.Name, name)
}
if desc != d.Description {
t.Fatalf("expected %v got %v", d.Description, desc)
}
if handler != d.Handler {
t.Fatalf("expected %v got %v", d.Handler, handler)
}
if ok := compare(d.Method, method); !ok {
t.Fatalf("expected %v got %v", d.Method, method)
}
if ok := compare(d.Path, path); !ok {
t.Fatalf("expected %v got %v", d.Path, path)
}
if ok := compare(d.Host, host); !ok {
t.Fatalf("expected %v got %v", d.Host, host)
}
if de.Name != d.Name {
t.Fatalf("expected %v got %v", d.Name, de.Name)
}
if de.Description != d.Description {
t.Fatalf("expected %v got %v", d.Description, de.Description)
}
if de.Handler != d.Handler {
t.Fatalf("expected %v got %v", d.Handler, de.Handler)
}
if ok := compare(d.Method, de.Method); !ok {
t.Fatalf("expected %v got %v", d.Method, de.Method)
}
if ok := compare(d.Path, de.Path); !ok {
t.Fatalf("expected %v got %v", d.Path, de.Path)
}
if ok := compare(d.Host, de.Host); !ok {
t.Fatalf("expected %v got %v", d.Host, de.Host)
}
}
}
func TestValidate(t *testing.T) {
epPcre := &Endpoint{
Name: "Foo.Bar",
Description: "A test endpoint",
Handler: "meta",
Host: []string{"foo.com"},
Method: []string{"GET"},
Path: []string{"^/test/?$"},
}
if err := Validate(epPcre); err != nil {
t.Fatal(err)
}
epGpath := &Endpoint{
Name: "Foo.Bar",
Description: "A test endpoint",
Handler: "meta",
Host: []string{"foo.com"},
Method: []string{"GET"},
Path: []string{"/test/{id}"},
}
if err := Validate(epGpath); err != nil {
t.Fatal(err)
}
epPcreInvalid := &Endpoint{
Name: "Foo.Bar",
Description: "A test endpoint",
Handler: "meta",
Host: []string{"foo.com"},
Method: []string{"GET"},
Path: []string{"/test/?$"},
}
if err := Validate(epPcreInvalid); err == nil {
t.Fatalf("invalid pcre %v", epPcreInvalid.Path[0])
}
}
func TestWithEndpoint(t *testing.T) {
ep := &Endpoint{
Name: "Foo.Bar",
Description: "A test endpoint",
Handler: "meta",
Host: []string{"foo.com"},
Method: []string{"GET"},
Path: []string{"/test/{id}"},
}
o := WithEndpoint(ep)
opts := server.NewHandlerOptions(o)
if opts.Metadata == nil {
t.Fatalf("WithEndpoint not works %#+v", opts)
}
md, ok := opts.Metadata[ep.Name]
if !ok {
t.Fatalf("WithEndpoint not works %#+v", opts)
}
if v, ok := md.Get("Endpoint"); !ok || v != "Foo.Bar" {
t.Fatalf("WithEndpoint not works %#+v", md)
}
}
func TestValidateNilErr(t *testing.T) {
var ep *Endpoint
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}
func TestValidateMissingNameErr(t *testing.T) {
ep := &Endpoint{}
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}
func TestValidateMissingHandlerErr(t *testing.T) {
ep := &Endpoint{Name: "test"}
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}
func TestValidateRegexpStartErr(t *testing.T) {
ep := &Endpoint{Name: "test", Handler: "test"}
ep.Path = []string{"^/"}
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}
func TestValidateRegexpEndErr(t *testing.T) {
ep := &Endpoint{Name: "test", Handler: "test", Path: []string{""}}
ep.Path[0] = "/$"
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}
func TestValidateRegexpNonErr(t *testing.T) {
ep := &Endpoint{Name: "test", Handler: "test", Path: []string{""}}
ep.Path[0] = "^/(.*)$"
if err := Validate(ep); err != nil {
t.Fatalf("Validate not works")
}
}
func TestValidateRegexpErr(t *testing.T) {
ep := &Endpoint{Name: "test", Handler: "test", Path: []string{""}}
ep.Path[0] = "^/(.$"
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}

View File

@ -1,25 +1,21 @@
// Package broker is an interface used for asynchronous messaging // Package broker is an interface used for asynchronous messaging
package broker package broker // import "go.unistack.org/micro/v3/broker"
import ( import (
"context" "context"
"errors" "errors"
"time"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
// DefaultBroker default memory broker // DefaultBroker default memory broker
var DefaultBroker Broker = NewBroker() var DefaultBroker = NewBroker()
var ( var (
// ErrNotConnected returns when broker used but not connected yet // ErrNotConnected returns when broker used but not connected yet
ErrNotConnected = errors.New("broker not connected") ErrNotConnected = errors.New("broker not connected")
// ErrDisconnected returns when broker disconnected // ErrDisconnected returns when broker disconnected
ErrDisconnected = errors.New("broker disconnected") ErrDisconnected = errors.New("broker disconnected")
// DefaultGracefulTimeout
DefaultGracefulTimeout = 5 * time.Second
) )
// Broker is an interface used for asynchronous messaging. // Broker is an interface used for asynchronous messaging.
@ -48,17 +44,6 @@ type Broker interface {
String() string String() string
} }
type (
FuncPublish func(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error
HookPublish func(next FuncPublish) FuncPublish
FuncBatchPublish func(ctx context.Context, msgs []*Message, opts ...PublishOption) error
HookBatchPublish func(next FuncBatchPublish) FuncBatchPublish
FuncSubscribe func(ctx context.Context, topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)
HookSubscribe func(next FuncSubscribe) FuncSubscribe
FuncBatchSubscribe func(ctx context.Context, topic string, h BatchHandler, opts ...SubscribeOption) (Subscriber, error)
HookBatchSubscribe func(next FuncBatchSubscribe) FuncBatchSubscribe
)
// Handler is used to process messages via a subscription of a topic. // Handler is used to process messages via a subscription of a topic.
type Handler func(Event) error type Handler func(Event) error
@ -88,8 +73,6 @@ type BatchHandler func(Events) error
// Event is given to a subscription handler for processing // Event is given to a subscription handler for processing
type Event interface { type Event interface {
// Context return context.Context for event
Context() context.Context
// Topic returns event topic // Topic returns event topic
Topic() string Topic() string
// Message returns broker message // Message returns broker message
@ -102,12 +85,33 @@ type Event interface {
SetError(err error) SetError(err error)
} }
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding.
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m *RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return *m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("RawMessage UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
// Message is used to transfer data // Message is used to transfer data
type Message struct { type Message struct {
// Header contains message metadata // Header contains message metadata
Header metadata.Metadata Header metadata.Metadata
// Body contains message body // Body contains message body
Body codec.RawMessage Body RawMessage
} }
// NewMessage create broker message with topic filled // NewMessage create broker message with topic filled

View File

@ -4,10 +4,8 @@ import (
"context" "context"
"sync" "sync"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/options"
maddr "go.unistack.org/micro/v3/util/addr" maddr "go.unistack.org/micro/v3/util/addr"
"go.unistack.org/micro/v3/util/id" "go.unistack.org/micro/v3/util/id"
mnet "go.unistack.org/micro/v3/util/net" mnet "go.unistack.org/micro/v3/util/net"
@ -15,13 +13,9 @@ import (
) )
type memoryBroker struct { type memoryBroker struct {
funcPublish broker.FuncPublish
funcBatchPublish broker.FuncBatchPublish
funcSubscribe broker.FuncSubscribe
funcBatchSubscribe broker.FuncBatchSubscribe
subscribers map[string][]*memorySubscriber subscribers map[string][]*memorySubscriber
addr string addr string
opts broker.Options opts Options
sync.RWMutex sync.RWMutex
connected bool connected bool
} }
@ -30,20 +24,20 @@ type memoryEvent struct {
err error err error
message interface{} message interface{}
topic string topic string
opts broker.Options opts Options
} }
type memorySubscriber struct { type memorySubscriber struct {
ctx context.Context ctx context.Context
exit chan bool exit chan bool
handler broker.Handler handler Handler
batchhandler broker.BatchHandler batchhandler BatchHandler
id string id string
topic string topic string
opts broker.SubscribeOptions opts SubscribeOptions
} }
func (m *memoryBroker) Options() broker.Options { func (m *memoryBroker) Options() Options {
return m.opts return m.opts
} }
@ -52,12 +46,6 @@ func (m *memoryBroker) Address() string {
} }
func (m *memoryBroker) Connect(ctx context.Context) error { func (m *memoryBroker) Connect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
@ -82,12 +70,6 @@ func (m *memoryBroker) Connect(ctx context.Context) error {
} }
func (m *memoryBroker) Disconnect(ctx context.Context) error { func (m *memoryBroker) Disconnect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
@ -99,54 +81,27 @@ func (m *memoryBroker) Disconnect(ctx context.Context) error {
return nil return nil
} }
func (m *memoryBroker) Init(opts ...broker.Option) error { func (m *memoryBroker) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&m.opts) o(&m.opts)
} }
m.funcPublish = m.fnPublish
m.funcBatchPublish = m.fnBatchPublish
m.funcSubscribe = m.fnSubscribe
m.funcBatchSubscribe = m.fnBatchSubscribe
m.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case broker.HookPublish:
m.funcPublish = h(m.funcPublish)
case broker.HookBatchPublish:
m.funcBatchPublish = h(m.funcBatchPublish)
case broker.HookSubscribe:
m.funcSubscribe = h(m.funcSubscribe)
case broker.HookBatchSubscribe:
m.funcBatchSubscribe = h(m.funcBatchSubscribe)
}
})
return nil return nil
} }
func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *broker.Message, opts ...broker.PublishOption) error { func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error {
return m.funcPublish(ctx, topic, msg, opts...)
}
func (m *memoryBroker) fnPublish(ctx context.Context, topic string, msg *broker.Message, opts ...broker.PublishOption) error {
msg.Header.Set(metadata.HeaderTopic, topic) msg.Header.Set(metadata.HeaderTopic, topic)
return m.publish(ctx, []*broker.Message{msg}, opts...) return m.publish(ctx, []*Message{msg}, opts...)
} }
func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error { func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
return m.funcBatchPublish(ctx, msgs, opts...)
}
func (m *memoryBroker) fnBatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
return m.publish(ctx, msgs, opts...) return m.publish(ctx, msgs, opts...)
} }
func (m *memoryBroker) publish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error { func (m *memoryBroker) publish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
m.RLock() m.RLock()
if !m.connected { if !m.connected {
m.RUnlock() m.RUnlock()
return broker.ErrNotConnected return ErrNotConnected
} }
m.RUnlock() m.RUnlock()
@ -156,9 +111,9 @@ func (m *memoryBroker) publish(ctx context.Context, msgs []*broker.Message, opts
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
default: default:
options := broker.NewPublishOptions(opts...) options := NewPublishOptions(opts...)
msgTopicMap := make(map[string]broker.Events) msgTopicMap := make(map[string]Events)
for _, v := range msgs { for _, v := range msgs {
p := &memoryEvent{opts: m.opts} p := &memoryEvent{opts: m.opts}
@ -206,7 +161,7 @@ func (m *memoryBroker) publish(ctx context.Context, msgs []*broker.Message, opts
} }
} else if sub.opts.AutoAck { } else if sub.opts.AutoAck {
if err = ms.Ack(); err != nil { if err = ms.Ack(); err != nil {
m.opts.Logger.Error(m.opts.Context, "broker ack error", err) m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
} }
} }
// single processing // single processing
@ -217,11 +172,11 @@ func (m *memoryBroker) publish(ctx context.Context, msgs []*broker.Message, opts
if eh != nil { if eh != nil {
_ = eh(p) _ = eh(p)
} else if m.opts.Logger.V(logger.ErrorLevel) { } else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, "broker handler error", err) m.opts.Logger.Error(m.opts.Context, err.Error())
} }
} else if sub.opts.AutoAck { } else if sub.opts.AutoAck {
if err = p.Ack(); err != nil { if err = p.Ack(); err != nil {
m.opts.Logger.Error(m.opts.Context, "broker ack error", err) m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
} }
} }
} }
@ -233,15 +188,11 @@ func (m *memoryBroker) publish(ctx context.Context, msgs []*broker.Message, opts
return nil return nil
} }
func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) { func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) {
return m.funcBatchSubscribe(ctx, topic, handler, opts...)
}
func (m *memoryBroker) fnBatchSubscribe(ctx context.Context, topic string, handler broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
m.RLock() m.RLock()
if !m.connected { if !m.connected {
m.RUnlock() m.RUnlock()
return nil, broker.ErrNotConnected return nil, ErrNotConnected
} }
m.RUnlock() m.RUnlock()
@ -250,7 +201,7 @@ func (m *memoryBroker) fnBatchSubscribe(ctx context.Context, topic string, handl
return nil, err return nil, err
} }
options := broker.NewSubscribeOptions(opts...) options := NewSubscribeOptions(opts...)
sub := &memorySubscriber{ sub := &memorySubscriber{
exit: make(chan bool, 1), exit: make(chan bool, 1),
@ -282,15 +233,11 @@ func (m *memoryBroker) fnBatchSubscribe(ctx context.Context, topic string, handl
return sub, nil return sub, nil
} }
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) { func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
return m.funcSubscribe(ctx, topic, handler, opts...)
}
func (m *memoryBroker) fnSubscribe(ctx context.Context, topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
m.RLock() m.RLock()
if !m.connected { if !m.connected {
m.RUnlock() m.RUnlock()
return nil, broker.ErrNotConnected return nil, ErrNotConnected
} }
m.RUnlock() m.RUnlock()
@ -299,7 +246,7 @@ func (m *memoryBroker) fnSubscribe(ctx context.Context, topic string, handler br
return nil, err return nil, err
} }
options := broker.NewSubscribeOptions(opts...) options := NewSubscribeOptions(opts...)
sub := &memorySubscriber{ sub := &memorySubscriber{
exit: make(chan bool, 1), exit: make(chan bool, 1),
@ -343,12 +290,12 @@ func (m *memoryEvent) Topic() string {
return m.topic return m.topic
} }
func (m *memoryEvent) Message() *broker.Message { func (m *memoryEvent) Message() *Message {
switch v := m.message.(type) { switch v := m.message.(type) {
case *broker.Message: case *Message:
return v return v
case []byte: case []byte:
msg := &broker.Message{} msg := &Message{}
if err := m.opts.Codec.Unmarshal(v, msg); err != nil { if err := m.opts.Codec.Unmarshal(v, msg); err != nil {
if m.opts.Logger.V(logger.ErrorLevel) { if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, "[memory]: failed to unmarshal: %v", err) m.opts.Logger.Error(m.opts.Context, "[memory]: failed to unmarshal: %v", err)
@ -373,11 +320,7 @@ func (m *memoryEvent) SetError(err error) {
m.err = err m.err = err
} }
func (m *memoryEvent) Context() context.Context { func (m *memorySubscriber) Options() SubscribeOptions {
return m.opts.Context
}
func (m *memorySubscriber) Options() broker.SubscribeOptions {
return m.opts return m.opts
} }
@ -391,9 +334,9 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
} }
// NewBroker return new memory broker // NewBroker return new memory broker
func NewBroker(opts ...broker.Option) broker.Broker { func NewBroker(opts ...Option) Broker {
return &memoryBroker{ return &memoryBroker{
opts: broker.NewOptions(opts...), opts: NewOptions(opts...),
subscribers: make(map[string][]*memorySubscriber), subscribers: make(map[string][]*memorySubscriber),
} }
} }

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"testing" "testing"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
@ -13,10 +12,6 @@ func TestMemoryBatchBroker(t *testing.T) {
b := NewBroker() b := NewBroker()
ctx := context.Background() ctx := context.Background()
if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error %v", err)
}
if err := b.Connect(ctx); err != nil { if err := b.Connect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err) t.Fatalf("Unexpected connect error %v", err)
} }
@ -24,7 +19,7 @@ func TestMemoryBatchBroker(t *testing.T) {
topic := "test" topic := "test"
count := 10 count := 10
fn := func(evts broker.Events) error { fn := func(evts Events) error {
return evts.Ack() return evts.Ack()
} }
@ -33,9 +28,9 @@ func TestMemoryBatchBroker(t *testing.T) {
t.Fatalf("Unexpected error subscribing %v", err) t.Fatalf("Unexpected error subscribing %v", err)
} }
msgs := make([]*broker.Message, 0, count) msgs := make([]*Message, 0, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
message := &broker.Message{ message := &Message{
Header: map[string]string{ Header: map[string]string{
metadata.HeaderTopic: topic, metadata.HeaderTopic: topic,
"foo": "bar", "foo": "bar",
@ -63,10 +58,6 @@ func TestMemoryBroker(t *testing.T) {
b := NewBroker() b := NewBroker()
ctx := context.Background() ctx := context.Background()
if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error %v", err)
}
if err := b.Connect(ctx); err != nil { if err := b.Connect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err) t.Fatalf("Unexpected connect error %v", err)
} }
@ -74,7 +65,7 @@ func TestMemoryBroker(t *testing.T) {
topic := "test" topic := "test"
count := 10 count := 10
fn := func(p broker.Event) error { fn := func(p Event) error {
return nil return nil
} }
@ -83,9 +74,9 @@ func TestMemoryBroker(t *testing.T) {
t.Fatalf("Unexpected error subscribing %v", err) t.Fatalf("Unexpected error subscribing %v", err)
} }
msgs := make([]*broker.Message, 0, count) msgs := make([]*Message, 0, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
message := &broker.Message{ message := &Message{
Header: map[string]string{ Header: map[string]string{
metadata.HeaderTopic: topic, metadata.HeaderTopic: topic,
"foo": "bar", "foo": "bar",

View File

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

View File

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

View File

@ -8,9 +8,7 @@ import (
"go.unistack.org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register" "go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/sync"
"go.unistack.org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
@ -38,13 +36,6 @@ type Options struct {
Name string Name string
// Addrs holds the broker address // Addrs holds the broker address
Addrs []string Addrs []string
// Wait waits for a collection of goroutines to finish
Wait *sync.WaitGroup
// GracefulTimeout contains time to wait to finish in flight requests
GracefulTimeout time.Duration
// Hooks can be run before broker Publish/BatchPublish and
// Subscribe/BatchSubscribe methods
Hooks options.Hooks
} }
// NewOptions create new Options // NewOptions create new Options
@ -56,7 +47,6 @@ func NewOptions(opts ...Option) Options {
Meter: meter.DefaultMeter, Meter: meter.DefaultMeter,
Codec: codec.DefaultCodec, Codec: codec.DefaultCodec,
Tracer: tracer.DefaultTracer, Tracer: tracer.DefaultTracer,
GracefulTimeout: DefaultGracefulTimeout,
} }
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
@ -234,13 +224,6 @@ func Name(n string) Option {
} }
} }
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}
// SubscribeContext set context // SubscribeContext set context
func SubscribeContext(ctx context.Context) SubscribeOption { func SubscribeContext(ctx context.Context) SubscribeOption {
return func(o *SubscribeOptions) { return func(o *SubscribeOptions) {

View File

@ -1,5 +1,5 @@
// Package client is an interface for an RPC client // Package client is an interface for an RPC client
package client package client // import "go.unistack.org/micro/v3/client"
import ( import (
"context" "context"
@ -44,17 +44,6 @@ type Client interface {
String() string String() string
} }
type (
FuncCall func(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
HookCall func(next FuncCall) FuncCall
FuncStream func(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
HookStream func(next FuncStream) FuncStream
FuncPublish func(ctx context.Context, msg Message, opts ...PublishOption) error
HookPublish func(next FuncPublish) FuncPublish
FuncBatchPublish func(ctx context.Context, msg []Message, opts ...PublishOption) error
HookBatchPublish func(next FuncBatchPublish) FuncBatchPublish
)
// Message is the interface for publishing asynchronously // Message is the interface for publishing asynchronously
type Message interface { type Message interface {
Topic() string Topic() string

View File

@ -0,0 +1,26 @@
package client
import (
"context"
"testing"
"time"
)
func TestNewClientCallOptions(t *testing.T) {
var flag bool
w := func(fn CallFunc) CallFunc {
flag = true
return fn
}
c := NewClientCallOptions(NewClient(),
WithAddress("127.0.0.1"),
WithCallWrapper(w),
WithRequestTimeout(1*time.Millisecond),
WithRetries(0),
WithBackoff(BackoffInterval(10*time.Millisecond, 100*time.Millisecond)),
)
_ = c.Call(context.TODO(), c.NewRequest("service", "endpoint", nil), nil)
if !flag {
t.Fatalf("NewClientCallOptions not works")
}
}

View File

@ -3,18 +3,13 @@ package client
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strconv"
"time" "time"
"go.unistack.org/micro/v3/broker" "go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/errors" "go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/selector" "go.unistack.org/micro/v3/selector"
"go.unistack.org/micro/v3/semconv"
"go.unistack.org/micro/v3/tracer"
) )
// DefaultCodecs will be used to encode/decode data // DefaultCodecs will be used to encode/decode data
@ -23,10 +18,6 @@ var DefaultCodecs = map[string]codec.Codec{
} }
type noopClient struct { type noopClient struct {
funcPublish FuncPublish
funcBatchPublish FuncBatchPublish
funcCall FuncCall
funcStream FuncStream
opts Options opts Options
} }
@ -48,14 +39,16 @@ type noopRequest struct {
// NewClient returns new noop client // NewClient returns new noop client
func NewClient(opts ...Option) Client { func NewClient(opts ...Option) Client {
n := &noopClient{opts: NewOptions(opts...)} nc := &noopClient{opts: NewOptions(opts...)}
// wrap in reverse
n.funcCall = n.fnCall c := Client(nc)
n.funcStream = n.fnStream
n.funcPublish = n.fnPublish
n.funcBatchPublish = n.fnBatchPublish
return n for i := len(nc.opts.Wrappers); i > 0; i-- {
c = nc.opts.Wrappers[i-1](c)
}
return c
} }
func (n *noopClient) Name() string { func (n *noopClient) Name() string {
@ -107,13 +100,10 @@ func (n *noopResponse) Read() ([]byte, error) {
return nil, nil return nil, nil
} }
type noopStream struct { type noopStream struct{}
err error
ctx context.Context
}
func (n *noopStream) Context() context.Context { func (n *noopStream) Context() context.Context {
return n.ctx return context.Background()
} }
func (n *noopStream) Request() Request { func (n *noopStream) Request() Request {
@ -141,21 +131,15 @@ func (n *noopStream) RecvMsg(interface{}) error {
} }
func (n *noopStream) Error() error { func (n *noopStream) Error() error {
return n.err return nil
} }
func (n *noopStream) Close() error { func (n *noopStream) Close() error {
if sp, ok := tracer.SpanFromContext(n.ctx); ok && sp != nil { return nil
if n.err != nil {
sp.SetStatus(tracer.SpanStatusError, n.err.Error())
}
sp.Finish()
}
return n.err
} }
func (n *noopStream) CloseSend() error { func (n *noopStream) CloseSend() error {
return n.err return nil
} }
func (n *noopMessage) Topic() string { func (n *noopMessage) Topic() string {
@ -188,25 +172,6 @@ func (n *noopClient) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
} }
n.funcCall = n.fnCall
n.funcStream = n.fnStream
n.funcPublish = n.fnPublish
n.funcBatchPublish = n.fnBatchPublish
n.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookCall:
n.funcCall = h(n.funcCall)
case HookStream:
n.funcStream = h(n.funcStream)
case HookPublish:
n.funcPublish = h(n.funcPublish)
case HookBatchPublish:
n.funcBatchPublish = h(n.funcBatchPublish)
}
})
return nil return nil
} }
@ -219,31 +184,6 @@ func (n *noopClient) String() string {
} }
func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error { func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error {
ts := time.Now()
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Inc()
var sp tracer.Span
ctx, sp = n.opts.Tracer.Start(ctx, req.Endpoint()+" rpc-client",
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels("endpoint", req.Endpoint()),
)
err := n.funcCall(ctx, req, rsp, opts...)
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Dec()
te := time.Since(ts)
n.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", req.Endpoint()).Update(te.Seconds())
n.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", req.Endpoint()).Update(te.Seconds())
if me := errors.FromError(err); me == nil {
sp.Finish()
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "success", "code", strconv.Itoa(int(200))).Inc()
} else {
sp.SetStatus(tracer.SpanStatusError, err.Error())
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "failure", "code", strconv.Itoa(int(me.Code))).Inc()
}
return err
}
func (n *noopClient) fnCall(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error {
// make a copy of call opts // make a copy of call opts
callOpts := n.opts.CallOptions callOpts := n.opts.CallOptions
for _, opt := range opts { for _, opt := range opts {
@ -272,8 +212,11 @@ func (n *noopClient) fnCall(ctx context.Context, req Request, rsp interface{}, o
} }
// make copy of call method // make copy of call method
hcall := func(ctx context.Context, addr string, req Request, rsp interface{}, opts CallOptions) error { hcall := n.call
return nil
// wrap the call in reverse
for i := len(callOpts.CallWrappers); i > 0; i-- {
hcall = callOpts.CallWrappers[i-1](hcall)
} }
// use the router passed as a call option, or fallback to the rpc clients router // use the router passed as a call option, or fallback to the rpc clients router
@ -372,6 +315,10 @@ func (n *noopClient) fnCall(ctx context.Context, req Request, rsp interface{}, o
return gerr return gerr
} }
func (n *noopClient) call(ctx context.Context, addr string, req Request, rsp interface{}, opts CallOptions) error {
return nil
}
func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request { func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request {
return &noopRequest{service: service, endpoint: endpoint} return &noopRequest{service: service, endpoint: endpoint}
} }
@ -382,31 +329,6 @@ func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOp
} }
func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) { func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) {
ts := time.Now()
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Inc()
var sp tracer.Span
ctx, sp = n.opts.Tracer.Start(ctx, req.Endpoint()+" rpc-client",
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels("endpoint", req.Endpoint()),
)
stream, err := n.funcStream(ctx, req, opts...)
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Dec()
te := time.Since(ts)
n.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", req.Endpoint()).Update(te.Seconds())
n.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", req.Endpoint()).Update(te.Seconds())
if me := errors.FromError(err); me == nil {
sp.Finish()
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "success", "code", strconv.Itoa(int(200))).Inc()
} else {
sp.SetStatus(tracer.SpanStatusError, err.Error())
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "failure", "code", strconv.Itoa(int(me.Code))).Inc()
}
return stream, err
}
func (n *noopClient) fnStream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) {
var err error var err error
// make a copy of call opts // make a copy of call opts
@ -547,22 +469,14 @@ func (n *noopClient) fnStream(ctx context.Context, req Request, opts ...CallOpti
} }
func (n *noopClient) stream(ctx context.Context, addr string, req Request, opts CallOptions) (Stream, error) { func (n *noopClient) stream(ctx context.Context, addr string, req Request, opts CallOptions) (Stream, error) {
return &noopStream{ctx: ctx}, nil return &noopStream{}, nil
} }
func (n *noopClient) BatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error { func (n *noopClient) BatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
return n.funcBatchPublish(ctx, ps, opts...)
}
func (n *noopClient) fnBatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
return n.publish(ctx, ps, opts...) return n.publish(ctx, ps, opts...)
} }
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error { func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error {
return n.funcPublish(ctx, p, opts...)
}
func (n *noopClient) fnPublish(ctx context.Context, p Message, opts ...PublishOption) error {
return n.publish(ctx, []Message{p}, opts...) return n.publish(ctx, []Message{p}, opts...)
} }
@ -571,34 +485,21 @@ func (n *noopClient) publish(ctx context.Context, ps []Message, opts ...PublishO
msgs := make([]*broker.Message, 0, len(ps)) msgs := make([]*broker.Message, 0, len(ps))
// get proxy for _, p := range ps {
exchange := "" md, ok := metadata.FromOutgoingContext(ctx)
if v, ok := os.LookupEnv("MICRO_PROXY"); ok { if !ok {
exchange = v md = metadata.New(0)
} }
md[metadata.HeaderContentType] = p.ContentType()
topic := p.Topic()
// get the exchange // get the exchange
if len(options.Exchange) > 0 { if len(options.Exchange) > 0 {
exchange = options.Exchange topic = options.Exchange
} }
omd, ok := metadata.FromOutgoingContext(ctx)
if !ok {
omd = metadata.New(0)
}
for _, p := range ps {
md := metadata.Copy(omd)
md[metadata.HeaderContentType] = p.ContentType()
topic := p.Topic()
if len(exchange) > 0 {
topic = exchange
}
md[metadata.HeaderTopic] = topic md[metadata.HeaderTopic] = topic
iter := p.Metadata().Iterator()
var k, v string
for iter.Next(&k, &v) {
md.Set(k, v)
}
var body []byte var body []byte
@ -623,13 +524,6 @@ func (n *noopClient) publish(ctx context.Context, ps []Message, opts ...PublishO
msgs = append(msgs, &broker.Message{Header: md, Body: body}) msgs = append(msgs, &broker.Message{Header: md, Body: body})
} }
if len(msgs) == 1 {
return n.opts.Broker.Publish(ctx, msgs[0].Header[metadata.HeaderTopic], msgs[0],
broker.PublishContext(options.Context),
broker.PublishBodyOnly(options.BodyOnly),
)
}
return n.opts.Broker.BatchPublish(ctx, msgs, return n.opts.Broker.BatchPublish(ctx, msgs,
broker.PublishContext(options.Context), broker.PublishContext(options.Context),
broker.PublishBodyOnly(options.BodyOnly), broker.PublishBodyOnly(options.BodyOnly),

View File

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

View File

@ -12,7 +12,6 @@ import (
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/network/transport" "go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register" "go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/router" "go.unistack.org/micro/v3/router"
"go.unistack.org/micro/v3/selector" "go.unistack.org/micro/v3/selector"
@ -60,9 +59,6 @@ type Options struct {
PoolTTL time.Duration PoolTTL time.Duration
// ContextDialer used to connect // ContextDialer used to connect
ContextDialer func(context.Context, string) (net.Conn, error) ContextDialer func(context.Context, string) (net.Conn, error)
// Hooks can be run before broker Publish/BatchPublish and
// Subscribe/BatchSubscribe methods
Hooks options.Hooks
} }
// NewCallOptions creates new call options struct // NewCallOptions creates new call options struct
@ -96,6 +92,8 @@ type CallOptions struct {
Address []string Address []string
// SelectOptions selector options // SelectOptions selector options
SelectOptions []selector.SelectOption SelectOptions []selector.SelectOption
// CallWrappers call wrappers
CallWrappers []CallWrapper
// StreamTimeout stream timeout // StreamTimeout stream timeout
StreamTimeout time.Duration StreamTimeout time.Duration
// RequestTimeout request timeout // RequestTimeout request timeout
@ -187,7 +185,7 @@ func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Context: context.Background(), Context: context.Background(),
ContentType: DefaultContentType, ContentType: DefaultContentType,
Codecs: DefaultCodecs, Codecs: make(map[string]codec.Codec),
CallOptions: CallOptions{ CallOptions: CallOptions{
Context: context.Background(), Context: context.Background(),
Backoff: DefaultBackoff, Backoff: DefaultBackoff,
@ -308,6 +306,20 @@ func Selector(s selector.Selector) Option {
} }
} }
// Wrap adds a wrapper to the list of options passed into the client
func Wrap(w Wrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
}
}
// WrapCall adds a wrapper to the list of CallFunc wrappers
func WrapCall(cw ...CallWrapper) Option {
return func(o *Options) {
o.CallOptions.CallWrappers = append(o.CallOptions.CallWrappers, cw...)
}
}
// Backoff is used to set the backoff function used when retrying Calls // Backoff is used to set the backoff function used when retrying Calls
func Backoff(fn BackoffFunc) Option { func Backoff(fn BackoffFunc) Option {
return func(o *Options) { return func(o *Options) {
@ -438,6 +450,13 @@ func WithAddress(a ...string) CallOption {
} }
} }
// WithCallWrapper is a CallOption which adds to the existing CallFunc wrappers
func WithCallWrapper(cw ...CallWrapper) CallOption {
return func(o *CallOptions) {
o.CallWrappers = append(o.CallWrappers, cw...)
}
}
// WithBackoff is a CallOption which overrides that which // WithBackoff is a CallOption which overrides that which
// set in Options.CallOptions // set in Options.CallOptions
func WithBackoff(fn BackoffFunc) CallOption { func WithBackoff(fn BackoffFunc) CallOption {
@ -572,10 +591,3 @@ func RequestContentType(ct string) RequestOption {
o.ContentType = ct o.ContentType = ct
} }
} }
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}

View File

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

View File

@ -1,8 +1,19 @@
// Package codec is an interface for encoding messages // Package codec is an interface for encoding messages
package codec package codec // import "go.unistack.org/micro/v3/codec"
import ( import (
"errors" "errors"
"io"
"go.unistack.org/micro/v3/metadata"
)
// Message types
const (
Error MessageType = iota
Request
Response
Event
) )
var ( var (
@ -13,44 +24,63 @@ var (
) )
var ( var (
// DefaultMaxMsgSize specifies how much data codec can handle
DefaultMaxMsgSize = 1024 * 1024 * 4 // 4Mb
// DefaultCodec is the global default codec // DefaultCodec is the global default codec
DefaultCodec = NewCodec() DefaultCodec = NewCodec()
// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal // DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal
DefaultTagName = "codec" DefaultTagName = "codec"
) )
// Codec encodes/decodes various types of messages. // MessageType specifies message type for codec
type MessageType int
// Codec encodes/decodes various types of messages used within micro.
// ReadHeader and ReadBody are called in pairs to read requests/responses
// from the connection. Close is called when finished with the
// connection. ReadBody may be called with a nil argument to force the
// body to be read and discarded.
type Codec interface { type Codec interface {
ReadHeader(r io.Reader, m *Message, mt MessageType) error
ReadBody(r io.Reader, v interface{}) error
Write(w io.Writer, m *Message, v interface{}) error
Marshal(v interface{}, opts ...Option) ([]byte, error) Marshal(v interface{}, opts ...Option) ([]byte, error)
Unmarshal(b []byte, v interface{}, opts ...Option) error Unmarshal(b []byte, v interface{}, opts ...Option) error
String() string String() string
} }
type CodecV2 interface { // Message represents detailed information about
Marshal(buf []byte, v interface{}, opts ...Option) ([]byte, error) // the communication, likely followed by the body.
Unmarshal(buf []byte, v interface{}, opts ...Option) error // In the case of an error, body may be nil.
String() string type Message struct {
Header metadata.Metadata
Target string
Method string
Endpoint string
Error string
ID string
Body []byte
Type MessageType
} }
// RawMessage is a raw encoded JSON value. // NewMessage creates new codec message
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding. func NewMessage(t MessageType) *Message {
type RawMessage []byte return &Message{Type: t, Header: metadata.New(0)}
}
// MarshalJSON returns m as the JSON encoding of m. // MarshalAppend calls codec.Marshal(v) and returns the data appended to buf.
func (m *RawMessage) MarshalJSON() ([]byte, error) { // If codec implements MarshalAppend, that is called instead.
if m == nil { func MarshalAppend(buf []byte, c Codec, v interface{}, opts ...Option) ([]byte, error) {
return []byte("null"), nil if nc, ok := c.(interface {
} else if len(*m) == 0 { MarshalAppend([]byte, interface{}, ...Option) ([]byte, error)
return []byte("null"), nil }); ok {
return nc.MarshalAppend(buf, v, opts...)
} }
return *m, nil
}
// UnmarshalJSON sets *m to a copy of data. mbuf, err := c.Marshal(v, opts...)
func (m *RawMessage) UnmarshalJSON(data []byte) error { if err != nil {
if m == nil { return nil, err
return errors.New("RawMessage UnmarshalJSON on nil pointer")
} }
*m = append((*m)[0:0], data...)
return nil return append(buf, mbuf...), nil
} }

View File

@ -2,14 +2,70 @@ package codec
import ( import (
"encoding/json" "encoding/json"
"io"
codecpb "go.unistack.org/micro-proto/v3/codec"
) )
type noopCodec struct { type noopCodec struct {
opts Options opts Options
} }
func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error {
return nil
}
func (c *noopCodec) ReadBody(conn io.Reader, b interface{}) error {
// read bytes
buf, err := io.ReadAll(conn)
if err != nil {
return err
}
if b == nil {
return nil
}
switch v := b.(type) {
case *string:
*v = string(buf)
case *[]byte:
*v = buf
case *Frame:
v.Data = buf
default:
return json.Unmarshal(buf, v)
}
return nil
}
func (c *noopCodec) Write(conn io.Writer, m *Message, b interface{}) error {
if b == nil {
return nil
}
var v []byte
switch vb := b.(type) {
case *Frame:
v = vb.Data
case string:
v = []byte(vb)
case *string:
v = []byte(*vb)
case *[]byte:
v = *vb
case []byte:
v = vb
default:
var err error
v, err = json.Marshal(vb)
if err != nil {
return err
}
}
_, err := conn.Write(v)
return err
}
func (c *noopCodec) String() string { func (c *noopCodec) String() string {
return "noop" return "noop"
} }
@ -35,8 +91,8 @@ func (c *noopCodec) Marshal(v interface{}, opts ...Option) ([]byte, error) {
return ve, nil return ve, nil
case *Frame: case *Frame:
return ve.Data, nil return ve.Data, nil
case *codecpb.Frame: case *Message:
return ve.Data, nil return ve.Body, nil
} }
return json.Marshal(v) return json.Marshal(v)
@ -59,8 +115,8 @@ func (c *noopCodec) Unmarshal(d []byte, v interface{}, opts ...Option) error {
case *Frame: case *Frame:
ve.Data = d ve.Data = d
return nil return nil
case *codecpb.Frame: case *Message:
ve.Data = d ve.Body = d
return nil return nil
} }

View File

@ -23,8 +23,15 @@ type Options struct {
Context context.Context Context context.Context
// TagName specifies tag name in struct to control codec // TagName specifies tag name in struct to control codec
TagName string TagName string
// Flatten specifies that struct must be analyzed for flatten tag // MaxMsgSize specifies max messages size that reads by codec
Flatten bool MaxMsgSize int
}
// MaxMsgSize sets the max message size
func MaxMsgSize(n int) Option {
return func(o *Options) {
o.MaxMsgSize = n
}
} }
// TagName sets the codec tag name in struct // TagName sets the codec tag name in struct
@ -34,13 +41,6 @@ func TagName(n string) Option {
} }
} }
// Flatten enables checking for flatten tag name
func Flatten(b bool) Option {
return func(o *Options) {
o.Flatten = b
}
}
// Logger sets the logger // Logger sets the logger
func Logger(l logger.Logger) Option { func Logger(l logger.Logger) Option {
return func(o *Options) { return func(o *Options) {
@ -69,8 +69,8 @@ func NewOptions(opts ...Option) Options {
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter, Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer, Tracer: tracer.DefaultTracer,
MaxMsgSize: DefaultMaxMsgSize,
TagName: DefaultTagName, TagName: DefaultTagName,
Flatten: false,
} }
for _, o := range opts { for _, o := range opts {

View File

@ -1,10 +1,9 @@
// Package config is an interface for dynamic configuration. // Package config is an interface for dynamic configuration.
package config package config // import "go.unistack.org/micro/v3/config"
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"reflect" "reflect"
"time" "time"
) )
@ -14,7 +13,7 @@ type Validator interface {
} }
// DefaultConfig default config // DefaultConfig default config
var DefaultConfig Config = NewConfig() var DefaultConfig = NewConfig()
// DefaultWatcherMinInterval default min interval for poll changes // DefaultWatcherMinInterval default min interval for poll changes
var DefaultWatcherMinInterval = 5 * time.Second var DefaultWatcherMinInterval = 5 * time.Second
@ -51,13 +50,6 @@ type Config interface {
String() string String() string
} }
type (
FuncLoad func(ctx context.Context, opts ...LoadOption) error
HookLoad func(next FuncLoad) FuncLoad
FuncSave func(ctx context.Context, opts ...SaveOption) error
HookSave func(next FuncSave) FuncSave
)
// Watcher is the config watcher // Watcher is the config watcher
type Watcher interface { type Watcher interface {
// Next blocks until update happens or error returned // Next blocks until update happens or error returned
@ -132,29 +124,11 @@ func Validate(ctx context.Context, cfg interface{}) error {
} }
var ( var (
// DefaultBeforeLoad default func that runs before config Load // DefaultAfterLoad default func that runs after config load
DefaultBeforeLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeLoad {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s BeforeLoad error", c.String()), err)
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
// DefaultAfterLoad default func that runs after config Load
DefaultAfterLoad = func(ctx context.Context, c Config) error { DefaultAfterLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterLoad { for _, fn := range c.Options().AfterLoad {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s AfterLoad error", c.String()), err) c.Options().Logger.Errorf(ctx, "%s AfterLoad err: %v", c.String(), err)
if !c.Options().AllowFail { if !c.Options().AllowFail {
return err return err
} }
@ -162,29 +136,11 @@ var (
} }
return nil return nil
} }
// DefaultBeforeSave default func that runs befora config Save // DefaultAfterSave default func that runs after config save
DefaultBeforeSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeSave {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s BeforeSave error", c.String()), err)
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
// DefaultAfterSave default func that runs after config Save
DefaultAfterSave = func(ctx context.Context, c Config) error { DefaultAfterSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterSave { for _, fn := range c.Options().AfterSave {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s AfterSave error", c.String()), err) c.Options().Logger.Errorf(ctx, "%s AfterSave err: %v", c.String(), err)
if !c.Options().AllowFail { if !c.Options().AllowFail {
return err return err
} }
@ -192,14 +148,11 @@ var (
} }
return nil return nil
} }
// DefaultBeforeInit default func that runs befora config Init // DefaultBeforeLoad default func that runs before config load
DefaultBeforeInit = func(ctx context.Context, c Config) error { DefaultBeforeLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeInit { for _, fn := range c.Options().BeforeLoad {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s BeforeInit error", c.String()), err) c.Options().Logger.Errorf(ctx, "%s BeforeLoad err: %v", c.String(), err)
if !c.Options().AllowFail { if !c.Options().AllowFail {
return err return err
} }
@ -207,14 +160,11 @@ var (
} }
return nil return nil
} }
// DefaultAfterInit default func that runs after config Init // DefaultBeforeSave default func that runs befora config save
DefaultAfterInit = func(ctx context.Context, c Config) error { DefaultBeforeSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterSave { for _, fn := range c.Options().BeforeSave {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s AfterInit error", c.String(), err), err) c.Options().Logger.Errorf(ctx, "%s BeforeSave err: %v", c.String(), err)
if !c.Options().AllowFail { if !c.Options().AllowFail {
return err return err
} }

View File

@ -5,19 +5,12 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"time"
"dario.cat/mergo" "github.com/imdario/mergo"
"github.com/google/uuid"
"go.unistack.org/micro/v3/options"
mid "go.unistack.org/micro/v3/util/id"
rutil "go.unistack.org/micro/v3/util/reflect" rutil "go.unistack.org/micro/v3/util/reflect"
mtime "go.unistack.org/micro/v3/util/time"
) )
type defaultConfig struct { type defaultConfig struct {
funcLoad FuncLoad
funcSave FuncSave
opts Options opts Options
} }
@ -29,42 +22,11 @@ func (c *defaultConfig) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&c.opts) o(&c.opts)
} }
if err := DefaultBeforeInit(c.opts.Context, c); err != nil && !c.opts.AllowFail {
return err
}
c.funcLoad = c.fnLoad
c.funcSave = c.fnSave
c.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookLoad:
c.funcLoad = h(c.funcLoad)
case HookSave:
c.funcSave = h(c.funcSave)
}
})
if err := DefaultAfterInit(c.opts.Context, c); err != nil && !c.opts.AllowFail {
return err
}
return nil return nil
} }
func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error { func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
return c.funcLoad(ctx, opts...) if err := DefaultBeforeLoad(ctx, c); err != nil {
}
func (c *defaultConfig) fnLoad(ctx context.Context, opts ...LoadOption) error {
var err error
if c.opts.SkipLoad != nil && c.opts.SkipLoad(ctx, c) {
return nil
}
if err = DefaultBeforeLoad(ctx, c); err != nil && !c.opts.AllowFail {
return err return err
} }
@ -87,20 +49,21 @@ func (c *defaultConfig) fnLoad(ctx context.Context, opts ...LoadOption) error {
if !c.opts.AllowFail { if !c.opts.AllowFail {
return err return err
} }
if err = DefaultAfterLoad(ctx, c); err != nil && !c.opts.AllowFail { return DefaultAfterLoad(ctx, c)
return err
}
} }
if err = fillValues(reflect.ValueOf(src), c.opts.StructTag); err == nil { if err = fillValues(reflect.ValueOf(src), c.opts.StructTag); err == nil {
err = mergo.Merge(dst, src, mopts...) err = mergo.Merge(dst, src, mopts...)
} }
if err != nil && !c.opts.AllowFail { if err != nil {
c.opts.Logger.Errorf(ctx, "default load error: %v", err)
if !c.opts.AllowFail {
return err return err
} }
}
if err := DefaultAfterLoad(ctx, c); err != nil && !c.opts.AllowFail { if err := DefaultAfterLoad(ctx, c); err != nil {
return err return err
} }
@ -112,7 +75,6 @@ func fillValue(value reflect.Value, val string) error {
if !rutil.IsEmpty(value) { if !rutil.IsEmpty(value) {
return nil return nil
} }
switch value.Kind() { switch value.Kind() {
case reflect.Map: case reflect.Map:
t := value.Type() t := value.Type()
@ -151,20 +113,6 @@ func fillValue(value reflect.Value, val string) error {
} }
value.Set(reflect.ValueOf(v)) value.Set(reflect.ValueOf(v))
case reflect.String: case reflect.String:
switch val {
case "micro:generate uuid":
uid, err := uuid.NewRandom()
if err != nil {
return err
}
val = uid.String()
case "micro:generate id":
uid, err := mid.New()
if err != nil {
return err
}
val = uid
}
value.Set(reflect.ValueOf(val)) value.Set(reflect.ValueOf(val))
case reflect.Float32: case reflect.Float32:
v, err := strconv.ParseFloat(val, 32) v, err := strconv.ParseFloat(val, 32)
@ -203,26 +151,11 @@ func fillValue(value reflect.Value, val string) error {
} }
value.Set(reflect.ValueOf(int32(v))) value.Set(reflect.ValueOf(int32(v)))
case reflect.Int64: case reflect.Int64:
switch {
case value.Type().String() == "time.Duration" && value.Type().PkgPath() == "time":
v, err := time.ParseDuration(val)
if err != nil {
return err
}
value.Set(reflect.ValueOf(v))
case value.Type().String() == "time.Duration" && value.Type().PkgPath() == "go.unistack.org/micro/v3/util/time":
v, err := mtime.ParseDuration(val)
if err != nil {
return err
}
value.SetInt(int64(v))
default:
v, err := strconv.ParseInt(val, 10, 64) v, err := strconv.ParseInt(val, 10, 64)
if err != nil { if err != nil {
return err return err
} }
value.Set(reflect.ValueOf(v)) value.Set(reflect.ValueOf(v))
}
case reflect.Uint: case reflect.Uint:
v, err := strconv.ParseUint(val, 10, 0) v, err := strconv.ParseUint(val, 10, 0)
if err != nil { if err != nil {
@ -254,7 +187,6 @@ func fillValue(value reflect.Value, val string) error {
} }
value.Set(reflect.ValueOf(v)) value.Set(reflect.ValueOf(v))
} }
return nil return nil
} }
@ -318,14 +250,6 @@ func fillValues(valueOf reflect.Value, tname string) error {
} }
func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error { func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error {
return c.funcSave(ctx, opts...)
}
func (c *defaultConfig) fnSave(ctx context.Context, opts ...SaveOption) error {
if c.opts.SkipSave != nil && c.opts.SkipSave(ctx, c) {
return nil
}
if err := DefaultBeforeSave(ctx, c); err != nil { if err := DefaultBeforeSave(ctx, c); err != nil {
return err return err
} }
@ -345,7 +269,7 @@ func (c *defaultConfig) Name() string {
return c.opts.Name return c.opts.Name
} }
func (c *defaultConfig) Watch(_ context.Context, _ ...WatchOption) (Watcher, error) { func (c *defaultConfig) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
return nil, ErrWatcherNotImplemented return nil, ErrWatcherNotImplemented
} }
@ -355,9 +279,5 @@ func NewConfig(opts ...Option) Config {
if len(options.StructTag) == 0 { if len(options.StructTag) == 0 {
options.StructTag = "default" options.StructTag = "default"
} }
c := &defaultConfig{opts: options} return &defaultConfig{opts: options}
c.funcLoad = c.fnLoad
c.funcSave = c.fnSave
return c
} }

View File

@ -4,11 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"time"
"go.unistack.org/micro/v3/config" "go.unistack.org/micro/v3/config"
mid "go.unistack.org/micro/v3/util/id"
mtime "go.unistack.org/micro/v3/util/time"
) )
type cfg struct { type cfg struct {
@ -16,11 +13,6 @@ type cfg struct {
IgnoreValue string `json:"-"` IgnoreValue string `json:"-"`
StructValue *cfgStructValue StructValue *cfgStructValue
IntValue int `default:"99"` IntValue int `default:"99"`
DurationValue time.Duration `default:"10s"`
MDurationValue mtime.Duration `default:"10s"`
MapValue map[string]bool `default:"key1=true,key2=false"`
UUIDValue string `default:"micro:generate uuid"`
IDValue string `default:"micro:generate id"`
} }
type cfgStructValue struct { type cfgStructValue struct {
@ -41,35 +33,6 @@ func (c *cfgStructValue) Validate() error {
return nil return nil
} }
type testHook struct {
f bool
}
func (t *testHook) Load(fn config.FuncLoad) config.FuncLoad {
return func(ctx context.Context, opts ...config.LoadOption) error {
t.f = true
return fn(ctx, opts...)
}
}
func TestHook(t *testing.T) {
h := &testHook{}
c := config.NewConfig(config.Struct(h), config.Hooks(config.HookLoad(h.Load)))
if err := c.Init(); err != nil {
t.Fatal(err)
}
if err := c.Load(context.TODO()); err != nil {
t.Fatal(err)
}
if !h.f {
t.Fatal("hook not works")
}
}
func TestDefault(t *testing.T) { func TestDefault(t *testing.T) {
ctx := context.Background() ctx := context.Background()
conf := &cfg{IntValue: 10} conf := &cfg{IntValue: 10}
@ -100,21 +63,6 @@ func TestDefault(t *testing.T) {
if conf.StringValue != "after_load" { if conf.StringValue != "after_load" {
t.Fatal("AfterLoad option not working") t.Fatal("AfterLoad option not working")
} }
if len(conf.MapValue) != 2 {
t.Fatalf("map value invalid: %#+v\n", conf.MapValue)
}
if conf.UUIDValue == "" {
t.Fatalf("uuid value empty")
} else if len(conf.UUIDValue) != 36 {
t.Fatalf("uuid value invalid: %s", conf.UUIDValue)
}
if conf.IDValue == "" {
t.Fatalf("id value empty")
} else if len(conf.IDValue) != mid.DefaultSize {
t.Fatalf("id value invalid: %s", conf.IDValue)
}
_ = conf _ = conf
// t.Logf("%#+v\n", conf) // t.Logf("%#+v\n", conf)
} }

View File

@ -7,7 +7,6 @@ import (
"go.unistack.org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
@ -29,26 +28,16 @@ type Options struct {
Name string Name string
// StructTag name // StructTag name
StructTag string StructTag string
// BeforeSave contains slice of funcs that runs before Save // BeforeSave contains slice of funcs that runs before save
BeforeSave []func(context.Context, Config) error BeforeSave []func(context.Context, Config) error
// AfterSave contains slice of funcs that runs after Save // AfterLoad contains slice of funcs that runs after load
AfterSave []func(context.Context, Config) error
// BeforeLoad contains slice of funcs that runs before Load
BeforeLoad []func(context.Context, Config) error
// AfterLoad contains slice of funcs that runs after Load
AfterLoad []func(context.Context, Config) error AfterLoad []func(context.Context, Config) error
// BeforeInit contains slice of funcs that runs before Init // BeforeLoad contains slice of funcs that runs before load
BeforeInit []func(context.Context, Config) error BeforeLoad []func(context.Context, Config) error
// AfterInit contains slice of funcs that runs after Init // AfterSave contains slice of funcs that runs after save
AfterInit []func(context.Context, Config) error AfterSave []func(context.Context, Config) error
// AllowFail flag to allow fail in config source // AllowFail flag to allow fail in config source
AllowFail bool AllowFail bool
// SkipLoad runs only if condition returns true
SkipLoad func(context.Context, Config) bool
// SkipSave runs only if condition returns true
SkipSave func(context.Context, Config) bool
// Hooks can be run before/after config Save/Load
Hooks options.Hooks
} }
// Option function signature // Option function signature
@ -75,9 +64,9 @@ type LoadOption func(o *LoadOptions)
// LoadOptions struct // LoadOptions struct
type LoadOptions struct { type LoadOptions struct {
Struct interface{} Struct interface{}
Context context.Context
Override bool Override bool
Append bool Append bool
Context context.Context
} }
// NewLoadOptions create LoadOptions struct with provided opts // NewLoadOptions create LoadOptions struct with provided opts
@ -142,20 +131,6 @@ func AllowFail(b bool) Option {
} }
} }
// BeforeInit run funcs before config Init
func BeforeInit(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.BeforeInit = fn
}
}
// AfterInit run funcs after config Init
func AfterInit(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.AfterInit = fn
}
}
// BeforeLoad run funcs before config load // BeforeLoad run funcs before config load
func BeforeLoad(fn ...func(context.Context, Config) error) Option { func BeforeLoad(fn ...func(context.Context, Config) error) Option {
return func(o *Options) { return func(o *Options) {
@ -291,10 +266,3 @@ func WatchStruct(src interface{}) WatchOption {
o.Struct = src o.Struct = src
} }
} }
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}

View File

@ -1,157 +0,0 @@
package database
import (
"crypto/tls"
"errors"
"fmt"
"net/url"
"strings"
)
var (
ErrInvalidDSNAddr = errors.New("invalid dsn addr")
ErrInvalidDSNUnescaped = errors.New("dsn must be escaped")
ErrInvalidDSNNoSlash = errors.New("dsn must contains slash")
)
type Config struct {
TLSConfig *tls.Config
Username string
Password string
Scheme string
Host string
Port string
Database string
Params []string
}
func (cfg *Config) FormatDSN() string {
var s strings.Builder
if len(cfg.Scheme) > 0 {
s.WriteString(cfg.Scheme + "://")
}
// [username[:password]@]
if len(cfg.Username) > 0 {
s.WriteString(cfg.Username)
if len(cfg.Password) > 0 {
s.WriteByte(':')
s.WriteString(url.PathEscape(cfg.Password))
}
s.WriteByte('@')
}
// [host:port]
if len(cfg.Host) > 0 {
s.WriteString(cfg.Host)
if len(cfg.Port) > 0 {
s.WriteByte(':')
s.WriteString(cfg.Port)
}
}
// /dbname
s.WriteByte('/')
s.WriteString(url.PathEscape(cfg.Database))
for i := 0; i < len(cfg.Params); i += 2 {
if i == 0 {
s.WriteString("?")
} else {
s.WriteString("&")
}
s.WriteString(cfg.Params[i])
s.WriteString("=")
s.WriteString(cfg.Params[i+1])
}
return s.String()
}
func ParseDSN(dsn string) (*Config, error) {
cfg := &Config{}
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find last '/' that goes before dbname
foundSlash := false
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] == '/' {
foundSlash = true
var j, k int
// left part is empty if i <= 0
if i > 0 {
// Find the first ':' in dsn
for j = i; j >= 0; j-- {
if dsn[j] == ':' {
cfg.Scheme = dsn[0:j]
}
}
// [username[:password]@][host]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the second ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
if cfg.Scheme == dsn[:k] {
continue
}
var err error
cfg.Password, err = url.PathUnescape(dsn[k+1 : j])
if err != nil {
return nil, err
}
break
}
}
cfg.Username = dsn[len(cfg.Scheme)+3 : k]
break
}
}
for k = j + 1; k < i; k++ {
if dsn[k] == ':' {
cfg.Host = dsn[j+1 : k]
cfg.Port = dsn[k+1 : i]
break
}
}
}
// dbname[?param1=value1&...&paramN=valueN]
// Find the first '?' in dsn[i+1:]
for j = i + 1; j < len(dsn); j++ {
if dsn[j] == '?' {
parts := strings.Split(dsn[j+1:], "&")
cfg.Params = make([]string, 0, len(parts)*2)
for _, p := range parts {
k, v, found := strings.Cut(p, "=")
if !found {
continue
}
cfg.Params = append(cfg.Params, k, v)
}
break
}
}
var err error
dbname := dsn[i+1 : j]
if cfg.Database, err = url.PathUnescape(dbname); err != nil {
return nil, fmt.Errorf("invalid dbname %q: %w", dbname, err)
}
break
}
}
if !foundSlash && len(dsn) > 0 {
return nil, ErrInvalidDSNNoSlash
}
return cfg, nil
}

View File

@ -1,31 +0,0 @@
package database
import (
"net/url"
"testing"
)
func TestParseDSN(t *testing.T) {
cfg, err := ParseDSN("postgres://username:p@ssword#@host:12345/dbname?key1=val2&key2=val2")
if err != nil {
t.Fatal(err)
}
if cfg.Password != "p@ssword#" {
t.Fatalf("parsing error")
}
}
func TestFormatDSN(t *testing.T) {
src := "postgres://username:p@ssword#@host:12345/dbname?key1=val2&key2=val2"
cfg, err := ParseDSN(src)
if err != nil {
t.Fatal(err)
}
dst, err := url.PathUnescape(cfg.FormatDSN())
if err != nil {
t.Fatal(err)
}
if src != dst {
t.Fatalf("\n%s\n%s", src, dst)
}
}

View File

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

View File

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

View File

@ -8,7 +8,6 @@ import (
) )
func TestDeps(t *testing.T) { func TestDeps(t *testing.T) {
t.Skip()
d := &dag.AcyclicGraph{} d := &dag.AcyclicGraph{}
v0 := d.Add(&node{"v0"}) v0 := d.Add(&node{"v0"})

View File

@ -188,7 +188,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
steps, err := w.getSteps(options.Start, options.Reverse) steps, err := w.getSteps(options.Start, options.Reverse)
if err != nil { if err != nil {
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil { if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
w.opts.Logger.Error(w.opts.Context, "store write error", werr) w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
} }
return "", err return "", err
} }
@ -212,7 +212,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
done := make(chan struct{}) done := make(chan struct{})
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil { if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
w.opts.Logger.Error(w.opts.Context, "store write error", werr) w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
return eid, werr return eid, werr
} }
for idx := range steps { for idx := range steps {
@ -237,7 +237,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
return return
} }
if w.opts.Logger.V(logger.TraceLevel) { if w.opts.Logger.V(logger.TraceLevel) {
w.opts.Logger.Trace(nctx, fmt.Sprintf("will be executed %v", steps[idx][nidx])) w.opts.Logger.Tracef(nctx, "will be executed %v", steps[idx][nidx])
} }
cstep := steps[idx][nidx] cstep := steps[idx][nidx]
// nolint: nestif // nolint: nestif
@ -257,21 +257,21 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
if serr != nil { if serr != nil {
step.SetStatus(StatusFailure) step.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Error(ctx, "store write error", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
} }
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Error(ctx, "store write error", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
} }
cherr <- serr cherr <- serr
return return
} }
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil {
w.opts.Logger.Error(ctx, "store write error", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr cherr <- werr
return return
} }
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Error(ctx, "store write error", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr cherr <- werr
return return
} }
@ -290,16 +290,16 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
if serr != nil { if serr != nil {
cstep.SetStatus(StatusFailure) cstep.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Error(ctx, "store write error", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
} }
if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Error(ctx, "store write error", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
} }
cherr <- serr cherr <- serr
return return
} }
if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil {
w.opts.Logger.Error(ctx, "store write error", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr cherr <- werr
return return
} }
@ -317,7 +317,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
return eid, nil return eid, nil
} }
logger.DefaultLogger.Trace(ctx, "wait for finish or error") logger.Tracef(ctx, "wait for finish or error")
select { select {
case <-nctx.Done(): case <-nctx.Done():
err = nctx.Err() err = nctx.Err()
@ -333,15 +333,15 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
switch { switch {
case nctx.Err() != nil: case nctx.Err() != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusAborted.String())}); werr != nil { if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusAborted.String())}); werr != nil {
w.opts.Logger.Error(w.opts.Context, "store write error", werr) w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
} }
case err == nil: case err == nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil { if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Error(w.opts.Context, "store write error", werr) w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
} }
case err != nil: case err != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil { if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil {
w.opts.Logger.Error(w.opts.Context, "store write error", werr) w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
} }
} }

View File

@ -1,5 +1,5 @@
// Package flow is an interface used for saga pattern microservice workflow // Package flow is an interface used for saga pattern microservice workflow
package flow package flow // import "go.unistack.org/micro/v3/flow"
import ( import (
"context" "context"

View File

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

View File

@ -17,7 +17,7 @@ func TestFSMStart(t *testing.T) {
wrapper := func(next StateFunc) StateFunc { wrapper := func(next StateFunc) StateFunc {
return func(sctx context.Context, s State, opts ...StateOption) (State, error) { return func(sctx context.Context, s State, opts ...StateOption) (State, error) {
sctx = logger.NewContext(sctx, logger.DefaultLogger.Fields("state", s.Name())) sctx = logger.NewContext(sctx, logger.Fields("state", s.Name()))
return next(sctx, s, opts...) return next(sctx, s, opts...)
} }
} }

33
go.mod
View File

@ -1,32 +1,13 @@
module go.unistack.org/micro/v3 module go.unistack.org/micro/v3
go 1.22 go 1.16
require ( require (
dario.cat/mergo v1.0.0 github.com/google/go-cmp v0.5.7 // indirect
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/imdario/mergo v0.3.13
github.com/KimMachineGun/automemlimit v0.6.1 github.com/kr/pretty v0.2.1 // indirect
github.com/google/uuid v1.3.0 github.com/kr/text v0.2.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35
go.uber.org/automaxprocs v1.6.0 go.unistack.org/micro-proto/v3 v3.3.1
go.unistack.org/micro-proto/v3 v3.4.1
golang.org/x/sync v0.3.0
google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.33.0
)
require (
github.com/cilium/ebpf v0.9.1 // indirect
github.com/containerd/cgroups/v3 v3.0.1 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/opencontainers/runtime-spec v1.0.2 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect
) )

214
go.sum
View File

@ -1,78 +1,160 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/KimMachineGun/automemlimit v0.6.1 h1:ILa9j1onAAMadBsyyUJv5cack8Y1WT26yLj/V+ulKp8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/KimMachineGun/automemlimit v0.6.1/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/containerd/cgroups/v3 v3.0.1 h1:4hfGvu8rfGIwVIDd+nLzn/B9ZXx4BcCjzt5ToenJRaE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containerd/cgroups/v3 v3.0.1/go.mod h1:/vtwk1VXrtoa5AaZLkypuOJgA/6DyPMZHJPGQNtlHnw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 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.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/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.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35 h1:4mohWoM/UGg1BvFFiqSPRl5uwJY3rVV0HQX0ETqauqQ=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 h1:G/FZtUu7a6NTWl3KUHMV9jkLAh/Rvtf03NWMHaEDl+E= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.unistack.org/micro-proto/v3 v3.3.1 h1:nQ0MtWvP2G3QrpOgawVOPhpZZYkq6umTGDqs8FxJYIo=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.unistack.org/micro-proto/v3 v3.3.1/go.mod h1:cwRyv8uInM2I7EbU7O8Fx2Ls3N90Uw9UCCcq4olOdfE=
go.unistack.org/micro-proto/v3 v3.4.1 h1:UTjLSRz2YZuaHk9iSlVqqsA50JQNAEK2ZFboGqtEa9Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
go.unistack.org/micro-proto/v3 v3.4.1/go.mod h1:okx/cnOhzuCX0ggl/vToatbCupi0O44diiiLLsZ93Zo= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -4,17 +4,6 @@ import "context"
type loggerKey struct{} type loggerKey struct{}
// MustContext returns logger from passed context or DefaultLogger if empty
func MustContext(ctx context.Context) Logger {
if ctx == nil {
return DefaultLogger
}
if l, ok := ctx.Value(loggerKey{}).(Logger); ok && l != nil {
return l
}
return DefaultLogger
}
// FromContext returns logger from passed context // FromContext returns logger from passed context
func FromContext(ctx context.Context) (Logger, bool) { func FromContext(ctx context.Context) (Logger, bool) {
if ctx == nil { if ctx == nil {

230
logger/default.go Normal file
View File

@ -0,0 +1,230 @@
package logger
import (
"context"
"encoding/json"
"fmt"
"os"
"runtime"
"strings"
"sync"
"time"
)
type defaultLogger struct {
enc *json.Encoder
opts Options
sync.RWMutex
}
// Init(opts...) should only overwrite provided options
func (l *defaultLogger) Init(opts ...Option) error {
l.Lock()
for _, o := range opts {
o(&l.opts)
}
l.enc = json.NewEncoder(l.opts.Out)
// wrap the Log func
l.Unlock()
return nil
}
func (l *defaultLogger) String() string {
return "micro"
}
func (l *defaultLogger) Clone(opts ...Option) Logger {
newopts := NewOptions(opts...)
oldopts := l.opts
for _, o := range opts {
o(&newopts)
o(&oldopts)
}
l.Lock()
cl := &defaultLogger{opts: oldopts, enc: json.NewEncoder(l.opts.Out)}
l.Unlock()
return cl
}
func (l *defaultLogger) V(level Level) bool {
l.RLock()
ok := l.opts.Level.Enabled(level)
l.RUnlock()
return ok
}
func (l *defaultLogger) Level(level Level) {
l.Lock()
l.opts.Level = level
l.Unlock()
}
func (l *defaultLogger) Fields(fields ...interface{}) Logger {
l.RLock()
nl := &defaultLogger{opts: l.opts, enc: l.enc}
if len(fields) == 0 {
l.RUnlock()
return nl
} else if len(fields)%2 != 0 {
fields = fields[:len(fields)-1]
}
nl.opts.Fields = copyFields(l.opts.Fields)
nl.opts.Fields = append(nl.opts.Fields, fields...)
l.RUnlock()
return nl
}
func copyFields(src []interface{}) []interface{} {
dst := make([]interface{}, len(src))
copy(dst, src)
return dst
}
// logCallerfilePath returns a package/file:line description of the caller,
// preserving only the leaf directory name and file name.
func logCallerfilePath(loggingFilePath string) string {
// To make sure we trim the path correctly on Windows too, we
// counter-intuitively need to use '/' and *not* os.PathSeparator here,
// because the path given originates from Go stdlib, specifically
// runtime.Caller() which (as of Mar/17) returns forward slashes even on
// Windows.
//
// See https://github.com/golang/go/issues/3335
// and https://github.com/golang/go/issues/18151
//
// for discussion on the issue on Go side.
idx := strings.LastIndexByte(loggingFilePath, '/')
if idx == -1 {
return loggingFilePath
}
idx = strings.LastIndexByte(loggingFilePath[:idx], '/')
if idx == -1 {
return loggingFilePath
}
return loggingFilePath[idx+1:]
}
func (l *defaultLogger) Info(ctx context.Context, args ...interface{}) {
l.Log(ctx, InfoLevel, args...)
}
func (l *defaultLogger) Error(ctx context.Context, args ...interface{}) {
l.Log(ctx, ErrorLevel, args...)
}
func (l *defaultLogger) Debug(ctx context.Context, args ...interface{}) {
l.Log(ctx, DebugLevel, args...)
}
func (l *defaultLogger) Warn(ctx context.Context, args ...interface{}) {
l.Log(ctx, WarnLevel, args...)
}
func (l *defaultLogger) Trace(ctx context.Context, args ...interface{}) {
l.Log(ctx, TraceLevel, args...)
}
func (l *defaultLogger) Fatal(ctx context.Context, args ...interface{}) {
l.Log(ctx, FatalLevel, args...)
os.Exit(1)
}
func (l *defaultLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, InfoLevel, msg, args...)
}
func (l *defaultLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, ErrorLevel, msg, args...)
}
func (l *defaultLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, DebugLevel, msg, args...)
}
func (l *defaultLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, WarnLevel, msg, args...)
}
func (l *defaultLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, TraceLevel, msg, args...)
}
func (l *defaultLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, FatalLevel, msg, args...)
os.Exit(1)
}
func (l *defaultLogger) Log(ctx context.Context, level Level, args ...interface{}) {
if !l.V(level) {
return
}
l.RLock()
fields := copyFields(l.opts.Fields)
l.RUnlock()
fields = append(fields, "level", level.String())
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
fields = append(fields, "caller", fmt.Sprintf("%s:%d", logCallerfilePath(file), line))
}
fields = append(fields, "timestamp", time.Now().Format("2006-01-02 15:04:05"))
if len(args) > 0 {
fields = append(fields, "msg", fmt.Sprint(args...))
}
out := make(map[string]interface{}, len(fields)/2)
for i := 0; i < len(fields); i += 2 {
out[fields[i].(string)] = fields[i+1]
}
l.RLock()
_ = l.enc.Encode(out)
l.RUnlock()
}
func (l *defaultLogger) Logf(ctx context.Context, level Level, msg string, args ...interface{}) {
if !l.V(level) {
return
}
l.RLock()
fields := copyFields(l.opts.Fields)
l.RUnlock()
fields = append(fields, "level", level.String())
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
fields = append(fields, "caller", fmt.Sprintf("%s:%d", logCallerfilePath(file), line))
}
fields = append(fields, "timestamp", time.Now().Format("2006-01-02 15:04:05"))
if len(args) > 0 {
fields = append(fields, "msg", fmt.Sprintf(msg, args...))
} else if msg != "" {
fields = append(fields, "msg", msg)
}
out := make(map[string]interface{}, len(fields)/2)
for i := 0; i < len(fields); i += 2 {
out[fields[i].(string)] = fields[i+1]
}
l.RLock()
_ = l.enc.Encode(out)
l.RUnlock()
}
func (l *defaultLogger) Options() Options {
return l.opts
}
// NewLogger builds a new logger based on options
func NewLogger(opts ...Option) Logger {
l := &defaultLogger{
opts: NewOptions(opts...),
}
l.enc = json.NewEncoder(l.opts.Out)
return l
}

View File

@ -1,19 +1,18 @@
// Package logger provides a log interface // Package logger provides a log interface
package logger package logger // import "go.unistack.org/micro/v3/logger"
import ( import (
"context" "context"
"os"
) )
type ContextAttrFunc func(ctx context.Context) []interface{}
var DefaultContextAttrFuncs []ContextAttrFunc
var ( var (
// DefaultLogger variable // DefaultLogger variable
DefaultLogger Logger = NewLogger() DefaultLogger = NewLogger(WithLevel(ParseLevel(os.Getenv("MICRO_LOG_LEVEL"))))
// DefaultLevel used by logger // DefaultLevel used by logger
DefaultLevel = InfoLevel DefaultLevel = InfoLevel
// DefaultCallerSkipCount used by logger
DefaultCallerSkipCount = 2
) )
// Logger is a generic logging interface // Logger is a generic logging interface
@ -31,24 +30,111 @@ type Logger interface {
// Fields set fields to always be logged with keyval pairs // Fields set fields to always be logged with keyval pairs
Fields(fields ...interface{}) Logger Fields(fields ...interface{}) Logger
// Info level message // Info level message
Info(ctx context.Context, msg string, args ...interface{}) Info(ctx context.Context, args ...interface{})
// Trace level message // Trace level message
Trace(ctx context.Context, msg string, args ...interface{}) Trace(ctx context.Context, args ...interface{})
// Debug level message // Debug level message
Debug(ctx context.Context, msg string, args ...interface{}) Debug(ctx context.Context, args ...interface{})
// Warn level message // Warn level message
Warn(ctx context.Context, msg string, args ...interface{}) Warn(ctx context.Context, args ...interface{})
// Error level message // Error level message
Error(ctx context.Context, msg string, args ...interface{}) Error(ctx context.Context, args ...interface{})
// Fatal level message // Fatal level message
Fatal(ctx context.Context, msg string, args ...interface{}) Fatal(ctx context.Context, args ...interface{})
// Infof level message
Infof(ctx context.Context, msg string, args ...interface{})
// Tracef level message
Tracef(ctx context.Context, msg string, args ...interface{})
// Debug level message
Debugf(ctx context.Context, msg string, args ...interface{})
// Warn level message
Warnf(ctx context.Context, msg string, args ...interface{})
// Error level message
Errorf(ctx context.Context, msg string, args ...interface{})
// Fatal level message
Fatalf(ctx context.Context, msg string, args ...interface{})
// Log logs message with needed level // Log logs message with needed level
Log(ctx context.Context, level Level, msg string, args ...interface{}) Log(ctx context.Context, level Level, args ...interface{})
// Name returns broker instance name // Logf logs message with needed level
Name() string Logf(ctx context.Context, level Level, msg string, args ...interface{})
// String returns the type of logger // String returns the name of logger
String() string String() string
} }
// Field contains keyval pair // Field contains keyval pair
type Field interface{} type Field interface{}
// Info writes msg to default logger on info level
func Info(ctx context.Context, args ...interface{}) {
DefaultLogger.Info(ctx, args...)
}
// Error writes msg to default logger on error level
func Error(ctx context.Context, args ...interface{}) {
DefaultLogger.Error(ctx, args...)
}
// Debug writes msg to default logger on debug level
func Debug(ctx context.Context, args ...interface{}) {
DefaultLogger.Debug(ctx, args...)
}
// Warn writes msg to default logger on warn level
func Warn(ctx context.Context, args ...interface{}) {
DefaultLogger.Warn(ctx, args...)
}
// Trace writes msg to default logger on trace level
func Trace(ctx context.Context, args ...interface{}) {
DefaultLogger.Trace(ctx, args...)
}
// Fatal writes msg to default logger on fatal level
func Fatal(ctx context.Context, args ...interface{}) {
DefaultLogger.Fatal(ctx, args...)
}
// Infof writes formatted msg to default logger on info level
func Infof(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Infof(ctx, msg, args...)
}
// Errorf writes formatted msg to default logger on error level
func Errorf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Errorf(ctx, msg, args...)
}
// Debugf writes formatted msg to default logger on debug level
func Debugf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Debugf(ctx, msg, args...)
}
// Warnf writes formatted msg to default logger on warn level
func Warnf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Warnf(ctx, msg, args...)
}
// Tracef writes formatted msg to default logger on trace level
func Tracef(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Tracef(ctx, msg, args...)
}
// Fatalf writes formatted msg to default logger on fatal level
func Fatalf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Fatalf(ctx, msg, args...)
}
// V returns true if passed level enabled in default logger
func V(level Level) bool {
return DefaultLogger.V(level)
}
// Init initialize logger
func Init(opts ...Option) error {
return DefaultLogger.Init(opts...)
}
// Fields create logger with specific fields
func Fields(fields ...interface{}) Logger {
return DefaultLogger.Fields(fields...)
}

138
logger/logger_test.go Normal file
View File

@ -0,0 +1,138 @@
package logger
import (
"bytes"
"context"
"log"
"testing"
)
func TestContext(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl, ok := FromContext(NewContext(ctx, l.Fields("key", "val")))
if !ok {
t.Fatal("context without logger")
}
nl.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Fields("key", "val")
nl.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestFromContextWithFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
var ok bool
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Fields("key", "val")
ctx = NewContext(ctx, nl)
l, ok = FromContext(ctx)
if !ok {
t.Fatalf("context does not have logger")
}
l.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestClone(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Clone(WithLevel(ErrorLevel))
if err := nl.Init(); err != nil {
t.Fatal(err)
}
nl.Info(ctx, "info message")
if len(buf.Bytes()) != 0 {
t.Fatal("message must not be logged")
}
l.Info(ctx, "info message")
if len(buf.Bytes()) == 0 {
t.Fatal("message must be logged")
}
}
func TestRedirectStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
fn := RedirectStdLogger(l, ErrorLevel)
defer fn()
log.Print("test")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test","timestamp"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
lg := NewStdLogger(l, ErrorLevel)
lg.Print("test")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test","timestamp"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestLogger(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Trace(ctx, "trace_msg1")
l.Warn(ctx, "warn_msg1")
l.Fields("error", "test").Info(ctx, "error message")
l.Warn(ctx, "first", " ", "second")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"warn","msg":"warn_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"test","level":"info","msg":"error message"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"level":"warn","msg":"first second"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}

View File

@ -1,78 +0,0 @@
package logger
import (
"context"
)
const (
defaultCallerSkipCount = 2
)
type noopLogger struct {
opts Options
}
func NewLogger(opts ...Option) Logger {
options := NewOptions(opts...)
options.CallerSkipCount = defaultCallerSkipCount
return &noopLogger{opts: options}
}
func (l *noopLogger) V(_ Level) bool {
return false
}
func (l *noopLogger) Level(_ Level) {
}
func (l *noopLogger) Name() string {
return l.opts.Name
}
func (l *noopLogger) Init(opts ...Option) error {
for _, o := range opts {
o(&l.opts)
}
return nil
}
func (l *noopLogger) Clone(opts ...Option) Logger {
nl := &noopLogger{opts: l.opts}
for _, o := range opts {
o(&nl.opts)
}
return nl
}
func (l *noopLogger) Fields(_ ...interface{}) Logger {
return l
}
func (l *noopLogger) Options() Options {
return l.opts
}
func (l *noopLogger) String() string {
return "noop"
}
func (l *noopLogger) Log(ctx context.Context, lvl Level, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Info(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Debug(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Error(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Trace(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Warn(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
}

View File

@ -3,14 +3,10 @@ package logger
import ( import (
"context" "context"
"io" "io"
"log/slog"
"os" "os"
"time"
"go.unistack.org/micro/v3/meter"
) )
// Option func signature // Option func
type Option func(*Options) type Option func(*Options)
// Options holds logger options // Options holds logger options
@ -19,36 +15,14 @@ type Options struct {
Out io.Writer Out io.Writer
// Context holds exernal options // Context holds exernal options
Context context.Context Context context.Context
// Name holds the logger name
Name string
// Fields holds additional metadata // Fields holds additional metadata
Fields []interface{} Fields []interface{}
// callerSkipCount number of frmaes to skip // Name holds the logger name
CallerSkipCount int Name string
// ContextAttrFuncs contains funcs that executed before log func on context
ContextAttrFuncs []ContextAttrFunc
// TimeKey is the key used for the time of the log call
TimeKey string
// LevelKey is the key used for the level of the log call
LevelKey string
// ErroreKey is the key used for the error of the log call
ErrorKey string
// MessageKey is the key used for the message of the log call
MessageKey string
// SourceKey is the key used for the source file and line of the log call
SourceKey string
// StacktraceKey is the key used for the stacktrace
StacktraceKey string
// AddStacktrace controls writing of stacktaces on error
AddStacktrace bool
// AddSource enabled writing source file and position in log
AddSource bool
// The logging level the logger should log // The logging level the logger should log
Level Level Level Level
// TimeFunc used to obtain current time // CallerSkipCount number of frmaes to skip
TimeFunc func() time.Time CallerSkipCount int
// Meter used to count logs for specific level
Meter meter.Meter
} }
// NewOptions creates new options struct // NewOptions creates new options struct
@ -57,29 +31,15 @@ func NewOptions(opts ...Option) Options {
Level: DefaultLevel, Level: DefaultLevel,
Fields: make([]interface{}, 0, 6), Fields: make([]interface{}, 0, 6),
Out: os.Stderr, Out: os.Stderr,
CallerSkipCount: DefaultCallerSkipCount,
Context: context.Background(), Context: context.Background(),
ContextAttrFuncs: DefaultContextAttrFuncs,
AddSource: true,
TimeFunc: time.Now,
Meter: meter.DefaultMeter,
} }
WithMicroKeys()(&options)
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
return options return options
} }
// WithContextAttrFuncs appends default funcs for the context attrs filler
func WithContextAttrFuncs(fncs ...ContextAttrFunc) Option {
return func(o *Options) {
o.ContextAttrFuncs = append(o.ContextAttrFuncs, fncs...)
}
}
// WithFields set default fields for the logger // WithFields set default fields for the logger
func WithFields(fields ...interface{}) Option { func WithFields(fields ...interface{}) Option {
return func(o *Options) { return func(o *Options) {
@ -101,17 +61,10 @@ func WithOutput(out io.Writer) Option {
} }
} }
// WithAddStacktrace controls writing stacktrace on error // WithCallerSkipCount set frame count to skip
func WithAddStacktrace(v bool) Option { func WithCallerSkipCount(c int) Option {
return func(o *Options) { return func(o *Options) {
o.AddStacktrace = v o.CallerSkipCount = c
}
}
// WithAddSource controls writing source file and pos in log
func WithAddSource(v bool) Option {
return func(o *Options) {
o.AddSource = v
} }
} }
@ -128,70 +81,3 @@ func WithName(n string) Option {
o.Name = n o.Name = n
} }
} }
// WithMeter sets the meter
func WithMeter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// WithTimeFunc sets the func to obtain current time
func WithTimeFunc(fn func() time.Time) Option {
return func(o *Options) {
o.TimeFunc = fn
}
}
func WithZapKeys() Option {
return func(o *Options) {
o.TimeKey = "@timestamp"
o.LevelKey = "level"
o.MessageKey = "msg"
o.SourceKey = "caller"
o.StacktraceKey = "stacktrace"
o.ErrorKey = "error"
}
}
func WithZerologKeys() Option {
return func(o *Options) {
o.TimeKey = "time"
o.LevelKey = "level"
o.MessageKey = "message"
o.SourceKey = "caller"
o.StacktraceKey = "stacktrace"
o.ErrorKey = "error"
}
}
func WithSlogKeys() Option {
return func(o *Options) {
o.TimeKey = slog.TimeKey
o.LevelKey = slog.LevelKey
o.MessageKey = slog.MessageKey
o.SourceKey = slog.SourceKey
o.StacktraceKey = "stacktrace"
o.ErrorKey = "error"
}
}
func WithMicroKeys() Option {
return func(o *Options) {
o.TimeKey = "timestamp"
o.LevelKey = "level"
o.MessageKey = "msg"
o.SourceKey = "caller"
o.StacktraceKey = "stacktrace"
o.ErrorKey = "error"
}
}
// WithAddCallerSkipCount add skip count for copy logger
func WithAddCallerSkipCount(n int) Option {
return func(o *Options) {
if n > 0 {
o.CallerSkipCount += n
}
}
}

View File

@ -1,332 +0,0 @@
package slog
import (
"context"
"log/slog"
"os"
"regexp"
"runtime"
"strconv"
"sync"
"sync/atomic"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/semconv"
"go.unistack.org/micro/v3/tracer"
)
const (
badKey = "!BADKEY"
// defaultCallerSkipCount used by logger
defaultCallerSkipCount = 3
)
var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`)
var (
traceValue = slog.StringValue("trace")
debugValue = slog.StringValue("debug")
infoValue = slog.StringValue("info")
warnValue = slog.StringValue("warn")
errorValue = slog.StringValue("error")
fatalValue = slog.StringValue("fatal")
)
type wrapper struct {
h slog.Handler
level atomic.Int64
}
func (h *wrapper) Enabled(ctx context.Context, level slog.Level) bool {
return level >= slog.Level(int(h.level.Load()))
}
func (h *wrapper) Handle(ctx context.Context, rec slog.Record) error {
return h.h.Handle(ctx, rec)
}
func (h *wrapper) WithAttrs(attrs []slog.Attr) slog.Handler {
return h.WithAttrs(attrs)
}
func (h *wrapper) WithGroup(name string) slog.Handler {
return h.WithGroup(name)
}
func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr {
switch a.Key {
case slog.SourceKey:
source := a.Value.Any().(*slog.Source)
a.Value = slog.StringValue(source.File + ":" + strconv.Itoa(source.Line))
a.Key = s.opts.SourceKey
case slog.TimeKey:
a.Key = s.opts.TimeKey
case slog.MessageKey:
a.Key = s.opts.MessageKey
case slog.LevelKey:
level := a.Value.Any().(slog.Level)
lvl := slogToLoggerLevel(level)
a.Key = s.opts.LevelKey
switch {
case lvl < logger.DebugLevel:
a.Value = traceValue
case lvl < logger.InfoLevel:
a.Value = debugValue
case lvl < logger.WarnLevel:
a.Value = infoValue
case lvl < logger.ErrorLevel:
a.Value = warnValue
case lvl < logger.FatalLevel:
a.Value = errorValue
case lvl >= logger.FatalLevel:
a.Value = fatalValue
default:
a.Value = infoValue
}
}
return a
}
type slogLogger struct {
leveler *slog.LevelVar
handler *wrapper
opts logger.Options
mu sync.RWMutex
}
func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger {
s.mu.RLock()
options := s.opts
s.mu.RUnlock()
for _, o := range opts {
o(&options)
}
if len(options.ContextAttrFuncs) == 0 {
options.ContextAttrFuncs = logger.DefaultContextAttrFuncs
}
attrs, _ := s.argsAttrs(options.Fields)
l := &slogLogger{
handler: &wrapper{h: s.handler.h.WithAttrs(attrs)},
opts: options,
}
l.handler.level.Store(int64(loggerToSlogLevel(options.Level)))
return l
}
func (s *slogLogger) V(level logger.Level) bool {
s.mu.Lock()
v := s.opts.Level.Enabled(level)
s.mu.Unlock()
return v
}
func (s *slogLogger) Level(level logger.Level) {
s.mu.Lock()
s.opts.Level = level
s.handler.level.Store(int64(loggerToSlogLevel(level)))
s.mu.Unlock()
}
func (s *slogLogger) Options() logger.Options {
return s.opts
}
func (s *slogLogger) Fields(fields ...interface{}) logger.Logger {
s.mu.RLock()
options := s.opts
s.mu.RUnlock()
l := &slogLogger{opts: options}
if len(options.ContextAttrFuncs) == 0 {
options.ContextAttrFuncs = logger.DefaultContextAttrFuncs
}
attrs, _ := s.argsAttrs(fields)
l.handler = &wrapper{h: s.handler.h.WithAttrs(attrs)}
l.handler.level.Store(int64(loggerToSlogLevel(l.opts.Level)))
return l
}
func (s *slogLogger) Init(opts ...logger.Option) error {
s.mu.Lock()
for _, o := range opts {
o(&s.opts)
}
if len(s.opts.ContextAttrFuncs) == 0 {
s.opts.ContextAttrFuncs = logger.DefaultContextAttrFuncs
}
handleOpt := &slog.HandlerOptions{
ReplaceAttr: s.renameAttr,
Level: loggerToSlogLevel(logger.TraceLevel),
AddSource: s.opts.AddSource,
}
attrs, _ := s.argsAttrs(s.opts.Fields)
s.handler = &wrapper{h: slog.NewJSONHandler(s.opts.Out, handleOpt).WithAttrs(attrs)}
s.handler.level.Store(int64(loggerToSlogLevel(s.opts.Level)))
s.mu.Unlock()
return nil
}
func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, msg string, attrs ...interface{}) {
s.printLog(ctx, lvl, msg, attrs...)
}
func (s *slogLogger) Info(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.InfoLevel, msg, attrs...)
}
func (s *slogLogger) Debug(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.DebugLevel, msg, attrs...)
}
func (s *slogLogger) Trace(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.TraceLevel, msg, attrs...)
}
func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.ErrorLevel, msg, attrs...)
}
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.FatalLevel, msg, attrs...)
os.Exit(1)
}
func (s *slogLogger) Warn(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.WarnLevel, msg, attrs...)
}
func (s *slogLogger) Name() string {
return s.opts.Name
}
func (s *slogLogger) String() string {
return "slog"
}
func (s *slogLogger) printLog(ctx context.Context, lvl logger.Level, msg string, args ...interface{}) {
if !s.V(lvl) {
return
}
var argError error
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", lvl.String()).Inc()
attrs, err := s.argsAttrs(args)
if err != nil {
argError = err
}
if argError != nil {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, argError.Error())
}
}
for _, fn := range s.opts.ContextAttrFuncs {
ctxAttrs, err := s.argsAttrs(fn(ctx))
if err != nil {
argError = err
}
attrs = append(attrs, ctxAttrs...)
}
if argError != nil {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, argError.Error())
}
}
if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1]))
}
}
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, printLog, LogLvlMethod]
r := slog.NewRecord(s.opts.TimeFunc(), loggerToSlogLevel(lvl), msg, pcs[0])
r.AddAttrs(attrs...)
_ = s.handler.Handle(ctx, r)
}
func NewLogger(opts ...logger.Option) logger.Logger {
s := &slogLogger{
opts: logger.NewOptions(opts...),
}
s.opts.CallerSkipCount = defaultCallerSkipCount
return s
}
func loggerToSlogLevel(level logger.Level) slog.Level {
switch level {
case logger.DebugLevel:
return slog.LevelDebug
case logger.WarnLevel:
return slog.LevelWarn
case logger.ErrorLevel:
return slog.LevelError
case logger.TraceLevel:
return slog.LevelDebug - 1
case logger.FatalLevel:
return slog.LevelError + 1
default:
return slog.LevelInfo
}
}
func slogToLoggerLevel(level slog.Level) logger.Level {
switch level {
case slog.LevelDebug:
return logger.DebugLevel
case slog.LevelWarn:
return logger.WarnLevel
case slog.LevelError:
return logger.ErrorLevel
case slog.LevelDebug - 1:
return logger.TraceLevel
case slog.LevelError + 1:
return logger.FatalLevel
default:
return logger.InfoLevel
}
}
func (s *slogLogger) argsAttrs(args []interface{}) ([]slog.Attr, error) {
attrs := make([]slog.Attr, 0, len(args))
var err error
for idx := 0; idx < len(args); idx++ {
switch arg := args[idx].(type) {
case slog.Attr:
attrs = append(attrs, arg)
case string:
if idx+1 < len(args) {
attrs = append(attrs, slog.Any(arg, args[idx+1]))
idx += 1
} else {
attrs = append(attrs, slog.String(badKey, arg))
}
case error:
attrs = append(attrs, slog.String(s.opts.ErrorKey, arg.Error()))
err = arg
}
}
return attrs, err
}

View File

@ -1,294 +0,0 @@
package slog
import (
"bytes"
"context"
"errors"
"fmt"
"log"
"strings"
"testing"
"github.com/google/uuid"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/logger"
)
func TestMultipleFieldsWithLevel(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l = l.Fields("key", "val")
l.Info(ctx, "msg1")
nl := l.Clone(logger.WithLevel(logger.DebugLevel))
nl.Debug(ctx, "msg2")
l.Debug(ctx, "msg3")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"msg1"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"msg2"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if bytes.Contains(buf.Bytes(), []byte(`"msg3"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestMultipleFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l = l.Fields("key", "val")
l = l.Fields("key1", "val1")
l.Info(ctx, "msg")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"key1":"val1"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestError(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf), logger.WithAddStacktrace(true))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Error(ctx, "message", fmt.Errorf("error message"))
if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) {
t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestErrorf(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf), logger.WithAddStacktrace(true))
if err := l.Init(logger.WithContextAttrFuncs(func(ctx context.Context) []interface{} {
return nil
})); err != nil {
t.Fatal(err)
}
l.Log(ctx, logger.ErrorLevel, "message", errors.New("error msg"))
l.Log(ctx, logger.ErrorLevel, "", errors.New("error msg"))
if !bytes.Contains(buf.Bytes(), []byte(`"error":"error msg"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) {
t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestContext(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl, ok := logger.FromContext(logger.NewContext(ctx, l.Fields("key", "val")))
if !ok {
t.Fatal("context without logger")
}
nl.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Fields("key", "val")
nl.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestFromContextWithFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
var ok bool
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Fields("key", "val")
ctx = logger.NewContext(ctx, nl)
l, ok = logger.FromContext(ctx)
if !ok {
t.Fatalf("context does not have logger")
}
l.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
l.Info(ctx, "test", "uncorrected number attributes")
if !bytes.Contains(buf.Bytes(), []byte(`"!BADKEY":"uncorrected number attributes"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestClone(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Clone(logger.WithLevel(logger.ErrorLevel))
if err := nl.Init(); err != nil {
t.Fatal(err)
}
nl.Info(ctx, "info message")
if len(buf.Bytes()) != 0 {
t.Fatal("message must not be logged")
}
l.Info(ctx, "info message")
if len(buf.Bytes()) == 0 {
t.Fatal("message must be logged")
}
}
func TestRedirectStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
fn := logger.RedirectStdLogger(l, logger.ErrorLevel)
defer fn()
log.Print("test")
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"error"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"test"`))) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
lg := logger.NewStdLogger(l, logger.ErrorLevel)
lg.Print("test")
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"error"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"test"`))) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestLogger(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Trace(ctx, "trace_msg1")
l.Warn(ctx, "warn_msg1")
l.Fields("error", "test").Info(ctx, "error message")
l.Warn(ctx, "first second")
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"trace"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"trace_msg1"`))) {
t.Fatalf("logger tracer, buf %s", buf.Bytes())
}
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"warn"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"warn_msg1"`))) {
t.Fatalf("logger warn, buf %s", buf.Bytes())
}
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"info"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"error message","error":"test"`))) {
t.Fatalf("logger info, buf %s", buf.Bytes())
}
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"warn"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"first second"`))) {
t.Fatalf("logger warn, buf %s", buf.Bytes())
}
}
func Test_WithContextAttrFunc(t *testing.T) {
loggerContextAttrFuncs := []logger.ContextAttrFunc{
func(ctx context.Context) []interface{} {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil
}
attrs := make([]interface{}, 0, 10)
for k, v := range md {
switch k {
case "X-Request-Id", "Phone", "External-Id", "Source-Service", "X-App-Install-Id", "Client-Id", "Client-Ip":
attrs = append(attrs, strings.ToLower(k), v)
}
}
return attrs
},
}
logger.DefaultContextAttrFuncs = append(logger.DefaultContextAttrFuncs, loggerContextAttrFuncs...)
ctx := context.TODO()
ctx = metadata.AppendIncomingContext(ctx, "X-Request-Id", uuid.New().String(),
"Source-Service", "Test-System")
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Info(ctx, "test message")
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"info"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"test message"`))) {
t.Fatalf("logger info, buf %s", buf.Bytes())
}
if !(bytes.Contains(buf.Bytes(), []byte(`"x-request-id":"`))) {
t.Fatalf("logger info, buf %s", buf.Bytes())
}
if !(bytes.Contains(buf.Bytes(), []byte(`"source-service":"Test-System"`))) {
t.Fatalf("logger info, buf %s", buf.Bytes())
}
buf.Reset()
imd, _ := metadata.FromIncomingContext(ctx)
l.Info(ctx, "test message1")
imd.Set("Source-Service", "Test-System2")
l.Info(ctx, "test message2")
// t.Logf("xxx %s", buf.Bytes())
}

View File

@ -46,21 +46,13 @@ var (
closeMapBytes = []byte("}") closeMapBytes = []byte("}")
) )
type protoMessage interface { type unwrap struct {
Reset()
ProtoMessage()
}
type Wrapper struct {
val interface{} val interface{}
s fmt.State s fmt.State
pointers map[uintptr]int pointers map[uintptr]int
opts *Options opts *Options
depth int depth int
ignoreNextType bool ignoreNextType bool
takeMap map[int]bool
protoWrapperType bool
sqlWrapperType bool
} }
// Options struct // Options struct
@ -114,14 +106,14 @@ func Tagged(b bool) Option {
} }
} }
func Unwrap(val interface{}, opts ...Option) *Wrapper { func Unwrap(val interface{}, opts ...Option) *unwrap {
options := NewOptions(opts...) options := NewOptions(opts...)
return &Wrapper{val: val, opts: &options, pointers: make(map[uintptr]int), takeMap: make(map[int]bool)} return &unwrap{val: val, opts: &options, pointers: make(map[uintptr]int)}
} }
func (w *Wrapper) unpackValue(v reflect.Value) reflect.Value { func (f *unwrap) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface { if v.Kind() == reflect.Interface {
w.ignoreNextType = false f.ignoreNextType = false
if !v.IsNil() { if !v.IsNil() {
v = v.Elem() v = v.Elem()
} }
@ -130,19 +122,19 @@ func (w *Wrapper) unpackValue(v reflect.Value) reflect.Value {
} }
// formatPtr handles formatting of pointers by indirecting them as necessary. // formatPtr handles formatting of pointers by indirecting them as necessary.
func (w *Wrapper) formatPtr(v reflect.Value) { func (f *unwrap) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil. // Display nil if top level pointer is nil.
showTypes := w.s.Flag('#') showTypes := f.s.Flag('#')
if v.IsNil() && (!showTypes || w.ignoreNextType) { if v.IsNil() && (!showTypes || f.ignoreNextType) {
_, _ = w.s.Write(nilAngleBytes) _, _ = f.s.Write(nilAngleBytes)
return return
} }
// Remove pointers at or below the current depth from map used to detect // Remove pointers at or below the current depth from map used to detect
// circular refs. // circular refs.
for k, depth := range w.pointers { for k, depth := range f.pointers {
if depth >= w.depth { if depth >= f.depth {
delete(w.pointers, k) delete(f.pointers, k)
} }
} }
@ -164,12 +156,12 @@ func (w *Wrapper) formatPtr(v reflect.Value) {
indirects++ indirects++
addr := ve.Pointer() addr := ve.Pointer()
pointerChain = append(pointerChain, addr) pointerChain = append(pointerChain, addr)
if pd, ok := w.pointers[addr]; ok && pd < w.depth { if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true cycleFound = true
indirects-- indirects--
break break
} }
w.pointers[addr] = w.depth f.pointers[addr] = f.depth
ve = ve.Elem() ve = ve.Elem()
if ve.Kind() == reflect.Interface { if ve.Kind() == reflect.Interface {
@ -182,49 +174,49 @@ func (w *Wrapper) formatPtr(v reflect.Value) {
} }
// Display type or indirection level depending on flags. // Display type or indirection level depending on flags.
if showTypes && !w.ignoreNextType { if showTypes && !f.ignoreNextType {
if w.depth > 0 { if f.depth > 0 {
_, _ = w.s.Write(openParenBytes) _, _ = f.s.Write(openParenBytes)
} }
if w.depth > 0 { if f.depth > 0 {
_, _ = w.s.Write(bytes.Repeat(asteriskBytes, indirects)) _, _ = f.s.Write(bytes.Repeat(asteriskBytes, indirects))
} else { } else {
_, _ = w.s.Write(bytes.Repeat(ampBytes, indirects)) _, _ = f.s.Write(bytes.Repeat(ampBytes, indirects))
} }
_, _ = w.s.Write([]byte(ve.Type().String())) _, _ = f.s.Write([]byte(ve.Type().String()))
if w.depth > 0 { if f.depth > 0 {
_, _ = w.s.Write(closeParenBytes) _, _ = f.s.Write(closeParenBytes)
} }
} else { } else {
if nilFound || cycleFound { if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*") indirects += strings.Count(ve.Type().String(), "*")
} }
_, _ = w.s.Write(openAngleBytes) _, _ = f.s.Write(openAngleBytes)
_, _ = w.s.Write([]byte(strings.Repeat("*", indirects))) _, _ = f.s.Write([]byte(strings.Repeat("*", indirects)))
_, _ = w.s.Write(closeAngleBytes) _, _ = f.s.Write(closeAngleBytes)
} }
// Display pointer information depending on flags. // Display pointer information depending on flags.
if w.s.Flag('+') && (len(pointerChain) > 0) { if f.s.Flag('+') && (len(pointerChain) > 0) {
_, _ = w.s.Write(openParenBytes) _, _ = f.s.Write(openParenBytes)
for i, addr := range pointerChain { for i, addr := range pointerChain {
if i > 0 { if i > 0 {
_, _ = w.s.Write(pointerChainBytes) _, _ = f.s.Write(pointerChainBytes)
} }
getHexPtr(w.s, addr) getHexPtr(f.s, addr)
} }
_, _ = w.s.Write(closeParenBytes) _, _ = f.s.Write(closeParenBytes)
} }
// Display dereferenced value. // Display dereferenced value.
switch { switch {
case nilFound: case nilFound:
_, _ = w.s.Write(nilAngleBytes) _, _ = f.s.Write(nilAngleBytes)
case cycleFound: case cycleFound:
_, _ = w.s.Write(circularShortBytes) _, _ = f.s.Write(circularShortBytes)
default: default:
w.ignoreNextType = true f.ignoreNextType = true
w.format(ve) f.format(ve)
} }
} }
@ -232,73 +224,54 @@ func (w *Wrapper) formatPtr(v reflect.Value) {
// uses the passed reflect value to figure out what kind of object we are // uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function, // dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly. // however circular data structures are detected and handled properly.
func (w *Wrapper) format(v reflect.Value) { func (f *unwrap) format(v reflect.Value) {
if w.opts.Codec != nil { if f.opts.Codec != nil {
buf, err := w.opts.Codec.Marshal(v.Interface()) buf, err := f.opts.Codec.Marshal(v.Interface())
if err != nil { if err != nil {
_, _ = w.s.Write(invalidAngleBytes) _, _ = f.s.Write(invalidAngleBytes)
return return
} }
_, _ = w.s.Write(buf) _, _ = f.s.Write(buf)
return return
} }
// Handle invalid reflect values immediately. // Handle invalid reflect values immediately.
kind := v.Kind() kind := v.Kind()
if kind == reflect.Invalid { if kind == reflect.Invalid {
_, _ = w.s.Write(invalidAngleBytes) _, _ = f.s.Write(invalidAngleBytes)
return return
} }
// Handle pointers specially. // Handle pointers specially.
switch kind { if kind == reflect.Ptr {
case reflect.Ptr: f.formatPtr(v)
if !v.IsZero() {
if strings.HasPrefix(reflect.Indirect(v).Type().String(), "wrapperspb.") {
w.protoWrapperType = true
} else if strings.HasPrefix(reflect.Indirect(v).Type().String(), "sql.Null") {
w.sqlWrapperType = true
} else if v.CanInterface() {
if _, ok := v.Interface().(protoMessage); ok {
w.protoWrapperType = true
}
}
}
w.formatPtr(v)
return return
case reflect.Struct:
if !v.IsZero() {
if strings.HasPrefix(reflect.Indirect(v).Type().String(), "sql.Null") {
w.sqlWrapperType = true
}
}
} }
// get type information unless already handled elsewhere. // get type information unless already handled elsewhere.
if !w.ignoreNextType && w.s.Flag('#') { if !f.ignoreNextType && f.s.Flag('#') {
if v.Type().Kind() != reflect.Map && if v.Type().Kind() != reflect.Map &&
v.Type().Kind() != reflect.String && v.Type().Kind() != reflect.String &&
v.Type().Kind() != reflect.Array && v.Type().Kind() != reflect.Array &&
v.Type().Kind() != reflect.Slice { v.Type().Kind() != reflect.Slice {
_, _ = w.s.Write(openParenBytes) _, _ = f.s.Write(openParenBytes)
} }
if v.Kind() != reflect.String { if v.Kind() != reflect.String {
_, _ = w.s.Write([]byte(v.Type().String())) _, _ = f.s.Write([]byte(v.Type().String()))
} }
if v.Type().Kind() != reflect.Map && if v.Type().Kind() != reflect.Map &&
v.Type().Kind() != reflect.String && v.Type().Kind() != reflect.String &&
v.Type().Kind() != reflect.Array && v.Type().Kind() != reflect.Array &&
v.Type().Kind() != reflect.Slice { v.Type().Kind() != reflect.Slice {
_, _ = w.s.Write(closeParenBytes) _, _ = f.s.Write(closeParenBytes)
} }
} }
w.ignoreNextType = false f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods // Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled. // flag is enabled.
if w.opts.Methods { if f.opts.Methods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) { if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(w.opts, w.s, v); handled { if handled := handleMethods(f.opts, f.s, v); handled {
return return
} }
} }
@ -306,48 +279,48 @@ func (w *Wrapper) format(v reflect.Value) {
switch kind { switch kind {
case reflect.Invalid: case reflect.Invalid:
_, _ = w.s.Write(invalidAngleBytes) _, _ = f.s.Write(invalidAngleBytes)
case reflect.Bool: case reflect.Bool:
getBool(w.s, v.Bool()) getBool(f.s, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
getInt(w.s, v.Int(), 10) getInt(f.s, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
getUint(w.s, v.Uint(), 10) getUint(f.s, v.Uint(), 10)
case reflect.Float32: case reflect.Float32:
getFloat(w.s, v.Float(), 32) getFloat(f.s, v.Float(), 32)
case reflect.Float64: case reflect.Float64:
getFloat(w.s, v.Float(), 64) getFloat(f.s, v.Float(), 64)
case reflect.Complex64: case reflect.Complex64:
getComplex(w.s, v.Complex(), 32) getComplex(f.s, v.Complex(), 32)
case reflect.Complex128: case reflect.Complex128:
getComplex(w.s, v.Complex(), 64) getComplex(f.s, v.Complex(), 64)
case reflect.Slice: case reflect.Slice:
if v.IsNil() { if v.IsNil() {
_, _ = w.s.Write(nilAngleBytes) _, _ = f.s.Write(nilAngleBytes)
break break
} }
fallthrough fallthrough
case reflect.Array: case reflect.Array:
_, _ = w.s.Write(openBraceBytes) _, _ = f.s.Write(openBraceBytes)
w.depth++ f.depth++
numEntries := v.Len() numEntries := v.Len()
for i := 0; i < numEntries; i++ { for i := 0; i < numEntries; i++ {
if i > 0 { if i > 0 {
_, _ = w.s.Write(commaBytes) _, _ = f.s.Write(commaBytes)
_, _ = w.s.Write(spaceBytes) _, _ = f.s.Write(spaceBytes)
} }
w.ignoreNextType = true f.ignoreNextType = true
w.format(w.unpackValue(v.Index(i))) f.format(f.unpackValue(v.Index(i)))
} }
w.depth-- f.depth--
_, _ = w.s.Write(closeBraceBytes) _, _ = f.s.Write(closeBraceBytes)
case reflect.String: case reflect.String:
_, _ = w.s.Write([]byte(`"` + v.String() + `"`)) _, _ = f.s.Write([]byte(`"` + v.String() + `"`))
case reflect.Interface: case reflect.Interface:
// The only time we should get here is for nil interfaces due to // The only time we should get here is for nil interfaces due to
// unpackValue calls. // unpackValue calls.
if v.IsNil() { if v.IsNil() {
_, _ = w.s.Write(nilAngleBytes) _, _ = f.s.Write(nilAngleBytes)
} }
case reflect.Ptr: case reflect.Ptr:
// Do nothing. We should never get here since pointers have already // Do nothing. We should never get here since pointers have already
@ -355,54 +328,34 @@ func (w *Wrapper) format(v reflect.Value) {
case reflect.Map: case reflect.Map:
// nil maps should be indicated as different than empty maps // nil maps should be indicated as different than empty maps
if v.IsNil() { if v.IsNil() {
_, _ = w.s.Write(nilAngleBytes) _, _ = f.s.Write(nilAngleBytes)
break break
} }
_, _ = w.s.Write(openMapBytes) _, _ = f.s.Write(openMapBytes)
w.depth++ f.depth++
keys := v.MapKeys() keys := v.MapKeys()
for i, key := range keys { for i, key := range keys {
if i > 0 { if i > 0 {
_, _ = w.s.Write(spaceBytes) _, _ = f.s.Write(spaceBytes)
} }
w.ignoreNextType = true f.ignoreNextType = true
w.format(w.unpackValue(key)) f.format(f.unpackValue(key))
_, _ = w.s.Write(colonBytes) _, _ = f.s.Write(colonBytes)
w.ignoreNextType = true f.ignoreNextType = true
w.format(w.unpackValue(v.MapIndex(key))) f.format(f.unpackValue(v.MapIndex(key)))
} }
w.depth-- f.depth--
_, _ = w.s.Write(closeMapBytes) _, _ = f.s.Write(closeMapBytes)
case reflect.Struct: case reflect.Struct:
numFields := v.NumField() numFields := v.NumField()
numWritten := 0 numWritten := 0
_, _ = w.s.Write(openBraceBytes) _, _ = f.s.Write(openBraceBytes)
w.depth++ f.depth++
vt := v.Type() vt := v.Type()
prevSkip := false prevSkip := false
for i := 0; i < numFields; i++ { for i := 0; i < numFields; i++ {
switch vt.Field(i).Type.PkgPath() {
case "google.golang.org/protobuf/internal/impl", "google.golang.org/protobuf/internal/pragma":
w.protoWrapperType = true
prevSkip = true
continue
}
if w.protoWrapperType && !vt.Field(i).IsExported() {
prevSkip = true
continue
} else if w.sqlWrapperType && vt.Field(i).Name == "Valid" {
prevSkip = true
continue
}
if _, ok := vt.Field(i).Tag.Lookup("protobuf"); ok && !w.protoWrapperType {
w.protoWrapperType = true
}
sv, ok := vt.Field(i).Tag.Lookup("logger") sv, ok := vt.Field(i).Tag.Lookup("logger")
switch { if ok {
case ok:
switch sv { switch sv {
case "omit": case "omit":
prevSkip = true prevSkip = true
@ -410,68 +363,57 @@ func (w *Wrapper) format(v reflect.Value) {
case "take": case "take":
break break
} }
case !ok && w.opts.Tagged: } else if f.opts.Tagged {
// skip top level untagged
if w.depth == 1 {
prevSkip = true prevSkip = true
continue continue
} }
if tv, ok := w.takeMap[w.depth]; ok && !tv { if i > 0 && !prevSkip {
prevSkip = true _, _ = f.s.Write(commaBytes)
continue _, _ = f.s.Write(spaceBytes)
} }
}
if prevSkip { if prevSkip {
prevSkip = false prevSkip = false
} }
vtf := vt.Field(i)
if numWritten > 0 { if f.s.Flag('+') || f.s.Flag('#') {
_, _ = w.s.Write(commaBytes) _, _ = f.s.Write([]byte(vtf.Name))
_, _ = w.s.Write(spaceBytes) _, _ = f.s.Write(colonBytes)
} }
f.format(f.unpackValue(v.Field(i)))
vt := vt.Field(i)
if w.s.Flag('+') || w.s.Flag('#') {
_, _ = w.s.Write([]byte(vt.Name))
_, _ = w.s.Write(colonBytes)
}
w.format(w.unpackValue(v.Field(i)))
numWritten++ numWritten++
} }
w.depth-- f.depth--
if numWritten == 0 && f.depth < 0 {
if numWritten == 0 && w.depth < 0 { _, _ = f.s.Write(filteredBytes)
_, _ = w.s.Write(filteredBytes)
} }
_, _ = w.s.Write(closeBraceBytes) _, _ = f.s.Write(closeBraceBytes)
case reflect.Uintptr: case reflect.Uintptr:
getHexPtr(w.s, uintptr(v.Uint())) getHexPtr(f.s, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func: case reflect.UnsafePointer, reflect.Chan, reflect.Func:
getHexPtr(w.s, v.Pointer()) getHexPtr(f.s, v.Pointer())
// There were not any other types at the time this code was written, but // There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added. // fall back to letting the default fmt package handle it if any get added.
default: default:
format := w.buildDefaultFormat() format := f.buildDefaultFormat()
if v.CanInterface() { if v.CanInterface() {
_, _ = fmt.Fprintf(w.s, format, v.Interface()) _, _ = fmt.Fprintf(f.s, format, v.Interface())
} else { } else {
_, _ = fmt.Fprintf(w.s, format, v.String()) _, _ = fmt.Fprintf(f.s, format, v.String())
} }
} }
} }
func (w *Wrapper) Format(s fmt.State, verb rune) { func (f *unwrap) Format(s fmt.State, verb rune) {
w.s = s f.s = s
// Use standard formatting for verbs that are not v. // Use standard formatting for verbs that are not v.
if verb != 'v' { if verb != 'v' {
format := w.constructOrigFormat(verb) format := f.constructOrigFormat(verb)
_, _ = fmt.Fprintf(s, format, w.val) _, _ = fmt.Fprintf(s, format, f.val)
return return
} }
if w.val == nil { if f.val == nil {
if s.Flag('#') { if s.Flag('#') {
_, _ = s.Write(interfaceBytes) _, _ = s.Write(interfaceBytes)
} }
@ -479,11 +421,7 @@ func (w *Wrapper) Format(s fmt.State, verb rune) {
return return
} }
if w.opts.Tagged { f.format(reflect.ValueOf(f.val))
w.buildTakeMap(reflect.ValueOf(w.val), 1)
}
w.format(reflect.ValueOf(w.val))
} }
// handle special methods like error.Error() or fmt.Stringer interface // handle special methods like error.Error() or fmt.Stringer interface
@ -599,11 +537,11 @@ func catchPanic(w io.Writer, _ reflect.Value) {
} }
} }
func (w *Wrapper) buildDefaultFormat() (format string) { func (f *unwrap) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes) buf := bytes.NewBuffer(percentBytes)
for _, flag := range sf { for _, flag := range sf {
if w.s.Flag(int(flag)) { if f.s.Flag(int(flag)) {
_, _ = buf.WriteRune(flag) _, _ = buf.WriteRune(flag)
} }
} }
@ -614,68 +552,26 @@ func (w *Wrapper) buildDefaultFormat() (format string) {
return format return format
} }
func (w *Wrapper) constructOrigFormat(verb rune) string { func (f *unwrap) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes) buf := bytes.NewBuffer(percentBytes)
for _, flag := range sf { for _, flag := range sf {
if w.s.Flag(int(flag)) { if f.s.Flag(int(flag)) {
_, _ = buf.WriteRune(flag) _, _ = buf.WriteRune(flag)
} }
} }
if width, ok := w.s.Width(); ok { if width, ok := f.s.Width(); ok {
_, _ = buf.WriteString(strconv.Itoa(width)) _, _ = buf.WriteString(strconv.Itoa(width))
} }
if precision, ok := w.s.Precision(); ok { if precision, ok := f.s.Precision(); ok {
_, _ = buf.Write(precisionBytes) _, _ = buf.Write(precisionBytes)
_, _ = buf.WriteString(strconv.Itoa(precision)) _, _ = buf.WriteString(strconv.Itoa(precision))
} }
_, _ = buf.WriteRune(verb) _, _ = buf.WriteRune(verb)
return buf.String() format = buf.String()
} return format
func (w *Wrapper) buildTakeMap(v reflect.Value, depth int) {
if !v.IsValid() || v.IsZero() {
return
}
switch v.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
w.buildTakeMap(v.Index(i), depth+1)
}
w.takeMap[depth] = true
return
case reflect.Struct:
break
case reflect.Ptr:
v = v.Elem()
if v.Kind() != reflect.Struct {
w.takeMap[depth] = true
return
}
default:
w.takeMap[depth] = true
return
}
vt := v.Type()
for i := 0; i < v.NumField(); i++ {
sv, ok := vt.Field(i).Tag.Lookup("logger")
if ok && sv == "take" {
w.takeMap[depth] = false
}
if v.Kind() == reflect.Struct ||
(v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
w.buildTakeMap(v.Field(i), depth+1)
}
}
if _, ok := w.takeMap[depth]; !ok {
w.takeMap[depth] = true
}
} }

View File

@ -86,12 +86,12 @@ func TestTaggedNested(t *testing.T) {
unk string unk string
} }
type str struct { type str struct {
key string `logger:"omit"`
val *val `logger:"take"` val *val `logger:"take"`
key string `logger:"omit"`
} }
var iface interface{} var iface interface{}
v := &str{val: &val{key: "test", unk: "unk"}} v := &str{key: "omit", val: &val{key: "test", val: "omit", unk: "unk"}}
iface = v iface = v
buf := fmt.Sprintf("%#v", Unwrap(iface, Tagged(true))) buf := fmt.Sprintf("%#v", Unwrap(iface, Tagged(true)))
if strings.Compare(buf, `&unwrap.str{val:(*unwrap.val){key:"test"}}`) != 0 { if strings.Compare(buf, `&unwrap.str{val:(*unwrap.val){key:"test"}}`) != 0 {

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

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

View File

@ -39,6 +39,8 @@ func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
// FromContext returns metadata from the given context // FromContext returns metadata from the given context
// returned metadata shoud not be modified or race condition happens // returned metadata shoud not be modified or race condition happens
//
// Deprecated: use FromIncomingContext or FromOutgoingContext
func FromContext(ctx context.Context) (Metadata, bool) { func FromContext(ctx context.Context) (Metadata, bool) {
if ctx == nil { if ctx == nil {
return nil, false return nil, false
@ -51,6 +53,8 @@ func FromContext(ctx context.Context) (Metadata, bool) {
} }
// NewContext creates a new context with the given metadata // NewContext creates a new context with the given metadata
//
// Deprecated: use NewIncomingContext or NewOutgoingContext
func NewContext(ctx context.Context, md Metadata) context.Context { func NewContext(ctx context.Context, md Metadata) context.Context {
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()

View File

@ -1,5 +1,5 @@
// Package metadata is a way of defining message headers // Package metadata is a way of defining message headers
package metadata package metadata // import "go.unistack.org/micro/v3/metadata"
import ( import (
"net/textproto" "net/textproto"
@ -19,8 +19,6 @@ var (
HeaderTimeout = "Micro-Timeout" HeaderTimeout = "Micro-Timeout"
// HeaderAuthorization specifies Authorization header // HeaderAuthorization specifies Authorization header
HeaderAuthorization = "Authorization" HeaderAuthorization = "Authorization"
// HeaderXRequestID specifies request id
HeaderXRequestID = "X-Request-Id"
) )
// Metadata is our way of representing request headers internally. // Metadata is our way of representing request headers internally.
@ -98,12 +96,11 @@ func (md Metadata) Del(keys ...string) {
} }
// Copy makes a copy of the metadata // Copy makes a copy of the metadata
func Copy(md Metadata, exclude ...string) Metadata { func Copy(md Metadata) Metadata {
nmd := New(len(md)) nmd := New(len(md))
for key, val := range md { for key, val := range md {
nmd.Set(key, val) nmd.Set(key, val)
} }
nmd.Del(exclude...)
return nmd return nmd
} }

View File

@ -190,14 +190,3 @@ func TestMetadataContext(t *testing.T) {
t.Errorf("Expected metadata length 1 got %d", i) t.Errorf("Expected metadata length 1 got %d", i)
} }
} }
func TestCopy(t *testing.T) {
md := New(2)
md.Set("key1", "val1", "key2", "val2")
nmd := Copy(md, "key2")
if len(nmd) != 1 {
t.Fatal("Copy exclude not works")
} else if nmd["Key1"] != "val1" {
t.Fatal("Copy exclude not works")
}
}

12
meter/generate.go Normal file
View File

@ -0,0 +1,12 @@
package meter
//go:generate sh -c "protoc -I./handler -I../ -I$(go list -f '{{ .Dir }}' -m go.unistack.org/micro-proto/v3) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./handler handler/handler.proto"
import (
// import required packages
_ "go.unistack.org/micro-proto/v3/api"
// import required packages
_ "go.unistack.org/micro-proto/v3/openapiv3"
)

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

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

View File

@ -0,0 +1,24 @@
syntax = "proto3";
package micro.meter.handler;
option go_package = "go.unistack.org/micro/v3/meter/handler;handler";
import "api/annotations.proto";
import "openapiv3/annotations.proto";
import "codec/frame.proto";
service Meter {
rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv3.openapiv3_operation) = {
operation_id: "Metrics";
responses: {
default: {
reference: {
_ref: "micro.codec.Frame";
};
};
};
};
option (micro.api.http) = { get: "/metrics"; };
};
};

View File

@ -0,0 +1,32 @@
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
// protoc-gen-go-micro version: v3.5.3
// source: handler.proto
package handler
import (
context "context"
api "go.unistack.org/micro/v3/api"
codec "go.unistack.org/micro/v3/codec"
)
var (
MeterName = "Meter"
MeterEndpoints = []api.Endpoint{
{
Name: "Meter.Metrics",
Path: []string{"/metrics"},
Method: []string{"GET"},
Handler: "rpc",
},
}
)
func NewMeterEndpoints() []api.Endpoint {
return MeterEndpoints
}
type MeterServer interface {
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
}

View File

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

View File

@ -1,5 +1,5 @@
// Package meter is for instrumentation // Package meter is for instrumentation
package meter package meter // import "go.unistack.org/micro/v3/meter"
import ( import (
"io" "io"
@ -11,24 +11,19 @@ import (
var ( var (
// DefaultMeter is the default meter // DefaultMeter is the default meter
DefaultMeter Meter = NewMeter() DefaultMeter = NewMeter()
// DefaultAddress data will be made available on this host:port // DefaultAddress data will be made available on this host:port
DefaultAddress = ":9090" DefaultAddress = ":9090"
// DefaultPath the meter endpoint where the Meter data will be made available // DefaultPath the meter endpoint where the Meter data will be made available
DefaultPath = "/metrics" DefaultPath = "/metrics"
// DefaultMeterStatsInterval specifies interval for meter updating // DefaultMetricPrefix holds the string that prepends to all metrics
DefaultMeterStatsInterval = 5 * time.Second DefaultMetricPrefix = "micro_"
// DefaultLabelPrefix holds the string that prepends to all labels
DefaultLabelPrefix = "micro_"
// DefaultSummaryQuantiles is the default spread of stats for summary // DefaultSummaryQuantiles is the default spread of stats for summary
DefaultSummaryQuantiles = []float64{0.5, 0.9, 0.97, 0.99, 1} DefaultSummaryQuantiles = []float64{0.5, 0.9, 0.97, 0.99, 1}
// DefaultSummaryWindow is the default window for summary // DefaultSummaryWindow is the default window for summary
DefaultSummaryWindow = 5 * time.Minute DefaultSummaryWindow = 5 * time.Minute
// DefaultSkipEndpoints is the slice of endpoint that must not be metered
DefaultSkipEndpoints = []string{
"MeterService.Metrics",
"HealthService.Live",
"HealthService.Ready",
"HealthService.Version",
}
) )
// Meter is an interface for collecting and instrumenting metrics // Meter is an interface for collecting and instrumenting metrics

View File

@ -2,6 +2,8 @@ package meter
import ( import (
"context" "context"
"go.unistack.org/micro/v3/logger"
) )
// Option powers the configuration for metrics implementations: // Option powers the configuration for metrics implementations:
@ -9,6 +11,8 @@ type Option func(*Options)
// Options for metrics implementations // Options for metrics implementations
type Options struct { type Options struct {
// Logger used for logging
Logger logger.Logger
// Context holds external options // Context holds external options
Context context.Context Context context.Context
// Name holds the meter name // Name holds the meter name
@ -17,6 +21,10 @@ type Options struct {
Address string Address string
// Path holds the path for metrics // Path holds the path for metrics
Path string Path string
// MetricPrefix holds the prefix for all metrics
MetricPrefix string
// LabelPrefix holds the prefix for all labels
LabelPrefix string
// Labels holds the default labels // Labels holds the default labels
Labels []string Labels []string
// WriteProcessMetrics flag to write process metrics // WriteProcessMetrics flag to write process metrics
@ -31,6 +39,9 @@ func NewOptions(opt ...Option) Options {
Address: DefaultAddress, Address: DefaultAddress,
Path: DefaultPath, Path: DefaultPath,
Context: context.Background(), Context: context.Background(),
Logger: logger.DefaultLogger,
MetricPrefix: DefaultMetricPrefix,
LabelPrefix: DefaultLabelPrefix,
} }
for _, o := range opt { for _, o := range opt {
@ -40,6 +51,20 @@ func NewOptions(opt ...Option) Options {
return opts return opts
} }
// LabelPrefix sets the labels prefix
func LabelPrefix(pref string) Option {
return func(o *Options) {
o.LabelPrefix = pref
}
}
// MetricPrefix sets the metric prefix
func MetricPrefix(pref string) Option {
return func(o *Options) {
o.MetricPrefix = pref
}
}
// Context sets the metrics context // Context sets the metrics context
func Context(ctx context.Context) Option { func Context(ctx context.Context) Option {
return func(o *Options) { return func(o *Options) {
@ -70,7 +95,14 @@ func TimingObjectives(value map[float64]float64) Option {
} }
*/ */
// Labels add the meter labels // Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Labels sets the meter labels
func Labels(ls ...string) Option { func Labels(ls ...string) Option {
return func(o *Options) { return func(o *Options) {
o.Labels = append(o.Labels, ls...) o.Labels = append(o.Labels, ls...)

347
meter/wrapper/wrapper.go Normal file
View File

@ -0,0 +1,347 @@
package wrapper // import "go.unistack.org/micro/v3/meter/wrapper"
import (
"context"
"fmt"
"time"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/server"
)
var (
// ClientRequestDurationSeconds specifies meter metric name
ClientRequestDurationSeconds = "client_request_duration_seconds"
// ClientRequestLatencyMicroseconds specifies meter metric name
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
// ClientRequestTotal specifies meter metric name
ClientRequestTotal = "client_request_total"
// ClientRequestInflight specifies meter metric name
ClientRequestInflight = "client_request_inflight"
// ServerRequestDurationSeconds specifies meter metric name
ServerRequestDurationSeconds = "server_request_duration_seconds"
// ServerRequestLatencyMicroseconds specifies meter metric name
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
// ServerRequestTotal specifies meter metric name
ServerRequestTotal = "server_request_total"
// ServerRequestInflight specifies meter metric name
ServerRequestInflight = "server_request_inflight"
// PublishMessageDurationSeconds specifies meter metric name
PublishMessageDurationSeconds = "publish_message_duration_seconds"
// PublishMessageLatencyMicroseconds specifies meter metric name
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
// PublishMessageTotal specifies meter metric name
PublishMessageTotal = "publish_message_total"
// PublishMessageInflight specifies meter metric name
PublishMessageInflight = "publish_message_inflight"
// SubscribeMessageDurationSeconds specifies meter metric name
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
// SubscribeMessageLatencyMicroseconds specifies meter metric name
SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds"
// SubscribeMessageTotal specifies meter metric name
SubscribeMessageTotal = "subscribe_message_total"
// SubscribeMessageInflight specifies meter metric name
SubscribeMessageInflight = "subscribe_message_inflight"
labelSuccess = "success"
labelFailure = "failure"
labelStatus = "status"
labelEndpoint = "endpoint"
// DefaultSkipEndpoints contains list of endpoints that not evaluted by wrapper
DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"}
)
// Options struct
type Options struct {
Meter meter.Meter
lopts []meter.Option
SkipEndpoints []string
}
// Option func signature
type Option func(*Options)
// NewOptions creates new Options struct
func NewOptions(opts ...Option) Options {
options := Options{
Meter: meter.DefaultMeter,
lopts: make([]meter.Option, 0, 5),
SkipEndpoints: DefaultSkipEndpoints,
}
for _, o := range opts {
o(&options)
}
return options
}
// ServiceName passes service name to meter label
func ServiceName(name string) Option {
return func(o *Options) {
o.lopts = append(o.lopts, meter.Labels("name", name))
}
}
// ServiceVersion passes service version to meter label
func ServiceVersion(version string) Option {
return func(o *Options) {
o.lopts = append(o.lopts, meter.Labels("version", version))
}
}
// ServiceID passes service id to meter label
func ServiceID(id string) Option {
return func(o *Options) {
o.lopts = append(o.lopts, meter.Labels("id", id))
}
}
// Meter passes meter
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// SkipEndoints add endpoint to skip
func SkipEndoints(eps ...string) Option {
return func(o *Options) {
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
}
}
type wrapper struct {
client.Client
callFunc client.CallFunc
opts Options
}
// NewClientWrapper create new client wrapper
func NewClientWrapper(opts ...Option) client.Wrapper {
return func(c client.Client) client.Client {
handler := &wrapper{
opts: NewOptions(opts...),
Client: c,
}
return handler
}
}
// NewCallWrapper create new call wrapper
func NewCallWrapper(opts ...Option) client.CallWrapper {
return func(fn client.CallFunc) client.CallFunc {
handler := &wrapper{
opts: NewOptions(opts...),
callFunc: fn,
}
return handler.CallFunc
}
}
func (w *wrapper) CallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.callFunc(ctx, addr, req, rsp, opts)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
err := w.callFunc(ctx, addr, req, rsp, opts)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err
}
func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.Client.Call(ctx, req, rsp, opts...)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
err := w.Client.Call(ctx, req, rsp, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err
}
func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.Client.Stream(ctx, req, opts...)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
stream, err := w.Client.Stream(ctx, req, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return stream, err
}
func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
endpoint := p.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Inc()
ts := time.Now()
err := w.Client.Publish(ctx, p, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Dec()
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(PublishMessageDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(PublishMessageTotal, labels...).Inc()
return err
}
// NewHandlerWrapper create new server handler wrapper
// deprecated
func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.HandlerFunc
}
// NewServerHandlerWrapper create new server handler wrapper
func NewServerHandlerWrapper(opts ...Option) server.HandlerWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.HandlerFunc
}
func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
endpoint := req.Service() + "." + req.Endpoint()
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return fn(ctx, req, rsp)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ServerRequestInflight, labels...).Inc()
ts := time.Now()
err := fn(ctx, req, rsp)
te := time.Since(ts)
w.opts.Meter.Counter(ServerRequestInflight, labels...).Dec()
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ServerRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ServerRequestTotal, labels...).Inc()
return err
}
}
// NewSubscriberWrapper create server subscribe wrapper
// deprecated
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.SubscriberFunc
}
func NewServerSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.SubscriberFunc
}
func (w *wrapper) SubscriberFunc(fn server.SubscriberFunc) server.SubscriberFunc {
return func(ctx context.Context, msg server.Message) error {
endpoint := msg.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Inc()
ts := time.Now()
err := fn(ctx, msg)
te := time.Since(ts)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Dec()
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(SubscribeMessageTotal, labels...).Inc()
return err
}
}

View File

@ -1,94 +0,0 @@
package micro
import (
"reflect"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/flow"
"go.unistack.org/micro/v3/fsm"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/resolver"
"go.unistack.org/micro/v3/router"
"go.unistack.org/micro/v3/selector"
"go.unistack.org/micro/v3/server"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/sync"
"go.unistack.org/micro/v3/tracer"
)
func As(b any, target any) bool {
if b == nil {
return false
}
if target == nil {
return false
}
val := reflect.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflect.Ptr || val.IsNil() {
return false
}
targetType := typ.Elem()
if targetType.Kind() != reflect.Interface {
switch {
case targetType.Implements(brokerType):
break
case targetType.Implements(loggerType):
break
case targetType.Implements(clientType):
break
case targetType.Implements(serverType):
break
case targetType.Implements(codecType):
break
case targetType.Implements(flowType):
break
case targetType.Implements(fsmType):
break
case targetType.Implements(meterType):
break
case targetType.Implements(registerType):
break
case targetType.Implements(resolverType):
break
case targetType.Implements(selectorType):
break
case targetType.Implements(storeType):
break
case targetType.Implements(syncType):
break
case targetType.Implements(serviceType):
break
case targetType.Implements(routerType):
break
default:
return false
}
}
if reflect.TypeOf(b).AssignableTo(targetType) {
val.Elem().Set(reflect.ValueOf(b))
return true
}
return false
}
var brokerType = reflect.TypeOf((*broker.Broker)(nil)).Elem()
var loggerType = reflect.TypeOf((*logger.Logger)(nil)).Elem()
var clientType = reflect.TypeOf((*client.Client)(nil)).Elem()
var serverType = reflect.TypeOf((*server.Server)(nil)).Elem()
var codecType = reflect.TypeOf((*codec.Codec)(nil)).Elem()
var flowType = reflect.TypeOf((*flow.Flow)(nil)).Elem()
var fsmType = reflect.TypeOf((*fsm.FSM)(nil)).Elem()
var meterType = reflect.TypeOf((*meter.Meter)(nil)).Elem()
var registerType = reflect.TypeOf((*register.Register)(nil)).Elem()
var resolverType = reflect.TypeOf((*resolver.Resolver)(nil)).Elem()
var routerType = reflect.TypeOf((*router.Router)(nil)).Elem()
var selectorType = reflect.TypeOf((*selector.Selector)(nil)).Elem()
var storeType = reflect.TypeOf((*store.Store)(nil)).Elem()
var syncType = reflect.TypeOf((*sync.Sync)(nil)).Elem()
var tracerType = reflect.TypeOf((*tracer.Tracer)(nil)).Elem()
var serviceType = reflect.TypeOf((*Service)(nil)).Elem()

View File

@ -1,115 +0,0 @@
package micro
import (
"context"
"fmt"
"reflect"
"testing"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/fsm"
)
func TestAs(t *testing.T) {
var b *bro
broTarget := &bro{name: "kafka"}
fsmTarget := &fsmT{name: "fsm"}
testCases := []struct {
b any
target any
match bool
want any
}{
{
broTarget,
&b,
true,
broTarget,
},
{
nil,
&b,
false,
nil,
},
{
fsmTarget,
&b,
false,
nil,
},
}
for i, tc := range testCases {
name := fmt.Sprintf("%d:As(Errorf(..., %v), %v)", i, tc.b, tc.target)
// Clear the target pointer, in case it was set in a previous test.
rtarget := reflect.ValueOf(tc.target)
rtarget.Elem().Set(reflect.Zero(reflect.TypeOf(tc.target).Elem()))
t.Run(name, func(t *testing.T) {
match := As(tc.b, tc.target)
if match != tc.match {
t.Fatalf("match: got %v; want %v", match, tc.match)
}
if !match {
return
}
if got := rtarget.Elem().Interface(); got != tc.want {
t.Fatalf("got %#v, want %#v", got, tc.want)
}
})
}
}
type bro struct {
name string
}
func (p *bro) Name() string { return p.name }
func (p *bro) Init(opts ...broker.Option) error { return nil }
// Options returns broker options
func (p *bro) Options() broker.Options { return broker.Options{} }
// Address return configured address
func (p *bro) Address() string { return "" }
// Connect connects to broker
func (p *bro) Connect(ctx context.Context) error { return nil }
// Disconnect disconnect from broker
func (p *bro) Disconnect(ctx context.Context) error { return nil }
// Publish message, msg can be single broker.Message or []broker.Message
func (p *bro) Publish(ctx context.Context, topic string, msg *broker.Message, opts ...broker.PublishOption) error {
return nil
}
// BatchPublish messages to broker with multiple topics
func (p *bro) BatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
return nil
}
// BatchSubscribe subscribes to topic messages via handler
func (p *bro) BatchSubscribe(ctx context.Context, topic string, h broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return nil, nil
}
// Subscribe subscribes to topic message via handler
func (p *bro) Subscribe(ctx context.Context, topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return nil, nil
}
// String type of broker
func (p *bro) String() string { return p.name }
type fsmT struct {
name string
}
func (f *fsmT) Start(ctx context.Context, a interface{}, o ...Option) (interface{}, error) {
return nil, nil
}
func (f *fsmT) Current() string { return f.name }
func (f *fsmT) Reset() {}
func (f *fsmT) State(s string, sf fsm.StateFunc) {}

View File

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

View File

@ -1,5 +1,5 @@
// Package network is for creating internetworks // Package network is for creating internetworks
package network package network // import "go.unistack.org/micro/v3/network"
import ( import (
"go.unistack.org/micro/v3/client" "go.unistack.org/micro/v3/client"

View File

@ -1,5 +1,5 @@
// Package transport is an interface for synchronous connection based communication // Package transport is an interface for synchronous connection based communication
package transport package transport // import "go.unistack.org/micro/v3/network/transport"
import ( import (
"context" "context"

View File

@ -1,5 +1,5 @@
// Package broker is a tunnel broker // Package broker is a tunnel broker
package broker package broker // import "go.unistack.org/micro/v3/network/tunnel/broker"
import ( import (
"context" "context"
@ -177,12 +177,12 @@ func (t *tunBatchSubscriber) run() {
// receive message // receive message
m := new(transport.Message) m := new(transport.Message)
if err := c.Recv(m); err != nil { if err := c.Recv(m); err != nil {
if logger.DefaultLogger.V(logger.ErrorLevel) { if logger.V(logger.ErrorLevel) {
logger.DefaultLogger.Error(t.opts.Context, err.Error(), err) logger.Error(t.opts.Context, err.Error())
} }
if err = c.Close(); err != nil { if err = c.Close(); err != nil {
if logger.DefaultLogger.V(logger.ErrorLevel) { if logger.V(logger.ErrorLevel) {
logger.DefaultLogger.Error(t.opts.Context, err.Error(), err) logger.Error(t.opts.Context, err.Error())
} }
} }
continue continue
@ -222,12 +222,12 @@ func (t *tunSubscriber) run() {
// receive message // receive message
m := new(transport.Message) m := new(transport.Message)
if err := c.Recv(m); err != nil { if err := c.Recv(m); err != nil {
if logger.DefaultLogger.V(logger.ErrorLevel) { if logger.V(logger.ErrorLevel) {
logger.DefaultLogger.Error(t.opts.Context, err.Error(), err) logger.Error(t.opts.Context, err.Error())
} }
if err = c.Close(); err != nil { if err = c.Close(); err != nil {
if logger.DefaultLogger.V(logger.ErrorLevel) { if logger.V(logger.ErrorLevel) {
logger.DefaultLogger.Error(t.opts.Context, err.Error(), err) logger.Error(t.opts.Context, err.Error())
} }
} }
continue continue
@ -305,10 +305,6 @@ func (t *tunEvent) SetError(err error) {
t.err = err t.err = err
} }
func (t *tunEvent) Context() context.Context {
return context.TODO()
}
// NewBroker returns new tunnel broker // NewBroker returns new tunnel broker
func NewBroker(opts ...broker.Option) (broker.Broker, error) { func NewBroker(opts ...broker.Option) (broker.Broker, error) {
options := broker.NewOptions(opts...) options := broker.NewOptions(opts...)

View File

@ -1,5 +1,5 @@
// Package transport provides a tunnel transport // Package transport provides a tunnel transport
package transport package transport // import "go.unistack.org/micro/v3/network/tunnel/transport"
import ( import (
"context" "context"

View File

@ -1,5 +1,5 @@
// Package tunnel provides gre network tunnelling // Package tunnel provides gre network tunnelling
package tunnel package tunnel // import "go.unistack.org/micro/v3/network/transport/tunnel"
import ( import (
"context" "context"

View File

@ -211,7 +211,6 @@ func Stores(s ...store.Store) Option {
} }
// Logger set the logger to use // Logger set the logger to use
//
//nolint:gocyclo //nolint:gocyclo
func Logger(l logger.Logger, opts ...LoggerOption) Option { func Logger(l logger.Logger, opts ...LoggerOption) Option {
return func(o *Options) error { return func(o *Options) error {
@ -269,7 +268,15 @@ func Logger(l logger.Logger, opts ...LoggerOption) Option {
} }
} }
} }
for _, mtr := range o.Meters {
for _, or := range lopts.meters {
if mtr.Name() == or || all {
if err = mtr.Init(meter.Logger(l)); err != nil {
return err
}
}
}
}
for _, trc := range o.Tracers { for _, trc := range o.Tracers {
for _, ot := range lopts.tracers { for _, ot := range lopts.tracers {
if trc.Name() == ot || all { if trc.Name() == ot || all {
@ -322,7 +329,6 @@ func Meters(m ...meter.Meter) Option {
// Register sets the register for the service // Register sets the register for the service
// and the underlying components // and the underlying components
//
//nolint:gocyclo //nolint:gocyclo
func Register(r register.Register, opts ...RegisterOption) Option { func Register(r register.Register, opts ...RegisterOption) Option {
return func(o *Options) error { return func(o *Options) error {
@ -397,7 +403,6 @@ func RegisterBroker(n string) RegisterOption {
} }
// Tracer sets the tracer // Tracer sets the tracer
//
//nolint:gocyclo //nolint:gocyclo
func Tracer(t tracer.Tracer, opts ...TracerOption) Option { func Tracer(t tracer.Tracer, opts ...TracerOption) Option {
return func(o *Options) error { return func(o *Options) error {

View File

@ -1,31 +0,0 @@
package options
// Hook func interface
type Hook interface{}
// Hooks func slice
type Hooks []Hook
// Append is used to add hooks
func (hs *Hooks) Append(h ...Hook) {
*hs = append(*hs, h...)
}
// Replace is used to set hooks
func (hs *Hooks) Replace(h ...Hook) {
*hs = h
}
// EachNext is used to itearate over hooks forward
func (hs *Hooks) EachNext(fn func(Hook)) {
for idx := 0; idx < len(*hs); idx++ {
fn((*hs)[idx])
}
}
// EachPrev is used to iterate over hooks backward
func (hs *Hooks) EachPrev(fn func(Hook)) {
for idx := len(*hs) - 1; idx >= 0; idx-- {
fn((*hs)[idx])
}
}

View File

@ -1,65 +0,0 @@
package options
import "testing"
func TestHooks_Append(t *testing.T) {
fn1 := func() {}
fn2 := func() {}
hs := &Hooks{}
hs.Append(fn1, fn2)
if len(*hs) != 2 {
t.Fatalf("unexpected Append error")
}
}
func TestHooks_Replace(t *testing.T) {
fn1 := func() {}
fn2 := func() {}
hs := &Hooks{}
hs.Append(fn1, fn2, fn1)
if len(*hs) != 3 {
t.Fatalf("unexpected Append error")
}
hs.Replace(fn1, fn2)
if len(*hs) != 2 {
t.Fatalf("unexpected Replace error")
}
}
func TestHooks_EachNext(t *testing.T) {
n := 5
fn1 := func() {
n *= 2
}
fn2 := func() {
n -= 10
}
hs := &Hooks{}
hs.Append(fn1, fn2)
hs.EachNext(func(h Hook) {
h.(func())()
})
if n != 0 {
t.Fatalf("unexpected EachNext")
}
}
func TestHooks_EachPrev(t *testing.T) {
n := 5
fn1 := func() {
n *= 2
}
fn2 := func() {
n -= 10
}
hs := &Hooks{}
hs.Append(fn2, fn1)
hs.EachPrev(func(h Hook) {
h.(func())()
})
if n != 0 {
t.Fatalf("unexpected EachPrev")
}
}

View File

@ -1,5 +1,5 @@
// Package http enables the http profiler // Package http enables the http profiler
package http package http // import "go.unistack.org/micro/v3/profiler/http"
import ( import (
"context" "context"

View File

@ -1,5 +1,5 @@
// Package pprof provides a pprof profiler which writes output to /tmp/[name].{cpu,mem}.pprof // Package pprof provides a pprof profiler which writes output to /tmp/[name].{cpu,mem}.pprof
package pprof package pprof // import "go.unistack.org/micro/v3/profiler/pprof"
import ( import (
"os" "os"

View File

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

View File

@ -1,5 +1,5 @@
// Package proxy is a transparent proxy built on the micro/server // Package proxy is a transparent proxy built on the micro/server
package proxy package proxy // import "go.unistack.org/micro/v3/proxy"
import ( import (
"context" "context"

View File

@ -2,12 +2,10 @@ package register
import ( import (
"context" "context"
"fmt"
"sync" "sync"
"time" "time"
"go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/util/id" "go.unistack.org/micro/v3/util/id"
) )
@ -18,7 +16,7 @@ var (
type node struct { type node struct {
LastSeen time.Time LastSeen time.Time
*register.Node *Node
TTL time.Duration TTL time.Duration
} }
@ -27,23 +25,23 @@ type record struct {
Version string Version string
Metadata map[string]string Metadata map[string]string
Nodes map[string]*node Nodes map[string]*node
Endpoints []*register.Endpoint Endpoints []*Endpoint
} }
type memory struct { type memory struct {
sync.RWMutex sync.RWMutex
records map[string]services records map[string]services
watchers map[string]*watcher watchers map[string]*watcher
opts register.Options opts Options
} }
// services is a KV map with service name as the key and a map of records as the value // services is a KV map with service name as the key and a map of records as the value
type services map[string]map[string]*record type services map[string]map[string]*record
// NewRegister returns an initialized in-memory register // NewRegister returns an initialized in-memory register
func NewRegister(opts ...register.Option) register.Register { func NewRegister(opts ...Option) Register {
r := &memory{ r := &memory{
opts: register.NewOptions(opts...), opts: NewOptions(opts...),
records: make(map[string]services), records: make(map[string]services),
watchers: make(map[string]*watcher), watchers: make(map[string]*watcher),
} }
@ -65,7 +63,7 @@ func (m *memory) ttlPrune() {
for id, n := range record.Nodes { for id, n := range record.Nodes {
if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL { if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL {
if m.opts.Logger.V(logger.DebugLevel) { if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register TTL expired for node %s of service %s", n.ID, service)) m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.ID, service)
} }
delete(m.records[domain][service][version].Nodes, id) delete(m.records[domain][service][version].Nodes, id)
} }
@ -77,7 +75,7 @@ func (m *memory) ttlPrune() {
} }
} }
func (m *memory) sendEvent(r *register.Result) { func (m *memory) sendEvent(r *Result) {
m.RLock() m.RLock()
watchers := make([]*watcher, 0, len(m.watchers)) watchers := make([]*watcher, 0, len(m.watchers))
for _, w := range m.watchers { for _, w := range m.watchers {
@ -108,7 +106,7 @@ func (m *memory) Disconnect(ctx context.Context) error {
return nil return nil
} }
func (m *memory) Init(opts ...register.Option) error { func (m *memory) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&m.opts) o(&m.opts)
} }
@ -120,15 +118,15 @@ func (m *memory) Init(opts ...register.Option) error {
return nil return nil
} }
func (m *memory) Options() register.Options { func (m *memory) Options() Options {
return m.opts return m.opts
} }
func (m *memory) Register(ctx context.Context, s *register.Service, opts ...register.RegisterOption) error { func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOption) error {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
options := register.NewRegisterOptions(opts...) options := NewRegisterOptions(opts...)
// get the services for this domain from the register // get the services for this domain from the register
srvs, ok := m.records[options.Domain] srvs, ok := m.records[options.Domain]
@ -152,10 +150,10 @@ func (m *memory) Register(ctx context.Context, s *register.Service, opts ...regi
if _, ok := srvs[s.Name][s.Version]; !ok { if _, ok := srvs[s.Name][s.Version]; !ok {
srvs[s.Name][s.Version] = r srvs[s.Name][s.Version] = r
if m.opts.Logger.V(logger.DebugLevel) { 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.opts.Logger.Debugf(m.opts.Context, "Register added new service: %s, version: %s", s.Name, s.Version)
} }
m.records[options.Domain] = srvs m.records[options.Domain] = srvs
go m.sendEvent(&register.Result{Action: "create", Service: s}) go m.sendEvent(&Result{Action: "create", Service: s})
} }
var addedNodes bool var addedNodes bool
@ -178,7 +176,7 @@ func (m *memory) Register(ctx context.Context, s *register.Service, opts ...regi
// add the node // add the node
srvs[s.Name][s.Version].Nodes[n.ID] = &node{ srvs[s.Name][s.Version].Nodes[n.ID] = &node{
Node: &register.Node{ Node: &Node{
ID: n.ID, ID: n.ID,
Address: n.Address, Address: n.Address,
Metadata: metadata, Metadata: metadata,
@ -192,14 +190,14 @@ func (m *memory) Register(ctx context.Context, s *register.Service, opts ...regi
if addedNodes { if addedNodes {
if m.opts.Logger.V(logger.DebugLevel) { 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)) m.opts.Logger.Debugf(m.opts.Context, "Register added new node to service: %s, version: %s", s.Name, s.Version)
} }
go m.sendEvent(&register.Result{Action: "update", Service: s}) go m.sendEvent(&Result{Action: "update", Service: s})
} else { } else {
// refresh TTL and timestamp // refresh TTL and timestamp
for _, n := range s.Nodes { for _, n := range s.Nodes {
if m.opts.Logger.V(logger.DebugLevel) { if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Updated registration for service: %s, version: %s", s.Name, s.Version)) m.opts.Logger.Debugf(m.opts.Context, "Updated registration for service: %s, version: %s", s.Name, s.Version)
} }
srvs[s.Name][s.Version].Nodes[n.ID].TTL = options.TTL srvs[s.Name][s.Version].Nodes[n.ID].TTL = options.TTL
srvs[s.Name][s.Version].Nodes[n.ID].LastSeen = time.Now() srvs[s.Name][s.Version].Nodes[n.ID].LastSeen = time.Now()
@ -210,11 +208,11 @@ func (m *memory) Register(ctx context.Context, s *register.Service, opts ...regi
return nil return nil
} }
func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...register.DeregisterOption) error { func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterOption) error {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
options := register.NewDeregisterOptions(opts...) options := NewDeregisterOptions(opts...)
// domain is set in metadata so it can be passed to watchers // domain is set in metadata so it can be passed to watchers
if s.Metadata == nil { if s.Metadata == nil {
@ -244,7 +242,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
for _, n := range s.Nodes { for _, n := range s.Nodes {
if _, ok := version.Nodes[n.ID]; ok { if _, ok := version.Nodes[n.ID]; ok {
if m.opts.Logger.V(logger.DebugLevel) { if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed node from service: %s, version: %s", s.Name, s.Version)) m.opts.Logger.Debugf(m.opts.Context, "Register removed node from service: %s, version: %s", s.Name, s.Version)
} }
delete(version.Nodes, n.ID) delete(version.Nodes, n.ID)
} }
@ -254,7 +252,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
// is cleanup // is cleanup
if len(version.Nodes) > 0 { if len(version.Nodes) > 0 {
m.records[options.Domain][s.Name][s.Version] = version m.records[options.Domain][s.Name][s.Version] = version
go m.sendEvent(&register.Result{Action: "update", Service: s}) go m.sendEvent(&Result{Action: "update", Service: s})
return nil return nil
} }
@ -262,38 +260,38 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
// register and exit // register and exit
if len(versions) == 1 { if len(versions) == 1 {
delete(m.records[options.Domain], s.Name) delete(m.records[options.Domain], s.Name)
go m.sendEvent(&register.Result{Action: "delete", Service: s}) go m.sendEvent(&Result{Action: "delete", Service: s})
if m.opts.Logger.V(logger.DebugLevel) { if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed service: %s", s.Name)) m.opts.Logger.Debugf(m.opts.Context, "Register removed service: %s", s.Name)
} }
return nil return nil
} }
// there are other versions of the service running, so only remove this version of it // there are other versions of the service running, so only remove this version of it
delete(m.records[options.Domain][s.Name], s.Version) delete(m.records[options.Domain][s.Name], s.Version)
go m.sendEvent(&register.Result{Action: "delete", Service: s}) go m.sendEvent(&Result{Action: "delete", Service: s})
if m.opts.Logger.V(logger.DebugLevel) { 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)) m.opts.Logger.Debugf(m.opts.Context, "Register removed service: %s, version: %s", s.Name, s.Version)
} }
return nil return nil
} }
func (m *memory) LookupService(ctx context.Context, name string, opts ...register.LookupOption) ([]*register.Service, error) { func (m *memory) LookupService(ctx context.Context, name string, opts ...LookupOption) ([]*Service, error) {
options := register.NewLookupOptions(opts...) options := NewLookupOptions(opts...)
// if it's a wildcard domain, return from all domains // if it's a wildcard domain, return from all domains
if options.Domain == register.WildcardDomain { if options.Domain == WildcardDomain {
m.RLock() m.RLock()
recs := m.records recs := m.records
m.RUnlock() m.RUnlock()
var services []*register.Service var services []*Service
for domain := range recs { for domain := range recs {
srvs, err := m.LookupService(ctx, name, append(opts, register.LookupDomain(domain))...) srvs, err := m.LookupService(ctx, name, append(opts, LookupDomain(domain))...)
if err == register.ErrNotFound { if err == ErrNotFound {
continue continue
} else if err != nil { } else if err != nil {
return nil, err return nil, err
@ -302,7 +300,7 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...registe
} }
if len(services) == 0 { if len(services) == 0 {
return nil, register.ErrNotFound return nil, ErrNotFound
} }
return services, nil return services, nil
} }
@ -313,17 +311,17 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...registe
// check the domain exists // check the domain exists
services, ok := m.records[options.Domain] services, ok := m.records[options.Domain]
if !ok { if !ok {
return nil, register.ErrNotFound return nil, ErrNotFound
} }
// check the service exists // check the service exists
versions, ok := services[name] versions, ok := services[name]
if !ok || len(versions) == 0 { if !ok || len(versions) == 0 {
return nil, register.ErrNotFound return nil, ErrNotFound
} }
// serialize the response // serialize the response
result := make([]*register.Service, len(versions)) result := make([]*Service, len(versions))
var i int var i int
@ -335,19 +333,19 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...registe
return result, nil return result, nil
} }
func (m *memory) ListServices(ctx context.Context, opts ...register.ListOption) ([]*register.Service, error) { func (m *memory) ListServices(ctx context.Context, opts ...ListOption) ([]*Service, error) {
options := register.NewListOptions(opts...) options := NewListOptions(opts...)
// if it's a wildcard domain, list from all domains // if it's a wildcard domain, list from all domains
if options.Domain == register.WildcardDomain { if options.Domain == WildcardDomain {
m.RLock() m.RLock()
recs := m.records recs := m.records
m.RUnlock() m.RUnlock()
var services []*register.Service var services []*Service
for domain := range recs { for domain := range recs {
srvs, err := m.ListServices(ctx, append(opts, register.ListDomain(domain))...) srvs, err := m.ListServices(ctx, append(opts, ListDomain(domain))...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -363,11 +361,11 @@ func (m *memory) ListServices(ctx context.Context, opts ...register.ListOption)
// ensure the domain exists // ensure the domain exists
services, ok := m.records[options.Domain] services, ok := m.records[options.Domain]
if !ok { if !ok {
return make([]*register.Service, 0), nil return make([]*Service, 0), nil
} }
// serialize the result, each version counts as an individual service // serialize the result, each version counts as an individual service
var result []*register.Service var result []*Service
for _, service := range services { for _, service := range services {
for _, version := range service { for _, version := range service {
@ -378,16 +376,16 @@ func (m *memory) ListServices(ctx context.Context, opts ...register.ListOption)
return result, nil return result, nil
} }
func (m *memory) Watch(ctx context.Context, opts ...register.WatchOption) (register.Watcher, error) { func (m *memory) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
id, err := id.New() id, err := id.New()
if err != nil { if err != nil {
return nil, err return nil, err
} }
wo := register.NewWatchOptions(opts...) wo := NewWatchOptions(opts...)
// construct the watcher // construct the watcher
w := &watcher{ w := &watcher{
exit: make(chan bool), exit: make(chan bool),
res: make(chan *register.Result), res: make(chan *Result),
id: id, id: id,
wo: wo, wo: wo,
} }
@ -408,13 +406,13 @@ func (m *memory) String() string {
} }
type watcher struct { type watcher struct {
res chan *register.Result res chan *Result
exit chan bool exit chan bool
wo register.WatchOptions wo WatchOptions
id string id string
} }
func (m *watcher) Next() (*register.Result, error) { func (m *watcher) Next() (*Result, error) {
for { for {
select { select {
case r := <-m.res: case r := <-m.res:
@ -431,15 +429,15 @@ func (m *watcher) Next() (*register.Result, error) {
if r.Service.Metadata != nil && len(r.Service.Metadata["domain"]) > 0 { if r.Service.Metadata != nil && len(r.Service.Metadata["domain"]) > 0 {
domain = r.Service.Metadata["domain"] domain = r.Service.Metadata["domain"]
} else { } else {
domain = register.DefaultDomain domain = DefaultDomain
} }
// only send the event if watching the wildcard or this specific domain // only send the event if watching the wildcard or this specific domain
if m.wo.Domain == register.WildcardDomain || m.wo.Domain == domain { if m.wo.Domain == WildcardDomain || m.wo.Domain == domain {
return r, nil return r, nil
} }
case <-m.exit: case <-m.exit:
return nil, register.ErrWatcherStopped return nil, ErrWatcherStopped
} }
} }
} }
@ -453,7 +451,7 @@ func (m *watcher) Stop() {
} }
} }
func serviceToRecord(s *register.Service, ttl time.Duration) *record { func serviceToRecord(s *Service, ttl time.Duration) *record {
metadata := make(map[string]string, len(s.Metadata)) metadata := make(map[string]string, len(s.Metadata))
for k, v := range s.Metadata { for k, v := range s.Metadata {
metadata[k] = v metadata[k] = v
@ -468,7 +466,7 @@ func serviceToRecord(s *register.Service, ttl time.Duration) *record {
} }
} }
endpoints := make([]*register.Endpoint, len(s.Endpoints)) endpoints := make([]*Endpoint, len(s.Endpoints))
for i, e := range s.Endpoints { for i, e := range s.Endpoints {
endpoints[i] = e endpoints[i] = e
} }
@ -482,7 +480,7 @@ func serviceToRecord(s *register.Service, ttl time.Duration) *record {
} }
} }
func recordToService(r *record, domain string) *register.Service { func recordToService(r *record, domain string) *Service {
metadata := make(map[string]string, len(r.Metadata)) metadata := make(map[string]string, len(r.Metadata))
for k, v := range r.Metadata { for k, v := range r.Metadata {
metadata[k] = v metadata[k] = v
@ -491,14 +489,14 @@ func recordToService(r *record, domain string) *register.Service {
// set the domain in metadata so it can be determined when a wildcard query is performed // set the domain in metadata so it can be determined when a wildcard query is performed
metadata["domain"] = domain metadata["domain"] = domain
endpoints := make([]*register.Endpoint, len(r.Endpoints)) endpoints := make([]*Endpoint, len(r.Endpoints))
for i, e := range r.Endpoints { for i, e := range r.Endpoints {
md := make(map[string]string, len(e.Metadata)) md := make(map[string]string, len(e.Metadata))
for k, v := range e.Metadata { for k, v := range e.Metadata {
md[k] = v md[k] = v
} }
endpoints[i] = &register.Endpoint{ endpoints[i] = &Endpoint{
Name: e.Name, Name: e.Name,
Request: e.Request, Request: e.Request,
Response: e.Response, Response: e.Response,
@ -506,7 +504,7 @@ func recordToService(r *record, domain string) *register.Service {
} }
} }
nodes := make([]*register.Node, len(r.Nodes)) nodes := make([]*Node, len(r.Nodes))
i := 0 i := 0
for _, n := range r.Nodes { for _, n := range r.Nodes {
md := make(map[string]string, len(n.Metadata)) md := make(map[string]string, len(n.Metadata))
@ -514,7 +512,7 @@ func recordToService(r *record, domain string) *register.Service {
md[k] = v md[k] = v
} }
nodes[i] = &register.Node{ nodes[i] = &Node{
ID: n.ID, ID: n.ID,
Address: n.Address, Address: n.Address,
Metadata: md, Metadata: md,
@ -522,7 +520,7 @@ func recordToService(r *record, domain string) *register.Service {
i++ i++
} }
return &register.Service{ return &Service{
Name: r.Name, Name: r.Name,
Version: r.Version, Version: r.Version,
Metadata: metadata, Metadata: metadata,

View File

@ -3,19 +3,18 @@ package register
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"sync" "sync"
"testing" "testing"
"time" "time"
"go.unistack.org/micro/v3/register"
) )
var testData = map[string][]*register.Service{ var testData = map[string][]*Service{
"foo": { "foo": {
{ {
Name: "foo", Name: "foo",
Version: "1.0.0", Version: "1.0.0",
Nodes: []*register.Node{ Nodes: []*Node{
{ {
ID: "foo-1.0.0-123", ID: "foo-1.0.0-123",
Address: "localhost:9999", Address: "localhost:9999",
@ -29,7 +28,7 @@ var testData = map[string][]*register.Service{
{ {
Name: "foo", Name: "foo",
Version: "1.0.1", Version: "1.0.1",
Nodes: []*register.Node{ Nodes: []*Node{
{ {
ID: "foo-1.0.1-321", ID: "foo-1.0.1-321",
Address: "localhost:6666", Address: "localhost:6666",
@ -39,7 +38,7 @@ var testData = map[string][]*register.Service{
{ {
Name: "foo", Name: "foo",
Version: "1.0.3", Version: "1.0.3",
Nodes: []*register.Node{ Nodes: []*Node{
{ {
ID: "foo-1.0.3-345", ID: "foo-1.0.3-345",
Address: "localhost:8888", Address: "localhost:8888",
@ -51,7 +50,7 @@ var testData = map[string][]*register.Service{
{ {
Name: "bar", Name: "bar",
Version: "default", Version: "default",
Nodes: []*register.Node{ Nodes: []*Node{
{ {
ID: "bar-1.0.0-123", ID: "bar-1.0.0-123",
Address: "localhost:9999", Address: "localhost:9999",
@ -65,7 +64,7 @@ var testData = map[string][]*register.Service{
{ {
Name: "bar", Name: "bar",
Version: "latest", Version: "latest",
Nodes: []*register.Node{ Nodes: []*Node{
{ {
ID: "bar-1.0.1-321", ID: "bar-1.0.1-321",
Address: "localhost:6666", Address: "localhost:6666",
@ -80,7 +79,7 @@ func TestMemoryRegistry(t *testing.T) {
ctx := context.TODO() ctx := context.TODO()
m := NewRegister() m := NewRegister()
fn := func(k string, v []*register.Service) { fn := func(k string, v []*Service) {
services, err := m.LookupService(ctx, k) services, err := m.LookupService(ctx, k)
if err != nil { if err != nil {
t.Errorf("Unexpected error getting service %s: %v", k, err) t.Errorf("Unexpected error getting service %s: %v", k, err)
@ -157,8 +156,8 @@ func TestMemoryRegistry(t *testing.T) {
for _, v := range testData { for _, v := range testData {
for _, service := range v { for _, service := range v {
services, err := m.LookupService(ctx, service.Name) services, err := m.LookupService(ctx, service.Name)
if err != register.ErrNotFound { if err != ErrNotFound {
t.Errorf("Expected error: %v, got: %v", register.ErrNotFound, err) t.Errorf("Expected error: %v, got: %v", ErrNotFound, err)
} }
if len(services) != 0 { if len(services) != 0 {
t.Errorf("Expected %d services for %s, got %d", 0, service.Name, len(services)) t.Errorf("Expected %d services for %s, got %d", 0, service.Name, len(services))
@ -173,7 +172,7 @@ func TestMemoryRegistryTTL(t *testing.T) {
for _, v := range testData { for _, v := range testData {
for _, service := range v { for _, service := range v {
if err := m.Register(ctx, service, register.RegisterTTL(time.Millisecond)); err != nil { if err := m.Register(ctx, service, RegisterTTL(time.Millisecond)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
@ -202,15 +201,15 @@ func TestMemoryRegistryTTLConcurrent(t *testing.T) {
ctx := context.TODO() ctx := context.TODO()
for _, v := range testData { for _, v := range testData {
for _, service := range v { for _, service := range v {
if err := m.Register(ctx, service, register.RegisterTTL(waitTime/2)); err != nil { if err := m.Register(ctx, service, RegisterTTL(waitTime/2)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
} }
//if len(os.Getenv("IN_TRAVIS_CI")) == 0 { if len(os.Getenv("IN_TRAVIS_CI")) == 0 {
// t.Logf("test will wait %v, then check TTL timeouts", waitTime) t.Logf("test will wait %v, then check TTL timeouts", waitTime)
//} }
errChan := make(chan error, concurrency) errChan := make(chan error, concurrency)
syncChan := make(chan struct{}) syncChan := make(chan struct{})
@ -251,34 +250,34 @@ func TestMemoryWildcard(t *testing.T) {
m := NewRegister() m := NewRegister()
ctx := context.TODO() ctx := context.TODO()
testSrv := &register.Service{Name: "foo", Version: "1.0.0"} testSrv := &Service{Name: "foo", Version: "1.0.0"}
if err := m.Register(ctx, testSrv, register.RegisterDomain("one")); err != nil { if err := m.Register(ctx, testSrv, RegisterDomain("one")); err != nil {
t.Fatalf("Register err: %v", err) t.Fatalf("Register err: %v", err)
} }
if err := m.Register(ctx, testSrv, register.RegisterDomain("two")); err != nil { if err := m.Register(ctx, testSrv, RegisterDomain("two")); err != nil {
t.Fatalf("Register err: %v", err) t.Fatalf("Register err: %v", err)
} }
if recs, err := m.ListServices(ctx, register.ListDomain("one")); err != nil { if recs, err := m.ListServices(ctx, ListDomain("one")); err != nil {
t.Errorf("List err: %v", err) t.Errorf("List err: %v", err)
} else if len(recs) != 1 { } else if len(recs) != 1 {
t.Errorf("Expected 1 record, got %v", len(recs)) t.Errorf("Expected 1 record, got %v", len(recs))
} }
if recs, err := m.ListServices(ctx, register.ListDomain("*")); err != nil { if recs, err := m.ListServices(ctx, ListDomain("*")); err != nil {
t.Errorf("List err: %v", err) t.Errorf("List err: %v", err)
} else if len(recs) != 2 { } else if len(recs) != 2 {
t.Errorf("Expected 2 records, got %v", len(recs)) t.Errorf("Expected 2 records, got %v", len(recs))
} }
if recs, err := m.LookupService(ctx, testSrv.Name, register.LookupDomain("one")); err != nil { if recs, err := m.LookupService(ctx, testSrv.Name, LookupDomain("one")); err != nil {
t.Errorf("Lookup err: %v", err) t.Errorf("Lookup err: %v", err)
} else if len(recs) != 1 { } else if len(recs) != 1 {
t.Errorf("Expected 1 record, got %v", len(recs)) t.Errorf("Expected 1 record, got %v", len(recs))
} }
if recs, err := m.LookupService(ctx, testSrv.Name, register.LookupDomain("*")); err != nil { if recs, err := m.LookupService(ctx, testSrv.Name, LookupDomain("*")); err != nil {
t.Errorf("Lookup err: %v", err) t.Errorf("Lookup err: %v", err)
} else if len(recs) != 2 { } else if len(recs) != 2 {
t.Errorf("Expected 2 records, got %v", len(recs)) t.Errorf("Expected 2 records, got %v", len(recs))
@ -286,7 +285,7 @@ func TestMemoryWildcard(t *testing.T) {
} }
func TestWatcher(t *testing.T) { func TestWatcher(t *testing.T) {
testSrv := &register.Service{Name: "foo", Version: "1.0.0"} testSrv := &Service{Name: "foo", Version: "1.0.0"}
ctx := context.TODO() ctx := context.TODO()
m := NewRegister() m := NewRegister()

View File

@ -1,72 +0,0 @@
package register
import "context"
type noop struct {
opts Options
}
func NewRegister(opts ...Option) Register {
return &noop{
opts: NewOptions(opts...),
}
}
func (n *noop) Name() string {
return n.opts.Name
}
func (n *noop) Init(opts ...Option) error {
for _, o := range opts {
o(&n.opts)
}
return nil
}
func (n *noop) Options() Options {
return n.opts
}
func (n *noop) Connect(ctx context.Context) error {
return nil
}
func (n *noop) Disconnect(ctx context.Context) error {
return nil
}
func (n *noop) Register(ctx context.Context, service *Service, option ...RegisterOption) error {
return nil
}
func (n *noop) Deregister(ctx context.Context, service *Service, option ...DeregisterOption) error {
return nil
}
func (n *noop) LookupService(ctx context.Context, s string, option ...LookupOption) ([]*Service, error) {
return nil, nil
}
func (n *noop) ListServices(ctx context.Context, option ...ListOption) ([]*Service, error) {
return nil, nil
}
func (n *noop) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
wOpts := NewWatchOptions(opts...)
return &watcher{wo: wOpts}, nil
}
func (n *noop) String() string {
return "noop"
}
type watcher struct {
wo WatchOptions
}
func (m *watcher) Next() (*Result, error) {
return nil, nil
}
func (m *watcher) Stop() {}

View File

@ -1,5 +1,5 @@
// Package register is an interface for service discovery // Package register is an interface for service discovery
package register package register // import "go.unistack.org/micro/v3/register"
import ( import (
"context" "context"
@ -18,7 +18,7 @@ var DefaultDomain = "micro"
var ( var (
// DefaultRegister is the global default register // DefaultRegister is the global default register
DefaultRegister Register = NewRegister() DefaultRegister = NewRegister()
// ErrNotFound returned when LookupService is called and no services found // ErrNotFound returned when LookupService is called and no services found
ErrNotFound = errors.New("service not found") ErrNotFound = errors.New("service not found")
// ErrWatcherStopped returned when when watcher is stopped // ErrWatcherStopped returned when when watcher is stopped

View File

@ -1,5 +1,5 @@
// Package dns resolves names to dns records // Package dns resolves names to dns records
package dns package dns // import "go.unistack.org/micro/v3/resolver/dns"
import ( import (
"context" "context"

View File

@ -1,5 +1,5 @@
// Package dnssrv resolves names to dns srv records // Package dnssrv resolves names to dns srv records
package dnssrv package dnssrv // import "go.unistack.org/micro/v3/resolver/dnssrv"
import ( import (
"fmt" "fmt"

View File

@ -1,5 +1,5 @@
// Package http resolves names to network addresses using a http request // Package http resolves names to network addresses using a http request
package http package http // import "go.unistack.org/micro/v3/resolver/http"
import ( import (
"encoding/json" "encoding/json"

View File

@ -1,5 +1,5 @@
// Package noop is a noop resolver // Package noop is a noop resolver
package noop package noop // import "go.unistack.org/micro/v3/resolver/noop"
import ( import (
"go.unistack.org/micro/v3/resolver" "go.unistack.org/micro/v3/resolver"

View File

@ -1,5 +1,5 @@
// Package register resolves names using the micro register // Package register resolves names using the micro register
package register package register // import "go.unistack.org/micro/v3/resolver/registry"
import ( import (
"context" "context"

View File

@ -1,5 +1,5 @@
// Package resolver resolves network names to addresses // Package resolver resolves network names to addresses
package resolver package resolver // import "go.unistack.org/micro/v3/resolver"
// Resolver is network resolver. It's used to find network nodes // Resolver is network resolver. It's used to find network nodes
// via the name to connect to. This is done based on Network.Name(). // via the name to connect to. This is done based on Network.Name().

View File

@ -1,5 +1,5 @@
// Package static is a static resolver // Package static is a static resolver
package static package static // import "go.unistack.org/micro/v3/resolver/static"
import ( import (
"go.unistack.org/micro/v3/resolver" "go.unistack.org/micro/v3/resolver"

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